dslkit 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,538 @@
1
+ # = dslkit - Building Blocks for Domain Specific Languages (DSL)
2
+ #
3
+ # == Description
4
+ #
5
+ # This library contains recurring patterns, that are useful in the creation of
6
+ # internal Domain Specific Languages (DSL) in Ruby.
7
+ #
8
+ # == Author
9
+ #
10
+ # Florian Frank <mailto:flori@ping.de>
11
+ #
12
+ # == License
13
+ #
14
+ # This is free software; you can redistribute it and/or modify it under the
15
+ # terms of the GNU General Public License Version 2 as published by the Free
16
+ # Software Foundation: www.gnu.org/copyleft/gpl.html
17
+ #
18
+ # == Download
19
+ #
20
+ # The latest version of this library can be downloaded at
21
+ #
22
+ # * http://rubyforge.org/frs?group_id=2248
23
+ #
24
+ # Online Documentation should be located at
25
+ #
26
+ # * http://dslkit.rubyforge.org
27
+ #
28
+ # == Examples
29
+ #
30
+ # Some nice examples on how to use dslkit are located in the examples/
31
+ # subdirectory of this library.
32
+ #
33
+ module DSLKit
34
+ # This module contains some handy methods to deal with eigenclasses. Those
35
+ # are also known as virtual classes, singleton classes, metaclasses, plus all
36
+ # the other names Matz doesn't like enough to actually accept one of the
37
+ # names.
38
+ #
39
+ # The module can be included into other modules/classes to make the methods available.
40
+ module Eigenclass
41
+ # Returns the eigenclass of this object.
42
+ def eigenclass
43
+ class << self; self; end
44
+ end
45
+
46
+ # Evaluates the _block_ in context of the eigenclass of this object.
47
+ def eigenclass_eval(&block)
48
+ eigenclass.instance_eval(&block)
49
+ end
50
+ end
51
+
52
+ module ClassMethod
53
+ include Eigenclass
54
+
55
+ # Define a class method named _name_ using _block_. To be able to take
56
+ # blocks as arguments in the given _block_ Ruby 1.9 is required.
57
+ def class_define_method(name, &block)
58
+ eigenclass_eval { define_method(name, &block) }
59
+ end
60
+
61
+ # Define reader and writer attribute methods for all <i>*ids</i>.
62
+ def class_attr_accessor(*ids)
63
+ eigenclass_eval { attr_accessor(*ids) }
64
+ end
65
+
66
+ # Define reader attribute methods for all <i>*ids</i>.
67
+ def class_attr_reader(*ids)
68
+ eigenclass_eval { attr_reader(*ids) }
69
+ end
70
+
71
+ # Define writer attribute methods for all <i>*ids</i>.
72
+ def class_attr_writer(*ids)
73
+ eigenclass_eval { attr_writer(*ids) }
74
+ end
75
+
76
+ # I boycott attr!
77
+ end
78
+
79
+ module ThreadLocal
80
+ # Define a thread local variable named _name_ in this module/class. If the
81
+ # value _value_ is given, it is used to initialize the variable.
82
+ def thread_local(name, value = nil)
83
+ is_a?(Module) or raise TypeError, "receiver has to be a Module"
84
+
85
+ name = name.to_s
86
+ my_id = "__thread_local_#{__id__}__"
87
+
88
+ cleanup = eval <<-'EOT', TOPLEVEL_BINDING
89
+ lambda do |my_object_id|
90
+ my_id = "__thread_local_#{my_object_id}__"
91
+ begin
92
+ old = Thread.critical
93
+ Thread.critical = true
94
+ for t in Thread.list
95
+ t[my_id] = nil if t[my_id]
96
+ end
97
+ ensure
98
+ Thread.critical = old
99
+ end
100
+ end
101
+ EOT
102
+ ObjectSpace.define_finalizer(self, cleanup)
103
+
104
+ define_method(name) do
105
+ Thread.current[my_id] ||= {}
106
+ Thread.current[my_id][name]
107
+ end
108
+
109
+ define_method("#{name}=") do |value|
110
+ Thread.current[my_id] ||= {}
111
+ Thread.current[my_id][name] = value
112
+ end
113
+
114
+ if value
115
+ Thread.current[my_id] = {}
116
+ Thread.current[my_id][name] = value
117
+ end
118
+ self
119
+ end
120
+
121
+ # Define a thread local variable for the current instance with name _name_.
122
+ # If the value _value_ is given, it is used to initialize the variable.
123
+ def instance_thread_local(name, value = nil)
124
+ sc = class << self
125
+ extend DSLKit::ThreadLocal
126
+ self
127
+ end
128
+ sc.thread_local name, value
129
+ self
130
+ end
131
+ end
132
+
133
+ module ThreadGlobal
134
+ # Define a thread global variable named _name_ in this module/class. If the
135
+ # value _value_ is given, it is used to initialize the variable.
136
+ def thread_global(name, value = nil)
137
+ is_a?(Module) or raise TypeError, "receiver has to be a Module"
138
+
139
+ require 'thread'
140
+ name = name.to_s
141
+ var_name = "@__#{name}_#{__id__.abs}__"
142
+
143
+ lock = Mutex.new
144
+ modul = self
145
+
146
+ define_method(name) do
147
+ lock.synchronize { modul.instance_variable_get var_name }
148
+ end
149
+
150
+ define_method(name + "=") do |value|
151
+ lock.synchronize { modul.instance_variable_set var_name, value }
152
+ end
153
+
154
+ modul.instance_variable_set var_name, value if value
155
+ self
156
+ end
157
+
158
+ # Define a thread global variable for the current instance with name
159
+ # _name_. If the value _value_ is given, it is used to initialize the
160
+ # variable.
161
+ def instance_thread_global(name, value = nil)
162
+ sc = class << self
163
+ extend DSLKit::ThreadGlobal
164
+ self
165
+ end
166
+ sc.thread_global name, value
167
+ self
168
+ end
169
+ end
170
+
171
+ module InstanceExec
172
+ class << self
173
+ attr_accessor :pool
174
+ attr_accessor :count
175
+ end
176
+ self.count = 0
177
+ self.pool = []
178
+
179
+ # This is a pure ruby implementation of Ruby 1.9's instance_exec method. It
180
+ # executes _block_ in the context of this object while parsing <i>*args</i> into
181
+ # the block.
182
+ def instance_exec(*args, &block)
183
+ instance = self
184
+ id = instance_exec_fetch_symbol
185
+ InstanceExec.module_eval do
186
+ begin
187
+ define_method id, block
188
+ instance.__send__ id, *args
189
+ ensure
190
+ remove_method id if method_defined?(id)
191
+ end
192
+ end
193
+ ensure
194
+ InstanceExec.pool << id
195
+ end
196
+
197
+ private
198
+
199
+ # Fetch a symbol from a pool in thread save way. If no more symbols are
200
+ # available create a new one, that will be pushed into the pool later.
201
+ def instance_exec_fetch_symbol
202
+ old = Thread.critical
203
+ Thread.critical = true
204
+ if InstanceExec.pool.empty?
205
+ InstanceExec.count += 1
206
+ symbol = :"__instance_exec_#{InstanceExec.count}__"
207
+ else
208
+ symbol = InstanceExec.pool.shift
209
+ end
210
+ return symbol
211
+ ensure
212
+ Thread.critical = old
213
+ end
214
+ end
215
+
216
+ module Interpreter
217
+ include InstanceExec
218
+
219
+ # Interpret the string _source_ as a body of a block, while passing
220
+ # <i>*args</i> into the block.
221
+ #
222
+ # A small example explains how the method is supposed to be used and how
223
+ # the <i>*args</i> can be fetched:
224
+ #
225
+ # class A
226
+ # include DSLKit::Interpreter
227
+ # def c
228
+ # 3
229
+ # end
230
+ # end
231
+ #
232
+ # A.new.interpret('|a,b| a + b + c', 1, 2) # => 6
233
+ #
234
+ # To use a specified binding see #interpret_with_binding.
235
+ def interpret(source, *args)
236
+ interpret_with_binding(source, binding, *args)
237
+ end
238
+
239
+ # Interpret the string _source_ as a body of a block, while passing
240
+ # <i>*args</i> into the block and using _my_binding_ for evaluation.
241
+ #
242
+ # A small example:
243
+ #
244
+ # class A
245
+ # include DSLKit::Interpreter
246
+ # def c
247
+ # 3
248
+ # end
249
+ # def foo
250
+ # b = 2
251
+ # interpret_with_binding('|a| a + b + c', binding, 1) # => 6
252
+ # end
253
+ # end
254
+ # A.new.foo # => 6
255
+ #
256
+ # See also #interpret.
257
+ def interpret_with_binding(source, my_binding, *args)
258
+ path = '(interpret)'
259
+ if source.respond_to? :to_io
260
+ path = source.path if source.respond_to? :path
261
+ source = source.to_io.read
262
+ end
263
+ block = lambda { |*a| eval("lambda { #{source} }", my_binding, path).call(*a) }
264
+ instance_exec(*args, &block)
265
+ end
266
+ end
267
+
268
+ # This module contains the _constant_ method. For small example of its usage
269
+ # see the documentation of the DSLAccessor module.
270
+ module Constant
271
+ # Create a constant named _name_, that refers to value _value_. _value is
272
+ # frozen, if this is possible. If you want to modify/exchange a value use
273
+ # DSLAccessor#dsl_reader/DSLAccessor#dsl_accessor instead.
274
+ def constant(name, value)
275
+ value = value.freeze rescue value
276
+ define_method(name) { value }
277
+ end
278
+ end
279
+
280
+ # The DSLAccessor module contains some methods, that can be used to make
281
+ # simple accessors for a DSL.
282
+ #
283
+ #
284
+ # class CoffeeMaker
285
+ # extend DSLKit::Constant
286
+ #
287
+ # constant :on, :on
288
+ # constant :off, :off
289
+ #
290
+ # extend DSLKit::DSLAccessor
291
+ #
292
+ # dsl_accessor(:state) { off } # Note: the off constant from above is used
293
+ #
294
+ # dsl_accessor :allowed_states, :on, :off
295
+ #
296
+ # def process
297
+ # allowed_states.include?(state) or fail "Explode!!!"
298
+ # if state == on
299
+ # puts "Make coffee."
300
+ # else
301
+ # puts "Idle..."
302
+ # end
303
+ # end
304
+ # end
305
+ #
306
+ # cm = CoffeeMaker.new
307
+ # cm.instance_eval do
308
+ # state # => :off
309
+ # state on
310
+ # state # => :on
311
+ # process # => outputs "Make coffee."
312
+ # end
313
+ #
314
+ # Note that DSLKit::SymbolMaker is an alternative for DSLKit::Constant in
315
+ # this example. On the other hand SymbolMaker can make debugging more
316
+ # difficult.
317
+ module DSLAccessor
318
+ # This method creates a dsl accessor named _name_. If nothing else is given
319
+ # as argument it defaults to nil. If <i>*default</i> is given as a single
320
+ # value it is used as a default value, if more than one value is given the
321
+ # _default_ array is used as the default value. If no default value but a
322
+ # block _block_ is given as an argument, the block is executed everytime
323
+ # the accessor is read <b>in the context of the current instance</b>.
324
+ #
325
+ # After setting up the accessor, the set or default value can be retrieved
326
+ # by calling the method +name+. To set a value one can call <code>name
327
+ # :foo</code> to set the attribute value to <code>:foo</code> or
328
+ # <code>name(:foo, :bar)</code> to set it to <code>[ :foo, :bar ]</code>.
329
+ def dsl_accessor(name, *default, &block)
330
+ variable = "@#{name}"
331
+ define_method(name) do |*args|
332
+ if args.empty?
333
+ result = instance_variable_get(variable)
334
+ if result.nil?
335
+ if default.empty?
336
+ block && instance_eval(&block)
337
+ elsif default.size == 1
338
+ default.first
339
+ else
340
+ default
341
+ end
342
+ else
343
+ result
344
+ end
345
+ else
346
+ instance_variable_set(variable, args.size == 1 ? args.first : args)
347
+ end
348
+ end
349
+ end
350
+
351
+ # This method creates a dsl reader accessor, that behaves exactly like a
352
+ # #dsl_accessor but can only be read not set.
353
+ def dsl_reader(name, *default, &block)
354
+ variable = "@#{name}"
355
+ define_method(name) do |*args|
356
+ if args.empty?
357
+ result = instance_variable_get(variable)
358
+ if result.nil?
359
+ if default.empty?
360
+ block && instance_eval(&block)
361
+ elsif default.size == 1
362
+ default.first
363
+ else
364
+ default
365
+ end
366
+ else
367
+ result
368
+ end
369
+ else
370
+ raise ArgumentError, "wrong number of arguments (#{args.size} for 0)"
371
+ end
372
+ end
373
+ end
374
+ end
375
+
376
+ # This module can be included in another module/class. It generates a symbol
377
+ # for every missing method that was called in the context of this
378
+ # module/class.
379
+ module SymbolMaker
380
+ # Returns a symbol (_id_) for every missing method named _id_.
381
+ def method_missing(id, *args)
382
+ if args.empty?
383
+ id
384
+ else
385
+ super
386
+ end
387
+ end
388
+ end
389
+
390
+ # This module can be used to extend another module/class. It generates
391
+ # symbols for every missing constant under the namespace of this
392
+ # module/class.
393
+ module ConstantMaker
394
+ # Returns a symbol (_id_) for every missing constant named _id_.
395
+ def const_missing(id)
396
+ id
397
+ end
398
+ end
399
+
400
+ module BlankSlate
401
+ # Creates an anonymous blank slate class, that only responds to the methods
402
+ # <i>*ids</i>. ids can be Symbols, Strings, and Regexps that have to match
403
+ # the method name with #===.
404
+ def self.with(*ids)
405
+ ids = ids.map { |id| Regexp === id ? id : id.to_s }
406
+ klass = Class.new
407
+ klass.instance_eval do
408
+ instance_methods.each do |m|
409
+ undef_method m unless m =~ /^__/ or ids.any? { |i| i === m }
410
+ end
411
+ end
412
+ klass
413
+ end
414
+ end
415
+
416
+ # See examples/recipe.rb and examples/recipe2.rb how this works at the
417
+ # moment.
418
+ module Deflect
419
+ # The basic Deflect exception
420
+ class DeflectError < StandardError; end
421
+
422
+ class << self
423
+ extend DSLKit::ThreadLocal
424
+
425
+ # A thread local variable, that holds a DeflectorCollection instance for
426
+ # the current thread.
427
+ thread_local :deflecting
428
+ end
429
+
430
+ # A deflector is called with a _class_, a method _id_, and its
431
+ # <i>*args</i>.
432
+ class Deflector < Proc; end
433
+
434
+ # This class implements a collection of deflectors, to make them available
435
+ # by emulating Ruby's message dispatch.
436
+ class DeflectorCollection
437
+ def initialize
438
+ @classes = {}
439
+ end
440
+
441
+ # Add a new deflector _deflector_ for class _klass_ and method name _id_,
442
+ # and return self.
443
+ #
444
+ def add(klass, id, deflector)
445
+ k = @classes[klass]
446
+ k = @classes[klass] = {} unless k
447
+ k[id.to_s] = deflector
448
+ self
449
+ end
450
+
451
+ # Return true if messages are deflected for class _klass_ and method name
452
+ # _id_, otherwise return false.
453
+ def member?(klass, id)
454
+ !!(k = @classes[klass] and k.key?(id.to_s))
455
+ end
456
+
457
+ # Delete the deflecotor class _klass_ and method name _id_. Returns the
458
+ # deflector if any was found, otherwise returns true.
459
+ def delete(klass, id)
460
+ if k = @classes[klass]
461
+ d = k.delete id.to_s
462
+ @classes.delete klass if k.empty?
463
+ d
464
+ end
465
+ end
466
+
467
+ # Try to find a deflector for class _klass_ and method _id_ and return
468
+ # it. If none was found, return nil instead.
469
+ def find(klass, id)
470
+ klass.ancestors.find do |k|
471
+ if d = @classes[k] and d = d[id.to_s]
472
+ return d
473
+ end
474
+ end
475
+ end
476
+ end
477
+
478
+ # Start deflecting method calls named _id_ to the _from_ class using the
479
+ # Deflector instance deflector.
480
+ def deflect_start(from, id, deflector)
481
+ old = Thread.critical
482
+ Thread.critical = true
483
+ Deflect.deflecting ||= DeflectorCollection.new
484
+ Deflect.deflecting.member?(from, id) and
485
+ raise DeflectError, "#{from}##{id} is already deflected"
486
+ Deflect.deflecting.add(from, id, deflector)
487
+ from.class_eval do
488
+ define_method(id) do |*args|
489
+ if Deflect.deflecting and d = Deflect.deflecting.find(self.class, id)
490
+ Thread.critical = old
491
+ d.call(self, id, *args)
492
+ else
493
+ Thread.critical = old
494
+ super
495
+ end
496
+ end
497
+ end
498
+ ensure
499
+ Thread.critical = old
500
+ end
501
+
502
+ # Return true if method _id_ is deflected from class _from_, otherwise
503
+ # return false.
504
+ def self.deflect?(from, id)
505
+ Deflect.deflecting && Deflect.deflecting.member?(from, id)
506
+ end
507
+
508
+ # Return true if method _id_ is deflected from class _from_, otherwise
509
+ # return false.
510
+ def deflect?(from, id)
511
+ Deflect.deflect?(from, id)
512
+ end
513
+
514
+ # Start deflecting method calls named _id_ to the _from_ class using the
515
+ # Deflector instance deflector. After that yield to the given block and
516
+ # stop deflecting again.
517
+ def deflect(from, id, deflector)
518
+ old = Thread.critical
519
+ Thread.critical = true
520
+ deflect_start(from, id, deflector)
521
+ yield
522
+ ensure
523
+ deflect_stop(from, id)
524
+ Thread.critical = old
525
+ end
526
+
527
+ # Stop deflection method calls named _id_ to class _from_.
528
+ def deflect_stop(from, id)
529
+ old = Thread.critical
530
+ Thread.critical = true
531
+ Deflect.deflecting.delete(from, id) or
532
+ raise DeflectError, "#{from}##{id} is not deflected from"
533
+ from.instance_eval { remove_method id }
534
+ ensure
535
+ Thread.critical = old
536
+ end
537
+ end
538
+ end
@@ -0,0 +1,23 @@
1
+ # This file contains some "rude" defaults that tamper with Ruby's open classes
2
+ # to augment them with DSLKit methods. Although this shouldn't break anything, it's
3
+ # perhaps better to require 'dslkit/polite' instead and include/extend your
4
+ # classes with a finer granularity.
5
+ require 'dslkit/polite'
6
+
7
+ module DSLKit
8
+ class ::Module
9
+ include DSLKit::Constant
10
+ include DSLKit::DSLAccessor
11
+ include DSLKit::ClassMethod
12
+ end
13
+
14
+ class ::Object
15
+ include DSLKit::ThreadLocal
16
+ include DSLKit::ThreadGlobal
17
+ include DSLKit::InstanceExec
18
+ include DSLKit::Interpreter
19
+ include DSLKit::Deflect
20
+ include DSLKit::ThreadLocal
21
+ include DSLKit::Eigenclass
22
+ end
23
+ end
data/lib/dslkit.rb ADDED
@@ -0,0 +1,2 @@
1
+ # This file just requires 'dslkit/polite', which is the default.
2
+ require 'dslkit/polite'
data/tests/runner.rb ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift '../lib'
4
+ $:.unshift 'tests'
5
+
6
+ warn "Test polite."
7
+ fork { load 'test_polite.rb' }
8
+ Process.waitpid
9
+ warn "Test rude."
10
+ fork { load 'test_rude.rb' }
11
+ Process.waitpid
12
+ # vim: set et sw=2 ts=2: