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,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
|