monocle-print 1.0.0
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 +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
|