ohboyohboyohboy-monocle-print 1.1.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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/History.txt +4 -0
- data/Manifest.txt +21 -0
- data/README.txt +40 -0
- data/Rakefile +21 -0
- data/lib/monocle-print.rb +87 -0
- data/lib/monocle-print/atomic.rb +461 -0
- data/lib/monocle-print/geometry.rb +66 -0
- data/lib/monocle-print/graphics.rb +86 -0
- data/lib/monocle-print/graphics/registry.rb +63 -0
- data/lib/monocle-print/layout.rb +58 -0
- data/lib/monocle-print/list.rb +138 -0
- data/lib/monocle-print/output-device.rb +529 -0
- data/lib/monocle-print/presentation.rb +132 -0
- data/lib/monocle-print/progress.rb +192 -0
- data/lib/monocle-print/table.rb +229 -0
- data/lib/monocle-print/table/column.rb +98 -0
- data/lib/monocle-print/table/members.rb +252 -0
- data/lib/monocle-print/table/segments.rb +62 -0
- data/lib/monocle-print/terminal-escapes.rb +205 -0
- data/lib/monocle-print/utils.rb +23 -0
- metadata +98 -0
@@ -0,0 +1,132 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
module Presentation
|
6
|
+
include MonoclePrint
|
7
|
+
ALIGNMENTS = [ :left, :right, :center ]
|
8
|
+
|
9
|
+
def self.included( klass )
|
10
|
+
super
|
11
|
+
klass.extend( ClassMethods )
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :owner
|
15
|
+
protected :owner=
|
16
|
+
|
17
|
+
for attr in %w( margin padding border )
|
18
|
+
class_eval( <<-END, __FILE__, __LINE__ + 1 )
|
19
|
+
def #{ attr }( value = nil )
|
20
|
+
value and self.#{ attr } = value
|
21
|
+
@#{ attr } ||= default_#{ attr }
|
22
|
+
block_given? ? yield( @#{ attr } ) : @#{ attr }
|
23
|
+
end
|
24
|
+
|
25
|
+
def #{ attr }= value
|
26
|
+
@#{ attr } = Rectangle( value )
|
27
|
+
end
|
28
|
+
END
|
29
|
+
end
|
30
|
+
|
31
|
+
def alignment( value = nil )
|
32
|
+
value and self.alignment = value
|
33
|
+
@alignment or @owner ? @owner.alignment : :left
|
34
|
+
end
|
35
|
+
|
36
|
+
def alignment= value
|
37
|
+
ALIGNMENTS.member?( value = value.to_sym ) or
|
38
|
+
raise( ArgumentError, "unkown alignment: %p" % value )
|
39
|
+
@alignment = value
|
40
|
+
end
|
41
|
+
|
42
|
+
def style( value = nil )
|
43
|
+
value and self.style = value
|
44
|
+
@style or @owner ? @owner.style : Graphics.default
|
45
|
+
end
|
46
|
+
|
47
|
+
def style= value
|
48
|
+
@style = Style( value )
|
49
|
+
end
|
50
|
+
|
51
|
+
def render( output = @output )
|
52
|
+
if output
|
53
|
+
render_content( output )
|
54
|
+
return output
|
55
|
+
else
|
56
|
+
OutputDevice.buffer do | out |
|
57
|
+
render_content( out )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
OutputDevice.buffer do | out |
|
64
|
+
render_content( out )
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def height
|
69
|
+
@height or calculate_height
|
70
|
+
end
|
71
|
+
|
72
|
+
def width
|
73
|
+
@width or calculate_width
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_writer :max_width
|
77
|
+
|
78
|
+
def max_width
|
79
|
+
@max_width or @owner && @owner.max_width or output.width
|
80
|
+
end
|
81
|
+
|
82
|
+
def output
|
83
|
+
@output ||= ( @owner and @owner.output or OutputDevice.stdout )
|
84
|
+
end
|
85
|
+
|
86
|
+
def output=( io )
|
87
|
+
@output = io.nil? ? io : Output( io )
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def initialize_view( options = nil, owner = nil )
|
93
|
+
@max_width = @width = @height = nil
|
94
|
+
@margin = @padding = @alignment = @style = nil
|
95
|
+
@output = @foreground = @background = nil
|
96
|
+
|
97
|
+
if options
|
98
|
+
val = options[ :width ] and self.width = val
|
99
|
+
val = options[ :align ] and self.alignment = val
|
100
|
+
val = options[ :style ] and self.style = val
|
101
|
+
val = options[ :padding ] and self.padding = val
|
102
|
+
val = options[ :margin ] and self.margin = val
|
103
|
+
val = options[ :output ] and self.output = val
|
104
|
+
end
|
105
|
+
|
106
|
+
@owner = owner
|
107
|
+
end
|
108
|
+
|
109
|
+
def default_margin
|
110
|
+
Rectangle.new( 0, 0, 0, 0 )
|
111
|
+
end
|
112
|
+
|
113
|
+
def default_padding
|
114
|
+
Rectangle.new( 0, 0, 0, 0 )
|
115
|
+
end
|
116
|
+
|
117
|
+
def default_border
|
118
|
+
Rectangle.new( false, false, false, false )
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
module Presentation::ClassMethods
|
124
|
+
def default( property, value = nil, &dynamic )
|
125
|
+
if dynamic
|
126
|
+
define_method( :"default_#{property}", &dynamic )
|
127
|
+
else
|
128
|
+
define_method( :"default_#{property}" ) { value }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
class Progress < OutputDevice
|
6
|
+
|
7
|
+
attr_accessor :position, :total, :bar_color
|
8
|
+
attr_reader :output, :title
|
9
|
+
attr_writer :width
|
10
|
+
|
11
|
+
def self.enumerate( collection, method = :each, *args )
|
12
|
+
block_given? or return enum_for( :enumerate, collection, method, *args )
|
13
|
+
#options = Hash === args.last ? args.pop : {}
|
14
|
+
|
15
|
+
#method = options.fetch( :method, nil )
|
16
|
+
#if method.nil? and Symbol === args.first
|
17
|
+
# method, *args = args
|
18
|
+
#else
|
19
|
+
# method ||= :each
|
20
|
+
#end
|
21
|
+
|
22
|
+
#size = options[ :size ]
|
23
|
+
#size ||= options.fetch( :length ) do
|
24
|
+
# collection.length rescue collection.size
|
25
|
+
#end
|
26
|
+
size = collection.length rescue collection.size
|
27
|
+
|
28
|
+
enum = collection.enum_for( method, *args )
|
29
|
+
run( size ) do | bar |
|
30
|
+
for item in enum
|
31
|
+
v = yield( item, bar )
|
32
|
+
bar.step
|
33
|
+
v
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.run( total, options = {} )
|
39
|
+
bar = new( total, options )
|
40
|
+
yield( bar )
|
41
|
+
ensure
|
42
|
+
bar.clear_line
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize( total, options = {} )
|
46
|
+
@total = Utils.at_least( total.to_i, 1 )
|
47
|
+
@position = 0
|
48
|
+
@title = Line( options[ :title ].to_s )
|
49
|
+
@limit = @width = @time = @next_time = nil
|
50
|
+
@bar_color = options.fetch( :bar_color, :red )
|
51
|
+
@text_color = options.fetch( :text_color, :black )
|
52
|
+
@progress = -1
|
53
|
+
@draw = true
|
54
|
+
super( options.fetch( :output, $stderr ), options )
|
55
|
+
end
|
56
|
+
|
57
|
+
def stage( title, limit = @total - @position, absolute = false )
|
58
|
+
limit += @position unless absolute
|
59
|
+
self.title = title
|
60
|
+
limit( limit ) do
|
61
|
+
return( yield( self ) )
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def step( inc = 1 )
|
66
|
+
limit = @limit || @total
|
67
|
+
@position = Utils.at_most( @position + inc, limit )
|
68
|
+
draw? and display
|
69
|
+
end
|
70
|
+
|
71
|
+
def draw?
|
72
|
+
progress = @position * 100 / @total
|
73
|
+
if @progress != progress
|
74
|
+
@progress = progress
|
75
|
+
@draw = true
|
76
|
+
end
|
77
|
+
|
78
|
+
return @draw
|
79
|
+
end
|
80
|
+
|
81
|
+
def limit( l = nil )
|
82
|
+
unless l.nil?
|
83
|
+
begin
|
84
|
+
before, @limit = @limit, Utils.at_most( l.to_i, @total )
|
85
|
+
yield( self )
|
86
|
+
ensure
|
87
|
+
self.limit = before
|
88
|
+
end
|
89
|
+
end
|
90
|
+
return( @limit )
|
91
|
+
end
|
92
|
+
|
93
|
+
def limit=( l )
|
94
|
+
@limit = Utils.at_most( l.to_i, @total )
|
95
|
+
end
|
96
|
+
|
97
|
+
def start_time
|
98
|
+
@start_time ||= Time.now
|
99
|
+
end
|
100
|
+
|
101
|
+
def duration
|
102
|
+
Time.now - start_time
|
103
|
+
end
|
104
|
+
|
105
|
+
def title= t
|
106
|
+
title = Line( t.to_s )
|
107
|
+
if title != @title
|
108
|
+
@title = title
|
109
|
+
@draw = true
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def display
|
114
|
+
return!
|
115
|
+
|
116
|
+
hour, r = time_remaining.divmod( 3600 )
|
117
|
+
min, sec = r.divmod( 60 )
|
118
|
+
sec = sec.round
|
119
|
+
eta = Line( ' %02i:%02i:%02i' % [ hour, min, sec ] )
|
120
|
+
|
121
|
+
center_width = 6
|
122
|
+
right_width = ( width - center_width ) / 2
|
123
|
+
left_width = width - center_width - right_width
|
124
|
+
|
125
|
+
center = ( @progress.to_s << '%' ).center( 6 ) # " ___% "
|
126
|
+
left = title.align( :left, left_width ).truncate!( left_width, '...' )
|
127
|
+
right = eta.align( :right, right_width )
|
128
|
+
|
129
|
+
bar = left << center << right
|
130
|
+
|
131
|
+
color_code = ''
|
132
|
+
@bar_color and color_code << ansi_color( ?b, @bar_color )
|
133
|
+
@text_color and color_code << ansi_color( ?f, @text_color )
|
134
|
+
|
135
|
+
unless color_code.empty?
|
136
|
+
fill_point = bar.char_byte( width * @position / @total )
|
137
|
+
bar.insert( fill_point, "\e[0m" )
|
138
|
+
bar.insert( 0, color_code )
|
139
|
+
end
|
140
|
+
|
141
|
+
print( bar )
|
142
|
+
@draw = false
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
def title_width
|
147
|
+
width * 0.3
|
148
|
+
end
|
149
|
+
|
150
|
+
def reset
|
151
|
+
@position = 0
|
152
|
+
@limit = nil
|
153
|
+
@start_time = nil
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_s
|
157
|
+
title_width = ( width * 0.4 ).round
|
158
|
+
title = @title.align( :center, title_width )[ 0, title_width ]
|
159
|
+
|
160
|
+
hour, r = time_remaining.divmod( 3600 )
|
161
|
+
min, sec = r.divmod( 60 )
|
162
|
+
sec = sec.round
|
163
|
+
eta = '%02i:%02i:%02i' % [ hour, min, sec ]
|
164
|
+
|
165
|
+
fill_width = width - title_width - eta.length - 9
|
166
|
+
filled = ( fill_width * @position / @total ).round
|
167
|
+
title << ' |' << ( '*' * filled ).ljust( fill_width ) <<
|
168
|
+
'| ' << ( '%3i%% ' % progress ) << eta
|
169
|
+
end
|
170
|
+
|
171
|
+
def progress
|
172
|
+
@position * 100 / @total
|
173
|
+
end
|
174
|
+
|
175
|
+
def time_remaining
|
176
|
+
@position < 2 and return( 0 )
|
177
|
+
sec_per_step = duration / @position
|
178
|
+
( @total - @position ) * sec_per_step
|
179
|
+
end
|
180
|
+
|
181
|
+
alias wipe clear_line
|
182
|
+
|
183
|
+
def hide
|
184
|
+
wipe
|
185
|
+
return yield
|
186
|
+
ensure
|
187
|
+
display
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'monocle-print/table/segments'
|
4
|
+
require 'monocle-print/table/members'
|
5
|
+
require 'monocle-print/table/column'
|
6
|
+
|
7
|
+
module MonoclePrint
|
8
|
+
class Table
|
9
|
+
include MonoclePrint
|
10
|
+
include Presentation
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
def self.build( *args )
|
14
|
+
table = new( *args ) do | table |
|
15
|
+
block_given? and yield( table )
|
16
|
+
end
|
17
|
+
return( table.render )
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def initialize( columns, options = {} )
|
22
|
+
initialize_view( options )
|
23
|
+
|
24
|
+
@titles = nil
|
25
|
+
@item = @head = Divider.new( self, :head )
|
26
|
+
@foot = Divider.new( self, :foot )
|
27
|
+
@columns = []
|
28
|
+
@body = []
|
29
|
+
|
30
|
+
case columns
|
31
|
+
when Fixnum
|
32
|
+
expand_columns( columns )
|
33
|
+
when Array
|
34
|
+
title_row( *columns )
|
35
|
+
end
|
36
|
+
|
37
|
+
block_given? and yield( self )
|
38
|
+
end
|
39
|
+
|
40
|
+
def render_content( out )
|
41
|
+
style = @style || out.style
|
42
|
+
lock do
|
43
|
+
width > out.width and resize( out.width )
|
44
|
+
each { | member | member.render( out, style ) }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def each
|
49
|
+
block_given? or return( enum_for( :each ) )
|
50
|
+
for item in @head
|
51
|
+
yield( item )
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_reader :columns, :titles
|
56
|
+
|
57
|
+
def row( *members )
|
58
|
+
@item = @item.row!( *members )
|
59
|
+
end
|
60
|
+
|
61
|
+
def title_row( *members )
|
62
|
+
@titles = @item = @item.title_row!( *members )
|
63
|
+
while @titles
|
64
|
+
if TitleRow === @titles
|
65
|
+
@titles = @titles.cells.map { | c | c.to_s }
|
66
|
+
break
|
67
|
+
end
|
68
|
+
@titles = @titles.before
|
69
|
+
end
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
def rows( *list_of_rows )
|
74
|
+
for row in list_of_rows do row( *row ) end
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def section( title, options = {} )
|
79
|
+
@item = @item.section!( title, options )
|
80
|
+
end
|
81
|
+
|
82
|
+
def divider( type = :row_divider )
|
83
|
+
@item = @item.divider!( type )
|
84
|
+
end
|
85
|
+
|
86
|
+
def fixed_columns( *column_indicies )
|
87
|
+
for column_index in column_indicies
|
88
|
+
if c = column( column_indicies )
|
89
|
+
if Array === c then c.each { | i | i.fixed = true }
|
90
|
+
else c.fixed = true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def malleable_columns( *column_indicies )
|
97
|
+
for column_index in column_indicies
|
98
|
+
if c = column( column_index )
|
99
|
+
if Array === c then c.each { | i | i.malleable = true }
|
100
|
+
else c.malleable = true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def column( name_or_index )
|
107
|
+
case name_or_index
|
108
|
+
when Integer, Range then @columns[ name_or_index ]
|
109
|
+
else
|
110
|
+
@columns.find do | col |
|
111
|
+
name_or_index === col.title
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def resize( new_size )
|
117
|
+
resizable = @columns.select { | c | c.malleable? }
|
118
|
+
if resizable.empty?
|
119
|
+
warn( "cannot resize #{ self.inspect } as all columns are fixed" )
|
120
|
+
return( self )
|
121
|
+
end
|
122
|
+
|
123
|
+
lock do
|
124
|
+
difference = new_size - @width
|
125
|
+
resize_columns( difference, resizable )
|
126
|
+
end
|
127
|
+
|
128
|
+
return( self )
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
def inner_width
|
134
|
+
@inner_width or calculate_inner_width
|
135
|
+
end
|
136
|
+
|
137
|
+
def width
|
138
|
+
@width or calculate_width
|
139
|
+
end
|
140
|
+
|
141
|
+
def expand_columns(new_size)
|
142
|
+
new_size.zero? and return
|
143
|
+
|
144
|
+
until @columns.length >= new_size
|
145
|
+
@columns << Column.new( self, @columns.length )
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def resize_columns( amount, columns )
|
152
|
+
leftover = amount
|
153
|
+
resizable_area = columns.inject( 0 ) do | sz, c |
|
154
|
+
sz + c.width
|
155
|
+
end
|
156
|
+
|
157
|
+
for column in columns
|
158
|
+
proportion = ( column.width.to_f / resizable_area )
|
159
|
+
delta = ( proportion * amount ).round
|
160
|
+
column.width += delta
|
161
|
+
leftover -= delta
|
162
|
+
end
|
163
|
+
|
164
|
+
columns.last.width += leftover
|
165
|
+
end
|
166
|
+
|
167
|
+
def expand( amount, columns )
|
168
|
+
leftover = amount
|
169
|
+
resizable_area = columns.inject( 0 ) do | sz, c |
|
170
|
+
sz + c.width
|
171
|
+
end
|
172
|
+
|
173
|
+
for column in columns
|
174
|
+
proportion = ( column.width.to_f / resizable_area )
|
175
|
+
delta = ( proportion * amount ).round
|
176
|
+
column.width += delta
|
177
|
+
leftover -= delta
|
178
|
+
end
|
179
|
+
|
180
|
+
columns.last.width += leftover
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
|
185
|
+
def lock
|
186
|
+
calculate_metrics
|
187
|
+
@item.link( @foot )
|
188
|
+
yield
|
189
|
+
ensure
|
190
|
+
@foot.unlink
|
191
|
+
clear_metrics
|
192
|
+
end
|
193
|
+
|
194
|
+
def calculate_metrics
|
195
|
+
@columns.each { | c | c.calculate_metrics }
|
196
|
+
@inner_width = calculate_inner_width
|
197
|
+
@width = calculate_width
|
198
|
+
end
|
199
|
+
|
200
|
+
def clear_metrics
|
201
|
+
@columns.each { | c | c.clear_metrics }
|
202
|
+
@inner_width = nil
|
203
|
+
@width = nil
|
204
|
+
end
|
205
|
+
|
206
|
+
def calculate_inner_width
|
207
|
+
w = @columns.inject( 0 ) { | w, c | w + c.width }
|
208
|
+
w + ( @columns.length - 1 ) * 3
|
209
|
+
end
|
210
|
+
|
211
|
+
def calculate_width
|
212
|
+
calculate_inner_width + 4
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
class ColumnLayout < Table
|
218
|
+
def initialize( columns, options = {} )
|
219
|
+
super( columns, options ) do
|
220
|
+
@item = @head = Blank.new( self )
|
221
|
+
@foot = Blank.new( self )
|
222
|
+
@style = Style( :blank )
|
223
|
+
block_given? and yield( self )
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|