hirb 0.1.2 → 0.2.2

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