Class: Hirb::Menu

Inherits:
Object
  • Object
show all
Defined in:
lib/hirb/menu.rb

Overview

This class provides a menu using Hirb’s table helpers by default to display choices. Menu choices (syntax at Hirb::Util.choose_from_array) refer to rows. However, when in two_d mode, choices refer to specific cells by appending a ‘:field’ to a choice. A field name can be an abbreviated. Menus can also have an action mode, which turns the menu prompt into a commandline that executes the choices as arguments and uses methods as actions/commands.

Defined Under Namespace

Classes: Error

Constant Summary collapse

CHOSEN_REGEXP =

Detects valid choices and optional field/column

/^(\d([^:]+)?)(?::)?(\S+)?/
CHOSEN_ARG =
'%s'
ALL_ARG =
'*'
ALL_REGEXP =
/^(\*)(?::)?(\S+)?/
DIRECTIONS =
"Specify individual choices (4,7), range of choices (1-3) or all choices (*)."

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Menu

:stopdoc:



51
52
53
54
55
# File 'lib/hirb/menu.rb', line 51

def initialize(options={})
  @options = {:helper_class=>Hirb::Helpers::AutoTable, :prompt=>"Choose: ", :ask=>true,
    :directions=>true}.merge options
  @options[:reopen] = '/dev/tty' if @options[:reopen] == true
end

Class Method Details

.render(output, options = {}, &block) ⇒ Object

This method will return an array unless it’s exited by simply pressing return, which returns nil. If given a block, the block will yield if and with any menu items are chosen. All options except for the ones below are passed to render the menu.

Options:

:helper_class

Helper class to render menu. Helper class is expected to implement numbering given a :number option. To use a very basic menu, set this to false. Defaults to Hirb::Helpers::AutoTable.

:prompt

String for menu prompt. Defaults to “Choose: ”.

:ask

Always ask for input, even if there is only one choice. Default is true.

:directions

Display directions before prompt. Default is true.

:readline

Use readline to get user input if available. Input strings are added to readline history. Default is false.

:two_d

Turn menu into a 2 dimensional (2D) menu by allowing user to pick values from table cells. Default is false.

:default_field

Default field for a 2D menu. Defaults to first field in a table.

:action

Turn menu into an action menu by letting user pass menu choices as an argument to a method/command. A menu choice’s place amongst other arguments is preserved. Default is false.

:multi_action

Execute action menu multiple times iterating over the menu choices. Default is false.

:action_object

Object that takes method/command calls. Default is main.

:command

Default method/command to call when no command given.

:reopen

Reopens $stdin with given file or with /dev/tty when set to true. Use when $stdin is already reading in piped data.

Examples:

>> extend Hirb::Console
=> self
>> menu [1,2,3], :prompt=> "So many choices, so little time: "
>> menu [{:a=>1, :b=>2}, {:a=>3, :b=>4}], :fields=>[:a,b], :two_d=>true)


44
45
46
47
48
# File 'lib/hirb/menu.rb', line 44

def self.render(output, options={}, &block)
  new(options).render(output, &block)
rescue Error=>e
  $stderr.puts "Error: #{e.message}"
end

Instance Method Details

#action_objectObject



212
213
214
# File 'lib/hirb/menu.rb', line 212

def action_object
  @options[:action_object] || eval("self", TOPLEVEL_BINDING)
end

#add_chosen_to_args(items) ⇒ Object



199
200
201
202
203
# File 'lib/hirb/menu.rb', line 199

def add_chosen_to_args(items)
  args = @new_args.dup
  args[args.index(CHOSEN_ARG)] = items
  args
end

#choose_from_menuObject



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/hirb/menu.rb', line 91

def choose_from_menu
  return unasked_choice if @output.size == 1 && !@options[:ask]

  if (helper_class = Util.any_const_get(@options[:helper_class]))
    View.render_output(@output, :class=>@options[:helper_class], :options=>@options.merge(:number=>true))
  else
    @output.each_with_index {|e,i| puts "#{i+1}: #{e}" }
  end

  parse_input get_input
end

#cleanup_new_argsObject



188
189
190
191
192
193
194
195
196
197
# File 'lib/hirb/menu.rb', line 188

def cleanup_new_args
  if @new_args.all? {|e| e == CHOSEN_ARG }
    @new_args = [CHOSEN_ARG]
  elsif @new_args.index(ALL_ARG)
  else
    i = @new_args.index(CHOSEN_ARG) || raise(Error, "No rows chosen")
    @new_args.delete(CHOSEN_ARG)
    @new_args.insert(i, CHOSEN_ARG)
  end
end

#commandObject



205
206
207
208
209
210
# File 'lib/hirb/menu.rb', line 205

def command
  @command ||= begin
    cmd = (@new_args == [CHOSEN_ARG]) ? nil : @new_args.shift
    cmd ||= @options[:command] || raise(Error, "No command given for action menu")
  end
end

#default_fieldObject



220
221
222
# File 'lib/hirb/menu.rb', line 220

def default_field
  @default_field ||= @options[:default_field] || fields[0]
end

#execute_action(chosen) ⇒ Object



110
111
112
113
114
115
116
117
# File 'lib/hirb/menu.rb', line 110

def execute_action(chosen)
  return nil if chosen.size.zero?
  if @options[:multi_action]
    chosen.each {|e| invoke command, add_chosen_to_args(e) }
  else
    invoke command, add_chosen_to_args(chosen)
  end
end

#fieldsObject

Has to be called after displaying menu



225
226
227
228
# File 'lib/hirb/menu.rb', line 225

def fields
  @fields ||= @options[:fields] || (@options[:ask] && table_helper_class? && Helpers::Table.last_table ?
    Helpers::Table.last_table.fields[1..-1] : [])
end

#get_inputObject



65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/hirb/menu.rb', line 65

def get_input
  prompt = pre_prompt + @options[:prompt]
  prompt = DIRECTIONS+"\n"+prompt if @options[:directions]
  $stdin.reopen @options[:reopen] if @options[:reopen]

  if @options[:readline] && readline_loads?
    get_readline_input(prompt)
  else
    print prompt
    $stdin.gets.chomp.strip
  end
end

#get_readline_input(prompt) ⇒ Object



78
79
80
81
82
# File 'lib/hirb/menu.rb', line 78

def get_readline_input(prompt)
  input = Readline.readline prompt
  Readline::HISTORY << input
  input
end

#input_to_tokens(input) ⇒ Object



162
163
164
165
166
167
# File 'lib/hirb/menu.rb', line 162

def input_to_tokens(input)
  @new_args = []
  tokens = (@args = split_input_args(input)).map {|word| parse_word(word) }.compact
  cleanup_new_args
  tokens
end

#invoke(cmd, args) ⇒ Object



119
120
121
# File 'lib/hirb/menu.rb', line 119

def invoke(cmd, args)
  action_object.send(cmd, *args)
end

#map_all_args(tokens) ⇒ Object



144
145
146
147
148
149
150
151
152
# File 'lib/hirb/menu.rb', line 144

def map_all_args(tokens)
  tokens.map { |arr,f|
    if arr == ALL_ARG
      f.nil? ? @output : yield(@output, f)
    else
      yield(arr, f)
    end
  }.flatten
end

#map_array_of_tokens(tokens) ⇒ Object



158
159
160
# File 'lib/hirb/menu.rb', line 158

def map_array_of_tokens(tokens)
  map_all_args(tokens) { |arr,f| arr.map {|e| e.is_a?(Array) && f.is_a?(Integer) ? e[f] : e.send(f) } }
end

#map_hash_of_tokens(tokens) ⇒ Object



154
155
156
# File 'lib/hirb/menu.rb', line 154

def map_hash_of_tokens(tokens)
  map_all_args(tokens) { |arr,f| arr.map {|e| e[f]} }
end

#map_tokens(tokens) ⇒ Object



132
133
134
135
136
137
138
# File 'lib/hirb/menu.rb', line 132

def map_tokens(tokens)
  if return_cell_values?
    @output[0].is_a?(Hash) ? map_hash_of_tokens(tokens) : map_array_of_tokens(tokens)
  else
    map_all_args(tokens) { |arr, f| arr[0] }
  end
end

#parse_input(input) ⇒ Object



123
124
125
126
127
128
129
130
# File 'lib/hirb/menu.rb', line 123

def parse_input(input)
  if (@options[:two_d] || @options[:action])
    tokens = input_to_tokens(input)
    map_tokens(tokens)
  else
    Util.choose_from_array(@output, input)
  end
end

#parse_word(word) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/hirb/menu.rb', line 169

def parse_word(word)
  if word[CHOSEN_REGEXP]
    @new_args << CHOSEN_ARG
    field = $3 ? unalias_field($3) : default_field ||
      raise(Error, "No default field/column found. Fields must be explicitly picked.")
    if return_cell_values?
      [Util.choose_from_array(@output, word), field]
    else
      [[Util.choose_from_array(@output, word)], nil]
    end
  elsif word[ALL_REGEXP]
    @new_args << CHOSEN_ARG
    $2 ? [ALL_ARG, unalias_field($2)] : ALL_ARG
  else
    @new_args << word
    nil
  end
end

#pre_promptObject



84
85
86
87
88
89
# File 'lib/hirb/menu.rb', line 84

def pre_prompt
  prompt = ''
  prompt << "Default field: #{default_field}\n" if @options[:two_d] && default_field
  prompt << "Default command: #{@options[:command]}\n" if @options[:action] && @options[:command]
  prompt
end

#readline_loads?Boolean

Returns:

  • (Boolean)


238
239
240
241
242
243
# File 'lib/hirb/menu.rb', line 238

def readline_loads?
  require 'readline'
  true
rescue LoadError
  false
end

#render(output, &block) ⇒ Object



57
58
59
60
61
62
63
# File 'lib/hirb/menu.rb', line 57

def render(output, &block)
  @output = Array(output)
  return [] if @output.size.zero?
  chosen = choose_from_menu
  block.call(chosen) if block && chosen.size > 0
  @options[:action] ? execute_action(chosen) : chosen
end

#return_cell_values?Boolean

Returns:

  • (Boolean)


140
141
142
# File 'lib/hirb/menu.rb', line 140

def return_cell_values?
  @options[:two_d]
end

#split_input_args(input) ⇒ Object



216
217
218
# File 'lib/hirb/menu.rb', line 216

def split_input_args(input)
  input.split(/\s+/)
end

#table_helper_class?Boolean

Returns:

  • (Boolean)


230
231
232
# File 'lib/hirb/menu.rb', line 230

def table_helper_class?
  @options[:helper_class].is_a?(Class) && @options[:helper_class] < Helpers::Table
end

#unalias_field(field) ⇒ Object



234
235
236
# File 'lib/hirb/menu.rb', line 234

def unalias_field(field)
  fields.sort_by {|e| e.to_s }.find {|e| e.to_s[/^#{field}/] } || raise(Error, "Invalid field '#{field}'")
end

#unasked_choiceObject

Raises:



103
104
105
106
107
108
# File 'lib/hirb/menu.rb', line 103

def unasked_choice
  return @output unless @options[:action]
  raise(Error, "Default command and field required for unasked action menu") unless default_field && @options[:command]
  @new_args = [@options[:command], CHOSEN_ARG]
  map_tokens([[@output, default_field]])
end