dslkit 0.2.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.
@@ -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: