Class: DSL::Maker

Inherits:
Object
  • Object
show all
Defined in:
lib/dsl/maker.rb,
lib/dsl/maker/version.rb

Overview

This is the base class we provide.

Defined Under Namespace

Modules: Boolean Classes: Base

Constant Summary collapse

VERSION =

The current version of this library

'0.0.4'
Yes =
On = True = true
No =
Off = False = false
@@dsl_elements =

FIXME: This may have to be changed when the elements can be altered because it is global to the hierarchy. But, that may be desirable.

{
  String => ->(klass, name, type) {
    as_attr = '@' + name.to_s
    klass.class_eval do
      define_method(name.to_sym) do |*args|
        ___set(as_attr, args[0].to_s) unless args.empty?
        ___get(as_attr)
      end
    end
  },
  DSL::Maker::Boolean => ->(klass, name, type) {
    as_attr = '@' + name.to_s
    klass.class_eval do
      define_method(name.to_sym) do |*args|
        ___set(as_attr, Boolean.coerce(args[0])) unless args.empty?
        # Ensure that the default nil returns as false.
        !!___get(as_attr)
      end
    end
  },
}

Class Method Summary collapse

Class Method Details

.add_entrypoint(name, args = {}, &defn_block) ⇒ Class

Note:

args could be a Hash (to be passed to generate_dsl()) or the result

Add an entrypoint (top-level DSL element) to this class's DSL.

This delegates to generate_dsl() for the majority of the work.

of a call to generate_dsl().

Parameters:

  • name (String)

    the name of the entrypoint

  • args (Hash) (defaults to: {})

    the elements of the DSL block (passed to generate_dsl)

  • defn_block (Proc)

    what is executed once the DSL block is parsed.

Returns:

  • (Class)

    The class that implements this level's DSL definition.



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/dsl/maker.rb', line 214

def self.add_entrypoint(name, args={}, &defn_block)
  # Without defn_block, there's no way to give back the result of the
  # DSL parsing. So, raise an error if we don't get one.
  # TODO: Provide a default block that returns the datastructure as a HoH.
  raise "Block required for add_entrypoint" unless block_given?

  if self.respond_to?(name.to_sym)
    raise "'#{name.to_s}' is already an entrypoint"
  end

  # FIXME: This is a wart. Really, we should be pulling out name, then
  # yielding to generate_dsl() in some fashion.
  if $is_dsl.call(args)
    dsl_class = args
  else
    dsl_class = generate_dsl(args, &defn_block)
  end

  define_singleton_method(name.to_sym) do |*args, &dsl_block|
    obj = dsl_class.new
    Docile.dsl_eval(obj, &dsl_block) if dsl_block
    rv = obj.instance_exec(*args, &defn_block)
    @accumulator.push(rv)
    return rv
  end

  @entrypoints ||= {}
  return @entrypoints[name.to_sym] = dsl_class
end

.add_helper(name, &block) ⇒ Object



257
258
259
260
261
262
263
264
265
266
267
# File 'lib/dsl/maker.rb', line 257

def self.add_helper(name, &block)
  raise "Block required for add_helper" unless block_given?

  if DSL::Maker::Base.new.respond_to? name.to_sym
    raise "'#{name.to_s}' is already a helper"
  end

  DSL::Maker::Base.class_eval do
    define_method(name.to_sym, &block)
  end
end

.build_dsl_element(klass, name, type) ⇒ Object

Add a single element of a DSL to a class representing a level in a DSL.

Each of the types represents a coercion - a guarantee and check of the value in that name. The standard coercions are:

  • String - whatever you give is returned.
  • Boolean - the truthiness of whatever you give is returned.
  • generate_dsl() - this represents a new level of the DSL.

Parameters:

  • klass (Class)

    The class representing this level in the DSL.

  • name (String)

    The name of the element we're working on.

  • type (Class)

    The type of this element we're working on. This is the coercion spoken above.

Returns:

  • nil



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/dsl/maker.rb', line 137

def self.build_dsl_element(klass, name, type)
  if @@dsl_elements.has_key?(type)
    @@dsl_elements[type].call(klass, name, type)
  elsif $is_dsl.call(type)
    as_attr = '@' + name.to_s
    klass.class_eval do
      define_method(name.to_sym) do |*args, &dsl_block|
        unless (args.empty? && !dsl_block)
          obj = type.new
          Docile.dsl_eval(obj, &dsl_block) if dsl_block

          # I don't know why this code doesn't work, but it's why __apply().
          #___set(as_attr, obj.instance_exec(*args, &defn_block))
          ___set(as_attr, obj.__apply(*args))
        end
        ___get(as_attr)
      end
    end
  else
    raise "Unrecognized element type '#{type}'"
  end

  return
end

.entrypoint(name) ⇒ Class

This returns the DSL corresponding to the entrypoint's name.

Parameters:

  • name (String)

    the name of the entrypoint

Returns:

  • (Class)

    The class that implements this name's DSL definition.



249
250
251
252
253
254
255
# File 'lib/dsl/maker.rb', line 249

def self.entrypoint(name)
  unless self.respond_to?(name.to_sym)
    raise "'#{name.to_s}' is not an entrypoint"
  end

  return @entrypoints[name.to_sym]
end

.execute_dsl(&block) ⇒ Object

Note:

If the DSL contains multiple entrypoints, then this will return an

Execute the DSL provided in the block.

Array. This is desirable.

Parameters:

  • &block (Block)

    The DSL to be executed by this class.

Returns:

  • (Object)

    Whatever is returned by &block



78
79
80
81
82
83
84
85
# File 'lib/dsl/maker.rb', line 78

def self.execute_dsl(&block)
  @accumulator = []
  instance_eval(&block)
  if @accumulator.length <= 1
    return @accumulator[0]
  end
  return @accumulator
end

.generate_dsl(args = {}, &defn_block) ⇒ Class

Add the meat of a DSL block to some level of this class's DSL.

In order for Docile to parse a DSL, each level must be represented by a different class. This method creates anonymous classes that each represents a different level in the DSL's structure.

The creation of each DSL element is delegated to build_dsl_element.

Parameters:

  • args (Hash) (defaults to: {})

    the elements of the DSL block (passed to generate_dsl)

  • defn_block (Proc)

    what is executed once the DSL block is parsed.

Returns:

  • (Class)

    The class that implements this level's DSL definition.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/dsl/maker.rb', line 174

def self.generate_dsl(args={}, &defn_block)
  raise 'Block required for generate_dsl' unless block_given?

  # Inherit from the Boolean class to gain access to the useful methods
  # TODO: Convert DSL::Maker::Boolean into a Role
  # TODO: Create a DSL::Maker::Base class to inherit from
  dsl_class = Class.new(DSL::Maker::Base) do
    include DSL::Maker::Boolean

    # This instance method exists because we cannot seem to inline its work
    # where we call it. Could it be a problem of incorrect binding?
    # It has to be defined here because it needs access to &defn_block
    define_method(:__apply) do |*args|
      instance_exec(*args, &defn_block)
    end
  end

  args.each do |name, type|
    if dsl_class.new.respond_to? name.to_sym
      raise "Illegal attribute name '#{name}'"
    end

    build_dsl_element(dsl_class, name, type)
  end

  return dsl_class
end

.get_bindingBinding

Returns the binding as needed by parse_dsl() and execute_dsl()

Returns:

  • (Binding)

    The binding of the invoking class.



90
91
92
# File 'lib/dsl/maker.rb', line 90

def self.get_binding
  binding
end

.parse_dsl(dsl) ⇒ Object

Note:

If the DSL contains multiple entrypoints, then this will return an

Parse the DSL provided in the parameter.

Array. This is desirable.

Parameters:

  • dsl (String)

    The DSL to be parsed by this class.

Returns:

  • (Object)

    Whatever is returned by the block defined in this class.



59
60
61
62
63
64
65
66
67
68
# File 'lib/dsl/maker.rb', line 59

def self.parse_dsl(dsl)
  # add_entrypoint() will use @accumulator to handle multiple entrypoints.
  # Reset it here so that we're only handling the values from this run.
  @accumulator = []
  eval dsl, self.get_binding
  if @accumulator.length <= 1
    return @accumulator[0]
  end
  return @accumulator
end