hsume2-hirb 0.6.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gemspec +21 -0
  2. data/CHANGELOG.rdoc +144 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.rdoc +194 -0
  5. data/Rakefile +35 -0
  6. data/lib/bond/completions/hirb.rb +15 -0
  7. data/lib/hirb/console.rb +43 -0
  8. data/lib/hirb/dynamic_view.rb +113 -0
  9. data/lib/hirb/formatter.rb +126 -0
  10. data/lib/hirb/helpers/auto_table.rb +24 -0
  11. data/lib/hirb/helpers/object_table.rb +14 -0
  12. data/lib/hirb/helpers/parent_child_tree.rb +24 -0
  13. data/lib/hirb/helpers/tab_table.rb +24 -0
  14. data/lib/hirb/helpers/table/filters.rb +10 -0
  15. data/lib/hirb/helpers/table/resizer.rb +82 -0
  16. data/lib/hirb/helpers/table.rb +349 -0
  17. data/lib/hirb/helpers/tree.rb +181 -0
  18. data/lib/hirb/helpers/unicode_table.rb +15 -0
  19. data/lib/hirb/helpers/vertical_table.rb +37 -0
  20. data/lib/hirb/helpers.rb +18 -0
  21. data/lib/hirb/import_object.rb +10 -0
  22. data/lib/hirb/menu.rb +238 -0
  23. data/lib/hirb/pager.rb +105 -0
  24. data/lib/hirb/string.rb +44 -0
  25. data/lib/hirb/util.rb +96 -0
  26. data/lib/hirb/version.rb +3 -0
  27. data/lib/hirb/view.rb +270 -0
  28. data/lib/hirb/views/couch_db.rb +11 -0
  29. data/lib/hirb/views/misc_db.rb +15 -0
  30. data/lib/hirb/views/mongo_db.rb +14 -0
  31. data/lib/hirb/views/orm.rb +11 -0
  32. data/lib/hirb/views/rails.rb +19 -0
  33. data/lib/hirb/views.rb +8 -0
  34. data/lib/hirb.rb +82 -0
  35. data/lib/ripl/hirb.rb +15 -0
  36. data/test/auto_table_test.rb +30 -0
  37. data/test/console_test.rb +27 -0
  38. data/test/deps.rip +4 -0
  39. data/test/dynamic_view_test.rb +94 -0
  40. data/test/formatter_test.rb +176 -0
  41. data/test/hirb_test.rb +39 -0
  42. data/test/import_test.rb +9 -0
  43. data/test/menu_test.rb +255 -0
  44. data/test/object_table_test.rb +79 -0
  45. data/test/pager_test.rb +162 -0
  46. data/test/resizer_test.rb +62 -0
  47. data/test/table_test.rb +630 -0
  48. data/test/test_helper.rb +61 -0
  49. data/test/tree_test.rb +184 -0
  50. data/test/util_test.rb +59 -0
  51. data/test/view_test.rb +165 -0
  52. data/test/views_test.rb +13 -0
  53. metadata +184 -0
@@ -0,0 +1,181 @@
1
+ # Base tree class which given an array of nodes produces different types of trees.
2
+ # The types of trees currently are:
3
+ # * basic:
4
+ # 0
5
+ # 1
6
+ # 2
7
+ # 3
8
+ # 4
9
+ #
10
+ # * directory:
11
+ # 0
12
+ # |-- 1
13
+ # | |-- 2
14
+ # | `-- 3
15
+ # `-- 4
16
+ #
17
+ # * number:
18
+ # 1. 0
19
+ # 1. 1
20
+ # 1. 2
21
+ # 2. 3
22
+ # 2. 4
23
+ #
24
+ # Tree nodes can be given as an array of arrays or an array of hashes.
25
+ # To render the above basic tree with an array of hashes:
26
+ # Hirb::Helpers::Tree.render([{:value=>0, :level=>0}, {:value=>1, :level=>1}, {:value=>2, :level=>2},
27
+ # {:value=>3, :level=>2}, {:value=>4, :level=>1}])
28
+ # Note from the hash keys that :level refers to the depth of the tree while :value refers to the text displayed
29
+ # for a node.
30
+ #
31
+ # To render the above basic tree with an array of arrays:
32
+ # Hirb::Helpers::Tree.render([[0,0], [1,1], [2,2], [2,3], [1,4]])
33
+ # Note that the each array pair consists of the level and the value for the node.
34
+ class Hirb::Helpers::Tree
35
+ class ParentlessNodeError < StandardError; end
36
+
37
+ class <<self
38
+ # Main method which renders a tree.
39
+ # ==== Options:
40
+ # [:type] Type of tree. Either :basic, :directory or :number. Default is :basic.
41
+ # [:validate] Boolean to validate tree. Checks to see if all nodes have parents. Raises ParentlessNodeError if
42
+ # an invalid node is found. Default is false.
43
+ # [:indent] Number of spaces to indent between levels for basic + number trees. Default is 4.
44
+ # [:limit] Limits the level or depth of a tree that is displayed. Root node is level 0.
45
+ # [:description] Displays brief description about tree ie how many nodes it has.
46
+ # [:multi_line_nodes] Handles multi-lined nodes by indenting their newlines. Default is false.
47
+ # Examples:
48
+ # Hirb::Helpers::Tree.render([[0, 'root'], [1, 'child']], :type=>:directory)
49
+ def render(nodes, options={})
50
+ new(nodes, options).render
51
+ end
52
+ end
53
+
54
+ # :stopdoc:
55
+ attr_accessor :nodes
56
+
57
+ def initialize(input_nodes, options={})
58
+ @options = options
59
+ @type = options[:type] || :basic
60
+ if input_nodes[0].is_a?(Array)
61
+ @nodes = input_nodes.map {|e| Node.new(:level=>e[0], :value=>e[1]) }
62
+ else
63
+ @nodes = input_nodes.map {|e| Node.new(e)}
64
+ end
65
+ @nodes.each_with_index {|e,i| e.merge!(:tree=>self, :index=>i)}
66
+ @nodes.each {|e| e[:value] = e[:value].to_s }
67
+ validate_nodes if options[:validate]
68
+ self
69
+ end
70
+
71
+ def render
72
+ body = render_tree
73
+ body += render_description if @options[:description]
74
+ body
75
+ end
76
+
77
+ def render_description
78
+ "\n\n#{@nodes.length} #{@nodes.length == 1 ? 'node' : 'nodes'} in tree"
79
+ end
80
+
81
+ def render_tree
82
+ @indent = ' ' * (@options[:indent] || 4 )
83
+ @nodes = @nodes.select {|e| e[:level] <= @options[:limit] } if @options[:limit]
84
+ case @type.to_s
85
+ when 'directory' then render_directory
86
+ when 'number' then render_number
87
+ else render_basic
88
+ end
89
+ end
90
+
91
+ def render_nodes
92
+ value_indent = @options[:multi_line_nodes] ? @indent : nil
93
+ @nodes.map {|e| yield(e) + e.value(value_indent) }.join("\n")
94
+ end
95
+
96
+ def render_directory
97
+ mark_last_nodes_per_level
98
+ render_nodes {|e|
99
+ value = ''
100
+ unless e.root?
101
+ value << e.render_parent_characters
102
+ value << (e[:last_node] ? "`-- " : "|-- ")
103
+ end
104
+ value
105
+ }
106
+ end
107
+
108
+ def render_number
109
+ counter = {}
110
+ @nodes.each {|e|
111
+ parent_level_key = "#{(e.parent ||{})[:index]}.#{e[:level]}"
112
+ counter[parent_level_key] ||= 0
113
+ counter[parent_level_key] += 1
114
+ e[:pre_value] = "#{counter[parent_level_key]}. "
115
+ }
116
+ render_nodes {|e| @indent * e[:level] + e[:pre_value] }
117
+ end
118
+
119
+ def render_basic
120
+ render_nodes {|e| @indent * e[:level] }
121
+ end
122
+
123
+ def validate_nodes
124
+ @nodes.each do |e|
125
+ raise ParentlessNodeError if (e[:level] > e.previous[:level]) && (e[:level] - e.previous[:level]) > 1
126
+ end
127
+ end
128
+
129
+ # walks tree accumulating last nodes per unique parent+level
130
+ def mark_last_nodes_per_level
131
+ @nodes.each {|e| e.delete(:last_node)}
132
+ last_node_hash = @nodes.inject({}) {|h,e|
133
+ h["#{(e.parent ||{})[:index]}.#{e[:level]}"] = e; h
134
+ }
135
+ last_node_hash.values.uniq.each {|e| e[:last_node] = true}
136
+ end
137
+ #:startdoc:
138
+ class Node < ::Hash #:nodoc:
139
+ class MissingLevelError < StandardError; end
140
+ class MissingValueError < StandardError; end
141
+
142
+ def initialize(hash)
143
+ super
144
+ raise MissingLevelError unless hash.has_key?(:level)
145
+ raise MissingValueError unless hash.has_key?(:value)
146
+ replace(hash)
147
+ end
148
+
149
+ def value(indent=nil)
150
+ indent ? self[:value].gsub("\n", "\n#{indent * self[:level]}") : self[:value]
151
+ end
152
+
153
+ def parent
154
+ self[:tree].nodes.slice(0 .. self[:index]).reverse.detect {|e| e[:level] < self[:level]}
155
+ end
156
+
157
+ def next
158
+ self[:tree].nodes[self[:index] + 1]
159
+ end
160
+
161
+ def previous
162
+ self[:tree].nodes[self[:index] - 1]
163
+ end
164
+
165
+ def root?; self[:level] == 0; end
166
+
167
+ # refers to characters which connect parent nodes
168
+ def render_parent_characters
169
+ parent_chars = []
170
+ get_parents_character(parent_chars)
171
+ parent_chars.reverse.map {|level| level + ' ' * 3 }.join('')
172
+ end
173
+
174
+ def get_parents_character(parent_chars)
175
+ if self.parent
176
+ parent_chars << (self.parent[:last_node] ? ' ' : '|') unless self.parent.root?
177
+ self.parent.get_parents_character(parent_chars)
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,15 @@
1
+ # -*- encoding : utf-8 -*-
2
+ class Hirb::Helpers::UnicodeTable < Hirb::Helpers::Table
3
+ CHARS = {
4
+ :top => {:left => '┌', :center => '┬', :right => '┐', :horizontal => '─',
5
+ :vertical => {:outside => '│', :inside => '│'} },
6
+ :middle => {:left => '├', :center => '┼', :right => '┤', :horizontal => '─'},
7
+ :bottom => {:left => '└', :center => '┴', :right => '┘', :horizontal => '─',
8
+ :vertical => {:outside => '│', :inside => '╎'} }
9
+ }
10
+
11
+ # Renders a unicode table
12
+ def self.render(rows, options={})
13
+ new(rows, options).render
14
+ end
15
+ end
@@ -0,0 +1,37 @@
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 the ones below
4
+ # and :max_fields, :vertical and :max_width which aren't used.
5
+ # ==== Options:
6
+ # [:hide_empty] Boolean which hides empty values (nil or '') from being displayed. Default is false.
7
+ def self.render(rows, options={})
8
+ new(rows, {:escape_special_chars=>false, :resize=>false}.merge(options)).render
9
+ end
10
+
11
+ #:stopdoc:
12
+ def setup_field_lengths
13
+ @field_lengths = default_field_lengths
14
+ end
15
+
16
+ def render_header; []; end
17
+ def render_footer; []; end
18
+
19
+ def render_rows
20
+ i = 0
21
+ longest_header = Hirb::String.size @headers.values.sort_by {|e| Hirb::String.size(e) }.last
22
+ stars = "*" * [(longest_header + (longest_header / 2)), 3].max
23
+ @rows.map do |row|
24
+ row = "#{stars} #{i+1}. row #{stars}\n" +
25
+ @fields.map {|f|
26
+ if !@options[:hide_empty] || (@options[:hide_empty] && !row[f].empty?)
27
+ "#{Hirb::String.rjust(@headers[f], longest_header)}: #{row[f]}"
28
+ else
29
+ nil
30
+ end
31
+ }.compact.join("\n")
32
+ i+= 1
33
+ row
34
+ end
35
+ end
36
+ #:startdoc:
37
+ end
@@ -0,0 +1,18 @@
1
+ module Hirb
2
+ module Helpers #:nodoc:
3
+ @helper_classes ||= {}
4
+ def self.helper_class(klass)
5
+ @helper_classes[klass.to_s] ||= begin
6
+ if (helper_class = constants.find {|e| e.to_s == Util.camelize(klass.to_s)})
7
+ klass = "Hirb::Helpers::#{helper_class}"
8
+ end
9
+ Util.any_const_get(klass)
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ %w{table object_table auto_table tree parent_child_tree vertical_table
16
+ unicode_table tab_table}.each do |e|
17
+ require "hirb/helpers/#{e}"
18
+ end
@@ -0,0 +1,10 @@
1
+ module Hirb
2
+ module ObjectMethods
3
+ # Takes same options as Hirb::View.render_output.
4
+ def view(*args)
5
+ Hirb::Console.render_output(*(args.unshift(self)))
6
+ end
7
+ end
8
+ end
9
+
10
+ Object.send :include, Hirb::ObjectMethods
data/lib/hirb/menu.rb ADDED
@@ -0,0 +1,238 @@
1
+ module Hirb
2
+ # This class provides a menu using Hirb's table helpers by default to display choices.
3
+ # Menu choices (syntax at Hirb::Util.choose_from_array) refer to rows. However, when in
4
+ # two_d mode, choices refer to specific cells by appending a ':field' to a choice.
5
+ # A field name can be an abbreviated. Menus can also have an action mode, which turns the
6
+ # menu prompt into a commandline that executes the choices as arguments and uses methods as
7
+ # actions/commands.
8
+ class Menu
9
+ class Error < StandardError; end
10
+
11
+ # Detects valid choices and optional field/column
12
+ CHOSEN_REGEXP = /^(\d([^:]+)?)(?::)?(\S+)?/
13
+ CHOSEN_ARG = '%s'
14
+ ALL_ARG = '*'
15
+ DIRECTIONS = "Specify individual choices (4,7), range of choices (1-3) or all choices (*)."
16
+
17
+
18
+ # This method will return an array unless it's exited by simply pressing return, which returns nil.
19
+ # If given a block, the block will yield if and with any menu items are chosen.
20
+ # All options except for the ones below are passed to render the menu.
21
+ #
22
+ # ==== Options:
23
+ # [*:helper_class*] Helper class to render menu. Helper class is expected to implement numbering given a :number option.
24
+ # To use a very basic menu, set this to false. Defaults to Hirb::Helpers::AutoTable.
25
+ # [*:prompt*] String for menu prompt. Defaults to "Choose: ".
26
+ # [*:ask*] Always ask for input, even if there is only one choice. Default is true.
27
+ # [*:directions*] Display directions before prompt. Default is true.
28
+ # [*:readline*] Use readline to get user input if available. Input strings are added to readline history. Default is false.
29
+ # [*:two_d*] Turn menu into a 2 dimensional (2D) menu by allowing user to pick values from table cells. Default is false.
30
+ # [*:default_field*] Default field for a 2D menu. Defaults to first field in a table.
31
+ # [*:action*] Turn menu into an action menu by letting user pass menu choices as an argument to a method/command.
32
+ # A menu choice's place amongst other arguments is preserved. Default is false.
33
+ # [*:multi_action*] Execute action menu multiple times iterating over the menu choices. Default is false.
34
+ # [*:action_object*] Object that takes method/command calls. Default is main.
35
+ # [*:command*] Default method/command to call when no command given.
36
+ # [*:reopen*] Reopens $stdin with given file or with /dev/tty when set to true. Use when
37
+ # $stdin is already reading in piped data.
38
+ # Examples:
39
+ # >> extend Hirb::Console
40
+ # => self
41
+ # >> menu [1,2,3], :prompt=> "So many choices, so little time: "
42
+ # >> menu [{:a=>1, :b=>2}, {:a=>3, :b=>4}], :fields=>[:a,b], :two_d=>true)
43
+ def self.render(output, options={}, &block)
44
+ new(options).render(output, &block)
45
+ rescue Error=>e
46
+ $stderr.puts "Error: #{e.message}"
47
+ end
48
+
49
+ #:stopdoc:
50
+ def initialize(options={})
51
+ @options = {:helper_class=>Hirb::Helpers::AutoTable, :prompt=>"Choose: ", :ask=>true,
52
+ :directions=>true}.merge options
53
+ @options[:reopen] = '/dev/tty' if @options[:reopen] == true
54
+ end
55
+
56
+ def render(output, &block)
57
+ @output = Array(output)
58
+ return [] if @output.size.zero?
59
+ chosen = choose_from_menu
60
+ block.call(chosen) if block && chosen.size > 0
61
+ @options[:action] ? execute_action(chosen) : chosen
62
+ end
63
+
64
+ def get_input
65
+ prompt = pre_prompt + @options[:prompt]
66
+ prompt = DIRECTIONS+"\n"+prompt if @options[:directions]
67
+ $stdin.reopen @options[:reopen] if @options[:reopen]
68
+
69
+ if @options[:readline] && readline_loads?
70
+ get_readline_input(prompt)
71
+ else
72
+ print prompt
73
+ $stdin.gets.chomp.strip
74
+ end
75
+ end
76
+
77
+ def get_readline_input(prompt)
78
+ input = Readline.readline prompt
79
+ Readline::HISTORY << input
80
+ input
81
+ end
82
+
83
+ def pre_prompt
84
+ prompt = ''
85
+ prompt << "Default field: #{default_field}\n" if @options[:two_d] && default_field
86
+ prompt << "Default command: #{@options[:command]}\n" if @options[:action] && @options[:command]
87
+ prompt
88
+ end
89
+
90
+ def choose_from_menu
91
+ return unasked_choice if @output.size == 1 && !@options[:ask]
92
+
93
+ if (helper_class = Util.any_const_get(@options[:helper_class]))
94
+ View.render_output(@output, :class=>@options[:helper_class], :options=>@options.merge(:number=>true))
95
+ else
96
+ @output.each_with_index {|e,i| puts "#{i+1}: #{e}" }
97
+ end
98
+
99
+ parse_input get_input
100
+ end
101
+
102
+ def unasked_choice
103
+ return @output unless @options[:action]
104
+ raise(Error, "Default command and field required for unasked action menu") unless default_field && @options[:command]
105
+ @new_args = [@options[:command], CHOSEN_ARG]
106
+ map_tokens([[@output, default_field]])
107
+ end
108
+
109
+ def execute_action(chosen)
110
+ return nil if chosen.size.zero?
111
+ if @options[:multi_action]
112
+ chosen.each {|e| invoke command, add_chosen_to_args(e) }
113
+ else
114
+ invoke command, add_chosen_to_args(chosen)
115
+ end
116
+ end
117
+
118
+ def invoke(cmd, args)
119
+ action_object.send(cmd, *args)
120
+ end
121
+
122
+ def parse_input(input)
123
+ if (@options[:two_d] || @options[:action])
124
+ tokens = input_to_tokens(input)
125
+ map_tokens(tokens)
126
+ else
127
+ Util.choose_from_array(@output, input)
128
+ end
129
+ end
130
+
131
+ def map_tokens(tokens)
132
+ if return_cell_values?
133
+ @output[0].is_a?(Hash) ? map_hash_of_tokens(tokens) : map_array_of_tokens(tokens)
134
+ else
135
+ map_all_args(tokens) { |arr, f| arr[0] }
136
+ end
137
+ end
138
+
139
+ def return_cell_values?
140
+ @options[:two_d]
141
+ end
142
+
143
+ def map_all_args(tokens)
144
+ tokens.map { |t| t == ALL_ARG ? @output : t }
145
+ tokens.map { |arr,f|
146
+ arr == ALL_ARG ? @output : yield(arr, f)
147
+ }.flatten
148
+ end
149
+
150
+ def map_hash_of_tokens(tokens)
151
+ map_all_args(tokens) { |arr,f| arr.map {|e| e[f]} }
152
+ end
153
+
154
+ def map_array_of_tokens(tokens)
155
+ map_all_args(tokens) { |arr,f| arr.map {|e| e.is_a?(Array) && f.is_a?(Integer) ? e[f] : e.send(f) } }
156
+ end
157
+
158
+ def input_to_tokens(input)
159
+ @new_args = []
160
+ tokens = (@args = split_input_args(input)).map {|word| parse_word(word) }.compact
161
+ cleanup_new_args
162
+ tokens
163
+ end
164
+
165
+ def parse_word(word)
166
+ if word[CHOSEN_REGEXP]
167
+ @new_args << CHOSEN_ARG
168
+ field = $3 ? unalias_field($3) : default_field ||
169
+ raise(Error, "No default field/column found. Fields must be explicitly picked.")
170
+ [Util.choose_from_array(@output, word), field ]
171
+ elsif word.index(ALL_ARG)
172
+ @new_args << CHOSEN_ARG
173
+ ALL_ARG
174
+ else
175
+ @new_args << word
176
+ nil
177
+ end
178
+ end
179
+
180
+ def cleanup_new_args
181
+ if @new_args.all? {|e| e == CHOSEN_ARG }
182
+ @new_args = [CHOSEN_ARG]
183
+ elsif @new_args.index(ALL_ARG)
184
+ else
185
+ i = @new_args.index(CHOSEN_ARG) || raise(Error, "No rows chosen")
186
+ @new_args.delete(CHOSEN_ARG)
187
+ @new_args.insert(i, CHOSEN_ARG)
188
+ end
189
+ end
190
+
191
+ def add_chosen_to_args(items)
192
+ args = @new_args.dup
193
+ args[args.index(CHOSEN_ARG)] = items
194
+ args
195
+ end
196
+
197
+ def command
198
+ @command ||= begin
199
+ cmd = (@new_args == [CHOSEN_ARG]) ? nil : @new_args.shift
200
+ cmd ||= @options[:command] || raise(Error, "No command given for action menu")
201
+ end
202
+ end
203
+
204
+ def action_object
205
+ @options[:action_object] || eval("self", TOPLEVEL_BINDING)
206
+ end
207
+
208
+ def split_input_args(input)
209
+ input.split(/\s+/)
210
+ end
211
+
212
+ def default_field
213
+ @default_field ||= @options[:default_field] || fields[0]
214
+ end
215
+
216
+ # Has to be called after displaying menu
217
+ def fields
218
+ @fields ||= @options[:fields] || (@options[:ask] && table_helper_class? && Helpers::Table.last_table ?
219
+ Helpers::Table.last_table.fields[1..-1] : [])
220
+ end
221
+
222
+ def table_helper_class?
223
+ @options[:helper_class].is_a?(Class) && @options[:helper_class] < Helpers::Table
224
+ end
225
+
226
+ def unalias_field(field)
227
+ fields.sort_by {|e| e.to_s }.find {|e| e.to_s[/^#{field}/] } || raise(Error, "Invalid field '#{field}'")
228
+ end
229
+
230
+ def readline_loads?
231
+ require 'readline'
232
+ true
233
+ rescue LoadError
234
+ false
235
+ end
236
+ #:startdoc:
237
+ end
238
+ end
data/lib/hirb/pager.rb ADDED
@@ -0,0 +1,105 @@
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
+ rescue Errno::EPIPE
42
+ ensure
43
+ STDOUT.reopen(save_stdout)
44
+ save_stdout.close
45
+ pager.close
46
+ end
47
+ end
48
+
49
+ def continue_paging?
50
+ puts "=== Press enter/return to continue or q to quit: ==="
51
+ !$stdin.gets.chomp[/q/i]
52
+ end
53
+ #:startdoc:
54
+ end
55
+
56
+ attr_reader :width, :height
57
+
58
+ def initialize(width, height, options={})
59
+ resize(width, height)
60
+ @pager_command = options[:pager_command] if options[:pager_command]
61
+ end
62
+
63
+ # Pages given string using configured pager.
64
+ def page(string, inspect_mode)
65
+ if self.class.valid_pager_command?(@pager_command)
66
+ self.class.command_pager(string, :pager_command=>@pager_command)
67
+ else
68
+ self.class.default_pager(string, :width=>@width, :height=>@height, :inspect=>inspect_mode)
69
+ end
70
+ end
71
+
72
+ def slice!(output, inspect_mode=false) #:nodoc:
73
+ effective_height = @height - 2 # takes into account pager prompt
74
+ if inspect_mode
75
+ sliced_output = String.slice(output, 0, @width * effective_height)
76
+ output.replace String.slice(output, char_count(sliced_output), String.size(output))
77
+ sliced_output
78
+ else
79
+ # could use output.scan(/[^\n]*\n?/) instead of split
80
+ sliced_output = output.split("\n").slice(0, effective_height).join("\n")
81
+ output.replace output.split("\n").slice(effective_height..-1).join("\n")
82
+ sliced_output
83
+ end
84
+ end
85
+
86
+ # Determines if string should be paged based on configured width and height.
87
+ def activated_by?(string_to_page, inspect_mode=false)
88
+ inspect_mode ? (String.size(string_to_page) > @height * @width) : (string_to_page.count("\n") > @height)
89
+ end
90
+
91
+ if String.method_defined? :chars
92
+ def char_count(string) #:nodoc:
93
+ string.chars.count
94
+ end
95
+ else
96
+ def char_count(string) #:nodoc:
97
+ String.size(string)
98
+ end
99
+ end
100
+
101
+ def resize(width, height) #:nodoc:
102
+ @width, @height = View.determine_terminal_size(width, height)
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,44 @@
1
+ module Hirb
2
+ # Provides string helpers to deal with UTF-8 and ruby 1.8.x
3
+ module String
4
+ extend self
5
+ # :stopdoc:
6
+ if RUBY_VERSION < '1.9'
7
+ def size(string)
8
+ string.scan(/./).length
9
+ end
10
+
11
+ def ljust(string, desired_length)
12
+ leftover = desired_length - size(string)
13
+ leftover > 0 ? string + " " * leftover : string
14
+ end
15
+
16
+ def rjust(string, desired_length)
17
+ leftover = desired_length - size(string)
18
+ leftover > 0 ? " " * leftover + string : string
19
+ end
20
+
21
+ def slice(string, start, finish)
22
+ string.scan(/./).slice(start, finish).join('')
23
+ end
24
+ else
25
+ def size(string)
26
+ string.length
27
+ end
28
+
29
+ def ljust(string, desired_length)
30
+ string.ljust(desired_length)
31
+ end
32
+
33
+ def rjust(string, desired_length)
34
+ string.rjust(desired_length)
35
+ end
36
+
37
+ def slice(*args)
38
+ string = args.shift
39
+ string.slice(*args)
40
+ end
41
+ end
42
+ #:startdoc:
43
+ end
44
+ end