hirber 0.8.0

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