hsume2-hirb 0.6.0.beta.1

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.
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