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