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,491 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'delegate'
|
5
|
+
autoload :StringIO, 'stringio' unless defined?( StringIO )
|
6
|
+
|
7
|
+
module MonoclePrint
|
8
|
+
class OutputDevice < DelegateClass( IO )
|
9
|
+
include MonoclePrint
|
10
|
+
include TerminalEscapes
|
11
|
+
|
12
|
+
def self.open( path, options = {} )
|
13
|
+
File.open( path, options.fetch( :mode, 'w' ) ) do | file |
|
14
|
+
return( yield( new( file, options ) ) )
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.buffer( options = {} )
|
19
|
+
case options
|
20
|
+
when Numeric then options = { :width => options }
|
21
|
+
when nil then options = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
buffer = StringIO.new( '', options.fetch( :mode, 'w' ) )
|
25
|
+
out = new( buffer, options )
|
26
|
+
if block_given?
|
27
|
+
begin
|
28
|
+
yield( out )
|
29
|
+
return( out.string )
|
30
|
+
ensure
|
31
|
+
out.close
|
32
|
+
end
|
33
|
+
else
|
34
|
+
return out
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.stdout( options = {} )
|
39
|
+
device = new( $stdout, options )
|
40
|
+
block_given? ? yield( device ) : device
|
41
|
+
end
|
42
|
+
|
43
|
+
DEFAULT_SIZE = Pair.new( 80, 22 ).freeze
|
44
|
+
IO_PRINT_METHODS = %w( puts print printf putc )
|
45
|
+
SIZE_IOCTL = 0x5413
|
46
|
+
SIZE_STRUCT = [ 0, 0, 0, 0 ].pack( "SSSS" ).freeze
|
47
|
+
|
48
|
+
private :reopen
|
49
|
+
attr_reader :device
|
50
|
+
attr_accessor :style
|
51
|
+
|
52
|
+
def initialize( output, options = {} )
|
53
|
+
@device =
|
54
|
+
case output
|
55
|
+
when String then File.open( output, options.fetch( :mode, 'w' ) )
|
56
|
+
when IO then output
|
57
|
+
else
|
58
|
+
if IO_PRINT_METHODS.all? { | m | output.respond_to?( m ) }
|
59
|
+
output
|
60
|
+
else
|
61
|
+
msg = "%s requires an IO-like object, but was given: %p" % [ self.class, output ]
|
62
|
+
raise( ArgumentError, msg )
|
63
|
+
end
|
64
|
+
end
|
65
|
+
super( @device )
|
66
|
+
|
67
|
+
@cursor = Pair.new( 0, 0 )
|
68
|
+
@background_stack = []
|
69
|
+
@foreground_stack = []
|
70
|
+
@background = @foreground = nil
|
71
|
+
@tab_width = options.fetch( :tab_width, 8 )
|
72
|
+
|
73
|
+
@newline = options.fetch( :newline, $/ )
|
74
|
+
@use_color = options.fetch( :use_color, tty? )
|
75
|
+
|
76
|
+
@margin = Pair.new( 0, 0 )
|
77
|
+
case margin = options.fetch( :margin, 0 )
|
78
|
+
when Numeric then @margin.left = @margin.right = margin.to_i
|
79
|
+
else
|
80
|
+
@margin.left = margin[ :left ].to_i
|
81
|
+
@margin.right = margin[ :right ].to_i
|
82
|
+
end
|
83
|
+
|
84
|
+
@tabs = {}
|
85
|
+
@screen_size = nil
|
86
|
+
@forced_width = options[ :width ]
|
87
|
+
@forced_height = options[ :height ]
|
88
|
+
@alignment = options.fetch( :alignment, :left )
|
89
|
+
@style = Style( options[ :style ] )
|
90
|
+
end
|
91
|
+
|
92
|
+
def foreground( color = nil )
|
93
|
+
color or return( @foreground_stack.last )
|
94
|
+
begin
|
95
|
+
@foreground_stack.push( color )
|
96
|
+
yield
|
97
|
+
ensure
|
98
|
+
@foreground_stack.pop
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def colors( fg, bg )
|
103
|
+
foreground( fg ) { background( bg ) { yield } }
|
104
|
+
end
|
105
|
+
|
106
|
+
def background( color = nil )
|
107
|
+
color or return( @background_stack.last )
|
108
|
+
begin
|
109
|
+
@background_stack.push( color )
|
110
|
+
yield
|
111
|
+
ensure
|
112
|
+
@background_stack.pop
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
alias on background
|
117
|
+
|
118
|
+
for color in ANSI_COLORS.keys
|
119
|
+
class_eval( <<-END, __FILE__, __LINE__ )
|
120
|
+
def #{ color }
|
121
|
+
foreground( :#{ color } ) { yield }
|
122
|
+
end
|
123
|
+
|
124
|
+
def on_#{ color }
|
125
|
+
background( :#{ color } ) { yield }
|
126
|
+
end
|
127
|
+
END
|
128
|
+
end
|
129
|
+
|
130
|
+
def use_color?
|
131
|
+
@use_color
|
132
|
+
end
|
133
|
+
|
134
|
+
def use_color( v = nil )
|
135
|
+
v.nil? or self.use_color = v
|
136
|
+
return( @use_color )
|
137
|
+
end
|
138
|
+
|
139
|
+
def use_color= v
|
140
|
+
@use_color = ! ! v # 'not not x' casts x to either `true' or 'false'
|
141
|
+
end
|
142
|
+
|
143
|
+
def margin= n
|
144
|
+
left_margin = right_margin = Utils.at_least( n.to_i, 0 )
|
145
|
+
end
|
146
|
+
|
147
|
+
def indent( n = 0, m = 0 )
|
148
|
+
n, m = n.to_i, m.to_i
|
149
|
+
self.left_margin += n
|
150
|
+
self.right_margin += m
|
151
|
+
if block_given?
|
152
|
+
begin
|
153
|
+
yield
|
154
|
+
ensure
|
155
|
+
self.left_margin -= n
|
156
|
+
self.right_margin -= m
|
157
|
+
end
|
158
|
+
else
|
159
|
+
self
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def outdent( n )
|
164
|
+
self.left_margin -= n.to_i
|
165
|
+
block_given? and
|
166
|
+
begin yield
|
167
|
+
ensure self.left_margin += n.to_i
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def left_margin
|
172
|
+
@margin.left
|
173
|
+
end
|
174
|
+
|
175
|
+
def right_margin
|
176
|
+
@margin.right
|
177
|
+
end
|
178
|
+
|
179
|
+
def left_margin= n
|
180
|
+
@margin.left = n.to_i
|
181
|
+
end
|
182
|
+
|
183
|
+
def right_margin= n
|
184
|
+
@margin.right = n.to_i
|
185
|
+
end
|
186
|
+
|
187
|
+
def reset!
|
188
|
+
@fg_stack.clear
|
189
|
+
@bg_stack.clear
|
190
|
+
@margin = Pair.new( 0, 0 )
|
191
|
+
@cursor = Pair.new( 0, 0 )
|
192
|
+
@screen_size = nil
|
193
|
+
end
|
194
|
+
|
195
|
+
alias use_color? use_color
|
196
|
+
|
197
|
+
def list( *args )
|
198
|
+
List.new( *args ) do | list |
|
199
|
+
list.output = self
|
200
|
+
block_given? and yield( list )
|
201
|
+
list.render
|
202
|
+
end
|
203
|
+
return( self )
|
204
|
+
end
|
205
|
+
|
206
|
+
def table( *args )
|
207
|
+
Table.new( *args ) do | t |
|
208
|
+
block_given? and yield( t )
|
209
|
+
t.render( self )
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def column_layout( *args )
|
214
|
+
ColumnLayout.new( *args ) do | t |
|
215
|
+
block_given? and yield( t )
|
216
|
+
t.render( self )
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def leger( char = '<h>', w = width )
|
221
|
+
puts( @style.format( char ).tile( w ) )
|
222
|
+
end
|
223
|
+
|
224
|
+
def print( *objs )
|
225
|
+
text = Text( [ objs ].flatten!.join )
|
226
|
+
@use_color or text.bleach!
|
227
|
+
last_line = text.pop
|
228
|
+
for line in text
|
229
|
+
put!( line )
|
230
|
+
end
|
231
|
+
fill( @margin.left )
|
232
|
+
@device.print( color_code )
|
233
|
+
@cursor + last_line.width
|
234
|
+
@device.print( last_line )
|
235
|
+
self
|
236
|
+
end
|
237
|
+
|
238
|
+
def puts( *objs )
|
239
|
+
text = Text( [ objs ].flatten!.join( @newline ) )
|
240
|
+
( text.empty? or text.last.empty? ) and text << Line( '' )
|
241
|
+
for line in text
|
242
|
+
put( line )
|
243
|
+
newline!
|
244
|
+
end
|
245
|
+
self
|
246
|
+
end
|
247
|
+
|
248
|
+
def printf( fmt, *args )
|
249
|
+
print( sprintf( fmt, *args ) )
|
250
|
+
end
|
251
|
+
|
252
|
+
def putsf( fmt, *args )
|
253
|
+
puts( sprintf( fmt, *args ) )
|
254
|
+
end
|
255
|
+
|
256
|
+
for m in %w( left right center )
|
257
|
+
class_eval( <<-END, __FILE__, __LINE__ + 1 )
|
258
|
+
def #{ m }
|
259
|
+
prior, @alignment = @alignment, :#{ m }
|
260
|
+
yield
|
261
|
+
ensure
|
262
|
+
@alignment = prior
|
263
|
+
end
|
264
|
+
END
|
265
|
+
end
|
266
|
+
|
267
|
+
def close
|
268
|
+
@device.close rescue nil
|
269
|
+
end
|
270
|
+
|
271
|
+
def height
|
272
|
+
screen_size.height
|
273
|
+
end
|
274
|
+
|
275
|
+
def full_width
|
276
|
+
screen_size.width
|
277
|
+
end
|
278
|
+
|
279
|
+
def width
|
280
|
+
screen_size.width - @margin.left - @margin.right
|
281
|
+
end
|
282
|
+
|
283
|
+
def put( str, options = nil )
|
284
|
+
if options
|
285
|
+
fill = @style.format( options.fetch( :fill, ' ' ) )
|
286
|
+
align = options.fetch( :align, @alignment )
|
287
|
+
else
|
288
|
+
fill, align = ' ', @alignment
|
289
|
+
end
|
290
|
+
|
291
|
+
str = SingleLine.new( str ).align!( align, width, fill )
|
292
|
+
code = color_code
|
293
|
+
str.gsub!( /\e\[0m/ ) { "\e[0m" << code }
|
294
|
+
|
295
|
+
fill( @margin.left )
|
296
|
+
@device.print( code )
|
297
|
+
@device.print( str )
|
298
|
+
@cursor + str.width
|
299
|
+
@device.print( clear_attr )
|
300
|
+
fill( @screen_size.width - @cursor.column )
|
301
|
+
self
|
302
|
+
end
|
303
|
+
|
304
|
+
def put!( str, options = nil )
|
305
|
+
put( str, options )
|
306
|
+
newline!
|
307
|
+
end
|
308
|
+
|
309
|
+
def return!
|
310
|
+
~@cursor
|
311
|
+
@device.print("\r")
|
312
|
+
@device.flush
|
313
|
+
end
|
314
|
+
|
315
|
+
def fill( width, char = ' ' )
|
316
|
+
width =
|
317
|
+
case width
|
318
|
+
when Symbol, String
|
319
|
+
distance_to( width )
|
320
|
+
when Fixnum
|
321
|
+
Utils.at_least( width, 0 )
|
322
|
+
end
|
323
|
+
if width > 0
|
324
|
+
fill_str =
|
325
|
+
if char.length > 1 and ( char = SingleLine( char ) ).width > 1
|
326
|
+
char.tile( width )
|
327
|
+
else
|
328
|
+
char * width
|
329
|
+
end
|
330
|
+
@cursor.column += width
|
331
|
+
@device.print( fill_str )
|
332
|
+
end
|
333
|
+
self
|
334
|
+
end
|
335
|
+
|
336
|
+
def newline!
|
337
|
+
+@cursor
|
338
|
+
@device.print( @newline )
|
339
|
+
@device.flush
|
340
|
+
self
|
341
|
+
end
|
342
|
+
|
343
|
+
def space( n_lines = 1 )
|
344
|
+
n_lines.times do
|
345
|
+
put( '' )
|
346
|
+
newline!
|
347
|
+
end
|
348
|
+
self
|
349
|
+
end
|
350
|
+
|
351
|
+
def column
|
352
|
+
@cursor.column
|
353
|
+
end
|
354
|
+
|
355
|
+
def line
|
356
|
+
@cursor.line
|
357
|
+
end
|
358
|
+
|
359
|
+
def clear
|
360
|
+
@cursor.line = @cursor.column = 0
|
361
|
+
@device.print( set_cursor( 0, 0 ), clear_screen )
|
362
|
+
@device.flush
|
363
|
+
return( self )
|
364
|
+
end
|
365
|
+
|
366
|
+
def clear_line
|
367
|
+
@device.print( super )
|
368
|
+
@device.flush
|
369
|
+
return!
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
for m in %w( horizontal_line box_top box_bottom )
|
374
|
+
class_eval( <<-END, __FILE__, __LINE__ + 1 )
|
375
|
+
def #{ m }
|
376
|
+
put( @style.#{ m }( width ) )
|
377
|
+
newline!
|
378
|
+
end
|
379
|
+
END
|
380
|
+
end
|
381
|
+
|
382
|
+
def color_code
|
383
|
+
@use_color or return ''
|
384
|
+
code = ''
|
385
|
+
case fg = @foreground_stack.last
|
386
|
+
when Fixnum then code << xterm_color( ?f, fg )
|
387
|
+
when String, Symbol then code << ansi_color( ?f, fg )
|
388
|
+
end
|
389
|
+
case bg = @background_stack.last
|
390
|
+
when Fixnum then code << xterm_color( ?b, bg )
|
391
|
+
when String, Symbol then code << ansi_color( ?b, bg )
|
392
|
+
end
|
393
|
+
code
|
394
|
+
end
|
395
|
+
|
396
|
+
private
|
397
|
+
|
398
|
+
def abs( col )
|
399
|
+
col < 0 and col += width
|
400
|
+
Utils.bound( col, 0, width - 1 )
|
401
|
+
end
|
402
|
+
|
403
|
+
|
404
|
+
def distance_to( tab )
|
405
|
+
Utils.at_least( abs( @tabs[ tab ] ) - @cursor.columm, 0 )
|
406
|
+
end
|
407
|
+
|
408
|
+
def screen_size
|
409
|
+
@screen_size ||= begin
|
410
|
+
data = SIZE_STRUCT.dup
|
411
|
+
if @device.ioctl( SIZE_IOCTL, data ) >= 0
|
412
|
+
height, width = data.unpack( "SS" )
|
413
|
+
@forced_width and width = @forced_width
|
414
|
+
@forced_height and height = @forced_height
|
415
|
+
Pair.new(
|
416
|
+
width > 0 ? width : default_width,
|
417
|
+
height > 0 ? height : default_height
|
418
|
+
)
|
419
|
+
else
|
420
|
+
default_size
|
421
|
+
end
|
422
|
+
rescue Exception
|
423
|
+
default_size
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
def default_size
|
428
|
+
Pair.new( @forced_width || default_width, @forced_height || default_height )
|
429
|
+
end
|
430
|
+
|
431
|
+
def default_height
|
432
|
+
( ENV[ 'LINES' ] || DEFAULT_SIZE.height ).to_i
|
433
|
+
end
|
434
|
+
|
435
|
+
def default_width
|
436
|
+
( ENV[ 'COLUMNS' ] || DEFAULT_SIZE.width ).to_i
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
class Pager < OutputDevice
|
441
|
+
unless pager_command = ENV['PAGER']
|
442
|
+
pagers = %w( most less more )
|
443
|
+
system_path = ENV[ "PATH" ].split( File::PATH_SEPARATOR )
|
444
|
+
pager_command = pagers.find do | cmd |
|
445
|
+
system_path.find { | dir | test( ?x, File.join( dir, cmd ) ) }
|
446
|
+
end
|
447
|
+
end
|
448
|
+
PAGER_COMMAND = pager_command
|
449
|
+
|
450
|
+
def self.open( options = {} )
|
451
|
+
unless PAGER_COMMAND
|
452
|
+
message = <<-END.gsub!( /\s+/, ' ' ).strip!
|
453
|
+
unable to locate a pager program on the system's PATH or from
|
454
|
+
the environmental variable, PAGER
|
455
|
+
END
|
456
|
+
raise( IOError, message )
|
457
|
+
end
|
458
|
+
|
459
|
+
options.fetch( :use_color ) { options[ :use_color ] = true }
|
460
|
+
|
461
|
+
if block_given?
|
462
|
+
IO.popen( PAGER_COMMAND, 'w' ) do | pager |
|
463
|
+
pager = new( pager, options )
|
464
|
+
return yield( pager )
|
465
|
+
end
|
466
|
+
else
|
467
|
+
return new( IO.popen( PAGER_COMMAND, 'w' ), options )
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
private
|
472
|
+
|
473
|
+
def screen_size
|
474
|
+
@screen_size ||= begin
|
475
|
+
data = SIZE_STRUCT.dup
|
476
|
+
if STDOUT.ioctl( SIZE_IOCTL, data ) >= 0
|
477
|
+
height, width = data.unpack( "SS" )
|
478
|
+
Pair.new(
|
479
|
+
width > 0 ? width : default_width,
|
480
|
+
height > 0 ? height : default_height
|
481
|
+
)
|
482
|
+
else
|
483
|
+
default_size
|
484
|
+
end
|
485
|
+
rescue Exception
|
486
|
+
default_size
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
module MonoclePrint
|
5
|
+
module Presentation
|
6
|
+
include MonoclePrint
|
7
|
+
ALIGNMENTS = [ :left, :right, :center ]
|
8
|
+
|
9
|
+
def self.included( klass )
|
10
|
+
super
|
11
|
+
klass.extend( ClassMethods )
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_accessor :owner
|
15
|
+
protected :owner=
|
16
|
+
|
17
|
+
for attr in %w( margin padding border )
|
18
|
+
class_eval( <<-END, __FILE__, __LINE__ + 1 )
|
19
|
+
def #{ attr }( value = nil )
|
20
|
+
value and self.#{ attr } = value
|
21
|
+
@#{ attr } ||= default_#{ attr }
|
22
|
+
block_given? ? yield( @#{ attr } ) : @#{ attr }
|
23
|
+
end
|
24
|
+
|
25
|
+
def #{ attr }= value
|
26
|
+
@#{ attr } = Rectangle( value )
|
27
|
+
end
|
28
|
+
END
|
29
|
+
end
|
30
|
+
|
31
|
+
def alignment( value = nil )
|
32
|
+
value and self.alignment = value
|
33
|
+
@alignment or @owner ? @owner.alignment : :left
|
34
|
+
end
|
35
|
+
|
36
|
+
def alignment= value
|
37
|
+
ALIGNMENTS.member?( value = value.to_sym ) or
|
38
|
+
raise( ArgumentError, "unkown alignment: %p" % value )
|
39
|
+
@alignment = value
|
40
|
+
end
|
41
|
+
|
42
|
+
def style( value = nil )
|
43
|
+
value and self.style = value
|
44
|
+
@style or @owner ? @owner.style : Graphics::NAMED_STYLES[ 'ascii' ]
|
45
|
+
end
|
46
|
+
|
47
|
+
def style= value
|
48
|
+
@style = Style( value )
|
49
|
+
end
|
50
|
+
|
51
|
+
def render( output = @output )
|
52
|
+
if output
|
53
|
+
render_content( output )
|
54
|
+
return output
|
55
|
+
else
|
56
|
+
OutputDevice.buffer do | out |
|
57
|
+
render_content( out )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
OutputDevice.buffer do | out |
|
64
|
+
render_content( out )
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def height
|
69
|
+
@height or calculate_height
|
70
|
+
end
|
71
|
+
|
72
|
+
def width
|
73
|
+
@width or calculate_width
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_writer :max_width
|
77
|
+
|
78
|
+
def max_width
|
79
|
+
@max_width or @owner && @owner.max_width or output.width
|
80
|
+
end
|
81
|
+
|
82
|
+
def output
|
83
|
+
@output ||= ( @owner and @owner.output or OutputDevice.stdout )
|
84
|
+
end
|
85
|
+
|
86
|
+
def output=( io )
|
87
|
+
@output = io.nil? ? io : Output( io )
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def initialize_view( options = nil, owner = nil )
|
93
|
+
@max_width = @width = @height = nil
|
94
|
+
@margin = @padding = @alignment = @style = nil
|
95
|
+
@output = @foreground = @background = nil
|
96
|
+
|
97
|
+
if options
|
98
|
+
val = options[ :width ] and self.width = val
|
99
|
+
val = options[ :align ] and self.alignment = val
|
100
|
+
val = options[ :style ] and self.style = val
|
101
|
+
val = options[ :padding ] and self.padding = val
|
102
|
+
val = options[ :margin ] and self.margin = val
|
103
|
+
val = options[ :output ] and self.output = val
|
104
|
+
end
|
105
|
+
|
106
|
+
@owner = owner
|
107
|
+
end
|
108
|
+
|
109
|
+
def default_margin
|
110
|
+
Rectangle.new( 0, 0, 0, 0 )
|
111
|
+
end
|
112
|
+
|
113
|
+
def default_padding
|
114
|
+
Rectangle.new( 0, 0, 0, 0 )
|
115
|
+
end
|
116
|
+
|
117
|
+
def default_border
|
118
|
+
Rectangle.new( false, false, false, false )
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
module Presentation::ClassMethods
|
124
|
+
def default( property, value = nil, &dynamic )
|
125
|
+
if dynamic
|
126
|
+
define_method( :"default_#{property}", &dynamic )
|
127
|
+
else
|
128
|
+
define_method( :"default_#{property}" ) { value }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|