hirb 0.2.10 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,17 @@
1
+ == 0.3.0
2
+ * Added dynamic views.
3
+ * Added default table views for the following database classes/modules:
4
+ CouchFoo::Base, CouchPotato::Persistence, CouchRest::ExtendedDocument,
5
+ DBI::Row, DataMapper::Resource, Friendly::Document, MongoMapper::Document, MongoMapper::EmbeddedDocument,
6
+ Mongoid::Document, Ripple::Document and Sequel::Model.
7
+ * Added Hirb.add_view and Hirb.add_dynamic_view for easier view manipulation.
8
+ * Added :multi_line_nodes option for Tree.
9
+ * Fixed :change_fields option bug in Table.
10
+ * Fixed no headers and nil fields bug in Table.
11
+ * Removed deprecations in Hirb.config_file + View.enable.
12
+ * Removed Views classes and View.format_class.
13
+ * Removed :return_rows option for Table.
14
+
1
15
  == 0.2.10
2
16
  * Added multiple options to Menu, most importantly :two_d and :action.
3
17
  * Improved table resizing algorithm.
@@ -1,12 +1,14 @@
1
1
  == Description
2
2
 
3
- Hirb currently provides a mini view framework for console applications, designed to improve irb's default output.
4
- Hirb improves console output by providing a smart pager and auto-formatting output. The smart pager detects when an output exceeds
5
- a screenful and thus only pages output as needed. Auto-formatting adds a view to an output's class. This is helpful in separating
6
- views from content (MVC anyone?). The framework encourages reusing views by letting you
7
- package them in classes and associate them with any number of output classes. Hirb comes with tree views (see
8
- Hirb::Helpers::Tree) and table views (see Hirb::Helpers::Table). By default Hirb displays Rails'
9
- model classes as tables. Hirb also sports a nice selection menu, Hirb::Menu.
3
+ Hirb provides a mini view framework for console applications and uses it to improve irb's default inspect output.
4
+ Given an object or array of objects, hirb renders a view based on the object's class and/or ancestry. Hirb offers reusable
5
+ views in the form of helper classes. The two main helpers, Hirb::Helpers::Table and Hirb::Helpers::Tree, provide several options
6
+ for generating ascii tables and trees. Using Hirb::Helpers::AutoTable, hirb has useful default views for at least ten popular database gems
7
+ i.e. Rails' ActiveRecord::Base. Other than views, hirb offers a smart pager and a console menu. The smart pager
8
+ only pages when the output exceeds the current screen size. The menu is used in conjunction with tables to offer
9
+ {two dimensional menus}[http://tagaholic.me/2010/02/16/two-dimensional-console-menus-with-hirb.html].
10
+
11
+ Note: To read a linked version of this README, {click here}[http://tagaholic.me/hirb/doc/].
10
12
 
11
13
  == Install
12
14
 
@@ -14,40 +16,28 @@ Install the gem with:
14
16
 
15
17
  sudo gem install hirb
16
18
 
17
- == Pager
18
-
19
- Hirb has both pager and formatter functionality enabled by default.
20
- If you want to turn off the functionality of either you can pass that in at startup:
19
+ == View Tutorials
21
20
 
22
- Hirb.enable :pager=>false
23
- Hirb.enable :formatter=>false
21
+ * To create and configure views, see Hirb::View or {here if on the web}[http://tagaholic.me/hirb/doc/classes/Hirb/View.html].
22
+ * To create dynamic views, see Hirb::DynamicView or {here if on the web}[http://tagaholic.me/hirb/doc/classes/Hirb/DynamicView.html].
24
23
 
25
- or toggle their state at runtime:
26
-
27
- Hirb::View.toggle_pager
28
- Hirb::View.toggle_formatter
29
-
30
- == Create and Configure Views
31
-
32
- If you'd like to learn how to create and configure views, {read the docs}[http://tagaholic.me/hirb/doc/classes/Hirb/Formatter.html].
33
-
34
- == Rails Formatter Example
24
+ == Rails Example
35
25
 
36
26
  Let's load and enable the view framework:
37
- bash> script/console
38
- Loading local environment (Rails 2.2.2)
39
- irb>> require 'hirb'
27
+ $ script/console
28
+ Loading local environment (Rails 2.3.5)
29
+ >> require 'hirb'
40
30
  => true
41
- irb>> Hirb.enable
31
+ >> Hirb.enable
42
32
  => nil
43
33
 
44
34
  The default configuration provides table views for ActiveRecord::Base descendants.
45
35
  If a class isn't configured, Hirb reverts to irb's default echo mode.
46
- irb>> Hirb::View.formatter_config
47
- => {"ActiveRecord::Base"=>{:class=>"Hirb::Views::ActiveRecord_Base", :ancestor=>true}}
36
+ >> Hirb::Formatter.dynamic_config['ActiveRecord::Base']
37
+ => {:class=>Hirb::Helpers::AutoTable, :ancestor=>true}
48
38
 
49
39
  # Tag is a model class and descendant of ActiveRecord::Base
50
- irb>> Tag.last
40
+ >> Tag.last
51
41
  +-----+-------------------------+-------------+---------------+-----------+-----------+-------+
52
42
  | id | created_at | description | name | namespace | predicate | value |
53
43
  +-----+-------------------------+-------------+---------------+-----------+-----------+-------+
@@ -55,19 +45,22 @@ If a class isn't configured, Hirb reverts to irb's default echo mode.
55
45
  +-----+-------------------------+-------------+---------------+-----------+-----------+-------+
56
46
  1 row in set
57
47
 
58
- irb>> 'plain ol irb'
48
+ >> Hirb::Formatter.dynamic_config['String']
49
+ => nil
50
+ >> 'plain ol irb'
59
51
  => 'plain ol irb'
60
- irb>> :blah
52
+ >> Hirb::Formatter.dynamic_config['Symbol']
53
+ => nil
54
+ >> :blah
61
55
  => :blah
62
56
 
63
- From above you can see there were no views configured for a String or a Symbol so Hirb defaulted to
64
- irb's echo mode. Also note that Tag was able to inherit its view from the ActiveRecord::Base config
65
- because it had an :ancestor option.
57
+ From above you can see there are no views configured for a String or a Symbol so Hirb defaults to
58
+ irb's echo mode. On the other hand, Tag has a view thanks to being a descendant of ActiveRecord::Base
59
+ and there being an :ancestor option.
66
60
 
67
- Now that you understand that Hirb displays views based on an output object's class,
68
- you may appreciate it also detects configured output objects in an array:
61
+ Having seen hirb display views based on an output object's class, let's see it handle an array of objects:
69
62
 
70
- irb>> Tag.all :limit=>3, :order=>"id DESC"
63
+ >> Tag.all :limit=>3, :order=>"id DESC"
71
64
  +-----+-------------------------+-------------+-------------------+-----------+-----------+----------+
72
65
  | id | created_at | description | name | namespace | predicate | value |
73
66
  +-----+-------------------------+-------------+-------------------+-----------+-----------+----------+
@@ -78,9 +71,9 @@ you may appreciate it also detects configured output objects in an array:
78
71
  3 rows in set
79
72
 
80
73
  At any time you can disable Hirb if you really like irb's lovely echo mode:
81
- irb>> Hirb.disable
74
+ >> Hirb.disable
82
75
  => nil
83
- irb>> Tag.all :limit=>3, :order=>"id DESC"
76
+ >> Tag.all :limit=>3, :order=>"id DESC"
84
77
  => [#<Tag id: 907, name: "gem:tags=yaml", description: nil, created_at: "2009-03-06 21:10:41",
85
78
  namespace: "gem", predicate: "tags", value: "yaml">, #<Tag id: 906, name: "gem:tags=nomonkey",
86
79
  description: nil, created_at: "2009-03-06 08:47:04", namespace: "gem", predicate: "tags", value:
@@ -92,15 +85,15 @@ While preconfigured tables are great for database records, sometimes you just wa
92
85
  tables/views for any output object:
93
86
 
94
87
  #These examples don't need to have Hirb::View enabled.
95
- irb>>Hirb.disable
96
- =>nil
88
+ >> Hirb.disable
89
+ => nil
97
90
 
98
91
  # Imports table() and view()
99
- irb>>extend Hirb::Console
100
- =>main
92
+ >> extend Hirb::Console
93
+ => main
101
94
 
102
95
  # Create a table of Dates comparing them with different formats.
103
- irb>> table [Date.today, Date.today.next_month], :fields=>[:to_s, :ld, :ajd, :amjd, :asctime]
96
+ >> table [Date.today, Date.today.next_month], :fields=>[:to_s, :ld, :ajd, :amjd, :asctime]
104
97
  +------------+--------+-----------+-------+--------------------------+
105
98
  | to_s | ld | ajd | amjd | asctime |
106
99
  +------------+--------+-----------+-------+--------------------------+
@@ -110,44 +103,56 @@ tables/views for any output object:
110
103
  2 rows in set
111
104
 
112
105
  # Same table as the previous method. However view() will be able to call any helper.
113
- irb>> view [Date.today, Date.today.next_month], :class=>:object_table,
106
+ >> view [Date.today, Date.today.next_month], :class=>:object_table,
114
107
  :fields=>[:to_s, :ld, :ajd, :amjd, :asctime]
115
108
 
116
109
  If these console methods weren't convenient enough, try:
117
110
 
118
111
  # Imports view() to all objects.
119
- irb>> require 'hirb/import_object'
112
+ >> require 'hirb/import_object'
120
113
  =>true
121
114
  # Yields same table as above examples.
122
- irb>> [Date.today, Date.today.next_month].view :class=>:object_table,
115
+ >> [Date.today, Date.today.next_month].view :class=>:object_table,
123
116
  :fields=>[:to_s, :ld, :ajd, :amjd, :asctime]
124
117
 
125
118
  Although views by default are printed to STDOUT, they can be easily modified to write anywhere:
126
119
  # Setup views to write to file 'console.log'.
127
- irb>> Hirb::View.render_method = lambda {|output| File.open("console.log", 'w') {|f| f.write(output) } }
120
+ >> Hirb::View.render_method = lambda {|output| File.open("console.log", 'w') {|f| f.write(output) } }
128
121
 
129
122
  # Writes to file with same table output as above example.
130
- irb>> view [Date.today, Date.today.next_month], :class=>:object_table,
123
+ >> view [Date.today, Date.today.next_month], :class=>:object_table,
131
124
  :fields=>[:to_s, :ld, :ajd, :amjd, :asctime]
132
125
 
133
- # Doesn't write to file because Symbol isn't configured to use Hirb::View and thus defaults to irb's echo mode.
134
- irb>> :blah
126
+ # Doesn't write to file because Symbol doesn't have a view and thus defaults to irb's echo mode.
127
+ >> :blah
135
128
  =>:blah
136
129
 
137
130
  # Go back to printing Hirb views to STDOUT.
138
- irb>> Hirb::View.reset_render_method
131
+ >> Hirb::View.reset_render_method
132
+
133
+ == Pager
134
+
135
+ Hirb has both pager and formatter functionality enabled by default.
136
+ If you want to turn off the functionality of either you can pass that in at startup:
137
+
138
+ Hirb.enable :pager=>false
139
+ Hirb.enable :formatter=>false
140
+
141
+ or toggle their state at runtime:
142
+
143
+ Hirb::View.toggle_pager
144
+ Hirb::View.toggle_formatter
139
145
 
140
- == Sharing Views
141
- If you have tested views you'd like to share, fork Hirb and put your views under
142
- the lib/hirb/views/ and/or helpers files under lib/hirb/helpers/. If not tested, feel free to share
143
- them on the wiki.
146
+ == Sharing Helpers and Views
147
+ If you have tested helpers you'd like to share, fork Hirb and put them under lib/hirb/helpers. To share
148
+ views for certain classes, put them under lib/hirb/views. Please submit views for gems that have a nontrivial
149
+ number of users.
144
150
 
145
151
  == Limitations
146
152
  If using Wirble, you should call Hirb after it since they both override irb's default output.
147
153
 
148
154
  == Motivation
149
- Table code from http://gist.github.com/72234 and {my console
150
- app's needs}[http://github.com/cldwalker/tag-tree].
155
+ Table code from http://gist.github.com/72234 and {my console app's needs}[http://github.com/cldwalker/tag-tree].
151
156
 
152
157
  == Credits
153
158
  * Chrononaut for vertical table helper.
@@ -162,7 +167,6 @@ Please report them {on github}[http://github.com/cldwalker/hirb/issues].
162
167
  * http://tagaholic.me/2009/06/19/page-irb-output-and-improve-ri-with-hirb.html
163
168
 
164
169
  == Todo
165
- * Consider applying multiple views/filters to output.
166
- * Consider mapping a class' methods to their own views.
167
- * Provides helper methods to all view classes.
168
- * Consider adding a template system as needed.
170
+ * Consider generating views based on methods an object responds to.
171
+ * Provide helper methods to all views.
172
+ * Consider adding a template helper.
data/Rakefile CHANGED
@@ -16,8 +16,11 @@ end
16
16
 
17
17
  begin
18
18
  require 'jeweler'
19
+ require File.dirname(__FILE__) + "/lib/hirb/version"
20
+
19
21
  Jeweler::Tasks.new do |s|
20
22
  s.name = "hirb"
23
+ s.version = Hirb::VERSION
21
24
  s.summary = "A mini view framework for console/irb that's easy to use, even while under its influence."
22
25
  s.description = "Hirb currently provides a mini view framework for console applications, designed to improve irb's default output. Hirb improves console output by providing a smart pager and auto-formatting output. The smart pager detects when an output exceeds a screenful and thus only pages output as needed. Auto-formatting adds a view to an output's class. This is helpful in separating views from content (MVC anyone?). The framework encourages reusing views by letting you package them in classes and associate them with any number of output classes."
23
26
  s.email = "gabriel.horner@gmail.com"
@@ -6,28 +6,29 @@ $KCODE = 'u' if RUBY_VERSION < '1.9'
6
6
 
7
7
  require 'hirb/util'
8
8
  require 'hirb/string'
9
- require 'hirb/hash_struct'
9
+ require 'hirb/formatter' # must come before helpers/auto_table
10
+ require 'hirb/dynamic_view'
10
11
  require 'hirb/helpers'
12
+ require 'hirb/views'
11
13
  require 'hirb/view'
12
- require 'hirb/views/activerecord_base'
13
14
  require 'hirb/console'
14
- require 'hirb/formatter'
15
15
  require 'hirb/pager'
16
16
  require 'hirb/menu'
17
+ require 'hirb/version'
17
18
 
18
- # Most of Hirb's functionality currently resides in Hirb::View.
19
- # For an in-depth tutorial on creating and configuring views see Hirb::Formatter.
19
+ # Most of Hirb's functionality is in Hirb::View.
20
+ # For a tutorial on configuring and creating views see Hirb::View. For a tutorial on dynamic views see Hirb::DynamicView.
21
+ #
22
+ # == Config Files
20
23
  # Hirb can have multiple config files defined by config_files(). These config files
21
24
  # have the following top level keys:
22
- # [:output] This hash is used by the formatter object. See Hirb::Formatter.config for its format.
23
- # [:width] Width of the terminal/console. Defaults to DEFAULT_WIDTH or possibly autodetected when Hirb is enabled.
24
- # [:height] Height of the terminal/console. Defaults to DEFAULT_HEIGHT or possibly autodetected when Hirb is enabled.
25
- # [:formatter] Boolean which determines if the formatter is enabled. Defaults to true.
26
- # [:pager] Boolean which determines if the pager is enabled. Defaults to true.
27
- # [:pager_command] Command to be used for paging. Command can have options after it i.e. 'less -r'.
28
- # Defaults to common pagers i.e. less and more if detected.
29
- #
30
-
25
+ # [*:output*] This hash is used by the formatter object. See Hirb::Formatter.config for its format.
26
+ # [*:width*] Width of the terminal/console. Defaults to Hirb::View::DEFAULT_WIDTH or possibly autodetected when Hirb is enabled.
27
+ # [*:height*] Height of the terminal/console. Defaults to Hirb::View::DEFAULT_HEIGHT or possibly autodetected when Hirb is enabled.
28
+ # [*:formatter*] Boolean which determines if the formatter is enabled. Defaults to true.
29
+ # [*:pager*] Boolean which determines if the pager is enabled. Defaults to true.
30
+ # [*:pager_command*] Command to be used for paging. Command can have options after it i.e. 'less -r'.
31
+ # Defaults to common pagers i.e. less and more if detected.
31
32
  module Hirb
32
33
  class <<self
33
34
  attr_accessor :config_files, :config
@@ -42,6 +43,16 @@ module Hirb
42
43
  View.disable
43
44
  end
44
45
 
46
+ # Adds views. See Hirb::View.add for details.
47
+ def add_view(view, options, &block)
48
+ View.add(view, options, &block)
49
+ end
50
+
51
+ # Adds views. See Hirb::DynamicView.add for details.
52
+ def add_dynamic_view(view, options, &block)
53
+ DynamicView.add(view, options, &block)
54
+ end
55
+
45
56
  # Array of config files which are merged sequentially to produce config.
46
57
  # Defaults to config/hirb.yml and ~/.hirb_yml
47
58
  def config_files
@@ -49,10 +60,6 @@ module Hirb
49
60
  end
50
61
 
51
62
  #:stopdoc:
52
- def config_file
53
- puts "Hirb.config_file is *deprecated*. Use Hirb.config_files"
54
- end
55
-
56
63
  def default_config_files
57
64
  [File.join(Util.find_home, ".hirb.yml")] +
58
65
  (File.exists?('config/hirb.yml') ? ['config/hirb.yml'] : [])
@@ -1,9 +1,9 @@
1
1
  module Hirb
2
2
  # This module is meant to be extended to provide methods for use in a console/irb shell.
3
3
  # For example:
4
- # irb>> extend Hirb::Console
5
- # irb>> view 'some string', :class=>Some::String::Formatter
6
- # irb>> table [[:row1], [:row2]]
4
+ # >> extend Hirb::Console
5
+ # >> view 'some string', :class=>Some::String::Formatter
6
+ # >> table [[:row1], [:row2]]
7
7
  module Console
8
8
  class<<self
9
9
  # A console version of render_output() which takes its same options but allows for shorthand. All options are passed to
@@ -0,0 +1,112 @@
1
+ module Hirb
2
+ # This module extends a Helper with the ability to have dynamic views for configured output classes.
3
+ # After a Helper has extended this module, it can use it within a render() by calling
4
+ # dynamic_options() to get dynamically generated options for the object it's rendering. See Hirb::Helpers::AutoTable as an example.
5
+ #
6
+ # == Dynamic Views
7
+ # Whereas normal views are generated from helpers with static helper options, dynamic views are generated from helpers and
8
+ # dynamically generated helper options. Let's look at an example for Rails' ActiveRecord classes:
9
+ #
10
+ # Hirb.add_dynamic_view("ActiveRecord::Base", :helper=>:auto_table) {|obj|
11
+ # {:fields=>obj.class.column_names} }
12
+ #
13
+ # From this dynamic view definition, _any_ ActiveRecord model class will render a table with the correct fields, since the fields
14
+ # are extracted from the output object's class at runtime. Note that dynamic view definitions should return a hash of helper options.
15
+ #
16
+ # To define multiple dynamic views, create a Views module where each method ending in '\_view' maps to a class/module:
17
+ #
18
+ # module Hirb::Views::ORM
19
+ # def data_mapper__resource_view(obj)
20
+ # {:fields=>obj.class.properties.map {|e| e.name }}
21
+ # end
22
+ #
23
+ # def sequel__model_view(obj)
24
+ # {:fields=>obj.class.columns}
25
+ # end
26
+ # end
27
+ #
28
+ # Hirb.add_dynamic_view Hirb::Views::ORM, :helper=>:auto_table
29
+ #
30
+ # In this example, 'data_mapper__resource_view' maps to DataMapper::Resource and 'sequel__model_view' maps to Sequel::Model.
31
+ # Note that when mapping method names to class names, '__' maps to '::' and '_' signals the next letter to be capitalized.
32
+ module DynamicView
33
+ # Add dynamic views to output class(es) for a given helper. If defining one view, the first argument is the output class
34
+ # and a block defines the dynamic view. If defining multiple views, the first argument should be a Views::* module where
35
+ # each method in the module ending in _view defines a view for an output class. To map output classes to method names in
36
+ # a Views module, translate'::' to '__' and a capital letter translates to a '_' and a lowercase letter.
37
+ # ==== Options:
38
+ # [*:helper*] Required option. Helper class that view(s) use to format. Hirb::Helpers::AutoTable is the only valid
39
+ # helper among default helpers. Can be given in aliased form i.e. :auto_table -> Hirb::Helpers::AutoTable.
40
+ #
41
+ # Examples:
42
+ # Hirb.add_dynamic_view Hirb::Views::ORM, :helper=>:auto_table
43
+ # Hirb.add_dynamic_view("ActiveRecord::Base", :helper=>:auto_table) {|obj| {:fields=>obj.class.column_names} }
44
+ def self.add(view, options, &block)
45
+ raise ArgumentError, ":helper option is required" unless options[:helper]
46
+ helper = Helpers.helper_class options[:helper]
47
+ unless helper.is_a?(Module) && class << helper; self.ancestors; end.include?(self)
48
+ raise ArgumentError, ":helper option must be a helper that has extended DynamicView"
49
+ end
50
+ mod = block ? generate_single_view_module(view, &block) : view
51
+ raise ArgumentError, "'#{mod}' must be a module" unless mod.is_a?(Module)
52
+ helper.add_module mod
53
+ end
54
+
55
+ def self.generate_single_view_module(output_mod, &block) #:nodoc:
56
+ mod_name = class_to_method output_mod.to_s
57
+ Views::Single.send(:remove_const, mod_name) if Views::Single.const_defined?(mod_name)
58
+ mod = Views::Single.const_set(mod_name, Module.new)
59
+ mod.send(:define_method, mod_name.downcase, block)
60
+ mod
61
+ end
62
+
63
+ def self.class_to_method(mod) #:nodoc:
64
+ mod.gsub("::","__").gsub(/([A-Z])/, '_\1').gsub(/(^|::)_/,'\1') + '_view'
65
+ end
66
+
67
+ # Returns a hash of options based on dynamic views defined for the object's ancestry. If no config is found returns nil.
68
+ def dynamic_options(obj)
69
+ view_methods.each do |meth|
70
+ if obj.class.ancestors.map {|e| e.to_s }.include?(method_to_class(meth))
71
+ begin
72
+ return send(meth, obj)
73
+ rescue
74
+ raise "View failed to generate for '#{method_to_class(meth)}' "+
75
+ "while in '#{meth}' with error:\n#{$!.message}"
76
+ end
77
+ end
78
+ end
79
+ nil
80
+ end
81
+
82
+ #:stopdoc:
83
+ def add_module(mod)
84
+ new_methods = mod.instance_methods.select {|e| e.to_s =~ /_view$/ }.map {|e| e.to_s}
85
+ return if new_methods.empty?
86
+ extend mod
87
+ view_methods.replace(view_methods + new_methods).uniq!
88
+ update_config(new_methods)
89
+ end
90
+
91
+ def update_config(meths)
92
+ output_config = meths.inject({}) {|t,e|
93
+ t[method_to_class(e)] = {:class=>self, :ancestor=>true}; t
94
+ }
95
+ Formatter.dynamic_config.merge! output_config
96
+ end
97
+
98
+ def method_to_class(meth)
99
+ view_method_classes[meth] ||= Util.camelize meth.sub(/_view$/, '').gsub('__', '/')
100
+ end
101
+
102
+ def view_method_classes
103
+ @view_method_classes ||= {}
104
+ end
105
+ #:startdoc:
106
+
107
+ # Stores view methods that a Helper has been given via DynamicView.add
108
+ def view_methods
109
+ @view_methods ||= []
110
+ end
111
+ end
112
+ end
@@ -1,160 +1,77 @@
1
1
  module Hirb
2
- =begin rdoc
3
- This class is format an output into a string using Hirb::Helpers::*, Hirb::Views::* or any user-created views.
4
- The formatter object looks for an output's class config in Hirb::Formatter.config and if found applies a helper to the output.
5
-
6
- == Create and Configure Views
7
- Let's create a simple view and configure it in different ways to be Hash's default view:
8
-
9
- === Setup
10
- irb>> require 'hirb'
11
- =>true
12
- irb>> Hirb.enable
13
- =>nil
14
- irb>> require 'yaml'
15
- =>true
16
-
17
- === Configure As View Method
18
- A view method is the smallest reuseable view.
19
- # Create yaml view method
20
- irb>> def yaml(output); output.to_yaml; end
21
- =>nil
22
-
23
- # Configure view
24
- irb>>Hirb::View.format_class Hash, :method=>:yaml
25
- =>true
26
-
27
- # Hashes now appear as yaml
28
- irb>>{:a=>1, :b=>{:c=>3}}
29
- ---
30
- :a : 1
31
- :b :
32
- :c : 3
33
- => true
34
-
35
- === Configure As View Class
36
- A view class is suited for more complex views. View classes can be under any namespace
37
- and are expected to provide a render method. However, if a class is under the Hirb::Views namespace,
38
- it will be automatically loaded with no configuration. Something to think about when
39
- sharing views with others.
40
-
41
- # Create yaml view class
42
- irb>> class Hirb::Views::Hash; def self.render(output, options={}); output.to_yaml; end ;end
43
- =>nil
44
- # Just reload since no configuration is necessary
45
- irb>>Hirb::View.formatter.reload
46
-
47
- # Hashes now appear as yaml ...
48
-
49
- Although the Hirb::Views namespace is great for quick classes that just plug and play, you
50
- often want view classes that can be reused with multiple outputs. For this case, it's recommended to
51
- use the Hirb::Helpers namespace.
52
-
53
- # Create yaml view class
54
- irb>> class Hirb::Helpers::Yaml; def self.render(output, options={}); output.to_yaml; end ;end
55
- =>nil
56
-
57
- # Configure view and reload it
58
- irb>>Hirb::View.format_class Hash, :class=>"Hirb::Helpers::Yaml"
59
- =>true
60
-
61
- # Hashes now appear as yaml ...
62
-
63
- == Configure At Startup
64
- Once you know what views are associated with what output classes, you can configure
65
- them at startup by passing Hirb.enable an options hash:
66
- # In .irbrc
67
- require 'hirb'
68
- # View class needs to come before enable()
69
- class Hirb::Helpers::Yaml; def self.render(output, options={}); output.to_yaml; end ;end
70
- Hirb.enable :output=>{"Hash"=>{:class=>"Hirb::Helpers::Yaml"}}
71
-
72
- Or by creating a config file at config/hirb.yml or ~/.hirb.yml:
73
- # The config file for the yaml example would look like:
74
- # ---
75
- # :output :
76
- # Hash :
77
- # :class : Hirb::Helpers::Yaml
78
-
79
- # In .irbrc
80
- require 'hirb'
81
- # View class needs to come before enable()
82
- class Hirb::Helpers::Yaml; def self.render(output, options={}); output.to_yaml; end ;end
83
- Hirb.enable
84
- =end
85
-
2
+ # A Formatter object formats an output object (using Formatter.format_output) into a string based on the views defined
3
+ # for its class and/or ancestry.
86
4
  class Formatter
87
- def initialize(additional_config={})
5
+ class<<self
6
+ # This config is used by Formatter.format_output to lazily load dynamic views defined with Hirb::DynamicView.
7
+ # This hash has the same format as Formatter.config.
8
+ attr_accessor :dynamic_config
9
+ end
10
+ self.dynamic_config = {}
11
+
12
+ def initialize(additional_config={}) #:nodoc:
88
13
  @klass_config = {}
89
- @config = Util.recursive_hash_merge default_config, additional_config || {}
14
+ @config = additional_config || {}
90
15
  end
91
16
 
92
- # A hash of Ruby class strings mapped to helper config hashes. A helper config hash must have at least a :method, :output_method
93
- # or :class option for a helper to be applied to an output. A helper config hash has the following keys:
94
- # [:method] Specifies a global (Kernel) method to do the formatting.
95
- # [:class] Specifies a class to do the formatting, using its render() class method. If a symbol it's converted to a corresponding
96
- # Hirb::Helpers::* class if it exists.
97
- # [:output_method] Specifies a method or proc to call on output before passing it to a helper. If the output is an array, it's applied
98
- # to every element in the array.
99
- # [:options] Options to pass the helper method or class.
100
- # [:ancestor] Boolean which when true causes subclasses of the output class to inherit its config. This doesn't effect the current
101
- # output class. Defaults to false. This is used by ActiveRecord classes.
17
+ # A hash of Ruby class strings mapped to view hashes. A view hash must have at least a :method, :output_method
18
+ # or :class option for a view to be applied to an output. A view hash has the following keys:
19
+ # [*:method*] Specifies a global (Kernel) method to do the formatting.
20
+ # [*:class*] Specifies a class to do the formatting, using its render() class method. If a symbol it's converted to a corresponding
21
+ # Hirb::Helpers::* class if it exists.
22
+ # [*:output_method*] Specifies a method or proc to call on output before passing it to a helper. If the output is an array, it's applied
23
+ # to every element in the array.
24
+ # [*:options*] Options to pass the helper method or class.
25
+ # [*:ancestor*] Boolean which when true causes subclasses of the output class to inherit its config. This doesn't effect the current
26
+ # output class. Defaults to false. This is used by ActiveRecord classes.
102
27
  #
103
28
  # Examples:
104
29
  # {'WWW::Delicious::Element'=>{:class=>'Hirb::Helpers::ObjectTable', :ancestor=>true, :options=>{:max_width=>180}}}
105
30
  # {'Date'=>{:class=>:auto_table, :ancestor=>true}}
31
+ # {'Hash'=>{:method=>:puts}}
106
32
  def config
107
33
  @config
108
34
  end
109
35
 
110
- # Sets the helper config for the given output class.
111
- def format_class(klass, helper_config)
36
+ # Adds the view for the given class and view hash config. See Formatter.config for valid keys for view hash.
37
+ def add_view(klass, view_config)
112
38
  @klass_config.delete(klass)
113
- @config[klass.to_s] = helper_config
39
+ @config[klass.to_s] = view_config
114
40
  true
115
41
  end
116
42
 
117
- # Reloads autodetected Hirb::Views
118
- def reload
119
- @config = Util.recursive_hash_merge default_config, @config
120
- end
121
-
122
- # This is the main method of this class. The formatter looks for the first helper in its config for the given output class.
123
- # If a helper is found, the output is converted by the helper into a string and returned. If not, nil is returned. The options
124
- # this class takes are a helper config hash as described in config. These options will be merged with any existing helper config hash
125
- # an output class has in config. Any block given is passed along to a helper class.
43
+ # This method looks for an output object's view in Formatter.config and then Formatter.dynamic_config.
44
+ # If a view is found, a stringified view is returned based on the object. If no view is found, nil is returned. The options this
45
+ # class takes are a view hash as described in Formatter.config. These options will be merged with any existing helper
46
+ # config hash an output class has in Formatter.config. Any block given is passed along to a helper class.
126
47
  def format_output(output, options={}, &block)
127
48
  output_class = determine_output_class(output)
128
49
  options = parse_console_options(options) if options.delete(:console)
129
50
  options = Util.recursive_hash_merge(klass_config(output_class), options)
130
- output = options[:output_method] ? (output.is_a?(Array) ? output.map {|e| call_output_method(options[:output_method], e) } :
51
+ _format_output(output, options, &block)
52
+ end
53
+
54
+ #:stopdoc:
55
+ def _format_output(output, options, &block)
56
+ output = options[:output_method] ? (output.is_a?(Array) ?
57
+ output.map {|e| call_output_method(options[:output_method], e) } :
131
58
  call_output_method(options[:output_method], output) ) : output
132
59
  args = [output]
133
60
  args << options[:options] if options[:options] && !options[:options].empty?
134
61
  if options[:method]
135
- new_output = send(options[:method],*args)
136
- elsif options[:class] && (helper_class = determine_helper_class(options[:class]))
137
- new_output = helper_class.render(*args, &block)
62
+ send(options[:method],*args)
63
+ elsif options[:class] && (helper_class = Helpers.helper_class(options[:class]))
64
+ helper_class.render(*args, &block)
138
65
  elsif options[:output_method]
139
- new_output = output
66
+ output
140
67
  end
141
- new_output
142
68
  end
143
69
 
144
- #:stopdoc:
145
70
  def parse_console_options(options) #:nodoc:
146
71
  real_options = [:method, :class, :output_method].inject({}) do |h, e|
147
72
  h[e] = options.delete(e) if options[e]; h
148
73
  end
149
74
  real_options.merge! :options=>options
150
- real_options
151
- end
152
-
153
- def determine_helper_class(klass)
154
- if (helper_class = Helpers.constants.find {|e| e.to_s == Util.camelize(klass.to_s)})
155
- klass = "Hirb::Helpers::#{helper_class}"
156
- end
157
- Util.any_const_get(klass)
158
75
  end
159
76
 
160
77
  def determine_output_class(output)
@@ -168,28 +85,32 @@ module Hirb
168
85
  # Internal view options built from user-defined ones. Options are built by recursively merging options from oldest
169
86
  # ancestors to the most recent ones.
170
87
  def klass_config(output_class)
171
- @klass_config[output_class] ||= begin
172
- output_ancestors_with_config = output_class.ancestors.map {|e| e.to_s}.select {|e| @config.has_key?(e)}
173
- @klass_config[output_class] = output_ancestors_with_config.reverse.inject({}) {|h, klass|
174
- (klass == output_class.to_s || @config[klass][:ancestor]) ? Util.recursive_hash_merge(h, @config[klass]) : h
175
- }
88
+ @klass_config[output_class] ||= build_klass_config(output_class)
89
+ end
90
+
91
+ def build_klass_config(output_class)
92
+ output_ancestors = output_class.ancestors.map {|e| e.to_s}.reverse
93
+ output_ancestors.pop
94
+ hash = output_ancestors.inject({}) {|h, klass|
95
+ add_klass_config_if_true(h, klass) {|c,klass| c[klass] && c[klass][:ancestor] }
96
+ }
97
+ add_klass_config_if_true(hash, output_class.to_s) {|c,klass| c[klass] }
98
+ end
99
+
100
+ def add_klass_config_if_true(hash, klass)
101
+ if yield(@config, klass)
102
+ Util.recursive_hash_merge hash, @config[klass]
103
+ elsif yield(self.class.dynamic_config, klass)
104
+ @config[klass] = self.class.dynamic_config[klass].dup # copy to local
105
+ Util.recursive_hash_merge hash, self.class.dynamic_config[klass]
106
+ else
107
+ hash
176
108
  end
177
109
  end
178
110
 
179
111
  def reset_klass_config
180
112
  @klass_config = {}
181
113
  end
182
-
183
- def default_config
184
- Views.constants.inject({}) {|h,e|
185
- output_class = e.to_s.gsub("_", "::")
186
- if (views_class = Views.const_get(e)) && views_class.respond_to?(:render)
187
- default_options = views_class.respond_to?(:default_options) ? views_class.default_options : {}
188
- h[output_class] = default_options.merge({:class=>"Hirb::Views::#{e}"})
189
- end
190
- h
191
- }
192
- end
193
114
  #:startdoc:
194
115
  end
195
- end
116
+ end