hirb 0.2.9 → 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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