Class: DSL::Maker
- Inherits:
-
Object
- Object
- DSL::Maker
- 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.8'- Any =
Create the DSL::Maker::Any type identifier, equivalent to Object.
Object- Yes =
On = True = true
- No =
Off = False = false
Class Method Summary collapse
-
.add_entrypoint(name, args = {}, &defn_block) ⇒ Class
Add an entrypoint (top-level DSL element) to this class's DSL.
-
.add_helper(name, &block) ⇒ Object
This adds a helper function that's accessible within the DSL.
-
.add_type(type, &block) ⇒ Object
This adds a type coercion that's used when creating the DSL.
-
.add_verification(name, &block) ⇒ Object
This adds a verification that's executed after the DSL is finished parsing.
-
.build_dsl_element(klass, name, type) ⇒ Object
Add a single element of a DSL to a class representing a level in a DSL.
-
.entrypoint(name) ⇒ Class
This returns the DSL corresponding to the entrypoint's name.
-
.execute_dsl(&block) ⇒ Object
Execute the DSL provided in the block.
-
.generate_dsl(args = {}, &defn_block) ⇒ Class
Add the meat of a DSL block to some level of this class's DSL.
- .is_dsl(proto) ⇒ Object
- .is_entrypoint(name) ⇒ Object
-
.parse_dsl(dsl = nil) ⇒ Object
Parse the DSL provided in the parameter.
- .run_dsl ⇒ Object
Class Method Details
.add_entrypoint(name, args = {}, &defn_block) ⇒ Class
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().
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/dsl/maker.rb', line 178 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.
236 237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/dsl/maker.rb', line 236 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
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.
113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/dsl/maker.rb', line 113 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] = ->(klass, name, type) { klass.class_eval do define_method(name.to_sym) do |*args| instance_exec('@' + name.to_s, *args, &block) end end } return end |
.add_verification(name, &block) ⇒ Object
These verifications are specific to the DSL you add them to.
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.
268 269 270 271 272 273 |
# File 'lib/dsl/maker.rb', line 268 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 |
.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 type coercions are:
- Any - whatever you give is returned.
- String - the string value of whatever you give is returned.
- Integer - the integer value of whatever you give is returned.
- Boolean - the truthiness of whatever you give is returned.
- generate_dsl() - this represents a new level of the DSL.
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 |
# File 'lib/dsl/maker.rb', line 300 def self.build_dsl_element(klass, name, type) if @@types.has_key?(type) @@types[type].call(klass, name, type) elsif is_dsl(type) as_attr = '@' + name.to_s klass.class_eval do define_method(name.to_sym) do |*args, &dsl_block| if (!args.empty? || dsl_block) obj = type.new Docile.dsl_eval(obj, &dsl_block) if dsl_block rv = obj.__apply(*args) if v = type.instance_variable_get(:@verifications) v.each do |verify| failure = verify.call(rv) raise failure if failure end end # This is the one place where we pull out the entrypoint results and # put them into the control class. if klass.parent_class # Use the full instance_variable_get() in order to avoid having to # create accessors that could be misused outside this class. klass.parent_class.instance_variable_get(:@accumulator).push(rv) end ___set(as_attr, rv) 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.
220 221 222 223 224 225 226 |
# File 'lib/dsl/maker.rb', line 220 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
If the DSL contains multiple entrypoints, then this will return an
Execute the DSL provided in the block.
Array. This is desirable.
91 92 93 94 95 96 |
# File 'lib/dsl/maker.rb', line 91 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.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/dsl/maker.rb', line 140 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_dsl(proto) ⇒ Object
353 354 355 |
# File 'lib/dsl/maker.rb', line 353 def self.is_dsl(proto) proto.is_a?(Class) && proto.ancestors.include?(DSL::Maker::Base) end |
.is_entrypoint(name) ⇒ Object
357 358 359 360 |
# File 'lib/dsl/maker.rb', line 357 def self.is_entrypoint(name) @entrypoints && @entrypoints.has_key?(name.to_sym) #@klass && @klass.new.respond_to?(name.to_sym) end |
.parse_dsl(dsl = nil) ⇒ Object
If the DSL contains multiple entrypoints, then this will return an
Parse the DSL provided in the parameter.
Array. This is desirable.
76 77 78 79 80 81 |
# File 'lib/dsl/maker.rb', line 76 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 |
.run_dsl ⇒ Object
339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/dsl/maker.rb', line 339 def self.run_dsl() # build_dsl_element() will use @accumulator to handle multiple entrypoints if # the class in question is a root DSL class. Reset it here so that we're only # handling the values from this run. @accumulator = [] yield if @accumulator.length <= 1 return @accumulator[0] end return @accumulator end |