collimator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ results.html
3
+ pkg
4
+ html
5
+ .idea
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'open4'
4
+
5
+ # Specify your gem's dependencies in tabula.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ collimator (0.0.1)
5
+ colored
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ colored (1.2)
11
+ json (1.7.5)
12
+ open4 (1.3.0)
13
+ rake (0.9.4)
14
+ rdoc (3.12)
15
+ json (~> 1.4)
16
+
17
+ PLATFORMS
18
+ ruby
19
+
20
+ DEPENDENCIES
21
+ collimator!
22
+ open4
23
+ rake (~> 0.9.2)
24
+ rdoc
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 geordie
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/LICENSE.txt ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 YOUR NAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Collimator
2
+
3
+ a little gem for making simple formatted tables of data in a command line ruby script/gem/app.
4
+
5
+ ## Travis-ci.org
6
+
7
+ [![Build Status](https://travis-ci.org/QuantumGeordie/collimator.png?branch=master)](https://travis-ci.org/QuantumGeordie/collimator)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'collimator'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install collimator
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'bundler'
2
+ require 'rake/clean'
3
+ require 'rake/testtask'
4
+
5
+ gem 'rdoc' # we need the installed RDoc gem, not the system one
6
+ require 'rdoc/task'
7
+
8
+ include Rake::DSL
9
+
10
+ Bundler::GemHelper.install_tasks
11
+
12
+ Rake::TestTask.new do |t|
13
+ t.libs << 'test'
14
+ t.pattern = 'test/*_test.rb'
15
+ end
16
+
17
+ Rake::RDocTask.new do |rd|
18
+ rd.main = "README.rdoc"
19
+ rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*")
20
+ end
21
+
22
+ task :default => [:test]
23
+
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/collimator/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["geordie"]
6
+ gem.email = ["george.speake@gmail.com"]
7
+ gem.description = %q{Collimator}
8
+ gem.summary = %q{Easy to use tabulation for displaying data in command line scripts}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "collimator"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Collimator::VERSION
17
+ gem.add_development_dependency('rdoc')
18
+ gem.add_development_dependency('rake', '~> 0.9.2')
19
+ gem.add_dependency('colored')
20
+ end
@@ -0,0 +1,3 @@
1
+ module Collimator
2
+ VERSION = "0.0.1"
3
+ end
data/lib/collimator.rb ADDED
@@ -0,0 +1,547 @@
1
+ require File.expand_path("../collimator/version", __FILE__)
2
+
3
+ require 'rubygems'
4
+ require 'colored'
5
+
6
+ module Collimator
7
+
8
+ class TooManyDataPoints < Exception
9
+ end
10
+
11
+ module Table
12
+
13
+ @horiz = '-'
14
+ @border = '|'
15
+ @corner = '+'
16
+ @columns = []
17
+ @rows = []
18
+ @headers = []
19
+ @footers = []
20
+ @column_names = []
21
+ @use_column_headings = false
22
+ @auto_clear = true
23
+ @separators = []
24
+ @live_update = false
25
+ @table_string = []
26
+ @use_capture_string = false
27
+ @use_capture_html = false
28
+
29
+ def self.live_update=(live_upate)
30
+ @live_update = live_upate
31
+ end
32
+
33
+ def self.live_update
34
+ @live_update
35
+ end
36
+
37
+ def self.set_auto_clear(auto_clear = true)
38
+ @auto_clear = auto_clear
39
+ end
40
+
41
+ def self.set_corner(corner_character)
42
+ @corner = corner_character
43
+ end
44
+
45
+ def self.set_border(border_character)
46
+ @border = border_character
47
+ end
48
+
49
+ def self.set_horizontal(horizontal_character)
50
+ @horiz = horizontal_character
51
+ end
52
+
53
+ def self.header(text, opts = {})
54
+ width, padding, justification = parse_options(opts)
55
+
56
+ @headers << { :text => text, :padding => padding, :justification => justification }
57
+ end
58
+
59
+ def self.footer(text, opts)
60
+ width, padding, justification = parse_options(opts)
61
+
62
+ @footers << { :text => text, :padding => padding, :justification => justification }
63
+ end
64
+
65
+ def self.column(heading, opts = {})
66
+ width, padding, justification, color = parse_options(opts)
67
+
68
+ @columns << { :width => width, :padding => padding, :justification => justification, :color => color }
69
+ @use_column_headings = true if heading.length > 0
70
+ @column_names << heading
71
+ end
72
+
73
+ def self.row(row)
74
+ colored_data_array = []
75
+ if row.class.to_s == "Hash"
76
+ colored_data_array = row[:data].map { |v| {:data => v, :color => row[:color] } }
77
+ else
78
+ colored_data_array = row.clone
79
+ end
80
+ if @live_update
81
+ put_line_of_data(colored_data_array)
82
+ else
83
+ @rows << colored_data_array
84
+ end
85
+ end
86
+
87
+ def self.separator
88
+ if @live_update
89
+ put_horizontal_line_with_dividers
90
+ else
91
+ @separators << @rows.length
92
+ end
93
+ end
94
+
95
+ def self.tabulate
96
+ put_header
97
+ put_column_heading_text
98
+ prep_data
99
+ put_table
100
+ put_horizontal_line
101
+ put_footer
102
+
103
+ clear_all if @auto_clear
104
+ end
105
+
106
+ def self.tabulate_to_string
107
+ @use_capture_html = false
108
+ @use_capture_string = true
109
+ @auto_clear = false
110
+ tabulate
111
+ @table_string.join("\n")
112
+ end
113
+
114
+ def self.tabulate_to_html
115
+ @use_capture_string = false
116
+ @use_capture_html = true
117
+ @auto_clear = false
118
+
119
+ prep_html_table
120
+ tabulate
121
+ complete_html_table
122
+
123
+ @table_string.join("\n")
124
+ end
125
+
126
+ def self.prep_html_table
127
+ @table_string = []
128
+ @table_string << "<table>"
129
+ end
130
+
131
+ def self.complete_html_table
132
+ @table_string << "</table>"
133
+ end
134
+
135
+ def self.start_live_update
136
+ put_header
137
+ put_column_heading_text
138
+ end
139
+
140
+ def self.complete_live_update
141
+ put_horizontal_line
142
+ put_footer
143
+
144
+ clear_all if @auto_clear
145
+ end
146
+
147
+ def self.csv
148
+ lines = []
149
+
150
+ lines.concat(@headers.map { |h| h[:text] }) if @headers.count > 0
151
+ lines << @column_names.join(',') if @use_column_headings
152
+ @rows.each { |row| lines << row.join(',') }
153
+ lines.concat(@footers.map { |h| h[:text] }) if @headers.count > 0
154
+
155
+ out = lines.join("\n")
156
+
157
+ clear_all if @auto_clear
158
+
159
+ out
160
+ end
161
+
162
+ def self.clear_all
163
+ @columns = []
164
+ @rows = []
165
+ @headers = []
166
+ @footers = []
167
+ @column_names = []
168
+ @use_column_headings = false
169
+ @horiz = '-'
170
+ @border = '|'
171
+ @corner = '+'
172
+ @auto_clear = true
173
+ @separators = []
174
+ @live_update = false
175
+ @table_string = []
176
+ @use_capture_string = false
177
+ @use_capture_html = false
178
+ end
179
+
180
+ def self.clear_data
181
+ @rows = []
182
+ @separators = []
183
+ end
184
+
185
+ private
186
+
187
+ def self.send_line(line)
188
+ if @use_capture_string || @use_capture_html
189
+ @table_string << line
190
+ else
191
+ puts line
192
+ end
193
+ end
194
+
195
+ def self.parse_options(opts)
196
+ width = opts.has_key?(:width) ? opts[:width] : 10
197
+ padding = opts.has_key?(:padding) ? opts[:padding] : 0
198
+ justification = opts.has_key?(:justification) ? opts[:justification] : :center
199
+ col_color = opts.has_key?(:color) ? opts[:color] : nil
200
+
201
+ padding = 0 if justification == :decimal
202
+
203
+ [width, padding, justification, col_color]
204
+ end
205
+
206
+ def self.put_table
207
+ put_horizontal_line if @headers.length == 0 and !@use_column_headings
208
+ row_number = 0
209
+ @table_string << "<tbody>" if @use_capture_html
210
+ @rows.each do |row|
211
+ put_horizontal_line_with_dividers if @separators.include?(row_number)
212
+ put_line_of_data(row)
213
+ row_number += 1
214
+ end
215
+ @table_string << "</tbody>" if @use_capture_html
216
+ end
217
+
218
+ def self.prep_data
219
+ column = 0
220
+ @columns.each do |c|
221
+ if c[:justification] == :decimal
222
+ column_width = c[:width]
223
+ column_center = column_width / 2
224
+ values = []
225
+ @rows.each do |r|
226
+ value = r[column].class.to_s == "Hash" ? r[column][:data] : r[column].to_s
227
+ color = r[column].class.to_s == "Hash" ? r[column][:color] : nil
228
+
229
+ v2 = value
230
+ decimal_place = v2.index('.') ? v2.index('.') : v2.length
231
+ v2 = ' '*(column_center -decimal_place) + v2
232
+ v2 = v2.ljust(column_width)
233
+
234
+ v2 = {:data => v2, :color => color} if color
235
+ values << v2
236
+ end
237
+
238
+ row = 0
239
+ @rows.each do |r|
240
+ r[column] = values[row]
241
+ row += 1
242
+ end
243
+ end
244
+
245
+ if c[:color]
246
+ @rows.each do |r|
247
+ value = ''
248
+ color = nil
249
+ if r[column].class.to_s == "Hash" # don't change the data point color if already set. single data point color overrides column color.
250
+ color = r[column][:color]
251
+ value = r[column][:data]
252
+ else
253
+ color = c[:color]
254
+ value = r[column]
255
+ end
256
+ r[column] = {:data => value, :color => color}
257
+ end
258
+ end
259
+ column += 1
260
+ end
261
+ end
262
+
263
+ def self.prep_data_orig
264
+ column = 0
265
+ @columns.each do |c|
266
+ if c[:justification] == :decimal
267
+ column_width = c[:width]
268
+ column_center = column_width / 2
269
+ values = []
270
+ length = 0
271
+ decimal_place = 0
272
+ @rows.each do |r|
273
+ value = r[column].class.to_s == "Hash" ? r[column][:data] : r[column].to_s
274
+ values << value
275
+ length = value.length if value.length > length
276
+ end
277
+ values = values.map { |v| v.ljust(length) }
278
+ values.each do |value|
279
+ decimal_place = value.index('.') if (value.index('.') and (value.index('.') > decimal_place))
280
+ end
281
+ values = values.map do |v|
282
+ place = v.index('.') ? v.index('.') : v.length
283
+ ' '*(decimal_place - place) + v
284
+ end
285
+
286
+ row = 0
287
+ @rows.each do |r|
288
+ r[column] = values[row]
289
+ row += 1
290
+ end
291
+ end
292
+
293
+ if c[:color]
294
+ @rows.each do |r|
295
+ value = ''
296
+ color = nil
297
+ if r[column].class.to_s == "Hash" # don't change the data point color if already set. single data point color overrides column color.
298
+ color = r[column][:color]
299
+ value = r[column][:data]
300
+ else
301
+ color = c[:color]
302
+ value = r[column]
303
+ end
304
+ r[column] = {:data => value, :color => color}
305
+ end
306
+ end
307
+ column += 1
308
+ end
309
+ end
310
+
311
+ def self.put_column_heading_text
312
+ @use_capture_html ? put_column_heading_text_html : put_column_heading_text_string if @use_column_headings
313
+ end
314
+
315
+ def self.put_column_heading_text_string
316
+ put_line_of_data(@column_names)
317
+ put_horizontal_line_with_dividers
318
+ end
319
+
320
+ def self.put_column_heading_text_html
321
+ out = "<tr>\n"
322
+
323
+ @column_names.each do |cname|
324
+ out += "<th>#{cname}</th>\n"
325
+ end
326
+
327
+ out += "</tr>\n"
328
+ out += "</thead>"
329
+
330
+ send_line out
331
+ end
332
+
333
+ def self.put_header_or_footer(header_or_footer = :header)
334
+ data = header_or_footer == :footer ? @footers.clone : @headers.clone
335
+
336
+ unless @use_capture_html
337
+ if header_or_footer == :header and data.length > 0
338
+ put_horizontal_line
339
+ end
340
+ end
341
+
342
+ return if data.length == 0
343
+
344
+ @table_string << "<thead>" if @use_capture_html if header_or_footer == :header
345
+
346
+ data.each do | header |
347
+ send_line make_header_line(header)
348
+ end
349
+
350
+ put_horizontal_line
351
+ end
352
+
353
+ def self.make_header_line(data)
354
+ out = @use_capture_html ? make_header_line_html(data) : make_header_line_string(data)
355
+ out
356
+ end
357
+
358
+ def self.make_header_line_string(header)
359
+ header_width = line_width
360
+ header_line = @border
361
+ header_line += ' '*header[:padding] if header[:justification] == :left
362
+ header_line += header[:text].center(header_width - 2) if header[:justification] == :center
363
+ header_line += header[:text].ljust(header_width - header[:padding] - 2) if header[:justification] == :left
364
+ header_line += header[:text].rjust(header_width - header[:padding] - 2) if header[:justification] == :right
365
+ header_line += ' '*header[:padding] if header[:justification] == :right
366
+
367
+ header_line += @border
368
+ end
369
+
370
+ def self.make_header_line_html(data)
371
+ header_line = "<tr>"
372
+ header_line += "<th colspan='#{@column_names.count + 1}'>#{data[:text]}</th>"
373
+ header_line += "</tr>"
374
+ header_line
375
+ end
376
+
377
+ def self.put_footer
378
+ put_header_or_footer(:footer) unless @use_capture_html
379
+ end
380
+
381
+ def self.put_header
382
+ put_header_or_footer(:header)
383
+ end
384
+
385
+ def self.line_width
386
+ @columns.length + 1 + @columns.inject(0) { |sum, c| sum + c[:width] + c[:padding] }
387
+ end
388
+
389
+ def self.format_data(value, width, padding, justification)
390
+ data_value = value.class.to_s == "Hash" ? value[:data] : value
391
+
392
+ s = ''
393
+ s = data_value.to_s
394
+ s = ' '*padding + s.ljust(width) if justification == :left
395
+ s = s.rjust(width) + ' '*padding if justification == :right
396
+ s = s.center(width) if justification == :center || justification == :decimal
397
+ #s = s.send(value[:color].to_s) if value.class.to_s == "Hash"
398
+ s = s.gsub(data_value.to_s, data_value.to_s.send(value[:color])) if value.class.to_s == "Hash"
399
+ s
400
+ end
401
+
402
+ def self.put_row_of_html_data(row_data)
403
+ column = 0
404
+ row_string = "<tr>\n"
405
+
406
+ row_data.each do | val |
407
+ row_string += "<td>#{val}</td>\n"
408
+ end
409
+
410
+ row_string += "</tr>"
411
+
412
+ send_line row_string
413
+ end
414
+
415
+ def self.put_row_of_string_data(row_data)
416
+ column = 0
417
+ row_string = @border
418
+
419
+ row_data.each do | val |
420
+ s = format_data(val, @columns[column][:width], @columns[column][:padding], @columns[column][:justification])
421
+ row_string = row_string + s + @border
422
+ column += 1
423
+ end
424
+
425
+ send_line row_string
426
+ end
427
+
428
+ def self.put_line_of_data(row_data)
429
+ raise TooManyDataPoints if row_data.count > @columns.length
430
+
431
+ row_data << '' while row_data.length < @columns.length
432
+
433
+ if @use_capture_html
434
+ put_row_of_html_data row_data
435
+ else
436
+ put_row_of_string_data row_data
437
+ end
438
+ end
439
+
440
+ def self.put_horizontal_line
441
+ unless @use_capture_html
442
+ width = line_width
443
+ send_line @corner + @horiz * (width - 2) + @corner
444
+ end
445
+ end
446
+
447
+ def self.put_horizontal_line_with_dividers
448
+ unless @use_capture_html
449
+ a = []
450
+ @columns.each { | c | a << @horiz*(c[:width] + c[:padding]) }
451
+ s = @border + a.join(@corner) + @border
452
+ send_line s
453
+ end
454
+ end
455
+ end
456
+
457
+ module ProgressBar
458
+ @display_width = 100
459
+ @max = 100
460
+ @min = 0
461
+ @method = :percent
462
+ @step_size = 5
463
+ @current = 0
464
+
465
+ def self.set_parameters(params)
466
+ @display_width = params[:display_width] if params.has_key?(:display_width)
467
+ @max = params[:max] if params.has_key?(:max)
468
+ @min = params[:min] if params.has_key?(:min)
469
+ @method = params[:method] if params.has_key?(:method)
470
+ @step_size = params[:step_size] if params.has_key?(:step_size)
471
+ end
472
+
473
+ def self.start(params = {})
474
+ set_parameters params
475
+ @current = @min
476
+ put_current_progress
477
+ end
478
+
479
+ def self.increment
480
+ @current += @step_size
481
+ put_current_progress unless @current > @max
482
+ end
483
+
484
+ def self.complete
485
+ clear
486
+ puts " "*(@display_width + 2)
487
+ end
488
+
489
+ def self.clear
490
+ print "\b"*(@display_width + 2)
491
+ end
492
+
493
+ private
494
+
495
+ def self.put_current_progress
496
+ clear
497
+ STDOUT.flush
498
+ print build_bar_string
499
+ #puts build_bar_string
500
+ end
501
+
502
+ def self.build_bar_string
503
+ units = @max - @min
504
+ unit_width = @display_width/units
505
+ percent_complete = @current / (units * 1.0)
506
+
507
+ s = '-'*(percent_complete * @display_width) + ' '*((100-percent_complete) * @display_width)
508
+ s1 = s[0..(@display_width/2 - 4)]
509
+ s2 = s[(@display_width/2 + 3)..(@display_width - 1)]
510
+
511
+ formatted_percent = "%0.1f" % (percent_complete * 100)
512
+
513
+ s = '|' + s1 + "#{formatted_percent}%".center(6) + s2 + '|'
514
+ #s = "#{units} - #{unit_width} - #{percent_complete} - #{@current}"
515
+ end
516
+ end
517
+
518
+ module Spinner
519
+ @spinning = nil
520
+ @icons = ['-', '\\', '|', '/']
521
+
522
+ def self.spin
523
+ @spinning = Thread.new(@icons) do |myIcons|
524
+ i = 0
525
+ while true do
526
+ print myIcons[i]
527
+ STDOUT.flush
528
+ if i == myIcons.length - 1
529
+ i = 0
530
+ else
531
+ i += 1
532
+ end
533
+ sleep 0.1
534
+
535
+ print "\b"
536
+ STDOUT.flush
537
+ end
538
+ end
539
+ end
540
+
541
+ def self.stop
542
+ @spinning.exit
543
+ print "\b"
544
+ STDOUT.flush
545
+ end
546
+ end
547
+ end