bbcloud 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/.gitignore +4 -0
  2. data/LICENSE +19 -0
  3. data/README +34 -0
  4. data/Rakefile +2 -0
  5. data/bbcloud.gemspec +29 -0
  6. data/bin/brightbox-accounts +9 -0
  7. data/bin/brightbox-cloudips +9 -0
  8. data/bin/brightbox-config +9 -0
  9. data/bin/brightbox-images +9 -0
  10. data/bin/brightbox-servers +9 -0
  11. data/bin/brightbox-types +9 -0
  12. data/bin/brightbox-users +9 -0
  13. data/bin/brightbox-zones +9 -0
  14. data/lib/bbcloud/accounts.rb +39 -0
  15. data/lib/bbcloud/api.rb +107 -0
  16. data/lib/bbcloud/cli.rb +127 -0
  17. data/lib/bbcloud/cloud_ips.rb +40 -0
  18. data/lib/bbcloud/commands/accounts-list.rb +9 -0
  19. data/lib/bbcloud/commands/accounts-reset-ftp-password.rb +27 -0
  20. data/lib/bbcloud/commands/accounts-show.rb +18 -0
  21. data/lib/bbcloud/commands/cloudips-create.rb +21 -0
  22. data/lib/bbcloud/commands/cloudips-destroy.rb +37 -0
  23. data/lib/bbcloud/commands/cloudips-list.rb +11 -0
  24. data/lib/bbcloud/commands/cloudips-map.rb +55 -0
  25. data/lib/bbcloud/commands/cloudips-show.rb +13 -0
  26. data/lib/bbcloud/commands/cloudips-unmap.rb +32 -0
  27. data/lib/bbcloud/commands/config-client-add.rb +45 -0
  28. data/lib/bbcloud/commands/config-client-default.rb +24 -0
  29. data/lib/bbcloud/commands/config-client-remove.rb +25 -0
  30. data/lib/bbcloud/commands/images-destroy.rb +18 -0
  31. data/lib/bbcloud/commands/images-list.rb +19 -0
  32. data/lib/bbcloud/commands/images-show.rb +17 -0
  33. data/lib/bbcloud/commands/servers-create.rb +65 -0
  34. data/lib/bbcloud/commands/servers-destroy.rb +25 -0
  35. data/lib/bbcloud/commands/servers-list.rb +13 -0
  36. data/lib/bbcloud/commands/servers-restart.rb +30 -0
  37. data/lib/bbcloud/commands/servers-show.rb +47 -0
  38. data/lib/bbcloud/commands/servers-shutdown.rb +17 -0
  39. data/lib/bbcloud/commands/servers-snapshot.rb +17 -0
  40. data/lib/bbcloud/commands/servers-start.rb +18 -0
  41. data/lib/bbcloud/commands/servers-stop.rb +18 -0
  42. data/lib/bbcloud/commands/types-list.rb +10 -0
  43. data/lib/bbcloud/commands/types-show.rb +22 -0
  44. data/lib/bbcloud/commands/users-list.rb +10 -0
  45. data/lib/bbcloud/commands/users-show.rb +27 -0
  46. data/lib/bbcloud/commands/users-update.rb +39 -0
  47. data/lib/bbcloud/commands/zones-list.rb +9 -0
  48. data/lib/bbcloud/config.rb +92 -0
  49. data/lib/bbcloud/images.rb +44 -0
  50. data/lib/bbcloud/servers.rb +66 -0
  51. data/lib/bbcloud/tables.rb +95 -0
  52. data/lib/bbcloud/types.rb +43 -0
  53. data/lib/bbcloud/users.rb +47 -0
  54. data/lib/bbcloud/vendor/hirb/.gemspec +22 -0
  55. data/lib/bbcloud/vendor/hirb/CHANGELOG.rdoc +106 -0
  56. data/lib/bbcloud/vendor/hirb/LICENSE.txt +22 -0
  57. data/lib/bbcloud/vendor/hirb/README.rdoc +182 -0
  58. data/lib/bbcloud/vendor/hirb/Rakefile +35 -0
  59. data/lib/bbcloud/vendor/hirb/lib/bond/completions/hirb.rb +15 -0
  60. data/lib/bbcloud/vendor/hirb/lib/hirb/console.rb +43 -0
  61. data/lib/bbcloud/vendor/hirb/lib/hirb/dynamic_view.rb +113 -0
  62. data/lib/bbcloud/vendor/hirb/lib/hirb/formatter.rb +116 -0
  63. data/lib/bbcloud/vendor/hirb/lib/hirb/helpers/auto_table.rb +24 -0
  64. data/lib/bbcloud/vendor/hirb/lib/hirb/helpers/object_table.rb +14 -0
  65. data/lib/bbcloud/vendor/hirb/lib/hirb/helpers/parent_child_tree.rb +24 -0
  66. data/lib/bbcloud/vendor/hirb/lib/hirb/helpers/table/filters.rb +10 -0
  67. data/lib/bbcloud/vendor/hirb/lib/hirb/helpers/table/resizer.rb +82 -0
  68. data/lib/bbcloud/vendor/hirb/lib/hirb/helpers/table.rb +323 -0
  69. data/lib/bbcloud/vendor/hirb/lib/hirb/helpers/tree.rb +181 -0
  70. data/lib/bbcloud/vendor/hirb/lib/hirb/helpers/vertical_table.rb +37 -0
  71. data/lib/bbcloud/vendor/hirb/lib/hirb/helpers.rb +17 -0
  72. data/lib/bbcloud/vendor/hirb/lib/hirb/import_object.rb +10 -0
  73. data/lib/bbcloud/vendor/hirb/lib/hirb/menu.rb +221 -0
  74. data/lib/bbcloud/vendor/hirb/lib/hirb/pager.rb +95 -0
  75. data/lib/bbcloud/vendor/hirb/lib/hirb/string.rb +44 -0
  76. data/lib/bbcloud/vendor/hirb/lib/hirb/util.rb +96 -0
  77. data/lib/bbcloud/vendor/hirb/lib/hirb/version.rb +3 -0
  78. data/lib/bbcloud/vendor/hirb/lib/hirb/view.rb +284 -0
  79. data/lib/bbcloud/vendor/hirb/lib/hirb/views/couch_db.rb +11 -0
  80. data/lib/bbcloud/vendor/hirb/lib/hirb/views/misc_db.rb +15 -0
  81. data/lib/bbcloud/vendor/hirb/lib/hirb/views/mongo_db.rb +14 -0
  82. data/lib/bbcloud/vendor/hirb/lib/hirb/views/orm.rb +11 -0
  83. data/lib/bbcloud/vendor/hirb/lib/hirb/views/rails.rb +19 -0
  84. data/lib/bbcloud/vendor/hirb/lib/hirb/views.rb +8 -0
  85. data/lib/bbcloud/vendor/hirb/lib/hirb.rb +81 -0
  86. data/lib/bbcloud/vendor/hirb/test/auto_table_test.rb +30 -0
  87. data/lib/bbcloud/vendor/hirb/test/console_test.rb +27 -0
  88. data/lib/bbcloud/vendor/hirb/test/deps.rip +4 -0
  89. data/lib/bbcloud/vendor/hirb/test/dynamic_view_test.rb +94 -0
  90. data/lib/bbcloud/vendor/hirb/test/formatter_test.rb +171 -0
  91. data/lib/bbcloud/vendor/hirb/test/hirb_test.rb +39 -0
  92. data/lib/bbcloud/vendor/hirb/test/import_test.rb +9 -0
  93. data/lib/bbcloud/vendor/hirb/test/menu_test.rb +239 -0
  94. data/lib/bbcloud/vendor/hirb/test/object_table_test.rb +79 -0
  95. data/lib/bbcloud/vendor/hirb/test/pager_test.rb +162 -0
  96. data/lib/bbcloud/vendor/hirb/test/resizer_test.rb +62 -0
  97. data/lib/bbcloud/vendor/hirb/test/table_test.rb +550 -0
  98. data/lib/bbcloud/vendor/hirb/test/test_helper.rb +61 -0
  99. data/lib/bbcloud/vendor/hirb/test/tree_test.rb +184 -0
  100. data/lib/bbcloud/vendor/hirb/test/util_test.rb +59 -0
  101. data/lib/bbcloud/vendor/hirb/test/view_test.rb +172 -0
  102. data/lib/bbcloud/vendor/hirb/test/views_test.rb +13 -0
  103. data/lib/bbcloud/version.rb +3 -0
  104. data/lib/bbcloud/zones.rb +27 -0
  105. metadata +241 -0
@@ -0,0 +1,323 @@
1
+ require 'hirb/helpers/table/filters'
2
+ require 'hirb/helpers/table/resizer'
3
+
4
+ module Hirb
5
+ # Base Table class from which other table classes inherit.
6
+ # By default, a table is constrained to a default width but this can be adjusted
7
+ # via the max_width option or Hirb::View.width.
8
+ # Rows can be an array of arrays or an array of hashes.
9
+ #
10
+ # An array of arrays ie [[1,2], [2,3]], would render:
11
+ # +---+---+
12
+ # | 0 | 1 |
13
+ # +---+---+
14
+ # | 1 | 2 |
15
+ # | 2 | 3 |
16
+ # +---+---+
17
+ #
18
+ # By default, the fields/columns are the numerical indices of the array.
19
+ #
20
+ # An array of hashes ie [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}], would render:
21
+ # +-----+--------+
22
+ # | age | weight |
23
+ # +-----+--------+
24
+ # | 10 | 100 |
25
+ # | 80 | 500 |
26
+ # +-----+--------+
27
+ #
28
+ # By default, the fields/columns are the keys of the first hash.
29
+ #
30
+ # === Custom Callbacks
31
+ # Callback methods can be defined to add your own options that modify rows right before they are rendered.
32
+ # Here's an example that allows for searching with a :query option:
33
+ # module Query
34
+ # # Searches fields given a query hash
35
+ # def query_callback(rows, options)
36
+ # return rows unless options[:query]
37
+ # options[:query].map {|field,query|
38
+ # rows.select {|e| e[field].to_s =~ /#{query}/i }
39
+ # }.flatten.uniq
40
+ # end
41
+ # end
42
+ # Hirb::Helpers::Table.send :include, Query
43
+ #
44
+ # >> puts Hirb::Helpers::Table.render [{:name=>'batman'}, {:name=>'robin'}], :query=>{:name=>'rob'}
45
+ # +-------+
46
+ # | name |
47
+ # +-------+
48
+ # | robin |
49
+ # +-------+
50
+ # 1 row in set
51
+ #
52
+ # Callback methods:
53
+ # * must be defined in Helpers::Table and end in '_callback'.
54
+ # * should expect rows and a hash of render options. Rows will be an array of hashes.
55
+ # * are expected to return an array of hashes.
56
+ # * are invoked in alphabetical order.
57
+ # For a thorough example, see {Boson::Pipe}[http://github.com/cldwalker/boson/blob/master/lib/boson/pipe.rb].
58
+ #--
59
+ # derived from http://gist.github.com/72234
60
+ class Helpers::Table
61
+ BORDER_LENGTH = 3 # " | " and "-+-" are the borders
62
+ MIN_FIELD_LENGTH = 3
63
+ class TooManyFieldsForWidthError < StandardError; end
64
+
65
+ class << self
66
+
67
+ # Main method which returns a formatted table.
68
+ # ==== Options:
69
+ # [*:fields*] An array which overrides the default fields and can be used to indicate field order.
70
+ # [*:headers*] A hash of fields and their header names. Fields that aren't specified here default to their name.
71
+ # When set to false, headers are hidden. Can also be an array but only for array rows.
72
+ # [*:max_fields*] A hash of fields and their maximum allowed lengths. Maximum length can also be a percentage of the total width
73
+ # (decimal less than one). When a field exceeds it's maximum then it's
74
+ # truncated and has a ... appended to it. Fields that aren't specified have no maximum.
75
+ # [*:max_width*] The maximum allowed width of all fields put together including field borders. Only valid when :resize is true.
76
+ # Default is Hirb::View.width.
77
+ # [*:resize*] Resizes table to display all columns in allowed :max_width. Default is true. Setting this false will display the full
78
+ # length of each field.
79
+ # [*:number*] When set to true, numbers rows by adding a :hirb_number column as the first column. Default is false.
80
+ # [*:change_fields*] A hash to change old field names to new field names. This can also be an array of new names but only for array rows.
81
+ # This is useful when wanting to change auto-generated keys to more user-friendly names i.e. for array rows.
82
+ # [*:filters*] A hash of fields and their filters, applied to every row in a field. A filter can be a proc, an instance method
83
+ # applied to the field value or a Filters method. Also see the filter_classes attribute below.
84
+ # [*:header_filter*] A filter, like one in :filters, that is applied to all headers after the :headers option.
85
+ # [*:filter_any*] When set to true, any cell defaults to being filtered by its class in :filter_classes.
86
+ # Default Hirb::Helpers::Table.filter_any().
87
+ # [*:filter_classes*] Hash which maps classes to filters. Default is Hirb::Helpers::Table.filter_classes().
88
+ # [*:vertical*] When set to true, renders a vertical table using Hirb::Helpers::VerticalTable. Default is false.
89
+ # [*:all_fields*] When set to true, renders fields in all rows. Valid only in rows that are hashes. Default is false.
90
+ # [*:description*] When set to true, renders row count description at bottom. Default is true.
91
+ # [*:escape_special_chars*] When set to true, escapes special characters \n,\t,\r so they don't disrupt tables. Default is false for
92
+ # vertical tables and true for anything else.
93
+ # Examples:
94
+ # Hirb::Helpers::Table.render [[1,2], [2,3]]
95
+ # Hirb::Helpers::Table.render [[1,2], [2,3]], :max_fields=>{0=>10}, :header_filter=>:capitalize
96
+ # Hirb::Helpers::Table.render [['a',1], ['b',2]], :change_fields=>%w{letters numbers}, :max_fields=>{'numbers'=>0.4}
97
+ # Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}]
98
+ # Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}], :headers=>{:weight=>"Weight(lbs)"}
99
+ # Hirb::Helpers::Table.render [{:age=>10, :weight=>100}, {:age=>80, :weight=>500}], :filters=>{:age=>[:to_f]}
100
+ def render(rows, options={})
101
+ options[:vertical] ? Helpers::VerticalTable.render(rows, options) :
102
+ new(rows, options).render
103
+ rescue TooManyFieldsForWidthError
104
+ $stderr.puts "", "** Error: Too many fields for the current width. Configure your width " +
105
+ "and/or fields to avoid this error. Defaulting to a vertical table. **"
106
+ Helpers::VerticalTable.render(rows, options)
107
+ end
108
+
109
+ # A hash which maps a cell value's class to a filter. This serves to set a default filter per field if all of its
110
+ # values are a class in this hash. By default, Array values are comma joined and Hashes are inspected.
111
+ # See the :filter_any option to apply this filter per value.
112
+ attr_accessor :filter_classes
113
+ # Boolean which sets the default for :filter_any option.
114
+ attr_accessor :filter_any
115
+ # Holds last table object created
116
+ attr_accessor :last_table
117
+ end
118
+ self.filter_classes = { Array=>:comma_join, Hash=>:inspect }
119
+
120
+ #:stopdoc:
121
+ attr_accessor :width, :max_fields, :field_lengths, :fields
122
+ def initialize(rows, options={})
123
+ raise ArgumentError, "Table must be an array of hashes or array of arrays" unless rows.is_a?(Array) &&
124
+ (rows[0].is_a?(Hash) or rows[0].is_a?(Array) or rows.empty?)
125
+ @options = {:description=>true, :filters=>{}, :change_fields=>{}, :escape_special_chars=>true,
126
+ :filter_any=>Helpers::Table.filter_any, :resize=>true}.merge(options)
127
+ @fields = set_fields(rows)
128
+ @rows = set_rows(rows)
129
+ @headers = set_headers
130
+ if @options[:number]
131
+ @headers[:hirb_number] = "number"
132
+ @fields.unshift :hirb_number
133
+ end
134
+ Helpers::Table.last_table = self
135
+ end
136
+
137
+ def set_fields(rows)
138
+ @options[:change_fields] = array_to_indices_hash(@options[:change_fields]) if @options[:change_fields].is_a?(Array)
139
+ return @options[:fields].dup if @options[:fields]
140
+
141
+ fields = if rows[0].is_a?(Hash)
142
+ keys = @options[:all_fields] ? rows.map {|e| e.keys}.flatten.uniq : rows[0].keys
143
+ keys.sort {|a,b| a.to_s <=> b.to_s}
144
+ else
145
+ rows[0].is_a?(Array) ? (0..rows[0].length - 1).to_a : []
146
+ end
147
+
148
+ @options[:change_fields].each do |oldf, newf|
149
+ (index = fields.index(oldf)) && fields[index] = newf
150
+ end
151
+ fields
152
+ end
153
+
154
+ def set_rows(rows)
155
+ rows = Array(rows)
156
+ if rows[0].is_a?(Array)
157
+ rows = rows.inject([]) {|new_rows, row|
158
+ new_rows << array_to_indices_hash(row)
159
+ }
160
+ end
161
+ @options[:change_fields].each do |oldf, newf|
162
+ rows.each {|e| e[newf] = e.delete(oldf) if e.key?(oldf) }
163
+ end
164
+ rows = filter_values(rows)
165
+ rows.each_with_index {|e,i| e[:hirb_number] = (i + 1).to_s} if @options[:number]
166
+ deleted_callbacks = Array(@options[:delete_callbacks]).map {|e| "#{e}_callback" }
167
+ (methods.grep(/_callback$/).map {|e| e.to_s} - deleted_callbacks).sort.each do |meth|
168
+ rows = send(meth, rows, @options.dup)
169
+ end
170
+ validate_values(rows)
171
+ rows
172
+ end
173
+
174
+ def set_headers
175
+ headers = @fields.inject({}) {|h,e| h[e] = e.to_s; h}
176
+ if @options.has_key?(:headers)
177
+ headers = @options[:headers].is_a?(Hash) ? headers.merge(@options[:headers]) :
178
+ (@options[:headers].is_a?(Array) ? array_to_indices_hash(@options[:headers]) : @options[:headers])
179
+ end
180
+ if @options[:header_filter]
181
+ headers.each {|k,v|
182
+ headers[k] = call_filter(@options[:header_filter], v)
183
+ }
184
+ end
185
+ headers
186
+ end
187
+
188
+ def render
189
+ body = []
190
+ unless @rows.length == 0
191
+ setup_field_lengths
192
+ body += render_header
193
+ body += render_rows
194
+ body += render_footer
195
+ end
196
+ body << render_table_description if @options[:description]
197
+ body.join("\n")
198
+ end
199
+
200
+ def render_header
201
+ @headers ? render_table_header : [render_border]
202
+ end
203
+
204
+ def render_footer
205
+ [render_border]
206
+ end
207
+
208
+ def render_table_header
209
+ title_row = '| ' + @fields.map {|f|
210
+ format_cell(@headers[f], @field_lengths[f])
211
+ }.join(' | ') + ' |'
212
+ [render_border, title_row, render_border]
213
+ end
214
+
215
+ def render_border
216
+ '+-' + @fields.map {|f| '-' * @field_lengths[f] }.join('-+-') + '-+'
217
+ end
218
+
219
+ def format_cell(value, cell_width)
220
+ text = String.size(value) > cell_width ?
221
+ (
222
+ (cell_width < 5) ? String.slice(value, 0, cell_width) : String.slice(value, 0, cell_width - 3) + '...'
223
+ ) : value
224
+ String.ljust(text, cell_width)
225
+ end
226
+
227
+ def render_rows
228
+ @rows.map do |row|
229
+ row = '| ' + @fields.map {|f|
230
+ format_cell(row[f], @field_lengths[f])
231
+ }.join(' | ') + ' |'
232
+ end
233
+ end
234
+
235
+ def render_table_description
236
+ (@rows.length == 0) ? "0 rows in set" :
237
+ "#{@rows.length} #{@rows.length == 1 ? 'row' : 'rows'} in set"
238
+ end
239
+
240
+ def setup_field_lengths
241
+ @field_lengths = default_field_lengths
242
+ if @options[:resize]
243
+ raise TooManyFieldsForWidthError if @fields.size > self.actual_width.to_f / MIN_FIELD_LENGTH
244
+ Resizer.resize!(self)
245
+ else
246
+ enforce_field_constraints
247
+ end
248
+ end
249
+
250
+ def enforce_field_constraints
251
+ max_fields.each {|k,max| @field_lengths[k] = max if @field_lengths[k].to_i > max }
252
+ end
253
+
254
+ def max_fields
255
+ @max_fields ||= (@options[:max_fields] ||= {}).each {|k,v|
256
+ @options[:max_fields][k] = (actual_width * v.to_f.abs).floor if v.to_f.abs < 1
257
+ }
258
+ end
259
+
260
+ def actual_width
261
+ @actual_width ||= self.width - (@fields.size * BORDER_LENGTH + 1)
262
+ end
263
+
264
+ def width
265
+ @width ||= @options[:max_width] || View.width
266
+ end
267
+
268
+ # find max length for each field; start with the headers
269
+ def default_field_lengths
270
+ field_lengths = @headers ? @headers.inject({}) {|h,(k,v)| h[k] = String.size(v); h} :
271
+ @fields.inject({}) {|h,e| h[e] = 1; h }
272
+ @rows.each do |row|
273
+ @fields.each do |field|
274
+ len = String.size(row[field])
275
+ field_lengths[field] = len if len > field_lengths[field].to_i
276
+ end
277
+ end
278
+ field_lengths
279
+ end
280
+
281
+ def set_filter_defaults(rows)
282
+ @filter_classes.each do |klass, filter|
283
+ @fields.each {|field|
284
+ if rows.all? {|r| r[field].class == klass }
285
+ @options[:filters][field] ||= filter
286
+ end
287
+ }
288
+ end
289
+ end
290
+
291
+ def filter_values(rows)
292
+ @filter_classes = Helpers::Table.filter_classes.merge @options[:filter_classes] || {}
293
+ set_filter_defaults(rows) unless @options[:filter_any]
294
+ rows.map {|row|
295
+ @fields.inject({}) {|new_row,f|
296
+ (filter = @options[:filters][f]) || (@options[:filter_any] && (filter = @filter_classes[row[f].class]))
297
+ new_row[f] = filter ? call_filter(filter, row[f]) : row[f]
298
+ new_row
299
+ }
300
+ }
301
+ end
302
+
303
+ def call_filter(filter, val)
304
+ filter.is_a?(Proc) ? filter.call(val) :
305
+ val.respond_to?(Array(filter)[0]) ? val.send(*filter) : Filters.send(filter, val)
306
+ end
307
+
308
+ def validate_values(rows)
309
+ rows.each {|row|
310
+ @fields.each {|f|
311
+ row[f] = row[f].to_s || ''
312
+ row[f] = row[f].gsub(/(\t|\r|\n)/) {|e| e.dump.gsub('"','') } if @options[:escape_special_chars]
313
+ }
314
+ }
315
+ end
316
+
317
+ # Converts an array to a hash mapping a numerical index to its array value.
318
+ def array_to_indices_hash(array)
319
+ array.inject({}) {|hash,e| hash[hash.size] = e; hash }
320
+ end
321
+ #:startdoc:
322
+ end
323
+ end
@@ -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,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,17 @@
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}.each do |e|
16
+ require "hirb/helpers/#{e}"
17
+ 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