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'
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:



49
50
51
52
53
# File 'lib/hirb/menu.rb', line 49

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)


42
43
44
45
46
# File 'lib/hirb/menu.rb', line 42

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



192
193
194
# File 'lib/hirb/menu.rb', line 192

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

#add_chosen_to_args(items) ⇒ Object



179
180
181
182
183
# File 'lib/hirb/menu.rb', line 179

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

#choose_from_menuObject



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

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

  if (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



169
170
171
172
173
174
175
176
177
# File 'lib/hirb/menu.rb', line 169

def cleanup_new_args
  if @new_args.all? {|e| e == CHOSEN_ARG }
    @new_args = [CHOSEN_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



185
186
187
188
189
190
# File 'lib/hirb/menu.rb', line 185

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



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

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

#execute_action(chosen) ⇒ Object



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

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



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

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

#get_inputObject



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

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



76
77
78
79
80
# File 'lib/hirb/menu.rb', line 76

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

#input_to_tokens(input) ⇒ Object



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

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



117
118
119
# File 'lib/hirb/menu.rb', line 117

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

#map_tokens(tokens) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/hirb/menu.rb', line 130

def map_tokens(tokens)
  values = if return_cell_values?
    @output[0].is_a?(Hash) ?
      tokens.map {|arr,f| arr.map {|e| e[f]} } :
      tokens.map {|arr,f|
        arr.map {|e| e.is_a?(Array) && f.is_a?(Integer) ? e[f] : e.send(f) }
      }
  else
    tokens.map {|arr, f| arr[0] }
  end
  values.flatten
end

#parse_input(input) ⇒ Object



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

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



154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/hirb/menu.rb', line 154

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.")

    token = Util.choose_from_array(@output, word)
    token = [token] if word[/\*|-|\.\.|,/] && !return_cell_values?
    [token, field]
  else
    @new_args << word
    nil
  end
end

#pre_promptObject



82
83
84
85
86
87
# File 'lib/hirb/menu.rb', line 82

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)


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

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

#render(output, &block) ⇒ Object



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

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)


143
144
145
# File 'lib/hirb/menu.rb', line 143

def return_cell_values?
  @options[:two_d]
end

#split_input_args(input) ⇒ Object



196
197
198
# File 'lib/hirb/menu.rb', line 196

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

#table_helper_class?Boolean

Returns:

  • (Boolean)


210
211
212
# File 'lib/hirb/menu.rb', line 210

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

#unalias_field(field) ⇒ Object



214
215
216
# File 'lib/hirb/menu.rb', line 214

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:



101
102
103
104
105
106
# File 'lib/hirb/menu.rb', line 101

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