ohboyohboyohboy-monocle-print 1.1.1

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,66 @@
1
+ #!/usr/bin/ruby
2
+ # encoding: utf-8
3
+
4
+ module MonoclePrint
5
+ Pair = Struct.new( :x, :y )
6
+ class Pair
7
+ alternate_names =
8
+ {
9
+ :x => %w( width column left ),
10
+ :y => %w( height line right )
11
+ }
12
+
13
+ alternate_names.each do | field, name_list |
14
+ name_list.each do | name |
15
+ alias_method( name, field )
16
+ alias_method( "#{ name }=", "#{ field }=" )
17
+ end
18
+ end
19
+
20
+ def initialize( x = 0, y = 0 )
21
+ super
22
+ end
23
+
24
+ def +@
25
+ self.column = 0
26
+ self.line += 1
27
+ self
28
+ end
29
+
30
+ def ~
31
+ self.column = 0
32
+ self
33
+ end
34
+
35
+ def -@
36
+ self.column = 0
37
+ self.line -= 1
38
+ self
39
+ end
40
+
41
+ def +( n )
42
+ self.column += n
43
+ self
44
+ end
45
+
46
+ def -( n )
47
+ self.column -= n
48
+ self
49
+ end
50
+ end
51
+
52
+ Rectangle = Struct.new( :left, :top, :right, :bottom )
53
+ class Rectangle
54
+ def self.create( params )
55
+ left = params.fetch( :left, 0 )
56
+ right = params.fetch( :right, left )
57
+ top = params.fetch( :top, left )
58
+ bottom = params.fetch( :bottom, top )
59
+ new( left, top, right, bottom )
60
+ end
61
+
62
+ def initialize( left = 0, top = left, right = left, bottom = top )
63
+ super( left, top, right, bottom )
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/ruby
2
+ # encoding: utf-8
3
+
4
+ require 'monocle-print/graphics/registry'
5
+
6
+ module MonoclePrint
7
+ Graphics =
8
+ Struct.new(
9
+ :ew, # west east - horizontal line
10
+ :ns, # north south - vertical line
11
+ :es, # north west - top right corner
12
+ :sw, # north east
13
+ :nw, # south east
14
+ :en, # south west
15
+ :ens, # north south east
16
+ :nsw, # north south west
17
+ :enw, # east north west
18
+ :esw, # east south west
19
+ :ensw, # east north south west - intersection of horiz & vertical lines
20
+ :tree_fork,
21
+ :tree_tail,
22
+ :blank
23
+ )
24
+
25
+ class Graphics
26
+ include MonoclePrint
27
+ extend GraphicsRegistry
28
+
29
+ define :blank, '', '', '', '', '', '', '', '', '', '', '', '', ''
30
+ define :ascii, '-', '|', '+', '+', '+', '+', '+', '+', '+', '+', '+', '|', '`'
31
+ define :single_line, '─', '│', '┌', '┐', '┘', '└', '├', '┤', '┴', '┬', '┼', '├', '└'
32
+ define :double_line, '═', '║', '╔', '╗', '╝', '╚', '╠', '╣', '╩', '╦', '╬', '╠', '╚'
33
+
34
+ def format( description )
35
+ out = Line( description )
36
+ out.gsub!( /<([nsewlrtbudhv]+)(?::(\d+))?>/i ) do
37
+ box_bit = resolve_name( $1 )
38
+ $2 ? box_bit.tile( $2.to_i ) : box_bit
39
+ end
40
+ return( out )
41
+ end
42
+
43
+ def horizontal_line( width )
44
+ ew.tile( width )
45
+ end
46
+
47
+ def box_top( width )
48
+ format( "<se><ew:#{ width }><sw>" )
49
+ end
50
+
51
+ def box_bottom( width )
52
+ format( "<ne><ew:#{ width }><nw>" )
53
+ end
54
+
55
+ def table_top( *column_widths )
56
+ nw + line_with_joints( esw, column_widths ) + ne
57
+ end
58
+
59
+ def table_divide( *column_widths )
60
+ ens + line_with_joints( ensw, column_widths ) + nsw
61
+ end
62
+
63
+ def table_bottom( *column_widths )
64
+ sw + line_with_joints( enw, column_widths ) + se
65
+ end
66
+
67
+ def line_with_joints( joint, *widths )
68
+ widths.map { | w | horizontal_line( w ) }.join( joint )
69
+ end
70
+
71
+ alias h ew
72
+ alias v ns
73
+
74
+ private
75
+
76
+ def resolve_name( name )
77
+ name.downcase!
78
+ name.tr!( 'lrtbud', 'wensns' )
79
+ name.gsub!( 'h', 'ew' )
80
+ name.gsub!( 'v', 'ns' )
81
+ chars = name.chars.to_a.sort!
82
+ chars.uniq!
83
+ self[ chars.join('') ]
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/ruby
2
+ # encoding: utf-8
3
+ #
4
+ # author: Kyle Yetter
5
+ #
6
+
7
+ module MonoclePrint
8
+ module GraphicsRegistry
9
+ ENV_KEY = "MONOCLE_PRINT_STYLE"
10
+ FALLBACK_STYLE = "single_line"
11
+
12
+ attr_accessor :default_style
13
+
14
+ def named_styles
15
+ @named_styles ||= Hash.new { |h, k| h[ default_style ].dup }
16
+ end
17
+
18
+ def style?( name )
19
+ named_styles.key?( name.to_s )
20
+ end
21
+
22
+ def style( name )
23
+ named_styles[ name.to_s ]
24
+ end
25
+
26
+ def styles
27
+ named_styles.keys
28
+ end
29
+
30
+ def default_style
31
+ @default_style ||= detect_style_from_env
32
+ end
33
+
34
+ def default
35
+ style( default_style )
36
+ end
37
+
38
+ def define( name, *parts )
39
+ parts.map! { | p | Line( p ).freeze }
40
+ name = name.to_s
41
+ definition = new( *parts ).freeze
42
+ named_styles.store( name, definition )
43
+ define_singleton_method( name ) { style( name ) }
44
+ definition
45
+ end
46
+
47
+ private
48
+
49
+ def detect_style_from_env
50
+ default_style = ENV.fetch( ENV_KEY, FALLBACK_STYLE )
51
+ unless style?( default_style )
52
+ message = <<-END.gsub!( /^\s*\| ?/, '' ).strip!.gsub!( /\s+/, ' ' )
53
+ | cannot set MonoclePrint's default graphics style
54
+ | from the MONOCLE_PRINT_STYLE environment variable as `%s'
55
+ | is not a known style; defaulting to `%s'
56
+ END
57
+ warn( message % [ default_style, FALLBACK_STYLE ] )
58
+ default_style = FALLBACK_STYLE
59
+ end
60
+ default_style
61
+ end
62
+ end
63
+ 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
@@ -0,0 +1,529 @@
1
+ #!/usr/bin/ruby
2
+ # encoding: utf-8
3
+
4
+
5
+ module MonoclePrint
6
+ class OutputDevice < DelegateClass( IO )
7
+ include MonoclePrint
8
+ include TerminalEscapes
9
+
10
+ def self.open( path, options = {} )
11
+ File.open( path, options.fetch( :mode, 'w' ) ) do | file |
12
+ return( yield( new( file, options ) ) )
13
+ end
14
+ end
15
+
16
+ def self.buffer( options = {} )
17
+ case options
18
+ when Numeric then options = { :width => options }
19
+ when nil then options = {}
20
+ end
21
+
22
+ buffer = StringIO.new( '', options.fetch( :mode, 'w' ) )
23
+ out = new( buffer, options )
24
+ if block_given?
25
+ begin
26
+ yield( out )
27
+ return( out.string )
28
+ ensure
29
+ out.close
30
+ end
31
+ else
32
+ return out
33
+ end
34
+ end
35
+
36
+ def self.stdout( options = {} )
37
+ device = new( $stdout, options )
38
+ block_given? ? yield( device ) : device
39
+ end
40
+
41
+ def self.stderr( options = {} )
42
+ device = new( $stderr, options )
43
+ block_given? ? yield( device ) : device
44
+ end
45
+
46
+ DEFAULT_SIZE = Pair.new( 80, 22 ).freeze
47
+ IO_PRINT_METHODS = %w( puts print printf putc )
48
+ SIZE_IOCTL = 0x5413
49
+ SIZE_STRUCT = [ 0, 0, 0, 0 ].pack( "SSSS" ).freeze
50
+
51
+ private :reopen
52
+ attr_reader :device
53
+ attr_accessor :style
54
+
55
+ def initialize( output, options = {} )
56
+ @device =
57
+ case output
58
+ when String then File.open( output, options.fetch( :mode, 'w' ) )
59
+ when IO then output
60
+ else
61
+ if IO_PRINT_METHODS.all? { | m | output.respond_to?( m ) }
62
+ output
63
+ else
64
+ msg = "%s requires an IO-like object, but was given: %p" % [ self.class, output ]
65
+ raise( ArgumentError, msg )
66
+ end
67
+ end
68
+
69
+ super( @device )
70
+
71
+ @background_stack = []
72
+ @foreground_stack = []
73
+ @modifier_stack = []
74
+
75
+ @background = @foreground = @modifier = nil
76
+ @use_color = options.fetch( :use_color, tty? )
77
+
78
+ @cursor = Pair.new( 0, 0 )
79
+ @tab_width = options.fetch( :tab_width, 8 )
80
+ @margin = Pair.new( 0, 0 )
81
+
82
+ @newline = options.fetch( :newline, $/ )
83
+
84
+ case margin = options.fetch( :margin, 0 )
85
+ when Numeric then @margin.left = @margin.right = margin.to_i
86
+ else
87
+ @margin.left = margin[ :left ].to_i
88
+ @margin.right = margin[ :right ].to_i
89
+ end
90
+
91
+ @tabs = {}
92
+ @screen_size = nil
93
+ @forced_width = options[ :width ]
94
+ @forced_height = options[ :height ]
95
+ @alignment = options.fetch( :alignment, :left )
96
+ @style = Style( options[ :style ] )
97
+ end
98
+
99
+ def foreground( color = nil )
100
+ color or return( @foreground_stack.last )
101
+ begin
102
+ @foreground_stack.push( color )
103
+ yield
104
+ ensure
105
+ @foreground_stack.pop
106
+ end
107
+ end
108
+
109
+ def colors( fg, bg )
110
+ foreground( fg ) { background( bg ) { yield } }
111
+ end
112
+
113
+ def background( color = nil )
114
+ color or return( @background_stack.last )
115
+ begin
116
+ @background_stack.push( color )
117
+ yield
118
+ ensure
119
+ @background_stack.pop
120
+ end
121
+ end
122
+
123
+ alias on background
124
+
125
+ def modifier( name = nil )
126
+ name or return( @modifier_stack.last )
127
+ begin
128
+ @modifier_stack.push( name )
129
+ yield
130
+ ensure
131
+ @modifier_stack.pop
132
+ end
133
+ end
134
+
135
+ for color in ANSI_COLOR_NAMES
136
+ class_eval( <<-END, __FILE__, __LINE__ )
137
+ def #{ color }
138
+ foreground( :#{ color } ) { yield }
139
+ end
140
+
141
+ def on_#{ color }
142
+ background( :#{ color } ) { yield }
143
+ end
144
+ END
145
+ end
146
+
147
+ for modifier in ANSI_MODIFIER_NAMES
148
+ class_eval( <<-END, __FILE__, __LINE__ )
149
+ def #{ modifier }
150
+ modifier( :#{ modifier } ) { yield }
151
+ end
152
+ END
153
+ end
154
+
155
+ def use_color?
156
+ @use_color
157
+ end
158
+
159
+ def use_color( v = nil )
160
+ v.nil? or self.use_color = v
161
+ return( @use_color )
162
+ end
163
+
164
+ def use_color= v
165
+ @use_color = !!v
166
+ end
167
+
168
+ def margin= n
169
+ self.left_margin = self.right_margin = Utils.at_least( n.to_i, 0 )
170
+ end
171
+
172
+ def indent( n = 0, m = 0 )
173
+ n, m = n.to_i, m.to_i
174
+ self.left_margin += n
175
+ self.right_margin += m
176
+ if block_given?
177
+ begin
178
+ yield
179
+ ensure
180
+ self.left_margin -= n
181
+ self.right_margin -= m
182
+ end
183
+ else
184
+ self
185
+ end
186
+ end
187
+
188
+ def outdent( n )
189
+ self.left_margin -= n.to_i
190
+ block_given? and
191
+ begin yield
192
+ ensure self.left_margin += n.to_i
193
+ end
194
+ end
195
+
196
+ def left_margin
197
+ @margin.left
198
+ end
199
+
200
+ def right_margin
201
+ @margin.right
202
+ end
203
+
204
+ def left_margin= n
205
+ @margin.left = n.to_i
206
+ end
207
+
208
+ def right_margin= n
209
+ @margin.right = n.to_i
210
+ end
211
+
212
+ def reset!
213
+ @fg_stack.clear
214
+ @bg_stack.clear
215
+ @margin = Pair.new( 0, 0 )
216
+ @cursor = Pair.new( 0, 0 )
217
+ @screen_size = nil
218
+ end
219
+
220
+ alias use_color? use_color
221
+
222
+ def list( *args )
223
+ List.new( *args ) do | list |
224
+ list.output = self
225
+ block_given? and yield( list )
226
+ list.render
227
+ end
228
+ return( self )
229
+ end
230
+
231
+ def table( *args )
232
+ Table.new( *args ) do | t |
233
+ block_given? and yield( t )
234
+ t.render( self )
235
+ end
236
+ end
237
+
238
+ def column_layout( *args )
239
+ ColumnLayout.new( *args ) do | t |
240
+ block_given? and yield( t )
241
+ t.render( self )
242
+ end
243
+ end
244
+
245
+ def leger( char = '<h>', w = width )
246
+ puts( @style.format( char ).tile( w ) )
247
+ end
248
+
249
+ def print( *objs )
250
+ text = Text( [ objs ].flatten!.join )
251
+ @use_color or text.bleach!
252
+ last_line = text.pop
253
+ for line in text
254
+ put!( line )
255
+ end
256
+ fill( @margin.left )
257
+
258
+ @device.print( color_code )
259
+ @cursor + last_line.width
260
+ @device.print( last_line )
261
+ @device.print( clear_attr )
262
+
263
+ self
264
+ end
265
+
266
+ def puts( *objs )
267
+ text = Text( [ objs ].flatten!.join( @newline ) )
268
+ ( text.empty? or text.last.empty? ) and text << Line( '' )
269
+ for line in text
270
+ put( line )
271
+ newline!
272
+ end
273
+ self
274
+ end
275
+
276
+ def printf( fmt, *args )
277
+ print( sprintf( fmt, *args ) )
278
+ end
279
+
280
+ def putsf( fmt, *args )
281
+ puts( sprintf( fmt, *args ) )
282
+ end
283
+
284
+ for m in %w( left right center )
285
+ class_eval( <<-END, __FILE__, __LINE__ + 1 )
286
+ def #{ m }
287
+ prior, @alignment = @alignment, :#{ m }
288
+ yield
289
+ ensure
290
+ @alignment = prior
291
+ end
292
+ END
293
+ end
294
+
295
+ def close
296
+ @device.close rescue nil
297
+ end
298
+
299
+ def height
300
+ screen_size.height
301
+ end
302
+
303
+ def full_width
304
+ screen_size.width
305
+ end
306
+
307
+ def width
308
+ screen_size.width - @margin.left - @margin.right
309
+ end
310
+
311
+ def put( str, options = nil )
312
+ if options
313
+ fill = @style.format( options.fetch( :fill, ' ' ) )
314
+ align = options.fetch( :align, @alignment )
315
+ else
316
+ fill, align = ' ', @alignment
317
+ end
318
+
319
+ str = SingleLine.new( str ).align!( align, width, fill )
320
+ code = color_code
321
+ str.gsub!( /\e\[0m/ ) { "\e[0m" << code }
322
+
323
+ fill( @margin.left )
324
+ @device.print( code )
325
+ @device.print( str )
326
+ @cursor + str.width
327
+ @device.print( clear_attr )
328
+ fill( screen_size.width - @cursor.column )
329
+ self
330
+ end
331
+
332
+ def put!( str, options = nil )
333
+ put( str, options )
334
+ newline!
335
+ end
336
+
337
+ def return!
338
+ ~@cursor
339
+ @device.print("\r")
340
+ @device.flush
341
+ end
342
+
343
+ def fill( width, char = ' ' )
344
+ width =
345
+ case width
346
+ when Symbol, String
347
+ distance_to( width )
348
+ when Fixnum
349
+ Utils.at_least( width, 0 )
350
+ end
351
+ if width > 0
352
+ fill_str =
353
+ if char.length > 1 and ( char = SingleLine( char ) ).width > 1
354
+ char.tile( width )
355
+ else
356
+ char * width
357
+ end
358
+ @cursor.column += width
359
+ @device.print( fill_str )
360
+ end
361
+ self
362
+ end
363
+
364
+ def newline!
365
+ +@cursor
366
+ @device.print( @newline )
367
+ @device.flush
368
+ self
369
+ end
370
+
371
+ def space( n_lines = 1 )
372
+ n_lines.times do
373
+ put( '' )
374
+ newline!
375
+ end
376
+ self
377
+ end
378
+
379
+ def column
380
+ @cursor.column
381
+ end
382
+
383
+ def line
384
+ @cursor.line
385
+ end
386
+
387
+ def clear
388
+ @cursor.line = @cursor.column = 0
389
+ @device.print( set_cursor( 0, 0 ), clear_screen )
390
+ @device.flush
391
+ return( self )
392
+ end
393
+
394
+ def clear_line
395
+ @device.print( super )
396
+ @device.flush
397
+ return!
398
+ end
399
+
400
+ for m in %w( horizontal_line box_top box_bottom )
401
+ class_eval( <<-END, __FILE__, __LINE__ + 1 )
402
+ def #{ m }
403
+ put( @style.#{ m }( width ) )
404
+ newline!
405
+ end
406
+ END
407
+ end
408
+
409
+ def color_code
410
+ @use_color or return ''
411
+ code = ''
412
+
413
+ case fg = @foreground_stack.last
414
+ when Fixnum then code << xterm_color( ?f, fg )
415
+ when String, Symbol then code << ansi_color( ?f, fg )
416
+ end
417
+
418
+ case bg = @background_stack.last
419
+ when Fixnum then code << xterm_color( ?b, bg )
420
+ when String, Symbol then code << ansi_color( ?b, bg )
421
+ end
422
+
423
+ case mod = @modifier_stack.last
424
+ when String, Symbol then code << ansi_modifier( mod )
425
+ end
426
+
427
+ code
428
+ end
429
+
430
+ private
431
+
432
+ def abs( col )
433
+ col < 0 and col += width
434
+ Utils.bound( col, 0, width - 1 )
435
+ end
436
+
437
+
438
+ def distance_to( tab )
439
+ Utils.at_least( abs( @tabs[ tab ] ) - @cursor.columm, 0 )
440
+ end
441
+
442
+ def screen_size
443
+ @screen_size ||=
444
+ begin
445
+ if device.respond_to?( :winsize )
446
+ detected_height, detected_width = device.winsize
447
+ elsif data = SIZE_STRUCT.dup and device.ioctl( SIZE_IOCTL, data ) >= 0
448
+ detected_height, detected_width = data.unpack( "SS" )
449
+ else
450
+ size = default_size
451
+ detected_height, detected_width = size.height, size.width
452
+ end
453
+ Pair.new(
454
+ @forced_width || detected_width,
455
+ @forced_height || detected_height
456
+ )
457
+ rescue Exception
458
+ default_size
459
+ end
460
+ end
461
+
462
+ def default_size
463
+ Pair.new( default_width, default_height )
464
+ end
465
+
466
+ def default_height
467
+ ( @forced_height || ENV[ 'LINES' ] || DEFAULT_SIZE.height ).to_i
468
+ end
469
+
470
+ def default_width
471
+ ( @forced_width || ENV[ 'COLUMNS' ] || DEFAULT_SIZE.width ).to_i
472
+ end
473
+ end
474
+
475
+ class Pager < OutputDevice
476
+ unless pager_command = ENV['PAGER']
477
+ pagers = %w( most less more )
478
+ system_path = ENV[ "PATH" ].split( File::PATH_SEPARATOR )
479
+ pager_command = pagers.find do | cmd |
480
+ system_path.find { | dir | test( ?x, File.join( dir, cmd ) ) }
481
+ end
482
+ end
483
+ PAGER_COMMAND = pager_command
484
+
485
+ def self.open( options = {} )
486
+ unless PAGER_COMMAND
487
+ message = <<-END.gsub!( /\s+/, ' ' ).strip!
488
+ unable to locate a pager program on the system's PATH or from
489
+ the environmental variable, PAGER
490
+ END
491
+ raise( IOError, message )
492
+ end
493
+
494
+ options.fetch( :use_color ) { options[ :use_color ] = true }
495
+
496
+ if block_given?
497
+ IO.popen( PAGER_COMMAND, 'w' ) do | pager |
498
+ pager = new( pager, options )
499
+ return yield( pager )
500
+ end
501
+ else
502
+ return new( IO.popen( PAGER_COMMAND, 'w' ), options )
503
+ end
504
+ end
505
+
506
+ private
507
+
508
+ def screen_size
509
+ @screen_size ||=
510
+ begin
511
+ if STDOUT.respond_to?( :winsize )
512
+ detected_height, detected_width = STDOUT.winsize
513
+ elsif data = SIZE_STRUCT.dup and STDOUT.ioctl( SIZE_IOCTL, data ) >= 0
514
+ detected_height, detected_width = data.unpack( "SS" )
515
+ else
516
+ size = default_size
517
+ detected_height, detected_width = size.height, size.width
518
+ end
519
+ Pair.new(
520
+ @forced_height || detected_height,
521
+ @forced_width || detected_width
522
+ )
523
+ rescue Exception
524
+ default_size
525
+ end
526
+ end
527
+ end
528
+
529
+ end