hirber 0.8.0

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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gemspec +21 -0
  3. data/.travis.yml +11 -0
  4. data/CHANGELOG.rdoc +165 -0
  5. data/CONTRIBUTING.md +1 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.rdoc +205 -0
  8. data/Rakefile +35 -0
  9. data/lib/bond/completions/hirb.rb +15 -0
  10. data/lib/hirb.rb +84 -0
  11. data/lib/hirb/console.rb +43 -0
  12. data/lib/hirb/dynamic_view.rb +113 -0
  13. data/lib/hirb/formatter.rb +126 -0
  14. data/lib/hirb/helpers.rb +18 -0
  15. data/lib/hirb/helpers/auto_table.rb +24 -0
  16. data/lib/hirb/helpers/markdown_table.rb +14 -0
  17. data/lib/hirb/helpers/object_table.rb +14 -0
  18. data/lib/hirb/helpers/parent_child_tree.rb +24 -0
  19. data/lib/hirb/helpers/tab_table.rb +24 -0
  20. data/lib/hirb/helpers/table.rb +376 -0
  21. data/lib/hirb/helpers/table/filters.rb +10 -0
  22. data/lib/hirb/helpers/table/resizer.rb +82 -0
  23. data/lib/hirb/helpers/tree.rb +181 -0
  24. data/lib/hirb/helpers/unicode_table.rb +15 -0
  25. data/lib/hirb/helpers/vertical_table.rb +37 -0
  26. data/lib/hirb/import_object.rb +10 -0
  27. data/lib/hirb/menu.rb +226 -0
  28. data/lib/hirb/pager.rb +106 -0
  29. data/lib/hirb/string.rb +44 -0
  30. data/lib/hirb/util.rb +96 -0
  31. data/lib/hirb/version.rb +3 -0
  32. data/lib/hirb/view.rb +272 -0
  33. data/lib/hirb/views.rb +8 -0
  34. data/lib/hirb/views/couch_db.rb +11 -0
  35. data/lib/hirb/views/misc_db.rb +15 -0
  36. data/lib/hirb/views/mongo_db.rb +17 -0
  37. data/lib/hirb/views/orm.rb +11 -0
  38. data/lib/hirb/views/rails.rb +19 -0
  39. data/lib/ripl/hirb.rb +15 -0
  40. data/test/auto_table_test.rb +33 -0
  41. data/test/console_test.rb +27 -0
  42. data/test/dynamic_view_test.rb +94 -0
  43. data/test/formatter_test.rb +176 -0
  44. data/test/hirb_test.rb +39 -0
  45. data/test/import_test.rb +9 -0
  46. data/test/menu_test.rb +272 -0
  47. data/test/object_table_test.rb +79 -0
  48. data/test/pager_test.rb +162 -0
  49. data/test/resizer_test.rb +62 -0
  50. data/test/table_test.rb +667 -0
  51. data/test/test_helper.rb +60 -0
  52. data/test/tree_test.rb +184 -0
  53. data/test/util_test.rb +59 -0
  54. data/test/view_test.rb +178 -0
  55. data/test/views_test.rb +22 -0
  56. metadata +164 -0
@@ -0,0 +1,15 @@
1
+ complete(:methods=>%w{Hirb::View.enable Hirb.enable}) {
2
+ %w{config_file output_method output width height formatter pager pager_command}
3
+ }
4
+ complete(:methods=>%w{Hirb::Helpers::Table.render table}) {
5
+ %w{fields headers max_fields max_width resize number change_fields}+
6
+ %w{filters header_filter filter_any filter_classes vertical all_fields}+
7
+ %w{description escape_special_chars table_class hide_empty unicode grep_fields}
8
+ }
9
+ complete(:method=>"Hirb::Helpers::Tree.render") {
10
+ %w{type validate indent limit description multi_line_nodes value_method children_method}
11
+ }
12
+ complete(:methods=>%w{Hirb::Menu.render menu}) {
13
+ %w{helper_class prompt ask directions readline two_d default_field action multi_action} +
14
+ %w{action_object command reopen}
15
+ }
@@ -0,0 +1,84 @@
1
+ # Needed by Hirb::String to handle multibyte characters
2
+ $KCODE = 'u' if RUBY_VERSION < '1.9'
3
+
4
+ require 'yaml'
5
+ require 'hirb/util'
6
+ require 'hirb/string'
7
+ require 'hirb/formatter' # must come before helpers/auto_table
8
+ require 'hirb/dynamic_view'
9
+ require 'hirb/helpers'
10
+ require 'hirb/views'
11
+ require 'hirb/view'
12
+ require 'hirb/console'
13
+ require 'hirb/pager'
14
+ require 'hirb/menu'
15
+ require 'hirb/version'
16
+
17
+ # Most of Hirb's functionality is in Hirb::View.
18
+ # For a tutorial on configuring and creating views see Hirb::View. For a tutorial on dynamic views see Hirb::DynamicView.
19
+ #
20
+ # == Config Files
21
+ # Hirb can have multiple config files defined by config_files(). These config files
22
+ # have the following top level keys:
23
+ # [*:output*] This hash is used by the formatter object. See Hirb::Formatter.config for its format.
24
+ # [*:width*] Width of the terminal/console. Defaults to Hirb::View::DEFAULT_WIDTH or possibly autodetected when Hirb is enabled.
25
+ # [*:height*] Height of the terminal/console. Defaults to Hirb::View::DEFAULT_HEIGHT or possibly autodetected when Hirb is enabled.
26
+ # [*:formatter*] Boolean which determines if the formatter is enabled. Defaults to true.
27
+ # [*:pager*] Boolean which determines if the pager is enabled. Defaults to true.
28
+ # [*:pager_command*] Command to be used for paging. Command can have options after it i.e. 'less -r'.
29
+ # Defaults to common pagers i.e. less and more if detected.
30
+ # [*:ignore_errors*] Boolean which ignores internal view errors and continues with original view
31
+ # (i.e. #inspect for irb). Defaults to false.
32
+ module Hirb
33
+ class <<self
34
+ attr_accessor :config_files, :config
35
+
36
+ # Enables view functionality. See Hirb::View.enable for details.
37
+ def enable(options={})
38
+ View.enable(options)
39
+ end
40
+
41
+ # Disables view functionality. See Hirb::View.disable for details.
42
+ def disable
43
+ View.disable
44
+ end
45
+
46
+ # Adds views. See Hirb::View.add for details.
47
+ def add_view(view, options)
48
+ View.add(view, options)
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
+
56
+ # Array of config files which are merged sequentially to produce config.
57
+ # Defaults to config/hirb.yml and ~/.hirb_yml
58
+ undef :config_files
59
+ def config_files
60
+ @config_files ||= default_config_files
61
+ end
62
+
63
+ #:stopdoc:
64
+ def default_config_files
65
+ [File.join(Util.find_home, ".hirb.yml")] +
66
+ (File.exist?('config/hirb.yml') ? ['config/hirb.yml'] : [])
67
+ end
68
+
69
+ def read_config_file(file=config_file)
70
+ File.exist?(file) ? YAML.load_file(file) : {}
71
+ end
72
+
73
+ undef :config
74
+ def config(reload=false)
75
+ if (@config.nil? || reload)
76
+ @config = config_files.inject({}) {|acc,e|
77
+ Util.recursive_hash_merge(acc,read_config_file(e))
78
+ }
79
+ end
80
+ @config
81
+ end
82
+ #:startdoc:
83
+ end
84
+ end
@@ -0,0 +1,43 @@
1
+ module Hirb
2
+ # This module is meant to be extended to provide methods for use in a console/irb shell.
3
+ # For example:
4
+ # >> extend Hirb::Console
5
+ # >> view 'some string', :class=>Some::String::Formatter
6
+ # >> table [[:row1], [:row2]]
7
+ module Console
8
+ class<<self
9
+ # A console version of render_output() which takes its same options but allows for shorthand. All options are passed to
10
+ # the helper except for the formatter options. Formatter options are :class, :method and :output_method.
11
+ # Examples:
12
+ # render_output output, :class=>:tree :type=>:directory
13
+ # # is the same as:
14
+ # render_output output, :class=>:tree, :options=> {:type=>:directory}
15
+ #
16
+ def render_output(output, options={})
17
+ View.load_config unless View.config_loaded?
18
+ View.render_output(output, options.merge(:console=>true))
19
+ end
20
+
21
+ # Takes same arguments and options as render_output() but returns formatted output instead of rendering it.
22
+ def format_output(output, options={}, &block)
23
+ View.load_config unless View.config_loaded?
24
+ View.formatter.format_output(output, options.merge(:console=>true), &block)
25
+ end
26
+ end
27
+
28
+ # Renders a table for the given object. Takes same options as Hirb::Helpers::Table.render.
29
+ def table(output, options={})
30
+ Console.render_output(output, options.merge(:class=>"Hirb::Helpers::AutoTable"))
31
+ end
32
+
33
+ # Renders any specified view for the given object. Takes same options as Hirb::View.render_output.
34
+ def view(output, options={})
35
+ Console.render_output(output, options)
36
+ end
37
+
38
+ # Renders a menu given an array using Hirb::Menu.render.
39
+ def menu(output, options={}, &block)
40
+ Console.format_output(output, options.merge(:class=>"Hirb::Menu"), &block)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,113 @@
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
+ meth = class_to_method output_mod.to_s
57
+ view_mod = meth.capitalize
58
+ Views::Single.send(:remove_const, view_mod) if Views::Single.const_defined?(view_mod)
59
+ mod = Views::Single.const_set(view_mod, Module.new)
60
+ mod.send(:define_method, meth, block)
61
+ mod
62
+ end
63
+
64
+ def self.class_to_method(mod) #:nodoc:
65
+ mod.gsub(/(?!^)([A-Z])/) {|e| '_'+e }.gsub('::_', '__').downcase + '_view'
66
+ end
67
+
68
+ # Returns a hash of options based on dynamic views defined for the object's ancestry. If no config is found returns nil.
69
+ def dynamic_options(obj)
70
+ view_methods.each do |meth|
71
+ if obj.class.ancestors.map {|e| e.to_s }.include?(method_to_class(meth))
72
+ begin
73
+ return send(meth, obj)
74
+ rescue
75
+ raise "View failed to generate for '#{method_to_class(meth)}' "+
76
+ "while in '#{meth}' with error:\n#{$!.message}"
77
+ end
78
+ end
79
+ end
80
+ nil
81
+ end
82
+
83
+ #:stopdoc:
84
+ def add_module(mod)
85
+ new_methods = mod.instance_methods.select {|e| e.to_s =~ /_view$/ }.map {|e| e.to_s}
86
+ return if new_methods.empty?
87
+ extend mod
88
+ view_methods.replace(view_methods + new_methods).uniq!
89
+ update_config(new_methods)
90
+ end
91
+
92
+ def update_config(meths)
93
+ output_config = meths.inject({}) {|t,e|
94
+ t[method_to_class(e)] = {:class=>self, :ancestor=>true}; t
95
+ }
96
+ Formatter.dynamic_config.merge! output_config
97
+ end
98
+
99
+ def method_to_class(meth)
100
+ view_method_classes[meth] ||= Util.camelize meth.sub(/_view$/, '').gsub('__', '/')
101
+ end
102
+
103
+ def view_method_classes
104
+ @view_method_classes ||= {}
105
+ end
106
+ #:startdoc:
107
+
108
+ # Stores view methods that a Helper has been given via DynamicView.add
109
+ def view_methods
110
+ @view_methods ||= []
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,126 @@
1
+ module Hirb
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.
4
+ class Formatter
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
+
10
+ # Array of classes whose objects respond to :to_a and allow the first
11
+ # element of the converted array to determine the output class.
12
+ attr_accessor :to_a_classes
13
+ end
14
+ self.dynamic_config = {}
15
+ self.to_a_classes = %w{Array Set ActiveRecord::Relation}
16
+
17
+ def initialize(additional_config={}) #:nodoc:
18
+ @klass_config = {}
19
+ @config = additional_config || {}
20
+ end
21
+
22
+ # A hash of Ruby class strings mapped to view hashes. A view hash must have at least a :method, :output_method
23
+ # or :class option for a view to be applied to an output. A view hash has the following keys:
24
+ # [*:method*] Specifies a global (Kernel) method to do the formatting.
25
+ # [*:class*] Specifies a class to do the formatting, using its render() class method. If a symbol it's converted to a corresponding
26
+ # Hirb::Helpers::* class if it exists.
27
+ # [*: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
28
+ # to every element in the array.
29
+ # [*:options*] Options to pass the helper method or class.
30
+ # [*:ancestor*] Boolean which when true causes subclasses of the output class to inherit its config. This doesn't effect the current
31
+ # output class. Defaults to false. This is used by ActiveRecord classes.
32
+ #
33
+ # Examples:
34
+ # {'WWW::Delicious::Element'=>{:class=>'Hirb::Helpers::ObjectTable', :ancestor=>true, :options=>{:max_width=>180}}}
35
+ # {'Date'=>{:class=>:auto_table, :ancestor=>true}}
36
+ # {'Hash'=>{:method=>:puts}}
37
+ def config
38
+ @config
39
+ end
40
+
41
+ # Adds the view for the given class and view hash config. See Formatter.config for valid keys for view hash.
42
+ def add_view(klass, view_config)
43
+ @klass_config.delete(klass)
44
+ @config[klass.to_s] = view_config
45
+ true
46
+ end
47
+
48
+ # This method looks for an output object's view in Formatter.config and then Formatter.dynamic_config.
49
+ # 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
50
+ # class takes are a view hash as described in Formatter.config. These options will be merged with any existing helper
51
+ # config hash an output class has in Formatter.config. Any block given is passed along to a helper class.
52
+ def format_output(output, options={}, &block)
53
+ output_class = determine_output_class(output)
54
+ options = parse_console_options(options) if options.delete(:console)
55
+ options = Util.recursive_hash_merge(klass_config(output_class), options)
56
+ _format_output(output, options, &block)
57
+ end
58
+
59
+ #:stopdoc:
60
+ def to_a_classes
61
+ @to_a_classes ||= self.class.to_a_classes.map {|e| Util.any_const_get(e) }.compact
62
+ end
63
+
64
+ def _format_output(output, options, &block)
65
+ output = options[:output_method] ? (output.is_a?(Array) ?
66
+ output.map {|e| call_output_method(options[:output_method], e) } :
67
+ call_output_method(options[:output_method], output) ) : output
68
+ args = [output]
69
+ args << options[:options] if options[:options] && !options[:options].empty?
70
+ if options[:method]
71
+ send(options[:method],*args)
72
+ elsif options[:class] && (helper_class = Helpers.helper_class(options[:class]))
73
+ helper_class.render(*args, &block)
74
+ elsif options[:output_method]
75
+ output
76
+ end
77
+ end
78
+
79
+ def parse_console_options(options) #:nodoc:
80
+ real_options = [:method, :class, :output_method].inject({}) do |h, e|
81
+ h[e] = options.delete(e) if options[e]; h
82
+ end
83
+ real_options.merge! :options=>options
84
+ end
85
+
86
+ def determine_output_class(output)
87
+ output.respond_to?(:to_a) && to_a_classes.any? {|e| output.is_a?(e) } ?
88
+ Array(output)[0].class : output.class
89
+ end
90
+
91
+ def call_output_method(output_method, output)
92
+ output_method.is_a?(Proc) ? output_method.call(output) : output.send(output_method)
93
+ end
94
+
95
+ # Internal view options built from user-defined ones. Options are built by recursively merging options from oldest
96
+ # ancestors to the most recent ones.
97
+ def klass_config(output_class)
98
+ @klass_config[output_class] ||= build_klass_config(output_class)
99
+ end
100
+
101
+ def build_klass_config(output_class)
102
+ output_ancestors = output_class.ancestors.map {|e| e.to_s}.reverse
103
+ output_ancestors.pop
104
+ hash = output_ancestors.inject({}) {|h, ancestor_klass|
105
+ add_klass_config_if_true(h, ancestor_klass) {|c, klass| c[klass] && c[klass][:ancestor] }
106
+ }
107
+ add_klass_config_if_true(hash, output_class.to_s) {|c, klass| c[klass] }
108
+ end
109
+
110
+ def add_klass_config_if_true(hash, klass)
111
+ if yield(@config, klass)
112
+ Util.recursive_hash_merge hash, @config[klass]
113
+ elsif yield(self.class.dynamic_config, klass)
114
+ @config[klass] = self.class.dynamic_config[klass].dup # copy to local
115
+ Util.recursive_hash_merge hash, self.class.dynamic_config[klass]
116
+ else
117
+ hash
118
+ end
119
+ end
120
+
121
+ def reset_klass_config
122
+ @klass_config = {}
123
+ end
124
+ #:startdoc:
125
+ end
126
+ end
@@ -0,0 +1,18 @@
1
+ module Hirb
2
+ module Helpers #:nodoc:
3
+ @helper_classes ||= {}
4
+ def self.helper_class(klass)
5
+ @helper_classes[klass.to_s] ||= begin
6
+ if (helper_class = constants.find {|e| e.to_s == Util.camelize(klass.to_s)})
7
+ klass = "Hirb::Helpers::#{helper_class}"
8
+ end
9
+ Util.any_const_get(klass)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ %w{table object_table auto_table tree parent_child_tree vertical_table
16
+ markdown_table unicode_table tab_table}.each do |e|
17
+ require "hirb/helpers/#{e}"
18
+ end
@@ -0,0 +1,24 @@
1
+ # This helper wraps around the other table helpers i.e. Hirb::Helpers::Table while
2
+ # providing default helper options via Hirb::DynamicView. Using these default options, this
3
+ # helper supports views for the following modules/classes:
4
+ # ActiveRecord::Base, CouchFoo::Base, CouchPotato::Persistence, CouchRest::ExtendedDocument,
5
+ # DBI::Row, DataMapper::Resource, Friendly::Document, MongoMapper::Document, MongoMapper::EmbeddedDocument,
6
+ # Mongoid::Document, Ripple::Document, Sequel::Model.
7
+ class Hirb::Helpers::AutoTable < Hirb::Helpers::Table
8
+ extend Hirb::DynamicView
9
+
10
+ # Takes same options as Hirb::Helpers::Table.render except as noted below.
11
+ #
12
+ # ==== Options:
13
+ # [:table_class] Explicit table class to use for rendering. Defaults to
14
+ # Hirb::Helpers::ObjectTable if output is not an Array or Hash. Otherwise
15
+ # defaults to Hirb::Helpers::Table.
16
+ def self.render(output, options={})
17
+ output = Array(output)
18
+ (defaults = dynamic_options(output[0])) && (options = defaults.merge(options))
19
+ klass = options.delete(:table_class) || (
20
+ !(output[0].is_a?(Hash) || output[0].is_a?(Array)) ?
21
+ Hirb::Helpers::ObjectTable : Hirb::Helpers::Table)
22
+ klass.render(output, options)
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ # Creates a markdown table for github
2
+ class Hirb::Helpers::MarkdownTable < Hirb::Helpers::Table
3
+ CHARS = {
4
+ :top => {:left => '', :center => '', :right => '', :horizontal => '',
5
+ :vertical => {:outside => '|', :inside => ' | '} },
6
+ :middle => {:left => '|', :center => ' | ', :right => '|', :horizontal => '-'},
7
+ :bottom => {:left => '', :center => '', :right => '', :horizontal => '',
8
+ :vertical => {:outside => '|', :inside => ' | '} }
9
+ }
10
+
11
+ def self.render(rows, options={})
12
+ new(rows, options).render
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ class Hirb::Helpers::ObjectTable < Hirb::Helpers::Table
2
+ # Rows are any ruby objects. Takes same options as Hirb::Helpers::Table.render except as noted below.
3
+ #
4
+ # ==== Options:
5
+ # [:fields] Methods of the object to represent as columns. Defaults to [:to_s].
6
+ def self.render(rows, options ={})
7
+ options[:fields] ||= [:to_s]
8
+ options[:headers] ||= {:to_s=>'value'} if options[:fields] == [:to_s]
9
+ item_hashes = options[:fields].empty? ? [] : Array(rows).inject([]) {|t,item|
10
+ t << options[:fields].inject({}) {|h,f| h[f] = item.__send__(f); h}
11
+ }
12
+ super(item_hashes, options)
13
+ end
14
+ end