Simple Ruby directory navigator functions In the process of writing scripts to work with CSVs and other files of the like I found myself in the need of a way to navigate and select files for various purposes. I wrote the following function for one of the scripts I needed to finish quickly and now I'm trying to get it into a more reusable form

def select_file()
  i = 1
  selection_set = Dir.glob("*") 
  puts "--------------------------------------------------------------------------"
  puts "Listing entries in #{File.basename(Dir.pwd)}:"
  puts
  selection_set.each do |entry|
    puts i.to_s + " - " + entry
    i = i+1 
  end
  print "Select File/Folder: "
  user_selection = gets.chomp
  selection = selection_set[user_selection.to_i - 1]
  puts "Selected: #{selection}"
  puts "--------------------------------------------------------------------------"
  selection
end


In an attempt to make this more reusable in the future I refactored this single function into a series of functions. Part of the motivation in doing it this way was to attempt to use this for displaying other sets of things the user may need to select. Was I better off sticking with the original approach or am I on a decent track but not quite doing it right? I get the sense this can all be done much simpler. Also I am deliberately avoiding using any kind of UI gem for no reason other than I am trying to learn.

def full_directory_navigator
  selection_set = Dir.glob('*', File::FNM_DOTMATCH)
  list_directory_entries(selection_set)
end

def dir_header(pattern)
  100.times { |i| !(i == 99) ? print('-') : puts }
  pattern_message = (pattern == '*') ? '' : "matching \"#{ pattern }\""
  puts
  puts "Listing entries in #{ File.basename(Dir.pwd) }#{ pattern_message }"
  puts
  puts 'Index          Entry'
  100.times { |i| !(i == 99) ? print('-') : puts }
end

def dir_select
  print 'Select File/Folder: '
end

def spacer(i)
  size = i.to_s.size
  spacer = ''
  (9 - size).times { spacer = ' ' + spacer }
  spacer
end

def display_selection_set(selection_set)
  i = 1
  selection_set.each do |entry|
    puts i.to_s + spacer(i) + '-     ' + entry
    i += 1
  end
end

def list_directory_entries(selection_set, pattern = '*')
  dir_header(pattern)
  display_selection_set(selection_set)
  dir_select
  user_selection = gets.chomp.to_i
  selection = selection_set[user_selection - 1]
  puts "Selected: #{selection}"
  selection
end

full_directory_navigator


Any and all comments are appreciated. If my code sucks please let me know so I can start writing less awful code. I also just realized I didn't comment the code at all, so I will begin commenting it and if there are any ambiguous sections it would be greatly appreciated if it could be brought to my attention.
Your two code samples are not exactly equivalent.  The second one has nicer formatting (fixed-width numbering) and attempts to parameterize the glob pattern (but fails in that regard — full_directory_navigator should accept a pattern parameter and pass it to both Dir.glob and list_directory_entries).

I'm not convinced that splitting up the select_file function into six functions makes it any better.  It's not any more readable or extensible than the original.  The parameter passing just gets in the way.  (If you had defined a class, there might be some advantage, in that a subclass could customize the behaviour by overriding one of the methods.)

With more effective string formatting, the code is simple enough to fit in one function.


To repeat a character, use string multiplication.
The ternary conditional could just be an if-qualified expression, since one of the outcomes is nil.
To make a fixed-width column of numbers, use '%-8d' % number.




def select_file(glob='*', glob_flags=File::FNM_DOTMATCH)
  entries = Dir.glob(glob, glob_flags)

  # Header
  puts '-' * 79,
       "Listing entries in #{File.basename(Dir.pwd)}#{
                               " matching \"#{glob}\"" if glob != '*'
                           }",
       '',
       '%-8s   %s' % ['Index', 'Entry'],
       '-' * 79

  # Listing
  entries.each_with_index { |entry, i| puts '%-8d - %s' % [i + 1, entry] }

  # Prompt
  print 'Select File/Folder: '
  entries[gets.to_i - 1]
end

selection = select_file()
puts "Selected: #{selection}"


Note that you haven't done any validation on the input.  If the input doesn't look like a number, then gets.to_i - 1 becomes 0 - 1, which refers to the last item in the array.