Class: Wrapture::TemplateSpec
- Inherits:
-
Object
- Object
- Wrapture::TemplateSpec
- Defined in:
- lib/wrapture/template_spec.rb
Overview
A template that can be referenced in other specs.
Templates provide a way to re-use common specification portions without needing to repeat them everywhere they’re needed. For example, if the error handling code within a wrapped library is the same for most functions, it can be defined once in a template and then simply referenced in each function specification that needs it. Not only does this reduce the size of the specifications, but it also allows changes to be made in one place instead of many.
Basic Usage
Templates are defined in a top-level templates member of a specification, which holds an array of templates. Each template has only two properties: name which holds the name of the template that is used to invoke it from other specifications, and value which holds the object(s) to insert when the template is used.
Templates can be used at any point in a specification by including a Hash member named use-template which is itself a Hash containing a name member and optionally a parameter list (see below). When a spec is created in a scope that has a template with the given name, the use-template object will be replaced with the template contents. Other members of the Hash will be left intact.
To illustrate, consider a template defined with some normal class properties for a library:
name: "standard-class-properties"
value:
namespace: "wrapturedemo"
type: "pointer"
This could then used in a class specification like this:
classes:
- name: "ClassA"
use-template:
name: "standard-class-properties"
- name: "ClassB"
use-template:
name: "standard-class-properties"
Which would result in an effective class specification of this:
classes:
- name: "ClassA"
namespace: "wrapturedemo"
type: "pointer"
- name: "ClassB"
namespace: "wrapturedemo"
type: "pointer"
Note that the properties included in the template were added to the other members of the object. If there is a conflict between members, the member of the invoking specification will override the template’s member.
In templates that don’t have any parameters, you can save a small bit of typing by simply setting the value of the use-template member to the name of the template directly. So, the previous invocation would become this:
classes:
- name: "ClassA"
use-template: "standard-class-properties"
- name: "ClassB"
use-template: "standard-class-properties"
Usage in Arrays
In some cases, you may want a template to expand to an array of elements that are added to an existing array. This can be accomplished by invoking the template in its own list element and making sure that the use-template member is the only member of the hash. This will result in the template result being inserted into the list at the point of the template invocation. Consider this example specification snippet:
templates:
- name: "default-includes"
value:
- "struct_decls.h"
- "error_handling.h"
- "macros.h"
classes:
- name: "StupendousMan"
equivalent-struct:
name: "stupendous_man"
includes:
- "man.h"
- use-template:
name: "default-includes"
- "stupendous.h"
This would result in an include list containing this:
includes:
- "man.h"
- "struct_decls.h"
- "error_handling.h"
- "macros.h"
- "stupendous.h"
Note that this behavior means that if your intention is to make a list element itself include a list, then you will need to put the template invocation into its own list, like this:
my_list:
- "element-1"
- "element-2"
-
- use-template:
name: "list-template"
Usage in other Templates
Templates may reference other templates within themselves. There is no limit to this nesting, which means that it is quite possible for a careless developer to get himself into trouble, for example by recursively referencing a template from itself. Responsible usage of this functionality is left to the users.
There are no guarantees made about the order in which templates are expanded. This is an attempt to keep template usage simple and direct.
Parameters
Templates may contain any number of parameters that can be supplied upon invocation. The supplied parameters are then used to replace values in the template upon template invocation. This allows templates to be reusable in a wider variety of situations where they may be a small number of differences between invocations, but not significant.
Paremeters are signified within a template by using a hash that has a is-param member set to true, and a name member containing the name of the parameter. In the template invocation, a params member is supplied which contains a list of parameter names and values to substitute for them.
A simple use of template parameters is shown here, where a template is used to wrap functions which differ only in the name of the underlying wrapped function:
templates:
- name: "simple-function"
value:
wrapped-function:
name:
is-param: true
name: "wrapped-function"
params:
- value: "equivalent-struct-pointer"
classes:
- name: "StupendousMan"
functions:
- name: "crawl"
use-template:
name: "simple-function"
params:
name: "wrapped-function"
value: "stupendous_man_crawl"
- name: "walk"
use-template:
name: "simple-function"
params:
name: "wrapped-function"
value: "stupendous_man_walk"
- name: "run"
use-template:
name: "simple-function"
params:
name: "wrapped-function"
value: "stupendous_man_run"
The above would result in a class specification of this:
name: "StupendousMan"
functions:
- name: "crawl"
wrapped-function:
name: "stupendous_man_crawl"
params:
- value: "equivalent-struct-pointer"
- name: "walk"
wrapped-function:
name: "stupendous_man_walk"
params:
- value: "equivalent-struct-pointer"
- name: "run"
wrapped-function:
name: "stupendous_man_run"
params:
- value: "equivalent-struct-pointer"
Parameter Replacement
The rules for parameter replacement are not as complex as for template invocation, as they are intended to hold single values rather than heirarchical object structures. Replacement of a parameter simply replaces the hash containing the is-param member with the given parameter of the same name. Objects may be supplied instead of single values, but they will be inserted directly into the position rather than merged with other hash or array members. If the more complex merging functionality is needed, then consider invoking a template instead of using a parameter.
Class Method Summary collapse
-
.param?(spec, param_name) ⇒ Boolean
True if the provided spec is a template parameter with the given name.
-
.replace_all_uses(spec, *templates) ⇒ Object
Replaces all instances of the given templates in the provided spec.
-
.replace_param(spec, param_name, param_value) ⇒ Object
Creates a new spec based on the given one with all instances of a parameter with the given name replaced with the given value.
-
.replace_param!(spec, param_name, param_value) ⇒ Object
Replaces all instances of a parameter with the given name with the given value in the provided spec.
Instance Method Summary collapse
-
#direct_use?(spec) ⇒ Boolean
True if the given spec is a reference to this template that will be completely replaced by the template.
-
#initialize(spec) ⇒ TemplateSpec
constructor
Creates a new template with the given hash spec.
-
#instantiate(params = nil) ⇒ Object
Returns a spec hash of this template with the provided parameters substituted.
-
#name ⇒ Object
The name of the template.
-
#replace_uses(spec) ⇒ Object
Replaces all references to this template with an instantiation of it in the given spec.
-
#use?(spec) ⇒ Boolean
True if the given spec is a reference to this template.
Constructor Details
#initialize(spec) ⇒ TemplateSpec
Creates a new template with the given hash spec.
301 302 303 |
# File 'lib/wrapture/template_spec.rb', line 301 def initialize(spec) @spec = spec end |
Class Method Details
.param?(spec, param_name) ⇒ Boolean
True if the provided spec is a template parameter with the given name.
242 243 244 245 246 247 |
# File 'lib/wrapture/template_spec.rb', line 242 def self.param?(spec, param_name) spec.is_a?(Hash) && spec.key?('is-param') && spec['is-param'] && spec['name'] == param_name end |
.replace_all_uses(spec, *templates) ⇒ Object
Replaces all instances of the given templates in the provided spec. This is done recursively until no more changes can be made. Returns true if any changes were made, false otherwise.
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/wrapture/template_spec.rb', line 224 def self.replace_all_uses(spec, *templates) return false unless spec.is_a?(Hash) || spec.is_a?(Array) changed = false loop do changes = templates.collect do |temp| temp.replace_uses(spec) end changed = true if changes.any? break unless changes.any? end changed end |
.replace_param(spec, param_name, param_value) ⇒ Object
Creates a new spec based on the given one with all instances of a parameter with the given name replaced with the given value.
251 252 253 254 |
# File 'lib/wrapture/template_spec.rb', line 251 def self.replace_param(spec, param_name, param_value) new_spec = Marshal.load(Marshal.dump(spec)) replace_param!(new_spec, param_name, param_value) end |
.replace_param!(spec, param_name, param_value) ⇒ Object
Replaces all instances of a parameter with the given name with the given value in the provided spec.
258 259 260 261 262 263 264 265 266 267 |
# File 'lib/wrapture/template_spec.rb', line 258 def self.replace_param!(spec, param_name, param_value) case spec when Hash replace_param_in_hash(spec, param_name, param_value) when Array replace_param_in_array(spec, param_name, param_value) else spec end end |
Instance Method Details
#direct_use?(spec) ⇒ Boolean
True if the given spec is a reference to this template that will be completely replaced by the template. A direct use can be recognized as a hash with only a ‘use-template’ key and no others.
308 309 310 |
# File 'lib/wrapture/template_spec.rb', line 308 def direct_use?(spec) use?(spec) && spec.length == 1 end |
#instantiate(params = nil) ⇒ Object
Returns a spec hash of this template with the provided parameters substituted.
314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/wrapture/template_spec.rb', line 314 def instantiate(params = nil) result_spec = Marshal.load(Marshal.dump(@spec['value'])) return result_spec if params.nil? params.each do |param| TemplateSpec.replace_param!(result_spec, param['name'], param['value']) end result_spec end |
#name ⇒ Object
The name of the template.
327 328 329 |
# File 'lib/wrapture/template_spec.rb', line 327 def name @spec['name'] end |
#replace_uses(spec) ⇒ Object
Replaces all references to this template with an instantiation of it in the given spec. Returns true if any changes were made, false otherwise.
Recursive template uses will not be replaced by this function. If multiple replacements are needed, then you will need to call this function multiple times.
337 338 339 340 341 342 343 344 345 346 |
# File 'lib/wrapture/template_spec.rb', line 337 def replace_uses(spec) case spec when Hash replace_uses_in_hash(spec) when Array replace_uses_in_array(spec) else false end end |
#use?(spec) ⇒ Boolean
True if the given spec is a reference to this template.
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 |
# File 'lib/wrapture/template_spec.rb', line 349 def use?(spec) return false unless spec.is_a?(Hash) && spec.key?(TEMPLATE_USE_KEYWORD) invocation = spec[TEMPLATE_USE_KEYWORD] case invocation when String invocation == name when Hash unless invocation.key?('name') = "invocations of #{TEMPLATE_USE_KEYWORD} must have a "\ 'name member' raise InvalidTemplateUsage, end invocation['name'] == name else = "#{TEMPLATE_USE_KEYWORD} must either be a String or a "\ 'Hash' raise InvalidTemplateUsage, end end |