hirb 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,11 @@
1
1
  class Hirb::Helpers::VerticalTable < Hirb::Helpers::Table
2
2
 
3
- # Renders a vertical table using the same options as Hirb::Helpers::Table.render except for :field_lengths,
4
- # :vertical and :max_width which aren't used.
3
+ # Renders a vertical table using the same options as Hirb::Helpers::Table.render except for the ones below
4
+ # and :max_fields, :vertical and :max_width which aren't used.
5
+ # ==== Options:
6
+ # [:hide_empty] Boolean which hides empty values (nil or '') from being displayed. Default is false.
5
7
  def self.render(rows, options={})
6
- new(rows, {:no_newlines=>false}.merge(options)).render
8
+ new(rows, {:escape_special_chars=>false, :resize=>false}.merge(options)).render
7
9
  end
8
10
 
9
11
  #:stopdoc:
@@ -21,11 +23,15 @@ class Hirb::Helpers::VerticalTable < Hirb::Helpers::Table
21
23
  @rows.map do |row|
22
24
  row = "#{stars} #{i+1}. row #{stars}\n" +
23
25
  @fields.map {|f|
24
- "#{Hirb::String.rjust(@headers[f], longest_header)}: #{row[f]}"
25
- }.join("\n")
26
+ if !@options[:hide_empty] || (@options[:hide_empty] && !row[f].empty?)
27
+ "#{Hirb::String.rjust(@headers[f], longest_header)}: #{row[f]}"
28
+ else
29
+ nil
30
+ end
31
+ }.compact.join("\n")
26
32
  i+= 1
27
33
  row
28
34
  end
29
- #:startdoc:
30
35
  end
36
+ #:startdoc:
31
37
  end
data/lib/hirb/menu.rb CHANGED
@@ -1,47 +1,215 @@
1
1
  module Hirb
2
- # This class provides a selection menu using Hirb's table helpers by default to display choices.
2
+ # This class provides a menu using Hirb's table helpers by default to display choices.
3
+ # Menu choices (syntax at Hirb::Util.choose_from_array) refer to rows. However, when in
4
+ # two_d mode, choices refer to specific cells by appending a ':field' to a choice.
5
+ # A field name can be an abbreviated. Menus can also have an action mode, which turns the
6
+ # menu prompt into a commandline that executes the choices as arguments and uses methods as
7
+ # actions/commands.
3
8
  class Menu
4
- # Menu which asks to select from the given array and returns the selected menu items as an array. See Hirb::Util.choose_from_array
5
- # for the syntax for specifying selections. All options except for the ones below are passed to render the menu.
9
+ class Error < StandardError; end
10
+
11
+ # Detects valid choices and optional field/column
12
+ CHOSEN_REGEXP = /^(\d([^:]+)?)(?::)?(\S+)?/
13
+ CHOSEN_ARG = '%s'
14
+ DIRECTIONS = "Specify individual choices (4,7), range of choices (1-3) or all choices (*)."
15
+
16
+
17
+ # This method will return an array unless it's exited by simply pressing return, which returns nil.
18
+ # If given a block, the block will yield if and with any menu items are chosen.
19
+ # All options except for the ones below are passed to render the menu.
6
20
  #
7
21
  # ==== Options:
8
- # [:helper_class] Helper class to render menu. Helper class is expected to implement numbering given a :number option.
9
- # To use a very basic menu, set this to false. Defaults to Hirb::Helpers::AutoTable.
10
- # [:prompt] String for menu prompt. Defaults to "Choose: ".
11
- # [:validate_one] Validates that only one item in array is chosen and returns just that item. Default is false.
12
- # [:ask] Always ask for input, even if there is only one choice. Default is true.
22
+ # [*:helper_class*] Helper class to render menu. Helper class is expected to implement numbering given a :number option.
23
+ # To use a very basic menu, set this to false. Defaults to Hirb::Helpers::AutoTable.
24
+ # [*:prompt*] String for menu prompt. Defaults to "Choose: ".
25
+ # [*:ask*] Always ask for input, even if there is only one choice. Default is true.
26
+ # [*:directions*] Display directions before prompt. Default is true.
27
+ # [*:readline*] Use readline to get user input if available. Input strings are added to readline history. Default is false.
28
+ # [*:two_d*] Turn menu into a 2 dimensional (2D) menu by allowing user to pick values from table cells. Default is false.
29
+ # [*:default_field*] Default field for a 2D menu. Defaults to first field in a table.
30
+ # [*:action*] Turn menu into an action menu by letting user pass menu choices as an argument to a method/command.
31
+ # A menu choice's place amongst other arguments is preserved. Default is false.
32
+ # [*:multi_action*] Execute action menu multiple times iterating over the menu choices. Default is false.
33
+ # [*:action_object*] Object that takes method/command calls. Default is main.
34
+ # [*:command*] Default method/command to call when no command given.
13
35
  # Examples:
14
- # extend Hirb::Console
15
- # menu([1,2,3], :fields=>[:field1, :field2], :validate_one=>true)
16
- # menu([1,2,3], :helper_class=>Hirb::Helpers::Table)
17
- def self.render(output, options={})
18
- default_options = {:helper_class=>Hirb::Helpers::AutoTable, :prompt=>"Choose #{options[:validate_one] ? 'one' : ''}: ", :ask=>true}
19
- options = default_options.merge(options)
20
- output = [output] unless output.is_a?(Array)
21
- chosen = choose_from_menu(output, options)
22
- yield(chosen) if block_given? && chosen.is_a?(Array) && chosen.size > 0
23
- chosen
24
- end
25
-
26
- def self.choose_from_menu(output, options) #:nodoc:
27
- return output if output.size == 1 && !options[:ask]
28
- if (helper_class = Util.any_const_get(options[:helper_class]))
29
- View.render_output(output, :class=>options[:helper_class], :options=>options.merge(:number=>true))
36
+ # >> extend Hirb::Console
37
+ # => self
38
+ # >> menu [1,2,3], :prompt=> "So many choices, so little time: "
39
+ # >> menu [{:a=>1, :b=>2}, {:a=>3, :b=>4}], :fields=>[:a,b], :two_d=>true)
40
+ def self.render(output, options={}, &block)
41
+ new(options).render(output, &block)
42
+ rescue Error=>e
43
+ $stderr.puts "Error: #{e.message}"
44
+ end
45
+
46
+ #:stopdoc:
47
+ def initialize(options={})
48
+ @options = {:helper_class=>Hirb::Helpers::AutoTable, :prompt=>"Choose: ", :ask=>true,
49
+ :directions=>true}.merge options
50
+ end
51
+
52
+ def render(output, &block)
53
+ @output = Array(output)
54
+ return [] if @output.size.zero?
55
+ chosen = choose_from_menu
56
+ block.call(chosen) if block && chosen.size > 0
57
+ @options[:action] ? execute_action(chosen) : chosen
58
+ end
59
+
60
+ def get_input
61
+ prompt = pre_prompt + @options[:prompt]
62
+ prompt = DIRECTIONS+"\n"+prompt if @options[:directions]
63
+
64
+ if @options[:readline] && readline_loads?
65
+ get_readline_input(prompt)
66
+ else
67
+ print prompt
68
+ $stdin.gets.chomp.strip
69
+ end
70
+ end
71
+
72
+ def get_readline_input(prompt)
73
+ input = Readline.readline prompt
74
+ Readline::HISTORY << input
75
+ input
76
+ end
77
+
78
+ def pre_prompt
79
+ prompt = ''
80
+ prompt << "Default field: #{default_field}\n" if @options[:two_d] && default_field
81
+ prompt << "Default command: #{@options[:command]}\n" if @options[:action] && @options[:command]
82
+ prompt
83
+ end
84
+
85
+ def choose_from_menu
86
+ return unasked_choice if @output.size == 1 && !@options[:ask]
87
+
88
+ if (helper_class = Util.any_const_get(@options[:helper_class]))
89
+ View.render_output(@output, :class=>@options[:helper_class], :options=>@options.merge(:number=>true))
90
+ else
91
+ @output.each_with_index {|e,i| puts "#{i+1}: #{e}" }
92
+ end
93
+
94
+ parse_input get_input
95
+ end
96
+
97
+ def unasked_choice
98
+ return @output unless @options[:action]
99
+ raise(Error, "Default command and field required for unasked action menu") unless default_field && @options[:command]
100
+ @new_args = [@options[:command], CHOSEN_ARG]
101
+ map_tokens([[@output, default_field]])
102
+ end
103
+
104
+ def execute_action(chosen)
105
+ return nil if chosen.size.zero?
106
+ if @options[:multi_action]
107
+ chosen.each {|e| invoke command, add_chosen_to_args(e) }
108
+ else
109
+ invoke command, add_chosen_to_args(chosen)
110
+ end
111
+ end
112
+
113
+ def invoke(cmd, args)
114
+ action_object.send(cmd, *args)
115
+ end
116
+
117
+ def parse_input(input)
118
+ if (@options[:two_d] || @options[:action])
119
+ tokens = input_to_tokens(input)
120
+ map_tokens(tokens)
121
+ else
122
+ Util.choose_from_array(@output, input)
123
+ end
124
+ end
125
+
126
+ def map_tokens(tokens)
127
+ if return_cell_values?
128
+ @output[0].is_a?(Hash) ? tokens.map {|arr,f| arr.map {|e| e[f]} }.flatten :
129
+ tokens.map {|arr,f| arr.map {|e| e.send(f) } }.flatten
30
130
  else
31
- output.each_with_index {|e,i| puts "#{i+1}: #{e}" }
131
+ tokens.map {|e| e[0] }.flatten
32
132
  end
33
- print options[:prompt]
34
- input = $stdin.gets.chomp.strip
35
- chosen = Util.choose_from_array(output, input)
36
- if options[:validate_one]
37
- if chosen.size != 1
38
- $stderr.puts "Choose one. You chose #{chosen.size} items."
39
- return nil
40
- else
41
- return chosen[0]
42
- end
133
+ end
134
+
135
+ def return_cell_values?
136
+ @options[:two_d]
137
+ end
138
+
139
+ def input_to_tokens(input)
140
+ @new_args = []
141
+ tokens = (@args = split_input_args(input)).map {|word| parse_word(word) }.compact
142
+ cleanup_new_args
143
+ tokens
144
+ end
145
+
146
+ def parse_word(word)
147
+ if word[CHOSEN_REGEXP]
148
+ @new_args << CHOSEN_ARG
149
+ field = $3 ? unalias_field($3) : default_field ||
150
+ raise(Error, "No default field/column found. Fields must be explicitly picked.")
151
+ [Util.choose_from_array(@output, word), field ]
152
+ else
153
+ @new_args << word
154
+ nil
155
+ end
156
+ end
157
+
158
+ def cleanup_new_args
159
+ if @new_args.all? {|e| e == CHOSEN_ARG }
160
+ @new_args = [CHOSEN_ARG]
161
+ else
162
+ i = @new_args.index(CHOSEN_ARG) || raise(Error, "No rows chosen")
163
+ @new_args.delete(CHOSEN_ARG)
164
+ @new_args.insert(i, CHOSEN_ARG)
43
165
  end
44
- chosen
45
166
  end
167
+
168
+ def add_chosen_to_args(items)
169
+ args = @new_args.dup
170
+ args[args.index(CHOSEN_ARG)] = items
171
+ args
172
+ end
173
+
174
+ def command
175
+ @command ||= begin
176
+ cmd = (@new_args == [CHOSEN_ARG]) ? nil : @new_args.shift
177
+ cmd ||= @options[:command] || raise(Error, "No command given for action menu")
178
+ end
179
+ end
180
+
181
+ def action_object
182
+ @options[:action_object] || eval("self", TOPLEVEL_BINDING)
183
+ end
184
+
185
+ def split_input_args(input)
186
+ input.split(/\s+/)
187
+ end
188
+
189
+ def default_field
190
+ @default_field ||= @options[:default_field] || fields[0]
191
+ end
192
+
193
+ # Has to be called after displaying menu
194
+ def fields
195
+ @fields ||= @options[:fields] || (@options[:ask] && table_helper_class? && Helpers::Table.last_table ?
196
+ Helpers::Table.last_table.fields[1..-1] : [])
197
+ end
198
+
199
+ def table_helper_class?
200
+ @options[:helper_class].is_a?(Class) && (@options[:helper_class] < Helpers::Table || @options[:helper_class] == Helpers::AutoTable)
201
+ end
202
+
203
+ def unalias_field(field)
204
+ fields.sort_by {|e| e.to_s }.find {|e| e.to_s[/^#{field}/] } || raise(Error, "Invalid field '#{field}'")
205
+ end
206
+
207
+ def readline_loads?
208
+ require 'readline'
209
+ true
210
+ rescue LoadError
211
+ false
212
+ end
213
+ #:startdoc:
46
214
  end
47
215
  end
data/lib/hirb/util.rb CHANGED
@@ -48,7 +48,7 @@ module Hirb
48
48
  result.push(array[index]) if array[index]
49
49
  end
50
50
  end
51
- return result
51
+ result
52
52
  end
53
53
 
54
54
  # Determines if a shell command exists by searching for it in ENV['PATH'].
@@ -61,7 +61,7 @@ module Hirb
61
61
  def detect_terminal_size
62
62
  if (ENV['COLUMNS'] =~ /^\d+$/) && (ENV['LINES'] =~ /^\d+$/)
63
63
  [ENV['COLUMNS'].to_i, ENV['LINES'].to_i]
64
- elsif RUBY_PLATFORM =~ /java/ && command_exists?('tput')
64
+ elsif (RUBY_PLATFORM =~ /java/ || !STDIN.tty?) && command_exists?('tput')
65
65
  [`tput cols`.to_i, `tput lines`.to_i]
66
66
  else
67
67
  command_exists?('stty') ? `stty size`.scan(/\d+/).map { |s| s.to_i }.reverse : nil
@@ -81,5 +81,14 @@ module Hirb
81
81
  end
82
82
  fake.string
83
83
  end
84
+
85
+ # From Rubygems, determine a user's home.
86
+ def find_home
87
+ ['HOME', 'USERPROFILE'].each {|e| return ENV[e] if ENV[e] }
88
+ return "#{ENV['HOMEDRIVE']}#{ENV['HOMEPATH']}" if ENV['HOMEDRIVE'] && ENV['HOMEPATH']
89
+ File.expand_path("~")
90
+ rescue
91
+ File::ALT_SEPARATOR ? "C:/" : "/"
92
+ end
84
93
  end
85
94
  end
data/lib/hirb/view.rb CHANGED
@@ -9,11 +9,12 @@ module Hirb
9
9
  attr_reader :config
10
10
 
11
11
  # This activates view functionality i.e. the formatter, pager and size detection. If irb exists, it overrides irb's output
12
- # method with Hirb::View.view_output. If using Wirble, you should call this after it. The view configuration
12
+ # method with Hirb::View.view_output. When called multiple times, new configs are merged into the existing config.
13
+ # If using Wirble, you should call this after it. The view configuration
13
14
  # can be specified in a hash via a config file, as options to this method, as this method's block or any combination of these three.
14
15
  # In addition to the config keys mentioned in Hirb, the options also take the following keys:
15
16
  # ==== Options:
16
- # * config_file: Name of config file to read.
17
+ # * config_file: Name of config file(s) that are merged into existing config
17
18
  # * output_method: Specify an object's class and instance method (separated by a period) to be realiased with
18
19
  # hirb's view system. The instance method should take a string to be output. Default is IRB::Irb.output_value
19
20
  # if using irb.
@@ -22,15 +23,15 @@ module Hirb
22
23
  # Hirb::View.enable :formatter=>false, :output_method=>"Mini.output"
23
24
  # Hirb::View.enable {|c| c.output = {'String'=>{:class=>'Hirb::Helpers::Table'}} }
24
25
  def enable(options={}, &block)
25
- return puts("Already enabled.") if @enabled
26
- @enabled = true
27
- Hirb.config_file = options.delete(:config_file) if options[:config_file]
28
- @output_method = "IRB::Irb.output_value" if Object.const_defined?(:IRB)
29
- @output_method = options.delete(:output_method) if options[:output_method]
30
- load_config(Util.recursive_hash_merge(options, HashStruct.block_to_hash(block)))
26
+ Array(options.delete(:config_file)).each {|e|
27
+ @new_config_file = true
28
+ Hirb.config_files << e
29
+ }
30
+ enable_output_method(options.delete(:output_method))
31
+ puts "Using a block with View.enable will be *deprecated* in the next release" if block_given?
32
+ merge_or_load_config(Util.recursive_hash_merge(options, HashStruct.block_to_hash(block)))
31
33
  resize(config[:width], config[:height])
32
- alias_output_method(@output_method) if @output_method
33
- true
34
+ @enabled = true
34
35
  end
35
36
 
36
37
  # Indicates if Hirb::View is enabled.
@@ -112,6 +113,13 @@ module Hirb
112
113
  end
113
114
 
114
115
  #:stopdoc:
116
+ def enable_output_method(meth)
117
+ if (meth ||= Object.const_defined?(:IRB) ? "IRB::Irb.output_value" : false) && !@output_method
118
+ @output_method = meth
119
+ alias_output_method(@output_method)
120
+ end
121
+ end
122
+
115
123
  def unalias_output_method(output_method)
116
124
  klass, klass_method = output_method.split(".")
117
125
  eval %[
@@ -119,6 +127,7 @@ module Hirb
119
127
  alias_method :#{klass_method}, :non_hirb_view_output
120
128
  end
121
129
  ]
130
+ @output_method = nil
122
131
  end
123
132
 
124
133
  def alias_output_method(output_method)
@@ -176,6 +185,16 @@ module Hirb
176
185
 
177
186
  def formatter=(value); @formatter = value; end
178
187
 
188
+ def merge_or_load_config(additional_config={})
189
+ if @config && (@new_config_file || !additional_config.empty?)
190
+ Hirb.config = nil
191
+ load_config Util.recursive_hash_merge(@config, additional_config)
192
+ @new_config_file = false
193
+ elsif !@enabled
194
+ load_config(additional_config)
195
+ end
196
+ end
197
+
179
198
  def load_config(additional_config={})
180
199
  @config = Util.recursive_hash_merge default_config, additional_config
181
200
  formatter(true)
@@ -17,31 +17,16 @@ class Hirb::Helpers::AutoTableTest < Test::Unit::TestCase
17
17
  Hirb::Helpers::AutoTable.render(::Set.new([1,2,3])).should == expected_table
18
18
  end
19
19
 
20
- test "converts hash with any value hashes to inspected values" do
20
+ test "renders hash" do
21
21
  expected_table = <<-TABLE.unindent
22
- +---+---------+
23
- | 0 | 1 |
24
- +---+---------+
25
- | c | "ok" |
26
- | a | {:b=>1} |
27
- +---+---------+
28
- 2 rows in set
22
+ +---+-------+
23
+ | 0 | 1 |
24
+ +---+-------+
25
+ | a | 12345 |
26
+ +---+-------+
27
+ 1 row in set
29
28
  TABLE
30
- Hirb::Helpers::AutoTable.render({:a=>{:b=>1}, :c=>'ok'}).should == expected_table
31
- end
32
-
33
- test "doesn't convert hash with value hashes if filter exists for value" do
34
- expected_table = <<-TABLE.unindent
35
- +------+-------+
36
- | name | value |
37
- +------+-------+
38
- | c | ok |
39
- | a | b1 |
40
- +------+-------+
41
- 2 rows in set
42
- TABLE
43
- Hirb::Helpers::AutoTable.render({:a=>{:b=>1}, :c=>'ok'}, :change_fields=>['name', 'value'],
44
- :filters=>{'value'=>:to_s}).should == expected_table
29
+ Hirb::Helpers::AutoTable.render({:a=>12345}).should == expected_table
45
30
  end
46
31
  end
47
32
  end
@@ -75,9 +75,9 @@ class FormatterTest < Test::Unit::TestCase
75
75
  formatter_config["Blah"].should == {:class=>"Hirb::Views::Blah", :ancestor=>true}
76
76
  end
77
77
 
78
- test "with block sets formatter config" do
78
+ test "sets formatter config" do
79
79
  class_hash = {"Something::Base"=>{:class=>"BlahBlah"}}
80
- Hirb.enable {|c| c.output = class_hash }
80
+ Hirb.enable :output=>class_hash
81
81
  formatter_config['Something::Base'].should == class_hash['Something::Base']
82
82
  end
83
83
  end