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.
@@ -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