monocle-print 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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