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



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
234
235
236
237
238
239
# File 'lib/dsl/maker.rb', line 204

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



262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/dsl/maker.rb', line 262

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

  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



145
146
147
148
149
150
151
152
# File 'lib/dsl/maker.rb', line 145

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



294
295
296
297
298
299
# File 'lib/dsl/maker.rb', line 294

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.



246
247
248
249
250
251
252
# File 'lib/dsl/maker.rb', line 246

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) ⇒ 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



123
124
125
126
127
128
# File 'lib/dsl/maker.rb', line 123

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.



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/dsl/maker.rb', line 166

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

  dsl_class = Class.new(DSL::Maker::Base) 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

.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) ⇒ 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) (defaults to: nil)

    The DSL to be parsed by this class.

Returns:

  • (Object)

    Whatever is returned by the block defined in this class.



108
109
110
111
112
113
# File 'lib/dsl/maker.rb', line 108

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