monocle-print 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gemtest +0 -0
- data/History.txt +4 -0
- data/Manifest.txt +19 -0
- data/README.txt +40 -0
- data/Rakefile +21 -0
- data/lib/monocle-print/atomic.rb +462 -0
- data/lib/monocle-print/geometry.rb +66 -0
- data/lib/monocle-print/graphics.rb +111 -0
- data/lib/monocle-print/layout.rb +58 -0
- data/lib/monocle-print/list.rb +138 -0
- data/lib/monocle-print/output-device.rb +491 -0
- data/lib/monocle-print/presentation.rb +132 -0
- data/lib/monocle-print/progress.rb +192 -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/table.rb +229 -0
- data/lib/monocle-print/terminal-escapes.rb +186 -0
- data/lib/monocle-print/utils.rb +23 -0
- data/lib/monocle-print.rb +70 -0
- metadata +97 -0
@@ -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,98 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
class Table
|
6
|
+
class Column
|
7
|
+
include MonoclePrint
|
8
|
+
|
9
|
+
def initialize( table, index )
|
10
|
+
@table = table
|
11
|
+
@index = index
|
12
|
+
@wrap = false
|
13
|
+
@flow = false
|
14
|
+
@alignment = :left
|
15
|
+
@fixed_width = nil
|
16
|
+
@cached_width = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :table, :index
|
20
|
+
attr_accessor :alignment
|
21
|
+
|
22
|
+
for m in %w( wrap flow )
|
23
|
+
attr_accessor( m )
|
24
|
+
alias_method( "#{m}?", m )
|
25
|
+
undef_method( m )
|
26
|
+
end
|
27
|
+
|
28
|
+
def malleable?
|
29
|
+
@wrap and @flow
|
30
|
+
end
|
31
|
+
|
32
|
+
def malleable=( bool )
|
33
|
+
@wrap = @flow = bool
|
34
|
+
end
|
35
|
+
|
36
|
+
def fixed?
|
37
|
+
not malleable?
|
38
|
+
end
|
39
|
+
|
40
|
+
def fixed=( bool )
|
41
|
+
self.malleable = !bool
|
42
|
+
end
|
43
|
+
|
44
|
+
def title
|
45
|
+
@table.titles[ @index ]
|
46
|
+
end
|
47
|
+
|
48
|
+
def cells
|
49
|
+
@table.grep( Row ) { | row | row[ @index ] || Line( '' ) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def previous_column
|
53
|
+
@index.zero? ? nil : @table.columns[ @index - 1 ]
|
54
|
+
end
|
55
|
+
|
56
|
+
def next_column
|
57
|
+
@table.columns[ @index + 1 ]
|
58
|
+
end
|
59
|
+
|
60
|
+
def first?
|
61
|
+
@index.zero?
|
62
|
+
end
|
63
|
+
|
64
|
+
def last?
|
65
|
+
@index == (table.columns.length - 1)
|
66
|
+
end
|
67
|
+
|
68
|
+
def prepare( cell_text )
|
69
|
+
cell_text = cell_text ? cell_text.dup : Text( ' ' )
|
70
|
+
@flow and cell_text.reflow!( false )
|
71
|
+
@wrap and cell_text = cell_text.wrap( width - 1 )
|
72
|
+
cell_text.align!( @alignment, width )
|
73
|
+
end
|
74
|
+
|
75
|
+
def width=( w )
|
76
|
+
@fixed_width = Utils.at_least( w.to_i, 1 )
|
77
|
+
end
|
78
|
+
|
79
|
+
def width
|
80
|
+
@fixed_width or @cached_width or calculate_width
|
81
|
+
end
|
82
|
+
|
83
|
+
def calculate_metrics
|
84
|
+
@cached_width = @fixed_width || calculate_width
|
85
|
+
end
|
86
|
+
|
87
|
+
def clear_metrics
|
88
|
+
@cached_width = nil
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def calculate_width
|
94
|
+
@table.grep( Row ) { |r| c = r[ @index ] and c.width or 0 }.max || 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
class Table
|
6
|
+
class Member
|
7
|
+
include MonoclePrint
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_reader :member_name
|
12
|
+
def define( member_name, sup = self, &body )
|
13
|
+
klass =
|
14
|
+
Class.new( sup ) do
|
15
|
+
@member_name = member_name
|
16
|
+
class_eval( &body )
|
17
|
+
end
|
18
|
+
|
19
|
+
define_method( "#{ member_name }!" ) do |*args|
|
20
|
+
klass.new( @table, *args ) { |m| link( m ) }.tail
|
21
|
+
end
|
22
|
+
return( klass )
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :table
|
27
|
+
attr_accessor :before, :after
|
28
|
+
protected :before=, :after=
|
29
|
+
|
30
|
+
def initialize( table, *args )
|
31
|
+
@table = table
|
32
|
+
@before = nil
|
33
|
+
@after = nil
|
34
|
+
@disabled = false
|
35
|
+
block_given? and yield( self )
|
36
|
+
initialize!( *args )
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize!( * )
|
40
|
+
# do nothing
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect( *args )
|
44
|
+
content = args.map! { |a| a.inspect }.join(', ')
|
45
|
+
"#{self.class.member_name}(#{content})"
|
46
|
+
end
|
47
|
+
|
48
|
+
def each
|
49
|
+
block_given? or return( enum_for( __method__ ) )
|
50
|
+
node = self
|
51
|
+
begin
|
52
|
+
yield( node )
|
53
|
+
node = node.after
|
54
|
+
end while( node )
|
55
|
+
end
|
56
|
+
|
57
|
+
def disable
|
58
|
+
@disabled = true
|
59
|
+
end
|
60
|
+
|
61
|
+
def enable
|
62
|
+
@disabled = false
|
63
|
+
end
|
64
|
+
|
65
|
+
def enabled?
|
66
|
+
not disabled?
|
67
|
+
end
|
68
|
+
|
69
|
+
def disabled?
|
70
|
+
@disabled
|
71
|
+
end
|
72
|
+
|
73
|
+
def first?
|
74
|
+
@before.nil?
|
75
|
+
end
|
76
|
+
|
77
|
+
def last?
|
78
|
+
@after.nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
def link( item )
|
82
|
+
after, @after, item.before = @after, item, self
|
83
|
+
after ? item.link( after ) : item
|
84
|
+
end
|
85
|
+
|
86
|
+
def unlink
|
87
|
+
@before and @before.after = nil
|
88
|
+
@before = nil
|
89
|
+
return( self )
|
90
|
+
end
|
91
|
+
|
92
|
+
def render( out, style )
|
93
|
+
render!( out, style ) unless disabled?
|
94
|
+
end
|
95
|
+
|
96
|
+
def columns
|
97
|
+
table.columns
|
98
|
+
end
|
99
|
+
|
100
|
+
def tail
|
101
|
+
@after ? @after.tail : self
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
Blank =
|
106
|
+
Member.define( 'blank' ) do
|
107
|
+
def render!( * )
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
Row =
|
112
|
+
Member.define( 'row' ) do
|
113
|
+
def initialize!( *content )
|
114
|
+
@cells = [ content ].flatten!.map! { | c | Text( c ) }
|
115
|
+
@table.expand_columns( @cells.length )
|
116
|
+
end
|
117
|
+
|
118
|
+
def []( index )
|
119
|
+
@cells[ index ]
|
120
|
+
end
|
121
|
+
|
122
|
+
def []=(index, value)
|
123
|
+
@cells[ index ] = value
|
124
|
+
end
|
125
|
+
|
126
|
+
def cells
|
127
|
+
@table.columns.zip( @cells ).
|
128
|
+
map! { | col, cell | col.prepare( cell ) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def height
|
132
|
+
cells.map! { | c | c.height }.max
|
133
|
+
end
|
134
|
+
|
135
|
+
def render!( out, style )
|
136
|
+
cells =
|
137
|
+
@table.columns.zip( @cells ).map! do | col, cell |
|
138
|
+
col.prepare( cell )
|
139
|
+
end
|
140
|
+
|
141
|
+
height = cells.map { | col, cell | cell ? cell.height : 1 }.max
|
142
|
+
|
143
|
+
joint = style.format( ' <v> ' )
|
144
|
+
left = style.format( '<v> ' )
|
145
|
+
right = style.format( ' <v>' )
|
146
|
+
|
147
|
+
result = cells.inject { | result, cell | result.juxtapose( cell, joint ) }
|
148
|
+
result.each do | line |
|
149
|
+
out.put!( left + line + right )
|
150
|
+
end
|
151
|
+
return( out )
|
152
|
+
end
|
153
|
+
|
154
|
+
def inspect
|
155
|
+
super( *cells )
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def prepare
|
161
|
+
height = cells.map { | c | c.height }.max
|
162
|
+
if height > 1
|
163
|
+
cell_lines.zip( @table.columns ) do | lines, col |
|
164
|
+
if lines.length < height
|
165
|
+
blank = col.fill_text( ' ' )
|
166
|
+
lines.fill( blank, lines.length, height - lines.length )
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
return( cell_lines )
|
171
|
+
end
|
172
|
+
|
173
|
+
def pad
|
174
|
+
n = @table.columns.length
|
175
|
+
m = @cells.length
|
176
|
+
@cells.fill( Text(' '), m, n - m ) if n > m
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
TitleRow =
|
181
|
+
Member.define( 'title_row', Row ) do
|
182
|
+
def initialize!( *content )
|
183
|
+
super
|
184
|
+
divider!( :title )
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
Divider =
|
189
|
+
Member.define( 'divider' ) do
|
190
|
+
attr_accessor :type
|
191
|
+
|
192
|
+
def initialize!( type )
|
193
|
+
@type = type.to_sym
|
194
|
+
end
|
195
|
+
|
196
|
+
def render( out, style )
|
197
|
+
super( out, style ) unless @after.is_a?( Divider )
|
198
|
+
end
|
199
|
+
|
200
|
+
def inspect( *args )
|
201
|
+
super( @type, *args )
|
202
|
+
end
|
203
|
+
|
204
|
+
def render!( out, style )
|
205
|
+
fills = @table.columns.map { | c | "<h:#{ c.width + 2 }>" }
|
206
|
+
template =
|
207
|
+
case @type
|
208
|
+
when :row, :title
|
209
|
+
'<nse>' << fills.join( '<hv>' ) << '<nsw>'
|
210
|
+
when :section_open
|
211
|
+
'<nse>' << fills.join( '<hs>' ) << '<nsw>'
|
212
|
+
when :section_close
|
213
|
+
'<nse>' << fills.join( '<hn>' ) << '<nsw>'
|
214
|
+
when :head
|
215
|
+
'<se>' << fills.join( '<hs>' ) << '<sw>'
|
216
|
+
when :foot
|
217
|
+
'<ne>' << fills.join( '<hn>' ) << '<nw>'
|
218
|
+
end
|
219
|
+
out.puts( style.format( template ) )
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
SectionTitle =
|
224
|
+
Member.define( 'section' ) do
|
225
|
+
attr_accessor :title, :alignment
|
226
|
+
|
227
|
+
def initialize!( title, options = {} )
|
228
|
+
@title = Text( title )
|
229
|
+
@alignment = options.fetch( :align, :left )
|
230
|
+
@before.divider!( :section_close )
|
231
|
+
divider!( :section_open )
|
232
|
+
end
|
233
|
+
|
234
|
+
def inspect
|
235
|
+
super( @title, @alignment )
|
236
|
+
end
|
237
|
+
|
238
|
+
def render!( out, style )
|
239
|
+
w = @table.inner_width
|
240
|
+
title = @title.width > w ? @title.wrap( w ) : @title
|
241
|
+
left = style.format( '<v> ' )
|
242
|
+
right = style.format( ' <v>' )
|
243
|
+
|
244
|
+
for line in title.align( @alignment, w )
|
245
|
+
out.puts( left + line + right )
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
class Table
|
6
|
+
|
7
|
+
Segments = Struct.new(
|
8
|
+
:head, :title_row, :title_divider, :row, :row_divider,
|
9
|
+
:section, :section_close, :section_open, :foot
|
10
|
+
)
|
11
|
+
|
12
|
+
class Segments
|
13
|
+
include MonoclePrint
|
14
|
+
|
15
|
+
def self.default_filling( style )
|
16
|
+
fill = style.new.dup
|
17
|
+
new( fill, nil, fill, nil, fill, nil, fill, fill, fill )
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.default_joints( style )
|
21
|
+
head = style.format( "<h><hd><h>" )
|
22
|
+
row = style.format( " <v> " )
|
23
|
+
div = style.format( "<h><hv><h>" )
|
24
|
+
foot = style.format( "<h><hu><h>" )
|
25
|
+
|
26
|
+
new( head, row, div, row, div, nil, foot, head, foot )
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.default_left_edge( style )
|
30
|
+
head = style.format( "<dr><h>" )
|
31
|
+
row = style.format( "<v> " )
|
32
|
+
div = style.format( "<vr><h>" )
|
33
|
+
foot = style.format( "<ur><h>" )
|
34
|
+
new( head, row, div, row, div, row, div, div, foot )
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.default_right_edge( style )
|
38
|
+
head = style.format( "<h><dl>" )
|
39
|
+
row = style.format( " <v>" )
|
40
|
+
div = style.format( "<h><vl>" )
|
41
|
+
foot = style.format( "<h><ul>" )
|
42
|
+
new( head, row, div, row, div, row, div, div, foot )
|
43
|
+
end
|
44
|
+
|
45
|
+
def mask( inclusion_settings )
|
46
|
+
masked = self.class.new
|
47
|
+
each_pair do | name, text |
|
48
|
+
if text and inclusion_settings[ name ]
|
49
|
+
masked[ name ] = text
|
50
|
+
end
|
51
|
+
end
|
52
|
+
return( masked )
|
53
|
+
end
|
54
|
+
|
55
|
+
def width( inclusion_mask = nil )
|
56
|
+
inclusion_mask and return( self.mask( inclusion_mask ).width )
|
57
|
+
return( map { |text| text ? text.width : 0 }.max )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|