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.6'- Yes =
On = True = true
- No =
Off = False = false
- @@types =
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.
{}
Class Method Summary collapse
- .__is_dsl(proto) ⇒ Object
- .__is_entrypoint(name) ⇒ Object
- .__run_dsl ⇒ Object
-
.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.
-
.get_binding ⇒ Binding
Returns the binding as needed by parse_dsl() and execute_dsl().
-
.parse_dsl(dsl) ⇒ Object
Parse the DSL provided in the parameter.
Class Method Details
.__is_dsl(proto) ⇒ Object
312 313 314 |
# File 'lib/dsl/maker.rb', line 312 def self.__is_dsl(proto) proto.is_a?(Class) && proto.ancestors.include?(DSL::Maker::Base) end |
.__is_entrypoint(name) ⇒ Object
316 317 318 |
# File 'lib/dsl/maker.rb', line 316 def self.__is_entrypoint(name) respond_to?(name.to_sym) end |
.__run_dsl ⇒ Object
299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/dsl/maker.rb', line 299 def self.__run_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 = [] yield if @accumulator.length <= 1 return @accumulator[0] end return @accumulator end |
.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().
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 214 215 216 217 218 219 220 221 222 |
# File 'lib/dsl/maker.rb', line 186 def self.add_entrypoint(name, args={}, &defn_block) symname = name.to_sym if self.respond_to?(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 define_singleton_method(symname) do |*args, &dsl_block| obj = dsl_class.new Docile.dsl_eval(obj, &dsl_block) if dsl_block rv = obj.__apply(*args) if @verifications && @verifications.has_key?(symname) @verifications[symname].each do |verify| failure = verify.call(rv) raise failure if failure end end @accumulator.push(rv) return rv 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.
245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/dsl/maker.rb', line 245 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
This adds a type coercion that's used when creating the DSL.
Note: These type coercions are global to all DSLs.
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.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/dsl/maker.rb', line 81 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
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.
Note: These verifications are specific to the DSL you add them to. Note: Verifications are called in the order you specify them.
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/dsl/maker.rb', line 272 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) @verifications ||= {} @verifications[name.to_sym] ||= [] # This craziness converts the block provided into a proc that can be called # in add_entrypoint(). Taken from http://stackoverflow.com/a/2946734/1732954 # Note: self is not preserved. This should be okay because the verification # should only care about the value provided. obj = Object.new obj.define_singleton_method(:_, &block) @verifications[name.to_sym].push(obj.method(:_).to_proc) return 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:
- String - 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.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/dsl/maker.rb', line 112 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| unless (args.empty? && !dsl_block) obj = type.new Docile.dsl_eval(obj, &dsl_block) if dsl_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.
229 230 231 232 233 234 235 |
# File 'lib/dsl/maker.rb', line 229 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.
56 57 58 59 60 |
# File 'lib/dsl/maker.rb', line 56 def self.execute_dsl(&block) raise 'Block required for execute_dsl' unless block_given? __run_dsl { 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.
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/dsl/maker.rb', line 146 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_binding ⇒ Binding
Returns the binding as needed by parse_dsl() and execute_dsl()
295 296 297 |
# File 'lib/dsl/maker.rb', line 295 def self.get_binding binding end |
.parse_dsl(dsl) ⇒ Object
If the DSL contains multiple entrypoints, then this will return an
Parse the DSL provided in the parameter.
Array. This is desirable.
44 45 46 |
# File 'lib/dsl/maker.rb', line 44 def self.parse_dsl(dsl) __run_dsl { eval dsl, self.get_binding } end |