hirb 0.1.2 → 0.2.2

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.
data/lib/hirb/view.rb CHANGED
@@ -1,195 +1,171 @@
1
1
  module Hirb
2
- # This class contains one method, render_output, which formats and renders the output its given from a console application.
3
- # However, this only happens for output classes that are configured to do so or if render_output is explicitly given
4
- # a view formatter. The hash with the following keys are valid for Hirb::View.config as well as the :view key mentioned in Hirb:
5
- # [:output] This hash is saved to output_config. It maps output classes to hashes that are passed to render_output. Thus these hashes
6
- # take the same options as render_output. In addition it takes the following keys:
7
- # * :ancestor- Boolean which if true allows all subclasses of the configured output class to inherit this config.
8
- #
9
- # Example: {'String'=>{:class=>'Hirb::Helpers::Table', :ancestor=>true, :options=>{:max_width=>180}}}
2
+ # This class is responsible for managing all view-related functionality. Its functionality is determined by setting up a configuration file
3
+ # as explained in Hirb and/or passed configuration directly to Hirb.enable. Most of the functionality in this class is dormant until enabled.
10
4
  module View
5
+ DEFAULT_WIDTH = 120
6
+ DEFAULT_HEIGHT = 40
11
7
  class<<self
12
- attr_accessor :config, :render_method
8
+ attr_accessor :render_method
9
+ attr_reader :config
13
10
 
14
- # Overrides irb's output method with Hirb::View.render_output. Takes an optional
15
- # block which sets the view config.
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
13
+ # 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
+ # In addition to the config keys mentioned in Hirb, the options also take the following keys:
15
+ # Options:
16
+ # * config_file: Name of config file to read.
16
17
  # Examples:
17
- # Hirb.enable
18
- # Hirb.enable {|c| c.output = {'String'=>{:class=>'Hirb::Helpers::Table'}} }
19
- def enable(&block)
18
+ # Hirb::View.enable
19
+ # Hirb::View.enable :formatter=>false
20
+ # Hirb::View.enable {|c| c.output = {'String'=>{:class=>'Hirb::Helpers::Table'}} }
21
+ def enable(options={}, &block)
20
22
  return puts("Already enabled.") if @enabled
21
23
  @enabled = true
22
- load_config(Hirb::HashStruct.block_to_hash(block))
23
- ::IRB::Irb.class_eval do
24
- alias :non_hirb_render_output :output_value
25
- def output_value #:nodoc:
26
- Hirb::View.render_output(@context.last_value) || non_hirb_render_output
24
+ Hirb.config_file = options.delete(:config_file) if options[:config_file]
25
+ load_config(Util.recursive_hash_merge(options, HashStruct.block_to_hash(block)))
26
+ resize(config[:width], config[:height])
27
+ if Object.const_defined?(:IRB)
28
+ ::IRB::Irb.class_eval do
29
+ alias :non_hirb_view_output :output_value
30
+ def output_value #:nodoc:
31
+ Hirb::View.view_output(@context.last_value) || Hirb::View.page_output(@context.last_value.inspect, true) ||
32
+ non_hirb_view_output
33
+ end
27
34
  end
28
35
  end
29
36
  end
30
-
31
- # Disable's Hirb's output by reverting back to irb's.
37
+
38
+ # Indicates if Hirb::View is enabled.
39
+ def enabled?
40
+ @enabled || false
41
+ end
42
+
43
+ # Disable's Hirb's output and revert's irb's output method if irb exists.
32
44
  def disable
33
45
  @enabled = false
34
- ::IRB::Irb.class_eval do
35
- alias :output_value :non_hirb_render_output
46
+ if Object.const_defined?(:IRB)
47
+ ::IRB::Irb.class_eval do
48
+ alias :output_value :non_hirb_view_output
49
+ end
36
50
  end
37
51
  end
52
+
53
+ # Toggles pager on or off. The pager only works while Hirb::View is enabled.
54
+ def toggle_pager
55
+ config[:pager] = !config[:pager]
56
+ end
57
+
58
+ # Toggles formatter on or off.
59
+ def toggle_formatter
60
+ config[:formatter] = !config[:formatter]
61
+ end
62
+
63
+ # Resizes the console width and height for use with the table and pager i.e. after having resized the console window. *nix users
64
+ # should only have to call this method. Non-*nix users should call this method with explicit width and height. If you don't know
65
+ # your width and height, in irb play with "a"* width to find width and puts "a\n" * height to find height.
66
+ def resize(width=nil, height=nil)
67
+ config[:width], config[:height] = determine_terminal_size(width, height)
68
+ pager.resize(config[:width], config[:height])
69
+ end
38
70
 
39
- # This is the main method of this class. This method searches for the first formatter it can apply
40
- # to the object in this order: local block, method option, class option. If a formatter is found it applies it to the object
41
- # and returns true. Returns false if no formatter found.
42
- # ==== Options:
43
- # [:method] Specifies a global (Kernel) method to do the formatting.
44
- # [:class] Specifies a class to do the formatting, using its render() class method. The render() method's arguments are the output and
45
- # an options hash.
46
- # [:output_method] Specifies a method to call on output before passing it to a formatter.
47
- # [:options] Options to pass the formatter method or class.
48
- def render_output(output, options={}, &block)
49
- if block && block.arity > 0
50
- formatted_output = block.call(output)
51
- render_method.call(formatted_output)
52
- true
53
- elsif (formatted_output = format_output(output, options))
54
- render_method.call(formatted_output)
55
- true
56
- else
57
- false
58
- end
71
+ # This is the main method of this class. When view is enabled, this method searches for a formatter it can use for the output and if
72
+ # successful renders it using render_method(). The options this method takes are helper config hashes as described in
73
+ # Hirb::Formatter.format_output(). Returns true if successful and false if no formatting is done or if not enabled.
74
+ def view_output(output, options={})
75
+ enabled? && config[:formatter] && render_output(output, options)
76
+ end
77
+
78
+ # Captures STDOUT and renders it using render_method(). The main use case is to conditionally page captured stdout.
79
+ def capture_and_render(&block)
80
+ render_method.call Util.capture_stdout(&block)
59
81
  end
60
82
 
61
83
  # A lambda or proc which handles the final formatted object.
62
- # Although this puts the object by default, it could be set to do other things
63
- # ie write the formatted object to a file.
84
+ # Although this pages/puts the object by default, it could be set to do other things
85
+ # i.e. write the formatted object to a file.
64
86
  def render_method
65
87
  @render_method ||= default_render_method
66
88
  end
67
89
 
90
+ # Resets render_method back to its default.
68
91
  def reset_render_method
69
92
  @render_method = default_render_method
70
93
  end
71
-
72
- # Config hash which maps classes to view hashes. View hashes are the same as the options hash of render_output().
73
- def output_config
74
- config[:output]
94
+
95
+ # Current console width
96
+ def width
97
+ config ? config[:width] : DEFAULT_WIDTH
75
98
  end
76
99
 
77
- def output_config=(value)
78
- @config[:output] = value
100
+ # Current console height
101
+ def height
102
+ config ? config[:height] : DEFAULT_HEIGHT
79
103
  end
80
-
81
- # Needs to be called for config changes to take effect. Reloads Hirb::Views classes and registers
82
- # most recent config changes.
83
- def reload_config
84
- current_config = self.config.dup.merge(:output=>output_config)
85
- load_config(current_config)
104
+
105
+ # Current formatter config
106
+ def formatter_config
107
+ formatter.config
86
108
  end
87
-
88
- # A console version of render_output which takes its same options but allows for some shortcuts.
89
- # Examples:
90
- # console_render_output output, :tree, :type=>:directory
91
- # # is the same as:
92
- # render_output output, :class=>"Hirb::Helpers::Tree", :options=> {:type=>:directory}
93
- #
94
- def console_render_output(*args, &block)
95
- load_config unless @config
96
- output = args.shift
97
- if args[0].is_a?(Symbol) && (view = args.shift)
98
- symbol_options = find_view(view)
99
- end
100
- options = args[-1].is_a?(Hash) ? args[-1] : {}
101
- options.merge!(symbol_options) if symbol_options
102
- # iterates over format_output options that aren't :options
103
- real_options = [:method, :class, :output_method].inject({}) do |h, e|
104
- h[e] = options.delete(e) if options[e]; h
105
- end
106
- render_output(output, real_options.merge(:options=>options), &block)
109
+
110
+ # Sets the helper config for the given output class.
111
+ def format_class(klass, helper_config)
112
+ formatter.format_class(klass, helper_config)
107
113
  end
108
114
 
109
115
  #:stopdoc:
110
- def find_view(name)
111
- name = name.to_s
112
- if (view_method = output_config.values.find {|e| e[:method] == name })
113
- {:method=>view_method[:method]}
114
- elsif (view_class = Hirb::Helpers.constants.find {|e| e == Util.camelize(name)})
115
- {:class=>"Hirb::Helpers::#{view_class}"}
116
+ def render_output(output, options={})
117
+ if (formatted_output = formatter.format_output(output, options))
118
+ render_method.call(formatted_output)
119
+ true
116
120
  else
117
- {}
121
+ false
118
122
  end
119
123
  end
120
124
 
121
- def format_output(output, options={})
122
- output_class = determine_output_class(output)
123
- options = Util.recursive_hash_merge(output_class_options(output_class), options)
124
- output = options[:output_method] ? (output.is_a?(Array) ? output.map {|e| e.send(options[:output_method])} :
125
- output.send(options[:output_method]) ) : output
126
- args = [output]
127
- args << options[:options] if options[:options] && !options[:options].empty?
128
- if options[:method]
129
- new_output = send(options[:method],*args)
130
- elsif options[:class] && (view_class = Util.any_const_get(options[:class]))
131
- new_output = view_class.render(*args)
132
- end
133
- new_output
125
+ def determine_terminal_size(width, height)
126
+ detected = (width.nil? || height.nil?) ? Util.detect_terminal_size || [] : []
127
+ [width || detected[0] || DEFAULT_WIDTH , height || detected[1] || DEFAULT_HEIGHT]
134
128
  end
135
129
 
136
- def determine_output_class(output)
137
- if output.is_a?(Array)
138
- output[0].class
130
+ def page_output(output, inspect_mode=false)
131
+ if enabled? && config[:pager] && pager.activated_by?(output, inspect_mode)
132
+ pager.page(output, inspect_mode)
133
+ true
139
134
  else
140
- output.class
135
+ false
141
136
  end
142
137
  end
143
138
 
144
- def load_config(additional_config={})
145
- self.config = Util.recursive_hash_merge default_config, additional_config
146
- true
139
+ def pager
140
+ @pager ||= Pager.new(config[:width], config[:height], :pager_command=>config[:pager_command])
147
141
  end
148
142
 
149
- # Stores all view config. Current valid keys:
150
- # :output- contains value of output_config
151
- def config=(value)
152
- reset_cached_output_config
153
- @config = value
143
+ def pager=(value); @pager = value; end
144
+
145
+ def formatter(reload=false)
146
+ @formatter = reload || @formatter.nil? ? Formatter.new(config[:output]) : @formatter
154
147
  end
155
-
156
- def reset_cached_output_config
157
- @cached_output_config = nil
148
+
149
+ def formatter=(value); @formatter = value; end
150
+
151
+ def load_config(additional_config={})
152
+ @config = Util.recursive_hash_merge default_config, additional_config
153
+ formatter(true)
154
+ true
158
155
  end
159
-
160
- # Internal view options built from user-defined ones. Options are built by recursively merging options from oldest
161
- # ancestors to the most recent ones.
162
- def output_class_options(output_class)
163
- @cached_output_config ||= {}
164
- @cached_output_config[output_class] ||=
165
- begin
166
- output_ancestors_with_config = output_class.ancestors.map {|e| e.to_s}.select {|e| output_config.has_key?(e)}
167
- @cached_output_config[output_class] = output_ancestors_with_config.reverse.inject({}) {|h, klass|
168
- (klass == output_class.to_s || output_config[klass][:ancestor]) ? h.update(output_config[klass]) : h
169
- }
170
- end
171
- @cached_output_config[output_class]
156
+
157
+ def config_loaded?; !!@config; end
158
+
159
+ def config
160
+ @config
172
161
  end
173
162
 
174
- def cached_output_config; @cached_output_config; end
175
-
176
163
  def default_render_method
177
- lambda {|output| puts output}
164
+ lambda {|output| page_output(output) || puts(output) }
178
165
  end
179
166
 
180
167
  def default_config
181
- Hirb::Util.recursive_hash_merge({:output=>default_output_config}, Hirb.config[:view] || {} )
182
- end
183
-
184
- def default_output_config
185
- Hirb::Views.constants.inject({}) {|h,e|
186
- output_class = e.to_s.gsub("_", "::")
187
- if (views_class = Hirb::Views.const_get(e)) && views_class.respond_to?(:render)
188
- default_options = views_class.respond_to?(:default_options) ? views_class.default_options : {}
189
- h[output_class] = default_options.merge({:class=>"Hirb::Views::#{e}"})
190
- end
191
- h
192
- }
168
+ Util.recursive_hash_merge({:pager=>true, :formatter=>true}, Hirb.config || {})
193
169
  end
194
170
  #:startdoc:
195
171
  end
@@ -0,0 +1,35 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class Hirb::Helpers::ActiveRecordTableTest < Test::Unit::TestCase
4
+ context "activerecord table" do
5
+ test "with no select renders" do
6
+ expected_table = <<-TABLE.unindent
7
+ +-----+-------+
8
+ | age | name |
9
+ +-----+-------+
10
+ | 7 | rufus |
11
+ | 101 | alf |
12
+ +-----+-------+
13
+ 2 rows in set
14
+ TABLE
15
+ @pets = [stub(:name=>'rufus', :age=>7, :attributes=>{"name"=>'rufus', 'age'=>7}, :class=>stub(:column_names=>%w{age name})),
16
+ stub(:name=>'alf', :age=>101)]
17
+ Hirb::Helpers::ActiveRecordTable.render(@pets).should == expected_table
18
+ end
19
+
20
+ test "with select renders" do
21
+ expected_table = <<-TABLE.unindent
22
+ +-------+
23
+ | name |
24
+ +-------+
25
+ | rufus |
26
+ | alf |
27
+ +-------+
28
+ 2 rows in set
29
+ TABLE
30
+ @pets = [stub(:name=>'rufus', :age=>7, :attributes=>{'name'=>'rufus'}, :class=>stub(:column_names=>%w{age name})),
31
+ stub(:name=>'alf', :age=>101)]
32
+ Hirb::Helpers::ActiveRecordTable.render(@pets).should == expected_table
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class Hirb::Helpers::AutoTableTest < Test::Unit::TestCase
4
+ context "auto table" do
5
+ test "converts nonarrays to arrays and renders" do
6
+ require 'set'
7
+ expected_table = <<-TABLE.unindent
8
+ +-------+
9
+ | value |
10
+ +-------+
11
+ | 1 |
12
+ | 2 |
13
+ | 3 |
14
+ +-------+
15
+ 3 rows in set
16
+ TABLE
17
+ Hirb::Helpers::AutoTable.render(::Set.new([1,2,3])).should == expected_table
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ class Hirb::ConsoleTest < Test::Unit::TestCase
4
+ context "parse_input" do
5
+ test "config is set if it wasn't before" do
6
+ reset_config
7
+ Hirb::View.expects(:render_output)
8
+ Hirb::Console.render_output('blah')
9
+ Hirb::View.config.is_a?(Hash).should == true
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,172 @@
1
+ require File.join(File.dirname(__FILE__), 'test_helper')
2
+
3
+ module Hirb
4
+ class FormatterTest < Test::Unit::TestCase
5
+ context "formatter" do
6
+ def set_formatter(hash={})
7
+ @formatter = Formatter.new(hash)
8
+ end
9
+
10
+ before(:all) { eval "module ::Dooda; end" }
11
+
12
+ test "klass_config merges ancestor options" do
13
+ set_formatter "String"=>{:args=>[1,2]}, "Object"=>{:method=>:object_output, :ancestor=>true}, "Kernel"=>{:method=>:default_output}
14
+ expected_result = {:method=>:object_output, :args=>[1, 2], :ancestor=>true}
15
+ @formatter.klass_config(String).should == expected_result
16
+ end
17
+
18
+ test "klass_config doesn't merge ancestor options" do
19
+ set_formatter "String"=>{:args=>[1,2]}, "Object"=>{:method=>:object_output}, "Kernel"=>{:method=>:default_output}
20
+ expected_result = {:args=>[1, 2]}
21
+ @formatter.klass_config(String).should == expected_result
22
+ end
23
+
24
+ test "klass_config returns hash when nothing found" do
25
+ set_formatter.klass_config(String).should == {}
26
+ end
27
+
28
+ test "reload detects new Hirb::Views" do
29
+ set_formatter
30
+ @formatter.config.keys.include?('Zzz').should be(false)
31
+ eval "module ::Hirb::Views::Zzz; def self.render; end; end"
32
+ @formatter.reload
33
+ @formatter.config.keys.include?('Zzz').should be(true)
34
+ end
35
+
36
+ test "format_class sets formatter config" do
37
+ set_formatter
38
+ @formatter.format_class ::Dooda, :class=>"DoodaView"
39
+ @formatter.klass_config(::Dooda).should == {:class=>"DoodaView"}
40
+ end
41
+
42
+ test "format_class overwrites existing formatter config" do
43
+ set_formatter "Dooda"=>{:class=>"DoodaView"}
44
+ @formatter.format_class ::Dooda, :class=>"DoodaView2"
45
+ @formatter.klass_config(::Dooda).should == {:class=>"DoodaView2"}
46
+ end
47
+
48
+ test "parse_console_options passes all options except for formatter options into :options" do
49
+ set_formatter
50
+ options = {:class=>'blah', :method=>'blah', :output_method=>'blah', :blah=>'blah'}
51
+ expected_options = {:class=>'blah', :method=>'blah', :output_method=>'blah', :options=>{:blah=>'blah'}}
52
+ @formatter.parse_console_options(options).should == expected_options
53
+ end
54
+ end
55
+
56
+ context "enable" do
57
+ before(:each) { View.formatter = nil; reset_config }
58
+ after(:each) { Hirb.disable }
59
+
60
+ def formatter_config
61
+ View.formatter.config
62
+ end
63
+
64
+ test "sets default formatter config" do
65
+ eval "module ::Hirb::Views::Something_Base; def self.render; end; end"
66
+ Hirb.enable
67
+ formatter_config["Something::Base"].should == {:class=>"Hirb::Views::Something_Base"}
68
+ end
69
+
70
+ test "sets default formatter config with default_options" do
71
+ eval "module ::Hirb::Views::Blah; def self.render; end; def self.default_options; {:ancestor=>true}; end; end"
72
+ Hirb.enable
73
+ formatter_config["Blah"].should == {:class=>"Hirb::Views::Blah", :ancestor=>true}
74
+ end
75
+
76
+ test "with block sets formatter config" do
77
+ class_hash = {"Something::Base"=>{:class=>"BlahBlah"}}
78
+ Hirb.enable {|c| c.output = class_hash }
79
+ formatter_config['Something::Base'].should == class_hash['Something::Base']
80
+ end
81
+ end
82
+
83
+ context "format_output" do
84
+ def view_output(*args, &block); View.view_output(*args, &block); end
85
+ def render_method(*args); View.render_method(*args); end
86
+
87
+ def enable_with_output(value)
88
+ Hirb.enable :output=>value
89
+ end
90
+
91
+ before(:all) {
92
+ eval %[module ::Commify
93
+ def self.render(strings)
94
+ strings = [strings] unless strings.is_a?(Array)
95
+ strings.map {|e| e.split('').join(',')}.join("\n")
96
+ end
97
+ end]
98
+ reset_config
99
+ }
100
+ before(:each) { View.formatter = nil; reset_config }
101
+ after(:each) { Hirb.disable }
102
+
103
+ test "formats with method option" do
104
+ eval "module ::Kernel; def commify(string); string.split('').join(','); end; end"
105
+ enable_with_output "String"=>{:method=>:commify}
106
+ render_method.expects(:call).with('d,u,d,e')
107
+ view_output('dude')
108
+ end
109
+
110
+ test "formats with class option" do
111
+ enable_with_output "String"=>{:class=>"Commify"}
112
+ render_method.expects(:call).with('d,u,d,e')
113
+ view_output('dude')
114
+ end
115
+
116
+ test "formats with class option as symbol" do
117
+ enable_with_output "String"=>{:class=>:auto_table}
118
+ Helpers::AutoTable.expects(:render)
119
+ view_output('dude')
120
+ end
121
+
122
+ test "formats output array" do
123
+ enable_with_output "String"=>{:class=>"Commify"}
124
+ render_method.expects(:call).with('d,u,d,e')
125
+ view_output(['dude'])
126
+ end
127
+
128
+ test "formats with options option" do
129
+ eval "module ::Blahify; def self.render(*args); end; end"
130
+ enable_with_output "String"=>{:class=>"Blahify", :options=>{:fields=>%w{a b}}}
131
+ Blahify.expects(:render).with('dude', :fields=>%w{a b})
132
+ view_output('dude')
133
+ end
134
+
135
+ test "doesn't format and returns false when no format method found" do
136
+ Hirb.enable
137
+ render_method.expects(:call).never
138
+ view_output(Date.today).should == false
139
+ end
140
+
141
+ test "formats with output_method option as method" do
142
+ enable_with_output 'String'=>{:class=>"Commify", :output_method=>:chop}
143
+ render_method.expects(:call).with('d,u,d')
144
+ view_output('dude')
145
+ end
146
+
147
+ test "formats with output_method option as proc" do
148
+ enable_with_output 'String'=>{:class=>"Commify", :output_method=>lambda {|e| e.chop}}
149
+ render_method.expects(:call).with('d,u,d')
150
+ view_output('dude')
151
+ end
152
+
153
+ test "formats output array with output_method option" do
154
+ enable_with_output 'String'=>{:class=>"Commify", :output_method=>:chop}
155
+ render_method.expects(:call).with("d,u,d\nm,a")
156
+ view_output(['dude', 'man'])
157
+ end
158
+
159
+ test "formats with explicit class option" do
160
+ enable_with_output 'String'=>{:class=>"Blahify"}
161
+ render_method.expects(:call).with('d,u,d,e')
162
+ view_output('dude', :class=>"Commify")
163
+ end
164
+
165
+ test "formats with explicit options option merges with existing options" do
166
+ enable_with_output "String"=>{:class=>"Commify", :options=>{:fields=>%w{f1 f2}}}
167
+ Commify.expects(:render).with('dude', :max_width=>10, :fields=>%w{f1 f2})
168
+ view_output('dude', :options=>{:max_width=>10})
169
+ end
170
+ end
171
+ end
172
+ end