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