Module: Blockenspiel::DSLSetupMethods

Defined in:
lib/blockenspiel/dsl_setup.rb

Overview

DSL setup methods

These class methods are available after you have included the Blockenspiel::DSL module.

By default, a class that has DSL capability will automatically make all public methods available to parameterless blocks, except for the initialize method, any methods whose names begin with an underscore, and any methods whose names end with an equals sign.

If you want to change this behavior, use the directives defined here to control exactly which methods are available to parameterless blocks.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

._setup_class(klass_) ⇒ Object

Set up a class. Creates a DSL module for this class, optionally delegating to the superclass's module. Also initializes the class's methods hash and active flag.



88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/blockenspiel/dsl_setup.rb', line 88

def self._setup_class(klass_)  # :nodoc:
  superclass_ = klass_.superclass
  superclass_ = nil unless superclass_.respond_to?(:_get_blockenspiel_module)
  mod_ = ::Module.new
  if superclass_
    mod_.module_eval do
      include superclass_._get_blockenspiel_module
    end
  end
  klass_.instance_variable_set(:@_blockenspiel_superclass, superclass_)
  klass_.instance_variable_set(:@_blockenspiel_module, mod_)
  klass_.instance_variable_set(:@_blockenspiel_methods, {})
  klass_.instance_variable_set(:@_blockenspiel_active, nil)
end

.extended(klass_) ⇒ Object

Called when DSLSetupMethods extends a class. This sets up the current class, and adds a hook that causes any subclass of the current class also to be set up.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/blockenspiel/dsl_setup.rb', line 65

def self.extended(klass_)
  unless klass_.instance_variable_defined?(:@_blockenspiel_module)
    _setup_class(klass_)
    def klass_.inherited(subklass_)
      ::Blockenspiel::DSLSetupMethods._setup_class(subklass_)
      super
    end
    class << klass_
      unless private_method_defined?(:_blockenspiel_default_include)
        alias_method :_blockenspiel_default_include, :include
        alias_method :include, :_blockenspiel_custom_include
      end
    end
  end
end

Instance Method Details

#_blockenspiel_auto_dsl_method(symbol_) ⇒ Object

Automatically make the given method a DSL method according to the current setting.



106
107
108
109
110
111
112
113
114
# File 'lib/blockenspiel/dsl_setup.rb', line 106

def _blockenspiel_auto_dsl_method(symbol_)  # :nodoc:
  if @_blockenspiel_active
    dsl_method(symbol_)
  elsif @_blockenspiel_active.nil?
    if symbol_ != :initialize && symbol_.to_s !~ /^_/ && symbol_.to_s !~ /=$/
      dsl_method(symbol_)
    end
  end
end

#_blockenspiel_custom_include(*modules_) ⇒ Object

Custom include method. Calls the main include implementation, but also goes through the public methods of the included module and calls _blockenspiel_auto_dsl_method on each to make them DSL methods (possibly) according to the current setting.



132
133
134
135
136
137
138
139
# File 'lib/blockenspiel/dsl_setup.rb', line 132

def _blockenspiel_custom_include(*modules_)  # :nodoc:
  _blockenspiel_default_include(*modules_)
  modules_.reverse_each do |mod_|
    mod_.public_instance_methods.each do |method_|
      _blockenspiel_auto_dsl_method(method_)
    end
  end
end

#_get_blockenspiel_delegate(name_) ⇒ Object

Get information on the given DSL method name. Possible values are the name of the delegate method, false for method disabled, or nil for method never defined.



153
154
155
156
157
158
159
160
# File 'lib/blockenspiel/dsl_setup.rb', line 153

def _get_blockenspiel_delegate(name_)  # :nodoc:
  delegate_ = @_blockenspiel_methods[name_]
  if delegate_.nil? && @_blockenspiel_superclass
    @_blockenspiel_superclass._get_blockenspiel_delegate(name_)
  else
    delegate_
  end
end

#_get_blockenspiel_moduleObject

Get this class's corresponding DSL module



144
145
146
# File 'lib/blockenspiel/dsl_setup.rb', line 144

def _get_blockenspiel_module  # :nodoc:
  @_blockenspiel_module
end

#dsl_attr_accessor(*names_) ⇒ Object

A DSL-friendly attr_accessor.

This creates the usual "name" and "name=" methods in the current class that can be used in the usual way. However, its implementation of the "name" method (the getter) also takes an optional parameter that causes it to behave as a setter. This is done because the usual setter syntax cannot be used in a parameterless block, since it is syntactically indistinguishable from a local variable assignment. The "name" method is exposed as a dsl_method.

For example:

dsl_attr_accessor :foo

enables the following:

my_block do |param| param.foo = 1 # Usual setter syntax works param.foo 2 # Alternate setter syntax also works puts param.foo # Usual getter syntax still works end

my_block do

foo = 1 # Usual setter syntax does NOT work since it

               #   looks like a local variable assignment
foo 2           # Alternate setter syntax does work
puts foo        # Usual getter syntax still works

end



263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/blockenspiel/dsl_setup.rb', line 263

def dsl_attr_accessor(*names_)
  names_.each do |name_|
    unless name_.kind_of?(::String) || name_.kind_of?(::Symbol)
      raise ::TypeError, "#{name_.inspect} is not a symbol"
    end
    unless name_.to_s =~ /^[_a-zA-Z]\w+$/
      raise ::NameError, "invalid attribute name #{name_.inspect}"
    end
    module_eval("def #{name_}(value_=::Blockenspiel::NO_VALUE); ::Blockenspiel::NO_VALUE.equal?(value_) ? @#{name_} : @#{name_} = value_; end\n")
    alias_method("#{name_}=", name_)
    dsl_method(name_)
  end
end

#dsl_attr_writer(*names_) ⇒ Object

A DSL-friendly attr_writer.

This creates the usual "name=" method in the current class that can be used in the usual way. However, it also creates the method "name", which also functions as a setter (but not a getter). This is done because the usual setter syntax cannot be used in a parameterless block, since it is syntactically indistinguishable from a local variable assignment. The "name" method is exposed as a dsl_method.

For example:

dsl_attr_writer :foo

is functionally equivalent to:

attr_writer :foo alias_method :foo, :foo= dsl_method :foo

which enables the following:

my_block do |param| param.foo = 1 # Usual setter syntax works param.foo 2 # Alternate setter syntax also works end my_block do

foo = 1 # Usual setter syntax does NOT work since it

               #   looks like a local variable assignment
foo(2)          # Alternate setter syntax does work

end



309
310
311
312
313
314
315
# File 'lib/blockenspiel/dsl_setup.rb', line 309

def dsl_attr_writer(*names_)
  names_.each do |name_|
    attr_writer(name_)
    alias_method(name_, "#{name_}=")
    dsl_method(name_)
  end
end

#dsl_method(name_, delegate_ = nil) ⇒ Object

Make a particular method available to parameterless DSL blocks.

To explicitly make a method available to parameterless blocks: dsl_method :my_method

To explicitly exclude a method from parameterless blocks: dsl_method :my_method, false

To explicitly make a method available to parameterless blocks, but point it to a method of a different name on the target class: dsl_method :my_method, :target_class_method



175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/blockenspiel/dsl_setup.rb', line 175

def dsl_method(name_, delegate_=nil)
  name_ = name_.to_sym
  if delegate_
    delegate_ = delegate_.to_sym
  elsif delegate_.nil?
    delegate_ = name_
  end
  @_blockenspiel_methods[name_] = delegate_
  unless @_blockenspiel_module.public_method_defined?(name_)
    @_blockenspiel_module.module_eval("def #{name_}(*params_, &block_); val_ = ::Blockenspiel._target_dispatch(self, :#{name_}, params_, block_); ::Blockenspiel::NO_VALUE.equal?(val_) ? super(*params_, &block_) : val_; end\n")
  end
end

#dsl_methods(*names_) ⇒ Object

Control the behavior of methods with respect to parameterless blocks, or make a list of methods available to parameterless blocks in bulk.

To enable automatic exporting of methods to parameterless blocks. After executing this command, all public methods defined in the class will be available on parameterless blocks, until dsl_methods false is called: dsl_methods true

To disable automatic exporting of methods to parameterless blocks. After executing this command, methods defined in this class will be excluded from parameterless blocks, until dsl_methods true is called: dsl_methods false

To make a list of methods available to parameterless blocks in bulk: dsl_methods :my_method1, :my_method2, ...

You can also point dsl methods to a method of a different name on the target class, by using a hash syntax, as follows: dsl_methods :my_method1 => :target_class_method1, :my_method2 => :target_class_method2

You can mix non-renamed and renamed method declarations as long as the renamed (hash) methods are at the end. e.g.: dsl_methods :my_method1, :my_method2 => :target_class_method2



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/blockenspiel/dsl_setup.rb', line 216

def dsl_methods(*names_)
  if names_.size == 0 || names_ == [true]
    @_blockenspiel_active = true
  elsif names_ == [false]
    @_blockenspiel_active = false
  else
    if names_.last.kind_of?(::Hash)
      names_.pop.each do |name_, delegate_|
        dsl_method(name_, delegate_)
      end
    end
    names_.each do |name_|
      dsl_method(name_, name_)
    end
  end
end

#method_added(symbol_) ⇒ Object

Hook called when a method is added. This calls _blockenspiel_auto_dsl_method to auto-handle the method, possibly making it a DSL method according to the current setting.



121
122
123
124
# File 'lib/blockenspiel/dsl_setup.rb', line 121

def method_added(symbol_)  # :nodoc:
  _blockenspiel_auto_dsl_method(symbol_)
  super
end