hirb 0.1.2 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.rdoc +16 -0
- data/README.rdoc +44 -108
- data/Rakefile +3 -2
- data/VERSION.yml +1 -1
- data/lib/hirb.rb +28 -3
- data/lib/hirb/console.rb +31 -5
- data/lib/hirb/formatter.rb +199 -0
- data/lib/hirb/helpers.rb +1 -1
- data/lib/hirb/helpers/active_record_table.rb +12 -3
- data/lib/hirb/helpers/auto_table.rb +3 -2
- data/lib/hirb/helpers/object_table.rb +4 -4
- data/lib/hirb/helpers/table.rb +89 -23
- data/lib/hirb/helpers/vertical_table.rb +31 -0
- data/lib/hirb/menu.rb +47 -0
- data/lib/hirb/pager.rb +94 -0
- data/lib/hirb/util.rb +53 -2
- data/lib/hirb/view.rb +116 -140
- data/test/active_record_table_test.rb +35 -0
- data/test/auto_table_test.rb +20 -0
- data/test/console_test.rb +12 -0
- data/test/formatter_test.rb +172 -0
- data/test/menu_test.rb +94 -0
- data/test/object_table_test.rb +49 -0
- data/test/pager_test.rb +164 -0
- data/test/table_test.rb +82 -52
- data/test/test_helper.rb +41 -0
- data/test/util_test.rb +53 -18
- data/test/view_test.rb +98 -151
- metadata +37 -15
data/lib/hirb/helpers.rb
CHANGED
@@ -2,6 +2,6 @@ module Hirb
|
|
2
2
|
module Helpers #:nodoc:
|
3
3
|
end
|
4
4
|
end
|
5
|
-
%w{table object_table active_record_table auto_table tree parent_child_tree}.each do |e|
|
5
|
+
%w{table object_table active_record_table auto_table tree parent_child_tree vertical_table}.each do |e|
|
6
6
|
require "hirb/helpers/#{e}"
|
7
7
|
end
|
@@ -8,10 +8,19 @@ class Hirb::Helpers::ActiveRecordTable < Hirb::Helpers::ObjectTable
|
|
8
8
|
rows = [rows] unless rows.is_a?(Array)
|
9
9
|
options[:fields] ||=
|
10
10
|
begin
|
11
|
-
fields = rows.first.
|
12
|
-
fields.unshift(fields.delete('id')) if fields.include?('id')
|
11
|
+
fields = rows.first.class.column_names
|
13
12
|
fields.map {|e| e.to_sym }
|
14
13
|
end
|
14
|
+
if query_used_select?(rows)
|
15
|
+
selected_columns = rows.first.attributes.keys
|
16
|
+
sorted_columns = rows.first.class.column_names.dup.delete_if {|e| !selected_columns.include?(e) }
|
17
|
+
sorted_columns += (selected_columns - sorted_columns)
|
18
|
+
options[:fields] = sorted_columns.map {|e| e.to_sym}
|
19
|
+
end
|
15
20
|
super(rows, options)
|
16
21
|
end
|
17
|
-
|
22
|
+
|
23
|
+
def self.query_used_select?(rows) #:nodoc:
|
24
|
+
rows.first.attributes.keys.sort != rows.first.class.column_names.sort
|
25
|
+
end
|
26
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
|
-
#
|
1
|
+
# Detects the table class the output should use and delegates rendering to it.
|
2
2
|
class Hirb::Helpers::AutoTable
|
3
3
|
# Same options as Hirb::Helpers::Table.render.
|
4
4
|
def self.render(output, options={})
|
5
|
+
output = output.to_a if !output.is_a?(Array) && output.respond_to?(:to_a)
|
5
6
|
klass = if ((output.is_a?(Array) && output[0].is_a?(ActiveRecord::Base)) or output.is_a?(ActiveRecord::Base) rescue false)
|
6
7
|
Hirb::Helpers::ActiveRecordTable
|
7
|
-
elsif
|
8
|
+
elsif (output.is_a?(Array) && !(output[0].is_a?(Hash) || output[0].is_a?(Array)))
|
8
9
|
Hirb::Helpers::ObjectTable
|
9
10
|
else
|
10
11
|
Hirb::Helpers::Table
|
@@ -2,13 +2,13 @@ class Hirb::Helpers::ObjectTable < Hirb::Helpers::Table
|
|
2
2
|
# Rows are any ruby objects. Takes same options as Hirb::Helpers::Table.render except as noted below.
|
3
3
|
#
|
4
4
|
# Options:
|
5
|
-
# :fields- Methods of the object
|
6
|
-
# All method values are converted to strings via to_s.
|
5
|
+
# :fields- Methods of the object to represent as columns. Defaults to [:to_s].
|
7
6
|
def self.render(rows, options ={})
|
8
|
-
|
7
|
+
options[:fields] ||= [:to_s]
|
8
|
+
options[:headers] = {:to_s=>'value'} if options[:fields] == [:to_s]
|
9
9
|
rows = [rows] unless rows.is_a?(Array)
|
10
10
|
item_hashes = rows.inject([]) {|t,item|
|
11
|
-
t << options[:fields].inject({}) {|h,f| h[f] = item.send(f)
|
11
|
+
t << options[:fields].inject({}) {|h,f| h[f] = item.send(f); h}
|
12
12
|
}
|
13
13
|
super(item_hashes, options)
|
14
14
|
end
|
data/lib/hirb/helpers/table.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Base Table class from which other table classes inherit.
|
2
2
|
# By default, a table is constrained to a default width but this can be adjusted
|
3
|
-
# via
|
3
|
+
# via the max_width option or Hirb::View.width.
|
4
4
|
# Rows can be an array of arrays or an array of hashes.
|
5
5
|
#
|
6
6
|
# An array of arrays ie [[1,2], [2,3]], would render:
|
@@ -25,10 +25,10 @@
|
|
25
25
|
#--
|
26
26
|
# derived from http://gist.github.com/72234
|
27
27
|
class Hirb::Helpers::Table
|
28
|
-
|
28
|
+
BORDER_LENGTH = 3 # " | " and "-+-" are the borders
|
29
29
|
class TooManyFieldsForWidthError < StandardError; end
|
30
|
+
|
30
31
|
class << self
|
31
|
-
attr_accessor :max_width
|
32
32
|
|
33
33
|
# Main method which returns a formatted table.
|
34
34
|
# ==== Options:
|
@@ -40,26 +40,40 @@ class Hirb::Helpers::Table
|
|
40
40
|
# length.
|
41
41
|
# [:max_width] The maximum allowed width of all fields put together. This option is enforced except when the field_lengths option is set.
|
42
42
|
# This doesn't count field borders as part of the total.
|
43
|
+
# [:number] When set to true, numbers rows by adding a :hirb_number column as the first column. Default is false.
|
44
|
+
# [:filters] A hash of fields and the filters that each row in the field must run through. The filter converts the cell's value by applying
|
45
|
+
# a given proc or an array containing a method and optional arguments to it.
|
46
|
+
# [:vertical] When set to true, renders a vertical table using Hirb::Helpers::VerticalTable. Default is false.
|
43
47
|
# Examples:
|
44
48
|
# Hirb::Helpers::Table.render [[1,2], [2,3]]
|
45
49
|
# Hirb::Helpers::Table.render [[1,2], [2,3]], :field_lengths=>{0=>10}
|
46
50
|
# Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}]
|
47
51
|
# Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}], :headers=>{:weight=>"Weight(lbs)"}
|
52
|
+
# Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}], :filters=>{:age=>[:to_f]}
|
48
53
|
def render(rows, options={})
|
49
|
-
new(rows,options).render
|
54
|
+
options.delete(:vertical) ? Hirb::Helpers::VerticalTable.render(rows, options) : new(rows, options).render
|
55
|
+
rescue TooManyFieldsForWidthError
|
56
|
+
$stderr.puts "", "** Error: Too many fields for the current width. Configure your width " +
|
57
|
+
"and/or fields to avoid this error. Defaulting to a vertical table. **"
|
58
|
+
Hirb::Helpers::VerticalTable.render(rows, options)
|
50
59
|
end
|
51
60
|
end
|
52
61
|
|
53
62
|
#:stopdoc:
|
54
63
|
def initialize(rows, options={})
|
55
64
|
@options = options
|
56
|
-
@
|
65
|
+
@options[:filters] ||= {}
|
66
|
+
@fields = @options[:fields] ? @options[:fields].dup : ((rows[0].is_a?(Hash)) ? rows[0].keys.sort {|a,b| a.to_s <=> b.to_s} :
|
57
67
|
rows[0].is_a?(Array) ? (0..rows[0].length - 1).to_a : [])
|
58
68
|
@rows = setup_rows(rows)
|
59
69
|
@headers = @fields.inject({}) {|h,e| h[e] = e.to_s; h}
|
60
|
-
if options.has_key?(:headers)
|
61
|
-
@headers = options[:headers].is_a?(Hash) ? @headers.merge(options[:headers]) :
|
62
|
-
(options[:headers].is_a?(Array) ? array_to_indices_hash(options[:headers]) : options[:headers])
|
70
|
+
if @options.has_key?(:headers)
|
71
|
+
@headers = @options[:headers].is_a?(Hash) ? @headers.merge(@options[:headers]) :
|
72
|
+
(@options[:headers].is_a?(Array) ? array_to_indices_hash(@options[:headers]) : @options[:headers])
|
73
|
+
end
|
74
|
+
if @options[:number]
|
75
|
+
@headers[:hirb_number] = "number"
|
76
|
+
@fields.unshift :hirb_number
|
63
77
|
end
|
64
78
|
end
|
65
79
|
|
@@ -71,6 +85,8 @@ class Hirb::Helpers::Table
|
|
71
85
|
new_rows << array_to_indices_hash(row)
|
72
86
|
}
|
73
87
|
end
|
88
|
+
rows = filter_values(rows)
|
89
|
+
rows.each_with_index {|e,i| e[:hirb_number] = (i + 1).to_s} if @options[:number]
|
74
90
|
validate_values(rows)
|
75
91
|
rows
|
76
92
|
end
|
@@ -79,15 +95,23 @@ class Hirb::Helpers::Table
|
|
79
95
|
body = []
|
80
96
|
unless @rows.length == 0
|
81
97
|
setup_field_lengths
|
82
|
-
body +=
|
98
|
+
body += render_header
|
83
99
|
body += render_rows
|
84
|
-
body
|
100
|
+
body += render_footer
|
85
101
|
end
|
86
102
|
body << render_table_description
|
87
103
|
body.join("\n")
|
88
104
|
end
|
89
|
-
|
105
|
+
|
90
106
|
def render_header
|
107
|
+
@headers ? render_table_header : [render_border]
|
108
|
+
end
|
109
|
+
|
110
|
+
def render_footer
|
111
|
+
[render_border]
|
112
|
+
end
|
113
|
+
|
114
|
+
def render_table_header
|
91
115
|
title_row = '| ' + @fields.map {|f|
|
92
116
|
format_cell(@headers[f], @field_lengths[f])
|
93
117
|
}.join(' | ') + ' |'
|
@@ -124,26 +148,53 @@ class Hirb::Helpers::Table
|
|
124
148
|
if @options[:field_lengths]
|
125
149
|
@field_lengths.merge!(@options[:field_lengths])
|
126
150
|
else
|
127
|
-
table_max_width =
|
128
|
-
table_max_width
|
129
|
-
restrict_field_lengths(@field_lengths, table_max_width)
|
151
|
+
table_max_width = @options.has_key?(:max_width) ? @options[:max_width] : Hirb::View.width
|
152
|
+
restrict_field_lengths(@field_lengths, table_max_width) if table_max_width
|
130
153
|
end
|
131
154
|
end
|
132
155
|
|
156
|
+
def restrict_field_lengths(field_lengths, max_width)
|
157
|
+
max_width -= @fields.size * BORDER_LENGTH + 1
|
158
|
+
original_field_lengths = field_lengths.dup
|
159
|
+
@min_field_length = BORDER_LENGTH
|
160
|
+
adjust_long_fields(field_lengths, max_width)
|
161
|
+
rescue TooManyFieldsForWidthError
|
162
|
+
raise
|
163
|
+
rescue
|
164
|
+
default_restrict_field_lengths(field_lengths, original_field_lengths, max_width)
|
165
|
+
end
|
166
|
+
|
133
167
|
# Simple algorithm which given a max width, allows smaller fields to be displayed while
|
134
168
|
# restricting longer fields at an average_long_field_length.
|
135
|
-
def
|
169
|
+
def adjust_long_fields(field_lengths, max_width)
|
136
170
|
total_length = field_lengths.values.inject {|t,n| t += n}
|
137
|
-
|
138
|
-
|
139
|
-
raise TooManyFieldsForWidthError if @fields.size > max_width.to_f / min_field_length
|
171
|
+
while total_length > max_width
|
172
|
+
raise TooManyFieldsForWidthError if @fields.size > max_width.to_f / @min_field_length
|
140
173
|
average_field_length = total_length / @fields.size.to_f
|
141
174
|
long_lengths = field_lengths.values.select {|e| e > average_field_length}
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
175
|
+
if long_lengths.empty?
|
176
|
+
raise "Algorithm didn't work, resort to default"
|
177
|
+
else
|
178
|
+
total_long_field_length = (long_lengths.inject {|t,n| t += n}) * max_width/total_length
|
179
|
+
average_long_field_length = total_long_field_length / long_lengths.size
|
180
|
+
field_lengths.each {|f,length|
|
181
|
+
field_lengths[f] = average_long_field_length if length > average_long_field_length
|
182
|
+
}
|
183
|
+
end
|
184
|
+
total_length = field_lengths.values.inject {|t,n| t += n}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Produces a field_lengths which meets the max_width requirement
|
189
|
+
def default_restrict_field_lengths(field_lengths, original_field_lengths, max_width)
|
190
|
+
original_total_length = original_field_lengths.values.inject {|t,n| t += n}
|
191
|
+
relative_lengths = original_field_lengths.values.map {|v| (v / original_total_length.to_f * max_width).to_i }
|
192
|
+
# set fields by their relative weight to original length
|
193
|
+
if relative_lengths.all? {|e| e > @min_field_length} && (relative_lengths.inject {|a,e| a += e} <= max_width)
|
194
|
+
original_field_lengths.each {|k,v| field_lengths[k] = (v / original_total_length.to_f * max_width).to_i }
|
195
|
+
else
|
196
|
+
# set all fields the same if nothing else works
|
197
|
+
field_lengths.each {|k,v| field_lengths[k] = max_width / @fields.size}
|
147
198
|
end
|
148
199
|
end
|
149
200
|
|
@@ -159,6 +210,21 @@ class Hirb::Helpers::Table
|
|
159
210
|
field_lengths
|
160
211
|
end
|
161
212
|
|
213
|
+
def filter_values(rows)
|
214
|
+
rows.map {|row|
|
215
|
+
new_row = {}
|
216
|
+
@fields.each {|f|
|
217
|
+
if @options[:filters][f]
|
218
|
+
new_row[f] = @options[:filters][f].is_a?(Proc) ? @options[:filters][f].call(row[f]) :
|
219
|
+
row[f].send(*@options[:filters][f])
|
220
|
+
else
|
221
|
+
new_row[f] = row[f]
|
222
|
+
end
|
223
|
+
}
|
224
|
+
new_row
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
162
228
|
def validate_values(rows)
|
163
229
|
rows.each {|row|
|
164
230
|
@fields.each {|f|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Hirb::Helpers::VerticalTable < Hirb::Helpers::Table
|
2
|
+
|
3
|
+
# Renders a vertical table using the same options as Hirb::Helpers::Table.render except for :field_lengths,
|
4
|
+
# :vertical and :max_width which aren't used.
|
5
|
+
def self.render(rows, options={})
|
6
|
+
new(rows, options).render
|
7
|
+
end
|
8
|
+
|
9
|
+
#:stopdoc:
|
10
|
+
def setup_field_lengths
|
11
|
+
@field_lengths = default_field_lengths
|
12
|
+
end
|
13
|
+
|
14
|
+
def render_header; []; end
|
15
|
+
def render_footer; []; end
|
16
|
+
|
17
|
+
def render_rows
|
18
|
+
i = 0
|
19
|
+
longest_header = @headers.values.sort_by {|e| e.length}.last.length
|
20
|
+
stars = "*" * [(longest_header + (longest_header / 2)), 3].max
|
21
|
+
@rows.map do |row|
|
22
|
+
row = "#{stars} #{i+1}. row #{stars}\n" +
|
23
|
+
@fields.map {|f|
|
24
|
+
"#{@headers[f].rjust(longest_header)}: #{row[f]}"
|
25
|
+
}.join("\n")
|
26
|
+
i+= 1
|
27
|
+
row
|
28
|
+
end
|
29
|
+
#:startdoc:
|
30
|
+
end
|
31
|
+
end
|
data/lib/hirb/menu.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Hirb
|
2
|
+
# This class provides a selection menu using Hirb's table helpers by default to display choices.
|
3
|
+
class Menu
|
4
|
+
# Menu which asks to select from the given array and returns the selected menu items as an array. See Hirb::Util.choose_from_array
|
5
|
+
# for the syntax for specifying selections. All options except for the ones below are passed to render the menu.
|
6
|
+
#
|
7
|
+
# ==== Options:
|
8
|
+
# [:helper_class] Helper class to render menu. Helper class is expected to implement numbering given a :number option.
|
9
|
+
# To use a very basic menu, set this to false. Defaults to Hirb::Helpers::AutoTable.
|
10
|
+
# [:prompt] String for menu prompt. Defaults to "Choose: ".
|
11
|
+
# [:validate_one] Validates that only one item in array is chosen and returns just that item. Default is false.
|
12
|
+
# [:ask] Always ask for input, even if there is only one choice. Default is true.
|
13
|
+
# Examples:
|
14
|
+
# extend Hirb::Console
|
15
|
+
# menu([1,2,3], :fields=>[:field1, :field2], :validate_one=>true)
|
16
|
+
# menu([1,2,3], :helper_class=>Hirb::Helpers::Table)
|
17
|
+
def self.render(output, options={})
|
18
|
+
default_options = {:helper_class=>Hirb::Helpers::AutoTable, :prompt=>"Choose #{options[:validate_one] ? 'one' : ''}: ", :ask=>true}
|
19
|
+
options = default_options.merge(options)
|
20
|
+
output = [output] unless output.is_a?(Array)
|
21
|
+
chosen = choose_from_menu(output, options)
|
22
|
+
yield(chosen) if block_given? && chosen.is_a?(Array) && chosen.size > 0
|
23
|
+
chosen
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.choose_from_menu(output, options) #:nodoc:
|
27
|
+
return output if output.size == 1 && !options[:ask]
|
28
|
+
if (helper_class = Util.any_const_get(options[:helper_class]))
|
29
|
+
View.render_output(output, :class=>options[:helper_class], :options=>options.merge(:number=>true))
|
30
|
+
else
|
31
|
+
output.each_with_index {|e,i| puts "#{i+1}: #{e}" }
|
32
|
+
end
|
33
|
+
print options[:prompt]
|
34
|
+
input = $stdin.gets.chomp.strip
|
35
|
+
chosen = Util.choose_from_array(output, input)
|
36
|
+
if options[:validate_one]
|
37
|
+
if chosen.size != 1
|
38
|
+
$stderr.puts "Choose one. You chose #{chosen.size} items."
|
39
|
+
return nil
|
40
|
+
else
|
41
|
+
return chosen[0]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
chosen
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/hirb/pager.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Hirb
|
2
|
+
# This class provides class methods for paging and an object which can conditionally page given a terminal size that is exceeded.
|
3
|
+
class Pager
|
4
|
+
class<<self
|
5
|
+
# Pages using a configured or detected shell command.
|
6
|
+
def command_pager(output, options={})
|
7
|
+
basic_pager(output) if valid_pager_command?(options[:pager_command])
|
8
|
+
end
|
9
|
+
|
10
|
+
def pager_command(*commands) #:nodoc:
|
11
|
+
@pager_command = (!@pager_command.nil? && commands.empty?) ? @pager_command :
|
12
|
+
begin
|
13
|
+
commands = [ENV['PAGER'], 'less', 'more', 'pager'] if commands.empty?
|
14
|
+
commands.compact.uniq.find {|e| Util.command_exists?(e[/\w+/]) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Pages with a ruby-only pager which either pages or quits.
|
19
|
+
def default_pager(output, options={})
|
20
|
+
pager = new(options[:width], options[:height])
|
21
|
+
while pager.activated_by?(output, options[:inspect])
|
22
|
+
puts pager.slice!(output, options[:inspect])
|
23
|
+
return unless continue_paging?
|
24
|
+
end
|
25
|
+
puts output
|
26
|
+
puts "=== Pager finished. ==="
|
27
|
+
end
|
28
|
+
|
29
|
+
#:stopdoc:
|
30
|
+
def valid_pager_command?(cmd)
|
31
|
+
cmd ? pager_command(cmd) : pager_command
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def basic_pager(output)
|
36
|
+
pager = IO.popen(pager_command, "w")
|
37
|
+
begin
|
38
|
+
save_stdout = STDOUT.clone
|
39
|
+
STDOUT.reopen(pager)
|
40
|
+
STDOUT.puts output
|
41
|
+
ensure
|
42
|
+
STDOUT.reopen(save_stdout)
|
43
|
+
save_stdout.close
|
44
|
+
pager.close
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def continue_paging?
|
49
|
+
puts "=== Press enter/return to continue or q to quit: ==="
|
50
|
+
!$stdin.gets.chomp[/q/i]
|
51
|
+
end
|
52
|
+
#:startdoc:
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_reader :width, :height
|
56
|
+
|
57
|
+
def initialize(width, height, options={})
|
58
|
+
resize(width, height)
|
59
|
+
@pager_command = options[:pager_command] if options[:pager_command]
|
60
|
+
end
|
61
|
+
|
62
|
+
# Pages given string using configured pager.
|
63
|
+
def page(string, inspect_mode)
|
64
|
+
if self.class.valid_pager_command?(@pager_command)
|
65
|
+
self.class.command_pager(string, :pager_command=>@pager_command)
|
66
|
+
else
|
67
|
+
self.class.default_pager(string, :width=>@width, :height=>@height, :inspect=>inspect_mode)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def slice!(output, inspect_mode=false) #:nodoc:
|
72
|
+
effective_height = @height - 2 # takes into account pager prompt
|
73
|
+
if inspect_mode
|
74
|
+
sliced_output = output.slice(0, @width * effective_height)
|
75
|
+
output.replace output.slice(@width * effective_height..-1)
|
76
|
+
sliced_output
|
77
|
+
else
|
78
|
+
# could use output.scan(/[^\n]*\n?/) instead of split
|
79
|
+
sliced_output = output.split("\n").slice(0, effective_height).join("\n")
|
80
|
+
output.replace output.split("\n").slice(effective_height..-1).join("\n")
|
81
|
+
sliced_output
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Determines if string should be paged based on configured width and height.
|
86
|
+
def activated_by?(string_to_page, inspect_mode=false)
|
87
|
+
inspect_mode ? (string_to_page.size > @height * @width) : (string_to_page.count("\n") > @height)
|
88
|
+
end
|
89
|
+
|
90
|
+
def resize(width, height) #:nodoc:
|
91
|
+
@width, @height = Hirb::View.determine_terminal_size(width, height)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/hirb/util.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Hirb
|
2
|
+
# Group of handy utility functions used throughout Hirb.
|
2
3
|
module Util
|
3
4
|
extend self
|
4
|
-
# Returns a constant like const_get
|
5
|
+
# Returns a constant like Module#const_get no matter what namespace it's nested in.
|
5
6
|
# Returns nil if the constant is not found.
|
6
7
|
def any_const_get(name)
|
7
8
|
return name if name.is_a?(Module)
|
@@ -21,9 +22,59 @@ module Hirb
|
|
21
22
|
hash1.merge(hash2) {|k,o,n| (o.is_a?(Hash)) ? recursive_hash_merge(o,n) : n}
|
22
23
|
end
|
23
24
|
|
24
|
-
#
|
25
|
+
# From Rails ActiveSupport, converting undescored lowercase to camel uppercase.
|
25
26
|
def camelize(string)
|
26
27
|
string.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
27
28
|
end
|
29
|
+
|
30
|
+
# Used by Hirb::Menu to select items from an array. Array counting starts at 1. Ranges of numbers are specified with a '-' or '..'.
|
31
|
+
# Multiple ranges can be comma delimited. Anything that isn't a valid number is ignored. All elements can be returned with a '*'.
|
32
|
+
# Examples:
|
33
|
+
# 1-3,5-6 -> [1,2,3,5,6]
|
34
|
+
# * -> all elements in array
|
35
|
+
# '' -> []
|
36
|
+
def choose_from_array(array, input, options={})
|
37
|
+
options = {:splitter=>","}.merge(options)
|
38
|
+
return array if input.strip == '*'
|
39
|
+
result = []
|
40
|
+
input.split(options[:splitter]).each do |e|
|
41
|
+
if e =~ /-|\.\./
|
42
|
+
min,max = e.split(/-|\.\./)
|
43
|
+
slice_min = min.to_i - 1
|
44
|
+
result.push(*array.slice(slice_min, max.to_i - min.to_i + 1))
|
45
|
+
elsif e =~ /\s*(\d+)\s*/
|
46
|
+
index = $1.to_i - 1
|
47
|
+
next if index < 0
|
48
|
+
result.push(array[index]) if array[index]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
return result
|
52
|
+
end
|
53
|
+
|
54
|
+
# Determines if a shell command exists by searching for it in ENV['PATH'].
|
55
|
+
def command_exists?(command)
|
56
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).any? {|d| File.exists? File.join(d, command) }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns [width, height] of terminal when detected, nil if not detected.
|
60
|
+
# Think of this as a simpler version of Highline's Highline::SystemExtensions.terminal_size()
|
61
|
+
def detect_terminal_size
|
62
|
+
(ENV['COLUMNS'] =~ /^\d+$/) && (ENV['LINES'] =~ /^\d+$/) ? [ENV['COLUMNS'].to_i, ENV['LINES'].to_i] :
|
63
|
+
( command_exists?('stty') ? `stty size`.scan(/\d+/).map { |s| s.to_i }.reverse : nil )
|
64
|
+
rescue
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Captures STDOUT of anything run in its block and returns it as string.
|
69
|
+
def capture_stdout(&block)
|
70
|
+
original_stdout = $stdout
|
71
|
+
$stdout = fake = StringIO.new
|
72
|
+
begin
|
73
|
+
yield
|
74
|
+
ensure
|
75
|
+
$stdout = original_stdout
|
76
|
+
end
|
77
|
+
fake.string
|
78
|
+
end
|
28
79
|
end
|
29
80
|
end
|