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: Alias, ArrayType, Base, HashType

Constant Summary collapse

VERSION =

The current version of this library

'1.0.1'
Any =

Create the DSL::Maker::Any type identifier, equivalent to Object.

Object
Yes =
On = True = true
No =
Off = False = false
ArrayOf =
Class.new do
  def self.[](type)
    raise "Cannot make an array of an alias" if DSL::Maker.is_alias?(type)
    raise "Unknown type provided to ArrayOf" unless @@types.has_key?(type) || DSL::Maker.is_dsl?(type)
    @@arrays[type] ||= ArrayType.new(type)
  end
end
@@aliases =
{}
@@arrays =
{}

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.



212
213
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
243
244
245
246
247
# File 'lib/dsl/maker.rb', line 212

def self.add_entrypoint(name, args={}, &defn_block)
  symname = name.to_sym

  if is_entrypoint?(symname)
    raise "'#{name.to_s}' is already an entrypoint"
  end

  if is_dsl?(args)
    dsl_class = args
  else
    # 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?
    dsl_class = generate_dsl(args, &defn_block)
  end

  if @klass
    build_dsl_element(@klass, symname, dsl_class)
  else
    # FIXME: We shouldn't need the blank block here ...
    # This blank block is representative of the implicit (and missing) outermost
    # block around the DSL that we are not putting into place in :parse_dsl or
    # :execute_dsl.
    @klass = generate_dsl({
      symname => dsl_class
    }) {}

    # This marks @klass as the root DSL class.
    @klass.parent_class = self
  end

  @entrypoints ||= {}
  return @entrypoints[symname] = dsl_class
end

.add_helper(name, &block) ⇒ Object

This adds a helper function that's accessible within the DSL.

Note: These helpers are global to all DSLs.

Parameters:

  • name (String)

    the name of the helper

  • &block (Block)

    The function to be executed when the helper is called.

Returns:

  • nil



270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/dsl/maker.rb', line 270

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

  if has_helper? name
    raise "'#{name.to_s}' is already a helper"
  end

  base_class.class_eval do
    define_method(name.to_sym, &block)
  end

  return
end

.add_type(type, &block) ⇒ Object

Note:

These type coercions are global to all DSLs.

This adds a type coercion that's used when creating the DSL.

Your block will receive the following signature: |attr, *args| where 'attr' is the name of the attribute and *args are the arguments passed into your method within the DSL. You are responsible for acting as a mutator. You have __get() and __set() available for your use. These are aliases to instance_variable_get and instance_variable_set, respectively. Please read the coercions provided for you in this source file as examples.

Parameters:

  • type (Object)

    the name of the helper

  • &block (Block)

    The function to be executed when the coercion is exercised.

Returns:

  • nil



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

def self.add_type(type, &block)
  raise "Block required for add_type" unless block_given?
  raise "'#{type}' is already a type coercion" if @@types.has_key? type

  @@types[type] = block

  return
end

.add_verification(name, &block) ⇒ Object

Note:

These verifications are specific to the DSL you add them to.

Note:

Verifications are called in the order you specify them.

This adds a verification that's executed after the DSL is finished parsing.

The verification will be called with the value(s) returned by the entrypoint's execution. If the verification returns a true value (of any kind), then that will be raised as a runtime exception.

You can also call add_verification on the return values from generate_dsl() or add_entrypoint(). In those cases, omit the :name because you have already chosen the DSL layer you're adding the verification to.

Parameters:

  • name (String)

    the name of the entrypoint to add a verification to

  • &block (Block)

    The function to be executed when verifications execute

Returns:

  • nil



326
327
328
329
330
331
# File 'lib/dsl/maker.rb', line 326

def self.add_verification(name, &block)
  raise "Block required for add_verification" unless block_given?
  raise "'#{name.to_s}' is not an entrypoint for a verification" unless is_entrypoint?(name)

  @entrypoints[name.to_sym].add_verification(&block)
end

.AliasOf(name) ⇒ Object



75
76
77
# File 'lib/dsl/maker.rb', line 75

def self.AliasOf(name)
  @@aliases[name] ||= Alias.new(name)
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.



254
255
256
257
258
259
260
# File 'lib/dsl/maker.rb', line 254

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

  return @entrypoints[name.to_sym]
end

.execute_dsl(&block) ⇒ Array

Execute the DSL provided in the block.

Parameters:

  • &block (Block)

    The DSL to be executed by this class.

Returns:

  • (Array)

    Whatever is returned by the block defined in this class.



131
132
133
134
135
136
# File 'lib/dsl/maker.rb', line 131

def self.execute_dsl(&block)
  raise 'Must call add_entrypoint before execute_dsl' unless @klass
  raise 'Block required for execute_dsl' unless block_given?

  run_dsl { @klass.new.instance_eval(&block) }
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
# File 'lib/dsl/maker.rb', line 174

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

  dsl_class = Class.new(base_class) do
    include DSL::Maker::Boolean

    class << self
      attr_accessor :parent_class, :verifications
    end

    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

.has_helper?(name) ⇒ Boolean

This returns if the helper has been added with #add_helper

Parameters:

  • name (String)

    the name of the helper

Returns:



304
305
306
# File 'lib/dsl/maker.rb', line 304

def self.has_helper?(name)
  base_class.method_defined?(name.to_sym)
end

.is_alias?(type) ⇒ Boolean

Returns:



78
79
80
# File 'lib/dsl/maker.rb', line 78

def self.is_alias?(type)
  type.instance_of? Alias
end

.is_array?(type) ⇒ Boolean

Returns:



96
97
98
# File 'lib/dsl/maker.rb', line 96

def self.is_array?(type)
  type.instance_of? ArrayType
end

.parse_dsl(dsl = nil) ⇒ Array

Parse the DSL provided in the parameter.

Parameters:

  • dsl (String) (defaults to: nil)

    The DSL to be parsed by this class.

Returns:

  • (Array)

    Whatever is returned by the block defined in this class.



119
120
121
122
123
124
# File 'lib/dsl/maker.rb', line 119

def self.parse_dsl(dsl=nil)
  raise 'Must call add_entrypoint before parse_dsl' unless @klass
  raise 'String required for parse_dsl' unless dsl.instance_of? String

  run_dsl { eval dsl, @klass.new.get_binding }
end

.remove_helper(name) ⇒ Object

This removes a helper function that's been added with #add_helper

Parameters:

  • name (String)

    the name of the helper

Returns:

  • nil



289
290
291
292
293
294
295
296
297
# File 'lib/dsl/maker.rb', line 289

def self.remove_helper(name)
  unless has_helper? name
    raise "'#{name.to_s}' is not a helper"
  end

  base_class.class_eval do
    remove_method(name.to_sym)
  end
end