ohboyohboyohboy-monocle-print 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|