hirb 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +9 -0
- data/LICENSE.txt +22 -0
- data/README.rdoc +231 -0
- data/Rakefile +49 -0
- data/VERSION.yml +4 -0
- data/lib/hirb.rb +31 -0
- data/lib/hirb/console.rb +17 -0
- data/lib/hirb/hash_struct.rb +17 -0
- data/lib/hirb/helpers.rb +7 -0
- data/lib/hirb/helpers/active_record_table.rb +17 -0
- data/lib/hirb/helpers/auto_table.rb +14 -0
- data/lib/hirb/helpers/object_table.rb +15 -0
- data/lib/hirb/helpers/parent_child_tree.rb +22 -0
- data/lib/hirb/helpers/table.rb +175 -0
- data/lib/hirb/helpers/tree.rb +177 -0
- data/lib/hirb/import_object.rb +10 -0
- data/lib/hirb/util.rb +29 -0
- data/lib/hirb/view.rb +201 -0
- data/lib/hirb/views/activerecord_base.rb +9 -0
- data/test/hirb_test.rb +23 -0
- data/test/import_test.rb +9 -0
- data/test/table_test.rb +263 -0
- data/test/test_helper.rb +17 -0
- data/test/tree_test.rb +167 -0
- data/test/util_test.rb +21 -0
- data/test/view_test.rb +169 -0
- metadata +83 -0
@@ -0,0 +1,22 @@
|
|
1
|
+
class Hirb::Helpers::ParentChildTree < Hirb::Helpers::Tree
|
2
|
+
class <<self
|
3
|
+
# Starting with the given node, this builds a tree by recursively calling a children method.
|
4
|
+
# Takes same options as Hirb::Helper::Table.render with some additional ones below.
|
5
|
+
# ==== Options:
|
6
|
+
# [:value_method] Method to call to display as a node's value. If not given, uses :name if node
|
7
|
+
# responds to :name or defaults to :object_id.
|
8
|
+
# [:children_method] Method to call to obtain a node's children. Default is :children.
|
9
|
+
def render(root_node, options={})
|
10
|
+
@value_method = options[:value_method] || (root_node.respond_to?(:name) ? :name : :object_id)
|
11
|
+
@children_method = options[:children_method] || :children
|
12
|
+
@nodes = []
|
13
|
+
build_node(root_node, 0)
|
14
|
+
super(@nodes, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_node(node, level) #:nodoc:
|
18
|
+
@nodes << {:value=>node.send(@value_method), :level=>level}
|
19
|
+
node.send(@children_method).each {|e| build_node(e, level + 1)}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# Base Table class from which other table classes inherit.
|
2
|
+
# By default, a table is constrained to a default width but this can be adjusted
|
3
|
+
# via options as well as Hirb:Helpers::Table.max_width.
|
4
|
+
# Rows can be an array of arrays or an array of hashes.
|
5
|
+
#
|
6
|
+
# An array of arrays ie [[1,2], [2,3]], would render:
|
7
|
+
# +---+---+
|
8
|
+
# | 0 | 1 |
|
9
|
+
# +---+---+
|
10
|
+
# | 1 | 2 |
|
11
|
+
# | 2 | 3 |
|
12
|
+
# +---+---+
|
13
|
+
#
|
14
|
+
# By default, the fields/columns are the numerical indices of the array.
|
15
|
+
#
|
16
|
+
# An array of hashes ie [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}], would render:
|
17
|
+
# +-----+--------+
|
18
|
+
# | age | weight |
|
19
|
+
# +-----+--------+
|
20
|
+
# | 10 | 100 |
|
21
|
+
# | 80 | 500 |
|
22
|
+
# +-----+--------+
|
23
|
+
#
|
24
|
+
# By default, the fields/columns are the keys of the first hash.
|
25
|
+
#--
|
26
|
+
# derived from http://gist.github.com/72234
|
27
|
+
class Hirb::Helpers::Table
|
28
|
+
DEFAULT_MAX_WIDTH = 150
|
29
|
+
class TooManyFieldsForWidthError < StandardError; end
|
30
|
+
class << self
|
31
|
+
attr_accessor :max_width
|
32
|
+
|
33
|
+
# Main method which returns a formatted table.
|
34
|
+
# ==== Options:
|
35
|
+
# [:fields] An array which overrides the default fields and can be used to indicate field order.
|
36
|
+
# [:headers] A hash of fields and their header names. Fields that aren't specified here default to their name.
|
37
|
+
# This option can also be an array but only for array rows.
|
38
|
+
# [:field_lengths] A hash of fields and their maximum allowed lengths. If a field exceeds it's maximum
|
39
|
+
# length than it's truncated and has a ... appended to it. Fields that aren't specified here have no maximum allowed
|
40
|
+
# length.
|
41
|
+
# [:max_width] The maximum allowed width of all fields put together. This option is enforced except when the field_lengths option is set.
|
42
|
+
# This doesn't count field borders as part of the total.
|
43
|
+
# Examples:
|
44
|
+
# Hirb::Helpers::Table.render [[1,2], [2,3]]
|
45
|
+
# Hirb::Helpers::Table.render [[1,2], [2,3]], :field_lengths=>{0=>10}
|
46
|
+
# Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}]
|
47
|
+
# Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}], :headers=>{:weight=>"Weight(lbs)"}
|
48
|
+
def render(rows, options={})
|
49
|
+
new(rows,options).render
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#:stopdoc:
|
54
|
+
def initialize(rows, options={})
|
55
|
+
@options = options
|
56
|
+
@fields = options[:fields] || ((rows[0].is_a?(Hash)) ? rows[0].keys.sort {|a,b| a.to_s <=> b.to_s} :
|
57
|
+
rows[0].is_a?(Array) ? (0..rows[0].length - 1).to_a : [])
|
58
|
+
@rows = setup_rows(rows)
|
59
|
+
@headers = @fields.inject({}) {|h,e| h[e] = e.to_s; h}
|
60
|
+
if options.has_key?(:headers)
|
61
|
+
@headers = options[:headers].is_a?(Hash) ? @headers.merge(options[:headers]) :
|
62
|
+
(options[:headers].is_a?(Array) ? array_to_indices_hash(options[:headers]) : options[:headers])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def setup_rows(rows)
|
67
|
+
rows ||= []
|
68
|
+
rows = [rows] unless rows.is_a?(Array)
|
69
|
+
if rows[0].is_a?(Array)
|
70
|
+
rows = rows.inject([]) {|new_rows, row|
|
71
|
+
new_rows << array_to_indices_hash(row)
|
72
|
+
}
|
73
|
+
end
|
74
|
+
validate_values(rows)
|
75
|
+
rows
|
76
|
+
end
|
77
|
+
|
78
|
+
def render
|
79
|
+
body = []
|
80
|
+
unless @rows.length == 0
|
81
|
+
setup_field_lengths
|
82
|
+
body += @headers ? render_header : [render_border]
|
83
|
+
body += render_rows
|
84
|
+
body << render_border
|
85
|
+
end
|
86
|
+
body << render_table_description
|
87
|
+
body.join("\n")
|
88
|
+
end
|
89
|
+
|
90
|
+
def render_header
|
91
|
+
title_row = '| ' + @fields.map {|f|
|
92
|
+
format_cell(@headers[f], @field_lengths[f])
|
93
|
+
}.join(' | ') + ' |'
|
94
|
+
[render_border, title_row, render_border]
|
95
|
+
end
|
96
|
+
|
97
|
+
def render_border
|
98
|
+
'+-' + @fields.map {|f| '-' * @field_lengths[f] }.join('-+-') + '-+'
|
99
|
+
end
|
100
|
+
|
101
|
+
def format_cell(value, cell_width)
|
102
|
+
text = value.length > cell_width ?
|
103
|
+
(
|
104
|
+
(cell_width < 5) ? value.slice(0,cell_width) : value.slice(0, cell_width - 3) + '...'
|
105
|
+
) : value
|
106
|
+
sprintf("%-#{cell_width}s", text)
|
107
|
+
end
|
108
|
+
|
109
|
+
def render_rows
|
110
|
+
@rows.map do |row|
|
111
|
+
row = '| ' + @fields.map {|f|
|
112
|
+
format_cell(row[f], @field_lengths[f])
|
113
|
+
}.join(' | ') + ' |'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def render_table_description
|
118
|
+
(@rows.length == 0) ? "0 rows in set" :
|
119
|
+
"#{@rows.length} #{@rows.length == 1 ? 'row' : 'rows'} in set"
|
120
|
+
end
|
121
|
+
|
122
|
+
def setup_field_lengths
|
123
|
+
@field_lengths = default_field_lengths
|
124
|
+
if @options[:field_lengths]
|
125
|
+
@field_lengths.merge!(@options[:field_lengths])
|
126
|
+
else
|
127
|
+
table_max_width = Hirb::Helpers::Table.max_width || DEFAULT_MAX_WIDTH
|
128
|
+
table_max_width = @options[:max_width] if @options.has_key?(:max_width)
|
129
|
+
restrict_field_lengths(@field_lengths, table_max_width)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Simple algorithm which given a max width, allows smaller fields to be displayed while
|
134
|
+
# restricting longer fields at an average_long_field_length.
|
135
|
+
def restrict_field_lengths(field_lengths, max_width)
|
136
|
+
total_length = field_lengths.values.inject {|t,n| t += n}
|
137
|
+
if max_width && total_length > max_width
|
138
|
+
min_field_length = 3
|
139
|
+
raise TooManyFieldsForWidthError if @fields.size > max_width.to_f / min_field_length
|
140
|
+
average_field_length = total_length / @fields.size.to_f
|
141
|
+
long_lengths = field_lengths.values.select {|e| e > average_field_length}
|
142
|
+
total_long_field_length = (long_lengths.inject {|t,n| t += n}) * max_width/total_length
|
143
|
+
average_long_field_length = total_long_field_length / long_lengths.size
|
144
|
+
field_lengths.each {|f,length|
|
145
|
+
field_lengths[f] = average_long_field_length if length > average_long_field_length
|
146
|
+
}
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# find max length for each field; start with the headers
|
151
|
+
def default_field_lengths
|
152
|
+
field_lengths = @headers ? @headers.inject({}) {|h,(k,v)| h[k] = v.length; h} : {}
|
153
|
+
@rows.each do |row|
|
154
|
+
@fields.each do |field|
|
155
|
+
len = row[field].length
|
156
|
+
field_lengths[field] = len if len > field_lengths[field].to_i
|
157
|
+
end
|
158
|
+
end
|
159
|
+
field_lengths
|
160
|
+
end
|
161
|
+
|
162
|
+
def validate_values(rows)
|
163
|
+
rows.each {|row|
|
164
|
+
@fields.each {|f|
|
165
|
+
row[f] = row[f].to_s || ''
|
166
|
+
}
|
167
|
+
}
|
168
|
+
end
|
169
|
+
|
170
|
+
# Converts an array to a hash mapping a numerical index to its array value.
|
171
|
+
def array_to_indices_hash(array)
|
172
|
+
array.inject({}) {|hash,e| hash[hash.size] = e; hash }
|
173
|
+
end
|
174
|
+
#:startdoc:
|
175
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
# Base tree class which given an array of nodes produces different types of trees.
|
2
|
+
# The types of trees currently are:
|
3
|
+
# * basic:
|
4
|
+
# 0
|
5
|
+
# 1
|
6
|
+
# 2
|
7
|
+
# 3
|
8
|
+
# 4
|
9
|
+
#
|
10
|
+
# * directory:
|
11
|
+
# 0
|
12
|
+
# |-- 1
|
13
|
+
# | |-- 2
|
14
|
+
# | `-- 3
|
15
|
+
# `-- 4
|
16
|
+
#
|
17
|
+
# * number:
|
18
|
+
# 1. 0
|
19
|
+
# 1. 1
|
20
|
+
# 1. 2
|
21
|
+
# 2. 3
|
22
|
+
# 2. 4
|
23
|
+
#
|
24
|
+
# Tree nodes can be given as an array of arrays or an array of hashes.
|
25
|
+
# To render the above basic tree with an array of hashes:
|
26
|
+
# Hirb::Helpers::Tree.render([{:value=>0, :level=>0}, {:value=>1, :level=>1}, {:value=>2, :level=>2},
|
27
|
+
# {:value=>3, :level=>2}, {:value=>4, :level=>1}])
|
28
|
+
# Note from the hash keys that :level refers to the depth of the tree while :value refers to the text displayed
|
29
|
+
# for a node.
|
30
|
+
#
|
31
|
+
# To render the above basic tree with an array of arrays:
|
32
|
+
# Hirb::Helpers::Tree.render([[0,0], [1,1], [2,2], [2,3], [1,4]])
|
33
|
+
# Note that the each array pair consists of the level and the value for the node.
|
34
|
+
class Hirb::Helpers::Tree
|
35
|
+
class ParentlessNodeError < StandardError; end
|
36
|
+
|
37
|
+
class <<self
|
38
|
+
# Main method which renders a tree.
|
39
|
+
# ==== Options:
|
40
|
+
# [:type] Type of tree. Either :basic, :directory or :number. Default is :basic.
|
41
|
+
# [:validate] Boolean to validate tree. Checks to see if all nodes have parents. Raises ParentlessNodeError if
|
42
|
+
# an invalid node is found. Default is false.
|
43
|
+
# [:indent] Number of spaces to indent between levels for basic + number trees. Default is 4.
|
44
|
+
# [:limit] Limits the level or depth of a tree that is displayed. Root node is level 0.
|
45
|
+
# [:description] Displays brief description about tree ie how many nodes it has.
|
46
|
+
# Examples:
|
47
|
+
# Hirb::Helpers::Tree.render([[0, 'root'], [1, 'child']], :type=>:directory)
|
48
|
+
def render(nodes, options={})
|
49
|
+
new(nodes, options).render
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# :stopdoc:
|
54
|
+
attr_accessor :nodes
|
55
|
+
|
56
|
+
def initialize(input_nodes, options={})
|
57
|
+
@options = options
|
58
|
+
@type = options[:type] || :basic
|
59
|
+
if input_nodes[0].is_a?(Array)
|
60
|
+
@nodes = input_nodes.map {|e| Node.new(:level=>e[0], :value=>e[1]) }
|
61
|
+
else
|
62
|
+
@nodes = input_nodes.map {|e| Node.new(e)}
|
63
|
+
end
|
64
|
+
@nodes.each_with_index {|e,i| e.merge!(:tree=>self, :index=>i)}
|
65
|
+
@nodes.each {|e| e[:value] = e[:value].to_s }
|
66
|
+
validate_nodes if options[:validate]
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def render
|
71
|
+
body = render_tree
|
72
|
+
body += render_description if @options[:description]
|
73
|
+
body
|
74
|
+
end
|
75
|
+
|
76
|
+
def render_description
|
77
|
+
"\n\n#{@nodes.length} #{@nodes.length == 1 ? 'node' : 'nodes'} in tree"
|
78
|
+
end
|
79
|
+
|
80
|
+
def render_tree
|
81
|
+
@indent = ' ' * (@options[:indent] || 4 )
|
82
|
+
@nodes = @nodes.select {|e| e[:level] <= @options[:limit] } if @options[:limit]
|
83
|
+
case @type.to_s
|
84
|
+
when 'directory'
|
85
|
+
render_directory
|
86
|
+
when 'number'
|
87
|
+
render_number
|
88
|
+
else
|
89
|
+
render_basic
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def render_directory
|
94
|
+
mark_last_nodes_per_level
|
95
|
+
new_nodes = []
|
96
|
+
@nodes.each_with_index {|e, i|
|
97
|
+
value = ''
|
98
|
+
unless e.root?
|
99
|
+
value << e.render_parent_characters
|
100
|
+
value << (e[:last_node] ? "`-- " : "|-- ")
|
101
|
+
end
|
102
|
+
value << e[:value]
|
103
|
+
new_nodes << value
|
104
|
+
}
|
105
|
+
new_nodes.join("\n")
|
106
|
+
end
|
107
|
+
|
108
|
+
def render_number
|
109
|
+
counter = {}
|
110
|
+
@nodes.each {|e|
|
111
|
+
parent_level_key = "#{(e.parent ||{})[:index]}.#{e[:level]}"
|
112
|
+
counter[parent_level_key] ||= 0
|
113
|
+
counter[parent_level_key] += 1
|
114
|
+
e[:pre_value] = "#{counter[parent_level_key]}. "
|
115
|
+
}
|
116
|
+
@nodes.map {|e| @indent * e[:level] + e[:pre_value] + e[:value]}.join("\n")
|
117
|
+
end
|
118
|
+
|
119
|
+
def render_basic
|
120
|
+
@nodes.map {|e| @indent * e[:level] + e[:value]}.join("\n")
|
121
|
+
end
|
122
|
+
|
123
|
+
def validate_nodes
|
124
|
+
@nodes.each do |e|
|
125
|
+
raise ParentlessNodeError if (e[:level] > e.previous[:level]) && (e[:level] - e.previous[:level]) > 1
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# walks tree accumulating last nodes per unique parent+level
|
130
|
+
def mark_last_nodes_per_level
|
131
|
+
@nodes.each {|e| e.delete(:last_node)}
|
132
|
+
last_node_hash = @nodes.inject({}) {|h,e|
|
133
|
+
h["#{(e.parent ||{})[:index]}.#{e[:level]}"] = e; h
|
134
|
+
}
|
135
|
+
last_node_hash.values.uniq.each {|e| e[:last_node] = true}
|
136
|
+
end
|
137
|
+
#:startdoc:
|
138
|
+
class Node < ::Hash #:nodoc:
|
139
|
+
class MissingLevelError < StandardError; end
|
140
|
+
class MissingValueError < StandardError; end
|
141
|
+
|
142
|
+
def initialize(hash)
|
143
|
+
super
|
144
|
+
raise MissingLevelError unless hash.has_key?(:level)
|
145
|
+
raise MissingValueError unless hash.has_key?(:value)
|
146
|
+
replace(hash)
|
147
|
+
end
|
148
|
+
|
149
|
+
def parent
|
150
|
+
self[:tree].nodes.slice(0 .. self[:index]).reverse.detect {|e| e[:level] < self[:level]}
|
151
|
+
end
|
152
|
+
|
153
|
+
def next
|
154
|
+
self[:tree].nodes[self[:index] + 1]
|
155
|
+
end
|
156
|
+
|
157
|
+
def previous
|
158
|
+
self[:tree].nodes[self[:index] - 1]
|
159
|
+
end
|
160
|
+
|
161
|
+
def root?; self[:level] == 0; end
|
162
|
+
|
163
|
+
# refers to characters which connect parent nodes
|
164
|
+
def render_parent_characters
|
165
|
+
parent_chars = []
|
166
|
+
get_parents_character(parent_chars)
|
167
|
+
parent_chars.reverse.map {|level| level + ' ' * 3 }.to_s
|
168
|
+
end
|
169
|
+
|
170
|
+
def get_parents_character(parent_chars)
|
171
|
+
if self.parent
|
172
|
+
parent_chars << (self.parent[:last_node] ? ' ' : '|') unless self.parent.root?
|
173
|
+
self.parent.get_parents_character(parent_chars)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
data/lib/hirb/util.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Hirb
|
2
|
+
module Util
|
3
|
+
extend self
|
4
|
+
# Returns a constant like const_get() no matter what namespace it's nested in.
|
5
|
+
# Returns nil if the constant is not found.
|
6
|
+
def any_const_get(name)
|
7
|
+
return name if name.is_a?(Module)
|
8
|
+
begin
|
9
|
+
klass = Object
|
10
|
+
name.split('::').each {|e|
|
11
|
+
klass = klass.const_get(e)
|
12
|
+
}
|
13
|
+
klass
|
14
|
+
rescue
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Recursively merge hash1 with hash2.
|
20
|
+
def recursive_hash_merge(hash1, hash2)
|
21
|
+
hash1.merge(hash2) {|k,o,n| (o.is_a?(Hash)) ? recursive_hash_merge(o,n) : n}
|
22
|
+
end
|
23
|
+
|
24
|
+
# from Rails ActiveSupport
|
25
|
+
def camelize(string)
|
26
|
+
string.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/hirb/view.rb
ADDED
@@ -0,0 +1,201 @@
|
|
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}}}
|
10
|
+
module View
|
11
|
+
class<<self
|
12
|
+
attr_accessor :config, :render_method
|
13
|
+
|
14
|
+
# Overrides irb's output method with Hirb::View.render_output. Takes an optional
|
15
|
+
# block which sets the view config.
|
16
|
+
# Examples:
|
17
|
+
# Hirb.enable
|
18
|
+
# Hirb.enable {|c| c.output = {'String'=>{:class=>'Hirb::Helpers::Table'}} }
|
19
|
+
def enable(&block)
|
20
|
+
return puts("Already enabled.") if @enabled
|
21
|
+
@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
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Disable's Hirb's output by reverting back to irb's.
|
32
|
+
def disable
|
33
|
+
@enabled = false
|
34
|
+
::IRB::Irb.class_eval do
|
35
|
+
alias :output_value :non_hirb_render_output
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
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
|
59
|
+
end
|
60
|
+
|
61
|
+
# 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.
|
64
|
+
def render_method
|
65
|
+
@render_method ||= default_render_method
|
66
|
+
end
|
67
|
+
|
68
|
+
def reset_render_method
|
69
|
+
@render_method = default_render_method
|
70
|
+
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]
|
75
|
+
end
|
76
|
+
|
77
|
+
def output_config=(value)
|
78
|
+
@config[:output] = value
|
79
|
+
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)
|
86
|
+
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)
|
107
|
+
end
|
108
|
+
|
109
|
+
#: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
|
+
else
|
117
|
+
{}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
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
|
134
|
+
end
|
135
|
+
|
136
|
+
def determine_output_class(output)
|
137
|
+
if output.is_a?(Array)
|
138
|
+
output[0].class
|
139
|
+
else
|
140
|
+
output.class
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def load_config(additional_config={})
|
145
|
+
self.config = Util.recursive_hash_merge default_config, additional_config
|
146
|
+
true
|
147
|
+
end
|
148
|
+
|
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
|
154
|
+
end
|
155
|
+
|
156
|
+
def reset_cached_output_config
|
157
|
+
@cached_output_config = nil
|
158
|
+
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]
|
172
|
+
end
|
173
|
+
|
174
|
+
def cached_output_config; @cached_output_config; end
|
175
|
+
|
176
|
+
def default_render_method
|
177
|
+
lambda {|output| puts output}
|
178
|
+
end
|
179
|
+
|
180
|
+
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
|
+
}
|
193
|
+
end
|
194
|
+
#:startdoc:
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Namespace for autoloaded views
|
199
|
+
module Views
|
200
|
+
end
|
201
|
+
end
|