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,111 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
|
5
|
+
module MonoclePrint
|
6
|
+
Graphics = Struct.new(
|
7
|
+
:ew, # west east - horizontal line
|
8
|
+
:ns, # north south - vertical line
|
9
|
+
:es, # north west - top right corner
|
10
|
+
:sw, # north east
|
11
|
+
:nw, # south east
|
12
|
+
:en, # south west
|
13
|
+
:ens, # north south east
|
14
|
+
:nsw, # north south west
|
15
|
+
:enw, # east north west
|
16
|
+
:esw, # east south west
|
17
|
+
:ensw, # east north south west - intersection of horiz & vertical lines
|
18
|
+
:tree_fork,
|
19
|
+
:tree_tail,
|
20
|
+
:blank
|
21
|
+
)
|
22
|
+
|
23
|
+
class Graphics
|
24
|
+
include MonoclePrint
|
25
|
+
NAMED_STYLES = Hash.new { |h, k| h[ DEFAULT_STYLE ].dup }
|
26
|
+
|
27
|
+
def self.styles
|
28
|
+
NAMED_STYLES.keys
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.define( name, *parts )
|
32
|
+
parts.map! { | p | Line( p ).freeze }
|
33
|
+
NAMED_STYLES[ name.to_s ] = new( *parts ).freeze
|
34
|
+
end
|
35
|
+
|
36
|
+
define :blank, '', '', '', '', '', '', '', '', '', '', '', '', ''
|
37
|
+
define :ascii, '-', '|', '+', '+', '+', '+', '+', '+', '+', '+', '+', '|', '`'
|
38
|
+
define :single_line, '─', '│', '┌', '┐', '┘', '└', '├', '┤', '┴', '┬', '┼', '├', '└'
|
39
|
+
define :double_line, '═', '║', '╔', '╗', '╝', '╚', '╠', '╣', '╩', '╦', '╬', '╠', '╚'
|
40
|
+
|
41
|
+
default_style = ENV.fetch( 'MONOCLE_PRINT_STYLE', 'ascii' )
|
42
|
+
unless styles.include?( default_style )
|
43
|
+
message = <<-END.gsub!( /^\s*\| ?/, '' ).strip!.gsub!( /\s+/, ' ' )
|
44
|
+
| cannot set MonoclePrint's default graphics style
|
45
|
+
| from the MONOCLE_PRINT_STYLE environment variable as `%p'
|
46
|
+
| is not a known style; defaulting to `ascii'
|
47
|
+
END
|
48
|
+
warn( message % default_style )
|
49
|
+
default_style = 'ascii'
|
50
|
+
end
|
51
|
+
|
52
|
+
DEFAULT_STYLE = default_style
|
53
|
+
|
54
|
+
def format( description )
|
55
|
+
out = Line( description )
|
56
|
+
out.gsub!( /<([nsewlrtbudhv]+)(?::(\d+))?>/i ) do
|
57
|
+
box_bit = resolve_name( $1 )
|
58
|
+
$2 ? box_bit.tile( $2.to_i ) : box_bit
|
59
|
+
end
|
60
|
+
return( out )
|
61
|
+
end
|
62
|
+
|
63
|
+
def horizontal_line( width )
|
64
|
+
ew.tile( width )
|
65
|
+
end
|
66
|
+
|
67
|
+
def box_top( width )
|
68
|
+
format( "<se><ew:#{ width }><sw>" )
|
69
|
+
end
|
70
|
+
|
71
|
+
def box_bottom( width )
|
72
|
+
format( "<ne><ew:#{ width }><nw>" )
|
73
|
+
end
|
74
|
+
|
75
|
+
def table_top( *column_widths )
|
76
|
+
nw + line_with_joints( esw, column_widths ) + ne
|
77
|
+
end
|
78
|
+
|
79
|
+
def table_divide( *column_widths )
|
80
|
+
ens + line_with_joints( ensw, column_widths ) + nsw
|
81
|
+
end
|
82
|
+
|
83
|
+
def table_bottom( *column_widths )
|
84
|
+
sw + line_with_joints( enw, column_widths ) + se
|
85
|
+
end
|
86
|
+
|
87
|
+
def line_with_joints( joint, *widths )
|
88
|
+
widths.map { | w | horizontal_line( w ) }.join( joint )
|
89
|
+
end
|
90
|
+
|
91
|
+
alias h ew
|
92
|
+
alias v ns
|
93
|
+
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def resolve_name( name )
|
98
|
+
name.downcase!
|
99
|
+
name.tr!( 'lrtbud', 'wensns' )
|
100
|
+
name.gsub!( 'h', 'ew' )
|
101
|
+
name.gsub!( 'v', 'ns' )
|
102
|
+
chars = name.chars.to_a.sort!
|
103
|
+
chars.uniq!
|
104
|
+
self[ chars.join('') ]
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
class Layout < Array
|
6
|
+
include Presentation
|
7
|
+
|
8
|
+
def initialize( options = nil )
|
9
|
+
initialize_view( options )
|
10
|
+
super( 0 )
|
11
|
+
block_given? and yield( self )
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Horizontal < Layout
|
16
|
+
|
17
|
+
def width
|
18
|
+
inject( 0 ) do | width, view |
|
19
|
+
width + view.width
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def height
|
24
|
+
empty? ? 0 : collect { | view | view.height }.max
|
25
|
+
end
|
26
|
+
|
27
|
+
def render
|
28
|
+
map { | i | i.render }.inject do | out, text |
|
29
|
+
out.juxtapose!( text )
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class Vertical < Layout
|
36
|
+
|
37
|
+
def width
|
38
|
+
empty? ? 0 : collect { | view | view.width }.max
|
39
|
+
end
|
40
|
+
|
41
|
+
def height
|
42
|
+
inject( 0 ) do | height, view |
|
43
|
+
height + view.height
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def render
|
48
|
+
map { | i | i.render }.inject do | out, text |
|
49
|
+
out.concat( text )
|
50
|
+
end.fix!
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
class Flow < Layout
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
class List
|
6
|
+
include Presentation
|
7
|
+
|
8
|
+
def initialize( *items )
|
9
|
+
@spacer = nil
|
10
|
+
@columns = nil
|
11
|
+
|
12
|
+
items = [ items ].flatten!
|
13
|
+
options = items.last.is_a?( Hash ) ? items.pop : nil
|
14
|
+
initialize_view( options )
|
15
|
+
|
16
|
+
@items = items.map! { | item | SingleLine.new( item ) }
|
17
|
+
@sorted = nil
|
18
|
+
|
19
|
+
if options
|
20
|
+
value = options[ :column_spacer ] and self.column_spacing = value
|
21
|
+
value = options[ :columns ] and self.columns = value
|
22
|
+
end
|
23
|
+
block_given? and yield( self )
|
24
|
+
end
|
25
|
+
|
26
|
+
def spacer( value = nil )
|
27
|
+
value and self.column_spacer = value
|
28
|
+
@spacer or SingleLine.new( ' ' )
|
29
|
+
end
|
30
|
+
|
31
|
+
def spacer= value
|
32
|
+
@spacer =
|
33
|
+
case value
|
34
|
+
when nil, SingleLine then value
|
35
|
+
else SingleLine.new( value )
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def spacing( n )
|
40
|
+
self.spacing = n
|
41
|
+
spacer.width
|
42
|
+
end
|
43
|
+
|
44
|
+
def spacing= width
|
45
|
+
self.spacer = ' ' * Utils.at_least( width.to_i, 0 )
|
46
|
+
end
|
47
|
+
|
48
|
+
def columns( n = nil )
|
49
|
+
n and self.columns = n
|
50
|
+
@columns or optimize_columns
|
51
|
+
end
|
52
|
+
|
53
|
+
def columns= n
|
54
|
+
@columns =
|
55
|
+
case n
|
56
|
+
when nil then n
|
57
|
+
else Utils.at_least( n.to_i, 1 )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def render_content( output )
|
64
|
+
unless columns = @columns
|
65
|
+
@sorted = sorted
|
66
|
+
columns = optimize_columns
|
67
|
+
@sorted = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
widths = calculate_columns( columns )
|
71
|
+
number_of_rows = ( @items.length.to_f / columns ).ceil
|
72
|
+
|
73
|
+
each_column = @items.each_slice( number_of_rows )
|
74
|
+
columns = each_column.zip( widths ).map do | cells, width |
|
75
|
+
if cells.length < number_of_rows
|
76
|
+
cells.fill( SingleLine.new( '' ), cells.length,
|
77
|
+
number_of_rows - cells.length )
|
78
|
+
end
|
79
|
+
cells.map! { | cell | cell.align( alignment, width ) }
|
80
|
+
end
|
81
|
+
|
82
|
+
for row in columns.transpose
|
83
|
+
output.puts( row.join( spacer ) )
|
84
|
+
end
|
85
|
+
return( output )
|
86
|
+
end
|
87
|
+
|
88
|
+
def sorted
|
89
|
+
@sorted or @items.each_with_index.sort_by { | item, index | -item.width }
|
90
|
+
end
|
91
|
+
|
92
|
+
def optimize_columns
|
93
|
+
limit = max_width
|
94
|
+
@items.empty? and return( nil )
|
95
|
+
c, rem = @items.length.divmod( 2 )
|
96
|
+
rem.zero? or c += 1
|
97
|
+
column_spacing = spacer.width
|
98
|
+
|
99
|
+
best_columns = 1
|
100
|
+
for number_of_rows in ( 1 .. c )
|
101
|
+
number_of_columns = ( @items.length.to_f / number_of_rows ).ceil
|
102
|
+
columns = calculate_columns( number_of_columns )
|
103
|
+
total_width = columns.inject( ( number_of_columns - 1 ) * column_spacing, :+ )
|
104
|
+
if total_width <= limit
|
105
|
+
best_columns = number_of_columns
|
106
|
+
break
|
107
|
+
end
|
108
|
+
end
|
109
|
+
return( best_columns )
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# create a fixed number of columns, calculating respsective
|
114
|
+
# widths and indices within the list
|
115
|
+
#
|
116
|
+
def calculate_columns( n )
|
117
|
+
rows, rem = @items.length.divmod( n )
|
118
|
+
rem.zero? or rows += 1
|
119
|
+
columns = Array.new( n )
|
120
|
+
items = sorted.dup
|
121
|
+
# items is now ordered from widest to thinnest, along with respective indices
|
122
|
+
|
123
|
+
# collect n columns and their respective width metrics,
|
124
|
+
# working through items from widest down
|
125
|
+
n.times do
|
126
|
+
# if a column has already been marked with a width,
|
127
|
+
# repeat this block
|
128
|
+
begin
|
129
|
+
item, index = items.shift
|
130
|
+
column_number = index / rows
|
131
|
+
end while columns[ column_number ]
|
132
|
+
columns[ column_number ] = item.width
|
133
|
+
end
|
134
|
+
return( columns )
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|