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

Constant Summary collapse

VERSION =

The current version of this library

'0.1.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.



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/dsl/maker.rb', line 198

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



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

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



139
140
141
142
143
144
145
146
# File 'lib/dsl/maker.rb', line 139

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



312
313
314
315
316
317
# File 'lib/dsl/maker.rb', line 312

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.



240
241
242
243
244
245
246
# File 'lib/dsl/maker.rb', line 240

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.



117
118
119
120
121
122
# File 'lib/dsl/maker.rb', line 117

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.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/dsl/maker.rb', line 160

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:



290
291
292
# File 'lib/dsl/maker.rb', line 290

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

.is_alias(type) ⇒ Object



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

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

.is_array(type) ⇒ Object



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.



105
106
107
108
109
110
# File 'lib/dsl/maker.rb', line 105

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



275
276
277
278
279
280
281
282
283
# File 'lib/dsl/maker.rb', line 275

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