ohboyohboyohboy-monocle-print 1.1.1

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