cldwalker-hirb 0.1.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.
- data/LICENSE.txt +22 -0
- data/README.rdoc +216 -0
- data/Rakefile +49 -0
- data/VERSION.yml +4 -0
- data/lib/hirb/console.rb +17 -0
- data/lib/hirb/hash_struct.rb +17 -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/table.rb +169 -0
- data/lib/hirb/helpers.rb +7 -0
- data/lib/hirb/import_object.rb +10 -0
- data/lib/hirb/util.rb +19 -0
- data/lib/hirb/view.rb +179 -0
- data/lib/hirb/views/activerecord_base.rb +9 -0
- data/lib/hirb.rb +31 -0
- data/test/hirb_test.rb +23 -0
- data/test/import_test.rb +9 -0
- data/test/table_test.rb +242 -0
- data/test/test_helper.rb +16 -0
- data/test/util_test.rb +15 -0
- data/test/view_test.rb +151 -0
- metadata +79 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT LICENSE
|
2
|
+
|
3
|
+
Copyright (c) 2009 Gabriel Horner
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,216 @@
|
|
1
|
+
== Description
|
2
|
+
|
3
|
+
Hirb currently provides a mini view framework for console applications, designed with irb in mind.
|
4
|
+
This framework is activated by one method, which given the output of a console application, renders
|
5
|
+
a configured view based on the output's class. The framework encourages reusing views by letting you
|
6
|
+
package them in classes and associate them with any number of output classes. Hirb comes with table
|
7
|
+
views (see Hirb::Helpers::Table) which work out of the box with any output class, especially Rails'
|
8
|
+
model classes.
|
9
|
+
|
10
|
+
== Install
|
11
|
+
|
12
|
+
Install the gem with:
|
13
|
+
|
14
|
+
sudo gem install cldwalker-hirb -s http://gems.github.com
|
15
|
+
|
16
|
+
== Rails Example
|
17
|
+
|
18
|
+
Let's load and enable the view framework:
|
19
|
+
bash> script/console
|
20
|
+
Loading local environment (Rails 2.2.2)
|
21
|
+
irb>> require 'hirb'
|
22
|
+
=> true
|
23
|
+
irb>> Hirb::View.enable
|
24
|
+
=> nil
|
25
|
+
|
26
|
+
The default configuration provides table views for ActiveRecord::Base descendants.
|
27
|
+
If a class isn't configured, Hirb reverts to irb's default echo mode.
|
28
|
+
irb>> Hirb::View.output_config
|
29
|
+
=> {"ActiveRecord::Base"=>{:class=>"Hirb::Views::ActiveRecord_Base", :ancestor=>true}}
|
30
|
+
|
31
|
+
# Tag is a model class and descendant of ActiveRecord::Base
|
32
|
+
irb>> Tag.last
|
33
|
+
+-----+-------------------------+-------------+---------------+-----------+-----------+-------+
|
34
|
+
| id | created_at | description | name | namespace | predicate | value |
|
35
|
+
+-----+-------------------------+-------------+---------------+-----------+-----------+-------+
|
36
|
+
| 907 | 2009-03-06 21:10:41 UTC | | gem:tags=yaml | gem | tags | yaml |
|
37
|
+
+-----+-------------------------+-------------+---------------+-----------+-----------+-------+
|
38
|
+
1 row in set
|
39
|
+
|
40
|
+
irb>> 'plain ol irb'
|
41
|
+
=> 'plain ol irb'
|
42
|
+
irb>> :blah
|
43
|
+
=> :blah
|
44
|
+
|
45
|
+
From above you can see there were no views configured for a String or a Symbol so Hirb defaulted to
|
46
|
+
irb's echo mode. Also note that Tag was able to inherit its view from the ActiveRecord::Base config
|
47
|
+
because it had an :ancestor option.
|
48
|
+
|
49
|
+
Now that you understand that Hirb displays views based on an output object's class,
|
50
|
+
you may appreciate it also detects configured output objects in an array:
|
51
|
+
|
52
|
+
irb>> Tag.all :limit=>3, :order=>"id DESC"
|
53
|
+
+-----+-------------------------+-------------+-------------------+-----------+-----------+----------+
|
54
|
+
| id | created_at | description | name | namespace | predicate | value |
|
55
|
+
+-----+-------------------------+-------------+-------------------+-----------+-----------+----------+
|
56
|
+
| 907 | 2009-03-06 21:10:41 UTC | | gem:tags=yaml | gem | tags | yaml |
|
57
|
+
| 906 | 2009-03-06 08:47:04 UTC | | gem:tags=nomonkey | gem | tags | nomonkey |
|
58
|
+
| 905 | 2009-03-04 00:30:10 UTC | | article:tags=ruby | article | tags | ruby |
|
59
|
+
+-----+-------------------------+-------------+-------------------+-----------+-----------+----------+
|
60
|
+
3 rows in set
|
61
|
+
|
62
|
+
At any time you can disable Hirb if you really like irb's lovely echo mode:
|
63
|
+
irb>> Hirb::View.disable
|
64
|
+
=> nil
|
65
|
+
irb>> Tag.all :limit=>3, :order=>"id DESC"
|
66
|
+
=> [#<Tag id: 907, name: "gem:tags=yaml", description: nil, created_at: "2009-03-06 21:10:41",
|
67
|
+
namespace: "gem", predicate: "tags", value: "yaml">, #<Tag id: 906, name: "gem:tags=nomonkey",
|
68
|
+
description: nil, created_at: "2009-03-06 08:47:04", namespace: "gem", predicate: "tags", value:
|
69
|
+
"nomonkey">, #<Tag id: 905, name: "article:tags=ruby", description: nil, created_at: "2009-03-04
|
70
|
+
00:30:10", namespace: "article", predicate: "tags", value: "ruby">]
|
71
|
+
|
72
|
+
== Views: Anytime, Anywhere
|
73
|
+
While preconfigured tables are great for database records, sometimes you just want to create
|
74
|
+
tables/views for any output object:
|
75
|
+
|
76
|
+
#These examples don't need to have Hirb::View enabled.
|
77
|
+
irb>>Hirb::View.disable
|
78
|
+
=>nil
|
79
|
+
|
80
|
+
# Imports table() and view()
|
81
|
+
irb>>extend Hirb::Console
|
82
|
+
=>main
|
83
|
+
|
84
|
+
# Create a table of Dates comparing them with different formats.
|
85
|
+
irb>> table [Date.today, Date.today.next_month], :fields=>[:to_s, :ld, :ajd, :amjd, :asctime]
|
86
|
+
+------------+--------+-----------+-------+--------------------------+
|
87
|
+
| to_s | ld | ajd | amjd | asctime |
|
88
|
+
+------------+--------+-----------+-------+--------------------------+
|
89
|
+
| 2009-03-11 | 155742 | 4909803/2 | 54901 | Wed Mar 11 00:00:00 2009 |
|
90
|
+
| 2009-04-11 | 155773 | 4909865/2 | 54932 | Sat Apr 11 00:00:00 2009 |
|
91
|
+
+------------+--------+-----------+-------+--------------------------+
|
92
|
+
2 rows in set
|
93
|
+
|
94
|
+
# Same table as the previous method. However view() will be able to call any view created.
|
95
|
+
irb>> view [Date.today, Date.today.next_month], :class=>Hirb::Helpers::ObjectTable,
|
96
|
+
:fields=>[:to_s, :ld, :ajd, :amjd, :asctime]
|
97
|
+
|
98
|
+
If these console methods weren't convenient enough, try:
|
99
|
+
|
100
|
+
# Imports view() to all objects.
|
101
|
+
irb>> require 'hirb/import_object'
|
102
|
+
=>true
|
103
|
+
# Yields same table as above examples.
|
104
|
+
irb>> [Date.today, Date.today.next_month].view :class=>Hirb::Helpers::ObjectTable,
|
105
|
+
:fields=>[:to_s, :ld, :ajd, :amjd, :asctime]
|
106
|
+
|
107
|
+
Although views by default are printed to STDOUT, they can be easily modified to write anywhere:
|
108
|
+
# Setup views to write to file 'console.log'.
|
109
|
+
irb>> Hirb::View.render_method = lambda {|output| File.open("console.log", 'w') {|f| f.write(output) } }
|
110
|
+
|
111
|
+
# Writes to file with same table output as above example.
|
112
|
+
irb>> view [Date.today, Date.today.next_month], :class=>Hirb::Helpers::ObjectTable,
|
113
|
+
:fields=>[:to_s, :ld, :ajd, :amjd, :asctime]
|
114
|
+
|
115
|
+
# Doesn't write to file because Symbol isn't configured to use Hirb::View and thus defaults to irb's echo mode.
|
116
|
+
irb>> :blah
|
117
|
+
=>:blah
|
118
|
+
|
119
|
+
# Go back to printing Hirb views to STDOUT.
|
120
|
+
irb>> Hirb::View.reset_render_method
|
121
|
+
|
122
|
+
== Create and Configure Views
|
123
|
+
Let's create a simple view and configure it in different ways to be Hash's default view:
|
124
|
+
|
125
|
+
=== Setup
|
126
|
+
irb>> require 'hirb'
|
127
|
+
=>true
|
128
|
+
irb>> Hirb::View.enable
|
129
|
+
=>nil
|
130
|
+
irb>> require 'yaml'
|
131
|
+
=>true
|
132
|
+
|
133
|
+
=== Configure As View Method
|
134
|
+
A view method is the smallest reuseable view.
|
135
|
+
# Create yaml view method
|
136
|
+
irb>> def yaml(output); output.to_yaml; end
|
137
|
+
=>nil
|
138
|
+
|
139
|
+
# Configure view and reload it
|
140
|
+
irb>>Hirb::View.output_config = {"Hash"=>{:method=>:yaml}}
|
141
|
+
=>{"Hash"=>{:method=>:yaml}}
|
142
|
+
irb>>Hash::View.reload_config
|
143
|
+
=>true
|
144
|
+
|
145
|
+
# Hashes now appear as yaml
|
146
|
+
irb>>{:a=>1, :b=>{:c=>3}}
|
147
|
+
---
|
148
|
+
:a : 1
|
149
|
+
:b :
|
150
|
+
:c : 3
|
151
|
+
=> true
|
152
|
+
|
153
|
+
=== Configure As View Class
|
154
|
+
A view class is suited for more complex views. View classes can be under any namespace
|
155
|
+
and are expected to provide a render method. However, if a class is under the Hirb::Views namespace,
|
156
|
+
it will be automatically loaded with no configuration. Something to think about when
|
157
|
+
sharing views with others.
|
158
|
+
|
159
|
+
# Create yaml view class
|
160
|
+
irb>> class Hirb::Views::Hash; def self.render(output); output.to_yaml; end ;end
|
161
|
+
=>nil
|
162
|
+
# Just reload since no configuration is necessary
|
163
|
+
irb>>Hirb::View.reload_config
|
164
|
+
|
165
|
+
# Hashes now appear as yaml ...
|
166
|
+
|
167
|
+
Although the Hirb::Views namespace is great for quick classes that just plug and play, you
|
168
|
+
often want view classes that can be reused with multiple outputs. For this case, it's recommended to
|
169
|
+
use the Hirb::Helpers namespace.
|
170
|
+
|
171
|
+
# Create yaml view class
|
172
|
+
irb>> class Hirb::Helpers::Yaml; def self.render(output); output.to_yaml; end ;end
|
173
|
+
=>nil
|
174
|
+
|
175
|
+
# Configure view and reload it
|
176
|
+
irb>>Hirb::View.output_config = {"Hash"=>{:class=>"Hirb::Helpers::Yaml"}}
|
177
|
+
=>{"Hash"=>{:class=>"Hirb::Helpers::Yaml"}}
|
178
|
+
irb>>Hirb::View.reload_config
|
179
|
+
|
180
|
+
# Hashes now appear as yaml ...
|
181
|
+
|
182
|
+
=== Configure At Startup
|
183
|
+
Once you know what views are associated with what output classes, you can configure
|
184
|
+
them at startup by passing Hirb::View.enable a block:
|
185
|
+
# In .irbrc
|
186
|
+
require 'hirb'
|
187
|
+
# View class needs to come before enable()
|
188
|
+
class Hirb::Helpers::Yaml; def self.render(output); output.to_yaml; end ;end
|
189
|
+
Hirb::View.enable {|conf| conf.output = {"Hash"=>{:class=>"Hirb::Helpers::Yaml"}} }
|
190
|
+
|
191
|
+
Or by creating a config file at config/hirb.yml or ~/.hirb.yml:
|
192
|
+
# The config file for the yaml example would look like:
|
193
|
+
# ---
|
194
|
+
# :view :
|
195
|
+
# :output :
|
196
|
+
# Hash :
|
197
|
+
# :class : Hirb::Helpers::Yaml
|
198
|
+
|
199
|
+
# In .irbrc
|
200
|
+
require 'hirb'
|
201
|
+
# View class needs to come before enable()
|
202
|
+
class Hirb::Helpers::Yaml; def self.render(output); output.to_yaml; end ;end
|
203
|
+
Hirb::View.enable
|
204
|
+
|
205
|
+
== Limitations
|
206
|
+
Although Hirb preserves Wirble colorizing irb's default echo mode, it doesn't colorize its own views.
|
207
|
+
This is mainly because colorizing caused table classes to render incorrectly. If you can get tables
|
208
|
+
and colors to work nicely, please fork. To colorize your Hirb output:
|
209
|
+
Hirb::View.render_method = lambda {|output| puts Wirble::Colorize.colorize(output) }
|
210
|
+
|
211
|
+
== Todo
|
212
|
+
* Create tree views.
|
213
|
+
* Possibly add non-view irb goodies ie command manager.
|
214
|
+
* Configurable max height, which if exceeded activates a pager.
|
215
|
+
* Provides helper methods to all view classes.
|
216
|
+
* Consider adding a template system as needed.
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
begin
|
5
|
+
require 'rcov/rcovtask'
|
6
|
+
|
7
|
+
Rcov::RcovTask.new do |t|
|
8
|
+
t.libs << 'test'
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
10
|
+
t.rcov_opts = ["-T -x '/Library/Ruby/*'"]
|
11
|
+
t.verbose = true
|
12
|
+
end
|
13
|
+
rescue LoadError
|
14
|
+
puts "Rcov not available. Install it for rcov-related tasks with: sudo gem install rcov"
|
15
|
+
end
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'jeweler'
|
19
|
+
Jeweler::Tasks.new do |s|
|
20
|
+
s.name = "hirb"
|
21
|
+
s.description = "A mini view framework for console/irb that's easy to use, even while under its influence."
|
22
|
+
s.summary = s.description
|
23
|
+
s.email = "gabriel.horner@gmail.com"
|
24
|
+
s.homepage = "http://github.com/cldwalker/hirb"
|
25
|
+
s.authors = ["Gabriel Horner"]
|
26
|
+
s.has_rdoc = true
|
27
|
+
s.extra_rdoc_files = ["README.rdoc", "LICENSE.txt"]
|
28
|
+
s.files = FileList["Rakefile", "VERSION.yml", "README.rdoc", "LICENSE.txt", "{bin,lib,test}/**/*"]
|
29
|
+
end
|
30
|
+
|
31
|
+
rescue LoadError
|
32
|
+
puts "Jeweler not available. Install it for jeweler-related tasks with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
33
|
+
end
|
34
|
+
|
35
|
+
Rake::TestTask.new do |t|
|
36
|
+
t.libs << 'lib'
|
37
|
+
t.pattern = 'test/**/*_test.rb'
|
38
|
+
t.verbose = false
|
39
|
+
end
|
40
|
+
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
rdoc.rdoc_dir = 'rdoc'
|
43
|
+
rdoc.title = 'test'
|
44
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
45
|
+
rdoc.rdoc_files.include('README*')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
48
|
+
|
49
|
+
task :default => :test
|
data/VERSION.yml
ADDED
data/lib/hirb/console.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Hirb
|
2
|
+
# This class is meant to be extended to provide methods for use in a console/irb shell.
|
3
|
+
# For example:
|
4
|
+
# irb>> extend Hirb::Console
|
5
|
+
# irb>> view 'some string', :class=>Some::String::Formatter
|
6
|
+
# irb>> table [[:row1], [:row2]]
|
7
|
+
module Console
|
8
|
+
# Renders a table for the given object. Takes same options as Hirb::Helpers::Table.render.
|
9
|
+
def table(output, options={})
|
10
|
+
Hirb::View.console_render_output(output, options.merge(:class=>"Hirb::Helpers::AutoTable"))
|
11
|
+
end
|
12
|
+
# Renders any specified view for the given object. Takes same options as Hirb::View.render_output.
|
13
|
+
def view(*args)
|
14
|
+
Hirb::View.console_render_output(*args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Hirb::Helpers::ActiveRecordTable < Hirb::Helpers::ObjectTable
|
2
|
+
# Rows are Rails' ActiveRecord::Base objects.
|
3
|
+
# Takes same options as Hirb::Helpers::Table.render except as noted below.
|
4
|
+
#
|
5
|
+
# Options:
|
6
|
+
# :fields- Can be any attribute, column or not. If not given, this defaults to the database table's columns.
|
7
|
+
def self.render(rows, options={})
|
8
|
+
rows = [rows] unless rows.is_a?(Array)
|
9
|
+
options[:fields] ||=
|
10
|
+
begin
|
11
|
+
fields = rows.first.attribute_names
|
12
|
+
fields.unshift(fields.delete('id')) if fields.include?('id')
|
13
|
+
fields
|
14
|
+
end
|
15
|
+
super(rows, options)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Attempts to autodetect the table class the output represents and delegates rendering to it.
|
2
|
+
class Hirb::Helpers::AutoTable
|
3
|
+
# Same options as Hirb::Helpers::Table.render.
|
4
|
+
def self.render(output, options={})
|
5
|
+
klass = if ((output.is_a?(Array) && output[0].is_a?(ActiveRecord::Base)) or output.is_a?(ActiveRecord::Base) rescue false)
|
6
|
+
Hirb::Helpers::ActiveRecordTable
|
7
|
+
elsif options[:fields]
|
8
|
+
Hirb::Helpers::ObjectTable
|
9
|
+
else
|
10
|
+
Hirb::Helpers::Table
|
11
|
+
end
|
12
|
+
klass.render(output, options)
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,15 @@
|
|
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 which are represented as columns in the table. Required option.
|
6
|
+
# All method values are converted to strings via to_s.
|
7
|
+
def self.render(rows, options ={})
|
8
|
+
raise(ArgumentError, "Option 'fields' is required.") unless options[:fields]
|
9
|
+
rows = [rows] unless rows.is_a?(Array)
|
10
|
+
item_hashes = rows.inject([]) {|t,item|
|
11
|
+
t << options[:fields].inject({}) {|h,f| h[f] = item.send(f).to_s; h}
|
12
|
+
}
|
13
|
+
super(item_hashes, options)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,169 @@
|
|
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 << self
|
30
|
+
attr_accessor :max_width
|
31
|
+
|
32
|
+
# Main method which returns a formatted table.
|
33
|
+
# ==== Options:
|
34
|
+
# [:fields] An array which overrides the default fields and can be used to indicate field order.
|
35
|
+
# [:headers] A hash of fields and their header names. Fields that aren't specified here default to their name.
|
36
|
+
# This option can also be an array but only for array rows.
|
37
|
+
# [:field_lengths] A hash of fields and their maximum allowed lengths. If a field exceeds it's maximum
|
38
|
+
# length than it's truncated and has a ... appended to it. Fields that aren't specified here have no maximum allowed
|
39
|
+
# length.
|
40
|
+
# [:max_width] The maximum allowed width of all fields put together. This option is enforced except when the field_lengths option is set.
|
41
|
+
# Examples:
|
42
|
+
# Hirb::Helpers::Table.render [[1,2], [2,3]]
|
43
|
+
# Hirb::Helpers::Table.render [[1,2], [2,3]], :field_lengths=>{0=>10}
|
44
|
+
# Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}]
|
45
|
+
# Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}], :headers=>{:weight=>"Weight(lbs)"}
|
46
|
+
def render(rows, options={})
|
47
|
+
new(rows,options).render
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
#:stopdoc:
|
52
|
+
def initialize(rows, options={})
|
53
|
+
@options = options
|
54
|
+
@fields = options[:fields] || ((rows[0].is_a?(Hash)) ? rows[0].keys.sort {|a,b| a.to_s <=> b.to_s} :
|
55
|
+
rows[0].is_a?(Array) ? (0..rows[0].length - 1).to_a : [])
|
56
|
+
@rows = setup_rows(rows)
|
57
|
+
@headers = @fields.inject({}) {|h,e| h[e] = e.to_s; h}
|
58
|
+
if options.has_key?(:headers)
|
59
|
+
@headers = options[:headers].is_a?(Hash) ? @headers.merge(options[:headers]) :
|
60
|
+
(options[:headers].is_a?(Array) ? array_to_indices_hash(options[:headers]) : options[:headers])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def setup_rows(rows)
|
65
|
+
rows ||= []
|
66
|
+
rows = [rows] unless rows.is_a?(Array)
|
67
|
+
if rows[0].is_a?(Array)
|
68
|
+
rows = rows.inject([]) {|new_rows, row|
|
69
|
+
new_rows << array_to_indices_hash(row)
|
70
|
+
}
|
71
|
+
end
|
72
|
+
validate_values(rows)
|
73
|
+
rows
|
74
|
+
end
|
75
|
+
|
76
|
+
def render
|
77
|
+
body = []
|
78
|
+
unless @rows.length == 0
|
79
|
+
setup_field_lengths
|
80
|
+
body += @headers ? render_header : [render_border]
|
81
|
+
body += render_rows
|
82
|
+
body << render_border
|
83
|
+
end
|
84
|
+
body << render_table_description
|
85
|
+
body.join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
def render_header
|
89
|
+
title_row = '| ' + @fields.map {|f|
|
90
|
+
format_cell(@headers[f], @field_lengths[f])
|
91
|
+
}.join(' | ') + ' |'
|
92
|
+
[render_border, title_row, render_border]
|
93
|
+
end
|
94
|
+
|
95
|
+
def render_border
|
96
|
+
'+-' + @fields.map {|f| '-' * @field_lengths[f] }.join('-+-') + '-+'
|
97
|
+
end
|
98
|
+
|
99
|
+
def format_cell(value, cell_width)
|
100
|
+
text = value.length > cell_width ?
|
101
|
+
(
|
102
|
+
(cell_width < 3) ? value.slice(0,cell_width) : value.slice(0, cell_width - 3) + '...'
|
103
|
+
) : value
|
104
|
+
sprintf("%-#{cell_width}s", text)
|
105
|
+
end
|
106
|
+
|
107
|
+
def render_rows
|
108
|
+
@rows.map do |row|
|
109
|
+
row = '| ' + @fields.map {|f|
|
110
|
+
format_cell(row[f], @field_lengths[f])
|
111
|
+
}.join(' | ') + ' |'
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def render_table_description
|
116
|
+
(@rows.length == 0) ? "0 rows in set" :
|
117
|
+
"#{@rows.length} #{@rows.length == 1 ? 'row' : 'rows'} in set"
|
118
|
+
end
|
119
|
+
|
120
|
+
def setup_field_lengths
|
121
|
+
@field_lengths = default_field_lengths
|
122
|
+
if @options[:field_lengths]
|
123
|
+
@field_lengths.merge!(@options[:field_lengths])
|
124
|
+
else
|
125
|
+
max_width = @options[:max_width] || Hirb::Helpers::Table.max_width || DEFAULT_MAX_WIDTH
|
126
|
+
restrict_field_lengths(@field_lengths, max_width)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Simple algorithm which given a max width, allows smaller fields to be displayed while
|
131
|
+
# restricting longer fields at a new_long_field_length.
|
132
|
+
def restrict_field_lengths(field_lengths, max_width)
|
133
|
+
total_length = field_lengths.values.inject {|t,n| t += n}
|
134
|
+
if total_length > max_width
|
135
|
+
average_field_length = total_length / @fields.size.to_f
|
136
|
+
long_lengths, short_lengths = field_lengths.values.partition {|e| e > average_field_length}
|
137
|
+
new_long_field_length = (max_width - short_lengths.inject {|t,n| t += n}) / long_lengths.size
|
138
|
+
field_lengths.each {|f,length|
|
139
|
+
field_lengths[f] = new_long_field_length if length > new_long_field_length
|
140
|
+
}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# find max length for each field; start with the headers
|
145
|
+
def default_field_lengths
|
146
|
+
field_lengths = @headers ? @headers.inject({}) {|h,(k,v)| h[k] = v.length; h} : {}
|
147
|
+
@rows.each do |row|
|
148
|
+
@fields.each do |field|
|
149
|
+
len = row[field].length
|
150
|
+
field_lengths[field] = len if len > field_lengths[field].to_i
|
151
|
+
end
|
152
|
+
end
|
153
|
+
field_lengths
|
154
|
+
end
|
155
|
+
|
156
|
+
def validate_values(rows)
|
157
|
+
rows.each {|row|
|
158
|
+
@fields.each {|f|
|
159
|
+
row[f] = row[f].to_s || ''
|
160
|
+
}
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
# Converts an array to a hash mapping a numerical index to its array value.
|
165
|
+
def array_to_indices_hash(array)
|
166
|
+
array.inject({}) {|hash,e| hash[hash.size] = e; hash }
|
167
|
+
end
|
168
|
+
#:startdoc:
|
169
|
+
end
|
data/lib/hirb/helpers.rb
ADDED
data/lib/hirb/util.rb
ADDED
@@ -0,0 +1,19 @@
|
|
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
|
+
end
|
19
|
+
end
|
data/lib/hirb/view.rb
ADDED
@@ -0,0 +1,179 @@
|
|
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.
|
45
|
+
# [:options] Options to pass the formatter method or class.
|
46
|
+
def render_output(output, options={}, &block)
|
47
|
+
if block && block.arity > 0
|
48
|
+
formatted_output = block.call(output)
|
49
|
+
render_method.call(formatted_output)
|
50
|
+
true
|
51
|
+
elsif (formatted_output = format_output(output, options))
|
52
|
+
render_method.call(formatted_output)
|
53
|
+
true
|
54
|
+
else
|
55
|
+
false
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# A lambda or proc which handles the final formatted object.
|
60
|
+
# Although this puts the object by default, it could be set to do other things
|
61
|
+
# ie write the formatted object to a file.
|
62
|
+
def render_method
|
63
|
+
@render_method ||= default_render_method
|
64
|
+
end
|
65
|
+
|
66
|
+
def reset_render_method
|
67
|
+
@render_method = default_render_method
|
68
|
+
end
|
69
|
+
|
70
|
+
# Config hash which maps classes to view hashes. View hashes are the same as the options hash of render_output().
|
71
|
+
def output_config
|
72
|
+
@config[:output]
|
73
|
+
end
|
74
|
+
|
75
|
+
def output_config=(value)
|
76
|
+
@config[:output] = value
|
77
|
+
end
|
78
|
+
|
79
|
+
# Needs to be called for config changes to take effect. Reloads Hirb::Views classes and registers
|
80
|
+
# most recent config changes.
|
81
|
+
def reload_config
|
82
|
+
current_config = self.config.dup.merge(:output=>output_config)
|
83
|
+
load_config(current_config)
|
84
|
+
end
|
85
|
+
|
86
|
+
#:stopdoc:
|
87
|
+
def console_render_output(output, options={}, &block)
|
88
|
+
# iterates over format_output options that aren't :options
|
89
|
+
real_options = [:method, :class].inject({}) do |h, e|
|
90
|
+
h[e] = options.delete(e) if options[e]
|
91
|
+
h
|
92
|
+
end
|
93
|
+
render_output(output, real_options.merge(:options=>options), &block)
|
94
|
+
end
|
95
|
+
|
96
|
+
def format_output(output, options={})
|
97
|
+
output_class = determine_output_class(output)
|
98
|
+
options = output_class_options(output_class).merge(options)
|
99
|
+
args = [output]
|
100
|
+
args << options[:options] if options[:options] && !options[:options].empty?
|
101
|
+
if options[:method]
|
102
|
+
new_output = send(options[:method],*args)
|
103
|
+
elsif options[:class] && (view_class = Util.any_const_get(options[:class]))
|
104
|
+
new_output = view_class.render(*args)
|
105
|
+
end
|
106
|
+
new_output
|
107
|
+
end
|
108
|
+
|
109
|
+
def determine_output_class(output)
|
110
|
+
if output.is_a?(Array)
|
111
|
+
output[0].class
|
112
|
+
else
|
113
|
+
output.class
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Loads config
|
118
|
+
def load_config(additional_config={})
|
119
|
+
new_config = default_config
|
120
|
+
new_config[:output].merge!(additional_config.delete(:output) || {})
|
121
|
+
self.config = new_config.merge(additional_config)
|
122
|
+
true
|
123
|
+
end
|
124
|
+
|
125
|
+
# Stores all view config. Current valid keys:
|
126
|
+
# :output- contains value of output_config
|
127
|
+
def config=(value)
|
128
|
+
reset_cached_output_config
|
129
|
+
@config = value
|
130
|
+
end
|
131
|
+
|
132
|
+
def reset_cached_output_config
|
133
|
+
@cached_output_config = nil
|
134
|
+
end
|
135
|
+
|
136
|
+
# Internal view options built from user-defined ones. Options are built by recursively merging options from oldest
|
137
|
+
# ancestors to the most recent ones.
|
138
|
+
def output_class_options(output_class)
|
139
|
+
@cached_output_config ||= {}
|
140
|
+
@cached_output_config[output_class] ||=
|
141
|
+
begin
|
142
|
+
output_ancestors_with_config = output_class.ancestors.map {|e| e.to_s}.select {|e| output_config.has_key?(e)}
|
143
|
+
@cached_output_config[output_class] = output_ancestors_with_config.reverse.inject({}) {|h, klass|
|
144
|
+
(klass == output_class.to_s || output_config[klass][:ancestor]) ? h.update(output_config[klass]) : h
|
145
|
+
}
|
146
|
+
end
|
147
|
+
@cached_output_config[output_class]
|
148
|
+
end
|
149
|
+
|
150
|
+
def cached_output_config; @cached_output_config; end
|
151
|
+
|
152
|
+
def default_render_method
|
153
|
+
lambda {|output| puts output}
|
154
|
+
end
|
155
|
+
|
156
|
+
def default_config
|
157
|
+
conf = Hirb.config[:view] || {}
|
158
|
+
conf[:output] = default_output_config.merge(conf[:output] || {})
|
159
|
+
conf
|
160
|
+
end
|
161
|
+
|
162
|
+
def default_output_config
|
163
|
+
Hirb::Views.constants.inject({}) {|h,e|
|
164
|
+
output_class = e.to_s.gsub("_", "::")
|
165
|
+
if (views_class = Hirb::Views.const_get(e)) && views_class.respond_to?(:render)
|
166
|
+
default_options = views_class.respond_to?(:default_options) ? views_class.default_options : {}
|
167
|
+
h[output_class] = default_options.merge({:class=>"Hirb::Views::#{e}"})
|
168
|
+
end
|
169
|
+
h
|
170
|
+
}
|
171
|
+
end
|
172
|
+
#:startdoc:
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Namespace for autoloaded views
|
177
|
+
module Views
|
178
|
+
end
|
179
|
+
end
|
data/lib/hirb.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
current_dir = File.dirname(__FILE__)
|
2
|
+
$:.unshift(current_dir) unless $:.include?(current_dir) || $:.include?(File.expand_path(current_dir))
|
3
|
+
require 'hirb/util'
|
4
|
+
require 'hirb/hash_struct'
|
5
|
+
require 'hirb/helpers'
|
6
|
+
require 'hirb/view'
|
7
|
+
require 'hirb/views/activerecord_base'
|
8
|
+
require 'hirb/console'
|
9
|
+
|
10
|
+
# Most of Hirb's functionality currently resides in Hirb::View.
|
11
|
+
# Hirb has an optional yaml config file defined by config_file. This config file
|
12
|
+
# has the following top level keys:
|
13
|
+
# [:view] See Hirb::View for the value of this entry.
|
14
|
+
module Hirb
|
15
|
+
class <<self
|
16
|
+
# Default is config/hirb.yml or ~/hirb.yml in that order.
|
17
|
+
def config_file
|
18
|
+
File.exists?('config/hirb.yml') ? 'config/hirb.yml' : File.expand_path(File.join("~",".hirb.yml"))
|
19
|
+
end
|
20
|
+
|
21
|
+
#:stopdoc:
|
22
|
+
def read_config_file(file=config_file)
|
23
|
+
File.exists?(file) ? YAML::load_file(file) : {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def config(reload=false)
|
27
|
+
@config = (@config.nil? || reload) ? read_config_file : @config
|
28
|
+
end
|
29
|
+
#:startdoc:
|
30
|
+
end
|
31
|
+
end
|
data/test/hirb_test.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class HirbTest < Test::Unit::TestCase
|
4
|
+
before(:each) {Hirb.instance_eval "@config = nil"}
|
5
|
+
|
6
|
+
test "config converts yaml when config file exists" do
|
7
|
+
yaml_data = {:blah=>'blah'}
|
8
|
+
File.stubs('exists?').returns(true)
|
9
|
+
YAML::expects(:load_file).returns(yaml_data)
|
10
|
+
Hirb.config.should == yaml_data
|
11
|
+
end
|
12
|
+
|
13
|
+
test "config defaults to hash when no config file" do
|
14
|
+
File.stubs('exists?').returns(false)
|
15
|
+
Hirb.config.should == {}
|
16
|
+
end
|
17
|
+
|
18
|
+
test "config reloads if given explicit reload" do
|
19
|
+
Hirb.config
|
20
|
+
Hirb.expects(:read_config_file)
|
21
|
+
Hirb.config(true)
|
22
|
+
end
|
23
|
+
end
|
data/test/import_test.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class Hirb::ImportTest < Test::Unit::TestCase
|
4
|
+
test "require import_object extends Object" do
|
5
|
+
Object.ancestors.map {|e| e.to_s}.include?("Hirb::ObjectMethods").should be(false)
|
6
|
+
require 'hirb/import_object'
|
7
|
+
Object.ancestors.map {|e| e.to_s}.include?("Hirb::ObjectMethods").should be(true)
|
8
|
+
end
|
9
|
+
end
|
data/test/table_test.rb
ADDED
@@ -0,0 +1,242 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class Hirb::Helpers::TableTest < Test::Unit::TestCase
|
4
|
+
def table(*args)
|
5
|
+
Hirb::Helpers::Table.render(*args)
|
6
|
+
end
|
7
|
+
|
8
|
+
context "basic table" do
|
9
|
+
test "renders" do
|
10
|
+
expected_table = <<-TABLE.unindent
|
11
|
+
+---+---+
|
12
|
+
| a | b |
|
13
|
+
+---+---+
|
14
|
+
| 1 | 2 |
|
15
|
+
| 3 | 4 |
|
16
|
+
+---+---+
|
17
|
+
2 rows in set
|
18
|
+
TABLE
|
19
|
+
table([{:a=>1, :b=>2}, {:a=>3, :b=>4}]).should == expected_table
|
20
|
+
end
|
21
|
+
|
22
|
+
test "with no headers renders" do
|
23
|
+
expected_table = <<-TABLE.unindent
|
24
|
+
+---+---+
|
25
|
+
| 1 | 2 |
|
26
|
+
+---+---+
|
27
|
+
1 row in set
|
28
|
+
TABLE
|
29
|
+
table([{:a=>1, :b=>2}], :headers=>nil).should == expected_table
|
30
|
+
end
|
31
|
+
|
32
|
+
test "with string keys renders" do
|
33
|
+
expected_table = <<-TABLE.unindent
|
34
|
+
+---+---+
|
35
|
+
| a | b |
|
36
|
+
+---+---+
|
37
|
+
| 1 | 2 |
|
38
|
+
| 3 | 4 |
|
39
|
+
+---+---+
|
40
|
+
2 rows in set
|
41
|
+
TABLE
|
42
|
+
table([{'a'=>1, 'b'=>2}, {'a'=>3, 'b'=>4}]).should == expected_table
|
43
|
+
end
|
44
|
+
|
45
|
+
test "with array only rows renders" do
|
46
|
+
expected_table = <<-TABLE.unindent
|
47
|
+
+---+---+
|
48
|
+
| 0 | 1 |
|
49
|
+
+---+---+
|
50
|
+
| 1 | 2 |
|
51
|
+
| 3 | 4 |
|
52
|
+
+---+---+
|
53
|
+
2 rows in set
|
54
|
+
TABLE
|
55
|
+
table([[1,2], [3,4]]).should == expected_table
|
56
|
+
end
|
57
|
+
|
58
|
+
test "with no rows renders" do
|
59
|
+
table([]).should == "0 rows in set"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "table with" do
|
64
|
+
test "fields option renders" do
|
65
|
+
expected_table = <<-TABLE.unindent
|
66
|
+
+---+---+
|
67
|
+
| b | a |
|
68
|
+
+---+---+
|
69
|
+
| 2 | 1 |
|
70
|
+
| 4 | 3 |
|
71
|
+
+---+---+
|
72
|
+
2 rows in set
|
73
|
+
TABLE
|
74
|
+
table([{:a=>1, :b=>2}, {:a=>3, :b=>4}], :fields=>[:b, :a]).should == expected_table
|
75
|
+
end
|
76
|
+
|
77
|
+
test "fields option and array only rows" do
|
78
|
+
expected_table = <<-TABLE.unindent
|
79
|
+
+---+---+
|
80
|
+
| 0 | 2 |
|
81
|
+
+---+---+
|
82
|
+
| 1 | 3 |
|
83
|
+
+---+---+
|
84
|
+
1 row in set
|
85
|
+
TABLE
|
86
|
+
table([[1,2,3]], :fields=>[0,2]).should == expected_table
|
87
|
+
end
|
88
|
+
|
89
|
+
test "invalid fields option renders empty columns" do
|
90
|
+
expected_table = <<-TABLE.unindent
|
91
|
+
+---+---+
|
92
|
+
| b | c |
|
93
|
+
+---+---+
|
94
|
+
| 2 | |
|
95
|
+
| 4 | |
|
96
|
+
+---+---+
|
97
|
+
2 rows in set
|
98
|
+
TABLE
|
99
|
+
table([{:a=>1, :b=>2}, {:a=>3, :b=>4}], :fields=>[:b, :c]).should == expected_table
|
100
|
+
end
|
101
|
+
|
102
|
+
test "invalid fields in field_lengths option renders" do
|
103
|
+
expected_table = <<-TABLE.unindent
|
104
|
+
+------------+---+
|
105
|
+
| a | b |
|
106
|
+
+------------+---+
|
107
|
+
| AAAAAAA... | 2 |
|
108
|
+
+------------+---+
|
109
|
+
1 row in set
|
110
|
+
TABLE
|
111
|
+
table([{:a=> "A" * 50, :b=>2}], :field_lengths=>{:a=>10,:c=>10}).should == expected_table
|
112
|
+
end
|
113
|
+
|
114
|
+
test "field_lengths option and field_lengths less than 3 characters renders" do
|
115
|
+
expected_table = <<-TABLE.unindent
|
116
|
+
+----+---+
|
117
|
+
| a | b |
|
118
|
+
+----+---+
|
119
|
+
| AA | 2 |
|
120
|
+
+----+---+
|
121
|
+
1 row in set
|
122
|
+
TABLE
|
123
|
+
table([{:a=> "A" * 50, :b=>2}], :field_lengths=>{:a=>2}).should == expected_table
|
124
|
+
end
|
125
|
+
|
126
|
+
test "field_lengths option renders" do
|
127
|
+
expected_table = <<-TABLE.unindent
|
128
|
+
+------------+---+
|
129
|
+
| a | b |
|
130
|
+
+------------+---+
|
131
|
+
| AAAAAAA... | 2 |
|
132
|
+
+------------+---+
|
133
|
+
1 row in set
|
134
|
+
TABLE
|
135
|
+
table([{:a=> "A" * 50, :b=>2}], :field_lengths=>{:a=>10}).should == expected_table
|
136
|
+
end
|
137
|
+
|
138
|
+
test "max_width option renders" do
|
139
|
+
expected_table = <<-TABLE.unindent
|
140
|
+
+---------------------+---+------------+
|
141
|
+
| a | b | c |
|
142
|
+
+---------------------+---+------------+
|
143
|
+
| AAAAAAAAAAAAAAAA... | 2 | CCCCCCCCCC |
|
144
|
+
+---------------------+---+------------+
|
145
|
+
1 row in set
|
146
|
+
TABLE
|
147
|
+
table([{:a=> "A" * 50, :b=>2, :c=>"C"*10}], :max_width=>30).should == expected_table
|
148
|
+
end
|
149
|
+
|
150
|
+
test "global max_width renders" do
|
151
|
+
expected_table = <<-TABLE.unindent
|
152
|
+
+---------------------+---+------------+
|
153
|
+
| a | b | c |
|
154
|
+
+---------------------+---+------------+
|
155
|
+
| AAAAAAAAAAAAAAAA... | 2 | CCCCCCCCCC |
|
156
|
+
+---------------------+---+------------+
|
157
|
+
1 row in set
|
158
|
+
TABLE
|
159
|
+
Hirb::Helpers::Table.max_width = 30
|
160
|
+
table([{:a=> "A" * 50, :b=>2, :c=>"C"*10}]).should == expected_table
|
161
|
+
Hirb::Helpers::Table.max_width = Hirb::Helpers::Table::DEFAULT_MAX_WIDTH
|
162
|
+
end
|
163
|
+
|
164
|
+
test "headers option and headers longer than fields renders" do
|
165
|
+
expected_table = <<-TABLE.unindent
|
166
|
+
+---+---------+---------+
|
167
|
+
| a | field B | field C |
|
168
|
+
+---+---------+---------+
|
169
|
+
| A | 2 | C |
|
170
|
+
+---+---------+---------+
|
171
|
+
1 row in set
|
172
|
+
TABLE
|
173
|
+
table([{:a=> "A", :b=>2, :c=>"C"}], :headers=>{:b=>"field B", :c=>"field C"}).should == expected_table
|
174
|
+
end
|
175
|
+
|
176
|
+
test "headers option and headers shortened by field_lengths renders" do
|
177
|
+
expected_table = <<-TABLE.unindent
|
178
|
+
+-------+---+
|
179
|
+
| fi... | b |
|
180
|
+
+-------+---+
|
181
|
+
| A | 2 |
|
182
|
+
+-------+---+
|
183
|
+
1 row in set
|
184
|
+
TABLE
|
185
|
+
table([{:a=> "A", :b=>2}], :headers=>{:a=>"field A"}, :field_lengths=>{:a=>5}).should == expected_table
|
186
|
+
end
|
187
|
+
|
188
|
+
test "with headers option as an array renders" do
|
189
|
+
expected_table = <<-TABLE.unindent
|
190
|
+
+---+---+
|
191
|
+
| A | B |
|
192
|
+
+---+---+
|
193
|
+
| 1 | 2 |
|
194
|
+
| 3 | 4 |
|
195
|
+
+---+---+
|
196
|
+
2 rows in set
|
197
|
+
TABLE
|
198
|
+
table([[1,2], [3,4]], :headers=>['A', 'B']).should == expected_table
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
context "object table" do
|
204
|
+
before(:all) {
|
205
|
+
@pets = [stub(:name=>'rufus', :age=>7), stub(:name=>'alf', :age=>101)]
|
206
|
+
}
|
207
|
+
test "renders" do
|
208
|
+
expected_table = <<-TABLE.unindent
|
209
|
+
+-------+-----+
|
210
|
+
| name | age |
|
211
|
+
+-------+-----+
|
212
|
+
| rufus | 7 |
|
213
|
+
| alf | 101 |
|
214
|
+
+-------+-----+
|
215
|
+
2 rows in set
|
216
|
+
TABLE
|
217
|
+
Hirb::Helpers::ObjectTable.render(@pets, :fields=>[:name, :age]).should == expected_table
|
218
|
+
end
|
219
|
+
|
220
|
+
test "with no options fields raises ArgumentError" do
|
221
|
+
assert_raises(ArgumentError) { Hirb::Helpers::ObjectTable.render(@pets) }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
context "activerecord table" do
|
226
|
+
before(:all) {
|
227
|
+
@pets = [stub(:name=>'rufus', :age=>7, :attribute_names=>['age', 'name']), stub(:name=>'alf', :age=>101)]
|
228
|
+
}
|
229
|
+
test "renders" do
|
230
|
+
expected_table = <<-TABLE.unindent
|
231
|
+
+-----+-------+
|
232
|
+
| age | name |
|
233
|
+
+-----+-------+
|
234
|
+
| 7 | rufus |
|
235
|
+
| 101 | alf |
|
236
|
+
+-----+-------+
|
237
|
+
2 rows in set
|
238
|
+
TABLE
|
239
|
+
Hirb::Helpers::ActiveRecordTable.render(@pets).should == expected_table
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'context' #gem install jeremymcanally-context -s http://gems.github.com
|
4
|
+
require 'matchy' #gem install jeremymcanally-matchy -s http://gems.github.com
|
5
|
+
require 'mocha'
|
6
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
require 'hirb'
|
8
|
+
|
9
|
+
class Test::Unit::TestCase
|
10
|
+
end
|
11
|
+
|
12
|
+
class String
|
13
|
+
def unindent
|
14
|
+
gsub(/^\s*/, '').chomp
|
15
|
+
end
|
16
|
+
end
|
data/test/util_test.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class Hirb::UtilTest < Test::Unit::TestCase
|
4
|
+
test "any_const_get returns nested class" do
|
5
|
+
Hirb::Util.any_const_get("Test::Unit").should == ::Test::Unit
|
6
|
+
end
|
7
|
+
|
8
|
+
test "any_const_get returns nil for invalid class" do
|
9
|
+
Hirb::Util.any_const_get("Basdfr").should == nil
|
10
|
+
end
|
11
|
+
|
12
|
+
test "any_const_get returns class when given class" do
|
13
|
+
Hirb::Util.any_const_get(String).should == String
|
14
|
+
end
|
15
|
+
end
|
data/test/view_test.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
# mocks IRB for testing
|
4
|
+
module ::IRB
|
5
|
+
class Irb
|
6
|
+
def initialize(context)
|
7
|
+
@context = context
|
8
|
+
end
|
9
|
+
def output_value; end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Hirb::ViewTest < Test::Unit::TestCase
|
14
|
+
def set_config(value)
|
15
|
+
Hirb::View.output_config = value
|
16
|
+
Hirb::View.reset_cached_output_config
|
17
|
+
end
|
18
|
+
|
19
|
+
def output_config
|
20
|
+
Hirb::View.config[:output]
|
21
|
+
end
|
22
|
+
|
23
|
+
test "output_class_options merges ancestor options" do
|
24
|
+
set_config "String"=>{:args=>[1,2]}, "Object"=>{:method=>:object_output, :ancestor=>true}, "Kernel"=>{:method=>:default_output}
|
25
|
+
expected_result = {:method=>:object_output, :args=>[1, 2], :ancestor=>true}
|
26
|
+
Hirb::View.output_class_options(String).should == expected_result
|
27
|
+
end
|
28
|
+
|
29
|
+
test "output_class_options doesn't ancestor options" do
|
30
|
+
set_config "String"=>{:args=>[1,2]}, "Object"=>{:method=>:object_output}, "Kernel"=>{:method=>:default_output}
|
31
|
+
expected_result = {:args=>[1, 2]}
|
32
|
+
Hirb::View.output_class_options(String).should == expected_result
|
33
|
+
end
|
34
|
+
|
35
|
+
test "output_class_options returns hash when nothing found" do
|
36
|
+
Hirb::View.load_config
|
37
|
+
Hirb::View.output_class_options(String).should == {}
|
38
|
+
end
|
39
|
+
|
40
|
+
context "enable" do
|
41
|
+
before(:each) {Hirb::View.config = {}}
|
42
|
+
after(:each) { Hirb::View.disable }
|
43
|
+
test "redefines irb output_value" do
|
44
|
+
Hirb::View.expects(:render_output).once
|
45
|
+
Hirb::View.enable
|
46
|
+
context_stub = stub(:last_value=>'')
|
47
|
+
::IRB::Irb.new(context_stub).output_value
|
48
|
+
end
|
49
|
+
|
50
|
+
test "sets default config" do
|
51
|
+
eval "module ::Hirb::Views::Something_Base; def self.render; end; end"
|
52
|
+
Hirb::View.enable
|
53
|
+
output_config["Something::Base"].should == {:class=>"Hirb::Views::Something_Base"}
|
54
|
+
end
|
55
|
+
|
56
|
+
test "sets default config with default_options" do
|
57
|
+
eval "module ::Hirb::Views::Blah; def self.render; end; def self.default_options; {:ancestor=>true}; end; end"
|
58
|
+
Hirb::View.enable
|
59
|
+
output_config["Blah"].should == {:class=>"Hirb::Views::Blah", :ancestor=>true}
|
60
|
+
end
|
61
|
+
|
62
|
+
test "with block sets config" do
|
63
|
+
class_hash = {"Something::Base"=>{:class=>"BlahBlah"}}
|
64
|
+
Hirb::View.enable {|c| c.output = class_hash }
|
65
|
+
output_config['Something::Base'].should == class_hash['Something::Base']
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
test "reload_config resets config to detect new Hirb::Views" do
|
70
|
+
Hirb::View.load_config
|
71
|
+
output_config.keys.include?('Zzz').should be(false)
|
72
|
+
eval "module ::Hirb::Views::Zzz; def self.render; end; end"
|
73
|
+
Hirb::View.reload_config
|
74
|
+
output_config.keys.include?('Zzz').should be(true)
|
75
|
+
end
|
76
|
+
|
77
|
+
test "reload_config picks up local changes" do
|
78
|
+
Hirb::View.load_config
|
79
|
+
output_config.keys.include?('Dooda').should be(false)
|
80
|
+
Hirb::View.output_config.merge!('Dooda'=>{:class=>"DoodaView"})
|
81
|
+
Hirb::View.reload_config
|
82
|
+
output_config['Dooda'].should == {:class=>"DoodaView"}
|
83
|
+
end
|
84
|
+
|
85
|
+
test "disable points output_value back to original output_value" do
|
86
|
+
Hirb::View.expects(:render_output).never
|
87
|
+
Hirb::View.enable
|
88
|
+
Hirb::View.disable
|
89
|
+
context_stub = stub(:last_value=>'')
|
90
|
+
::IRB::Irb.new(context_stub).output_value
|
91
|
+
end
|
92
|
+
|
93
|
+
context "render_output" do
|
94
|
+
before(:all) {
|
95
|
+
eval %[module ::Commify
|
96
|
+
def self.render(strings)
|
97
|
+
strings = [strings] unless strings.is_a?(Array)
|
98
|
+
strings.map {|e| e.split('').join(',')}.join("\n")
|
99
|
+
end
|
100
|
+
end]
|
101
|
+
Hirb::View.enable
|
102
|
+
}
|
103
|
+
after(:all) { Hirb::View.disable }
|
104
|
+
|
105
|
+
test "formats with config method option" do
|
106
|
+
eval "module ::Kernel; def commify(string); string.split('').join(','); end; end"
|
107
|
+
set_config "String"=>{:method=>:commify}
|
108
|
+
Hirb::View.render_method.expects(:call).with('d,u,d,e')
|
109
|
+
Hirb::View.render_output('dude')
|
110
|
+
end
|
111
|
+
|
112
|
+
test "formats with config class option" do
|
113
|
+
set_config "String"=>{:class=>"Commify"}
|
114
|
+
Hirb::View.render_method.expects(:call).with('d,u,d,e')
|
115
|
+
Hirb::View.render_output('dude')
|
116
|
+
end
|
117
|
+
|
118
|
+
test "formats with output array" do
|
119
|
+
set_config "String"=>{:class=>"Commify"}
|
120
|
+
Hirb::View.render_method.expects(:call).with('d,u,d,e')
|
121
|
+
Hirb::View.render_output(['dude'])
|
122
|
+
end
|
123
|
+
|
124
|
+
test "formats with config options option" do
|
125
|
+
eval "module ::Blahify; def self.render(*args); end; end"
|
126
|
+
set_config "String"=>{:class=>"Blahify", :options=>{:fields=>%w{a b}}}
|
127
|
+
Blahify.expects(:render).with('dude', :fields=>%w{a b})
|
128
|
+
Hirb::View.render_output('dude')
|
129
|
+
end
|
130
|
+
|
131
|
+
test "doesn't format and returns false when no format method found" do
|
132
|
+
Hirb::View.load_config
|
133
|
+
Hirb::View.render_method.expects(:call).never
|
134
|
+
Hirb::View.render_output(Date.today).should == false
|
135
|
+
end
|
136
|
+
|
137
|
+
test "formats with explicit class option" do
|
138
|
+
set_config 'String'=>{:class=>"Blahify"}
|
139
|
+
Hirb::View.render_method.expects(:call).with('d,u,d,e')
|
140
|
+
Hirb::View.render_output('dude', :class=>"Commify")
|
141
|
+
end
|
142
|
+
|
143
|
+
test "formats with block" do
|
144
|
+
Hirb::View.load_config
|
145
|
+
Hirb::View.render_method.expects(:call).with('=dude=')
|
146
|
+
Hirb::View.render_output('dude') {|output|
|
147
|
+
"=#{output}="
|
148
|
+
}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cldwalker-hirb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gabriel Horner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-12 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A mini view framework for irb that's easy to use, even while under its influence.
|
17
|
+
email: gabriel.horner@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
- LICENSE.txt
|
25
|
+
files:
|
26
|
+
- Rakefile
|
27
|
+
- VERSION.yml
|
28
|
+
- README.rdoc
|
29
|
+
- LICENSE.txt
|
30
|
+
- lib/hirb
|
31
|
+
- lib/hirb/console.rb
|
32
|
+
- lib/hirb/hash_struct.rb
|
33
|
+
- lib/hirb/helpers
|
34
|
+
- lib/hirb/helpers/active_record_table.rb
|
35
|
+
- lib/hirb/helpers/auto_table.rb
|
36
|
+
- lib/hirb/helpers/object_table.rb
|
37
|
+
- lib/hirb/helpers/table.rb
|
38
|
+
- lib/hirb/helpers.rb
|
39
|
+
- lib/hirb/import_object.rb
|
40
|
+
- lib/hirb/util.rb
|
41
|
+
- lib/hirb/view.rb
|
42
|
+
- lib/hirb/views
|
43
|
+
- lib/hirb/views/activerecord_base.rb
|
44
|
+
- lib/hirb.rb
|
45
|
+
- test/hirb_test.rb
|
46
|
+
- test/import_test.rb
|
47
|
+
- test/table_test.rb
|
48
|
+
- test/test_helper.rb
|
49
|
+
- test/util_test.rb
|
50
|
+
- test/view_test.rb
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://github.com/cldwalker/hirb
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --inline-source
|
56
|
+
- --charset=UTF-8
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "0"
|
64
|
+
version:
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
requirements: []
|
72
|
+
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.2.0
|
75
|
+
signing_key:
|
76
|
+
specification_version: 2
|
77
|
+
summary: A mini view framework for irb that's easy to use, even while under its influence.
|
78
|
+
test_files: []
|
79
|
+
|