builderator 0.3.15 → 1.0.0.pre.rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +9 -0
- data/Gemfile.lock +440 -0
- data/README.md +72 -18
- data/Rakefile +1 -2
- data/VERSION +1 -1
- data/bin/build-clean +102 -0
- data/bin/build-data +45 -0
- data/builderator.gemspec +7 -4
- data/docs/configuration.md +154 -0
- data/docs/configuration/cookbook.md +19 -0
- data/docs/configuration/profile.md +71 -0
- data/docs/versioning.md +65 -0
- data/lib/builderator.rb +3 -0
- data/lib/builderator/config.rb +93 -0
- data/lib/builderator/config/attributes.rb +287 -0
- data/lib/builderator/config/defaults.rb +163 -0
- data/lib/builderator/config/file.rb +336 -0
- data/lib/builderator/config/rash.rb +80 -0
- data/lib/builderator/control/cleaner.rb +138 -0
- data/lib/builderator/control/cookbook.rb +16 -0
- data/lib/builderator/control/data.rb +16 -0
- data/lib/builderator/control/data/image.rb +98 -0
- data/lib/builderator/control/version.rb +128 -0
- data/lib/builderator/control/version/auto.rb +48 -0
- data/lib/builderator/control/version/bump.rb +82 -0
- data/lib/builderator/control/version/comparable.rb +77 -0
- data/lib/builderator/control/version/git.rb +45 -0
- data/lib/builderator/control/version/scm.rb +92 -0
- data/lib/builderator/interface.rb +67 -0
- data/lib/builderator/interface/berkshelf.rb +38 -0
- data/lib/builderator/interface/packer.rb +75 -0
- data/lib/builderator/interface/vagrant.rb +31 -0
- data/lib/builderator/metadata.rb +5 -3
- data/lib/builderator/model/cleaner.rb +49 -0
- data/lib/builderator/model/cleaner/images.rb +93 -0
- data/lib/builderator/model/cleaner/instances.rb +58 -0
- data/lib/builderator/model/cleaner/launch_configs.rb +47 -0
- data/lib/builderator/model/cleaner/scaling_groups.rb +45 -0
- data/lib/builderator/model/cleaner/snapshots.rb +50 -0
- data/lib/builderator/model/cleaner/volumes.rb +48 -0
- data/lib/builderator/patch/berkshelf.rb +18 -0
- data/lib/builderator/patch/thor-actions.rb +47 -0
- data/lib/builderator/tasks.rb +127 -17
- data/lib/builderator/tasks/berkshelf.rb +63 -0
- data/lib/builderator/tasks/packer.rb +17 -56
- data/lib/builderator/tasks/vagrant.rb +111 -42
- data/lib/builderator/tasks/vendor.rb +94 -0
- data/lib/builderator/tasks/version.rb +58 -0
- data/lib/builderator/util.rb +37 -11
- data/lib/builderator/util/aws_exception.rb +1 -1
- data/lib/builderator/util/limit_exception.rb +12 -11
- data/lib/builderator/util/task_exception.rb +0 -2
- data/mkmf.log +4 -0
- data/spec/config_spec.rb +30 -0
- data/spec/data/Berksfile +6 -0
- data/spec/data/Buildfile +0 -0
- data/spec/data/Vagrantfile +0 -0
- data/spec/data/history.json +483 -0
- data/spec/data/packer.json +0 -0
- data/spec/interface_spec.rb +36 -0
- data/spec/resource/Buildfile +27 -0
- data/spec/spec_helper.rb +90 -0
- data/spec/version_spec.rb +282 -0
- data/template/Berksfile.erb +10 -0
- data/template/Buildfile.erb +28 -0
- data/template/Gemfile.erb +16 -0
- data/template/README.md.erb +61 -0
- data/template/Vagrantfile.erb +75 -0
- data/template/gitignore.erb +104 -0
- data/{.rubocop.yml → template/rubocop.erb} +0 -0
- metadata +203 -56
- data/.gitignore +0 -14
- data/lib/builderator/control/ami.rb +0 -65
- data/lib/builderator/control/clean.rb +0 -130
- data/lib/builderator/model.rb +0 -46
- data/lib/builderator/model/images.rb +0 -89
- data/lib/builderator/model/instances.rb +0 -55
- data/lib/builderator/model/launch_configs.rb +0 -46
- data/lib/builderator/model/scaling_groups.rb +0 -43
- data/lib/builderator/model/snapshots.rb +0 -49
- data/lib/builderator/model/volumes.rb +0 -48
- data/lib/builderator/tasks/ami.rb +0 -47
- data/lib/builderator/tasks/berks.rb +0 -68
- data/lib/builderator/tasks/clean.rb +0 -97
- data/lib/builderator/util/berkshim.rb +0 -34
- data/lib/builderator/util/cookbook.rb +0 -87
- data/lib/builderator/util/packer.rb +0 -39
- data/lib/builderator/util/shell.rb +0 -44
@@ -0,0 +1,71 @@
|
|
1
|
+
Collection `profile`
|
2
|
+
====================
|
3
|
+
|
4
|
+
A profile is a combination of Chef parameters, and Vagrant and Packer configurations. Profiles should provide
|
5
|
+
|
6
|
+
* `tags, type: hash` EC2 tags to apply to instances and AMIs
|
7
|
+
* `log_level` Chef log-level. Default `:info`
|
8
|
+
|
9
|
+
## Collection `artifact`
|
10
|
+
|
11
|
+
An externally managed resource to push to VMs and image builds, e.g. `bundle.tar.gz` from a Maven build.
|
12
|
+
|
13
|
+
* `path` The workspace-rooted path to the artifact
|
14
|
+
* `destination` The absolute path on the VM or image at which the artifact should be placed
|
15
|
+
|
16
|
+
## Namespace `chef`
|
17
|
+
* `run_list, type: list, singular: run_list_item, unique: true` The Chef runlist for this profile
|
18
|
+
* `environment` The Chef environment to load for this
|
19
|
+
* `node_attrs, type: hash` A hash of node attributes for this profile
|
20
|
+
|
21
|
+
|
22
|
+
## Namespace `packer`
|
23
|
+
|
24
|
+
Packer configurations for this profile
|
25
|
+
|
26
|
+
### Collection `build`
|
27
|
+
|
28
|
+
Add a packer build
|
29
|
+
|
30
|
+
* `type` the build provider (e.g. amazon-ebs, virtualbox)
|
31
|
+
* `instance_type` the EC2 instance type to use
|
32
|
+
* `source_ami` The source AMI ID for an `amazon-ebs`
|
33
|
+
* `ssh_username` Default `ubuntu`
|
34
|
+
* `ami_virtualization_type` Default `hvm`
|
35
|
+
|
36
|
+
## TODO: Share accounts
|
37
|
+
|
38
|
+
* `ami_name` Name for new AMI
|
39
|
+
* `ami_description` Description for the new AMI
|
40
|
+
|
41
|
+
|
42
|
+
## Namespace `vagrant`
|
43
|
+
|
44
|
+
Vagrant VM configurations
|
45
|
+
|
46
|
+
### Namespace `local`
|
47
|
+
|
48
|
+
Parameters for a local VM build
|
49
|
+
|
50
|
+
* `provider` Default `virtualbox`
|
51
|
+
* `box` Default `ubuntu-14.04-x86_64`
|
52
|
+
* `box_url` Default `https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box`
|
53
|
+
|
54
|
+
* `cpus` Default 2
|
55
|
+
* `memory` Default 1024 (MB)
|
56
|
+
|
57
|
+
## Namespace `ec2`
|
58
|
+
|
59
|
+
Parameters for the provisioning EC2 nodes with Vagrant
|
60
|
+
|
61
|
+
* `provider` Default `aws`
|
62
|
+
* `box` Default `dummy`
|
63
|
+
* `box_url` Default `https://github.com/mitchellh/vagrant-aws/raw/master/dummy.box`
|
64
|
+
* `instance_type`
|
65
|
+
* `source_ami`
|
66
|
+
* `ssh_username`
|
67
|
+
* `virtualization_type`
|
68
|
+
* `instance_profile`
|
69
|
+
* `subnet_id`
|
70
|
+
* `security_groups, type: list, singular: security_group, unique: true`
|
71
|
+
* `public_ip`
|
data/docs/versioning.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
Versioning
|
2
|
+
==========
|
3
|
+
|
4
|
+
This functionality is currently supported for Git.
|
5
|
+
|
6
|
+
## Version Bump Steps
|
7
|
+
|
8
|
+
* `major` - Increment major version.
|
9
|
+
* `major-prerelease` - Start a pre-release train from the next major version (increments major version).
|
10
|
+
* `minor` - Increment minor version.
|
11
|
+
* `minor-prerelease` - Start a pre-release train from the next minor version (increments minor version).
|
12
|
+
* `patch` - Increment the patch version.
|
13
|
+
* `patch-prerelease` - Force a new pre-release train from the next patch version, even if the current version is a pre-release.
|
14
|
+
* `release` - Release a pre-release train a the current `major`.`minor`.`patch` version.
|
15
|
+
* `prerelease NAME` Create or increment a pre-release version. If the current version is not a pre-release, the patch version will also be incremented.
|
16
|
+
* `build` Add or increment a build number.
|
17
|
+
|
18
|
+
Step types are an ordered-set. Incrementing a higher step resets all lower parameters.
|
19
|
+
|
20
|
+
## Commands
|
21
|
+
|
22
|
+
### `build version current`
|
23
|
+
|
24
|
+
Searches for the newest SCM tag that is a valid sem-ver string and writes it to VERSION in the project root.
|
25
|
+
|
26
|
+
### `build version bump [STEP [PRERELEASE_NAME=alpha]]`
|
27
|
+
|
28
|
+
Increment the current version by the desired step and create a new SCM tag at HEAD.
|
29
|
+
|
30
|
+
If STEP is omitted, Builderator will scan messages of commits between HEAD and the current version tag for hash-tag style annotations indicating how to increment the version, finally defaulting to a `patch` step if no annotations are found. If multiple `#STEP` annotations are detected, the largest (e.g. `#major` > `#patch`) step will be applied.
|
31
|
+
|
32
|
+
## Configuration
|
33
|
+
|
34
|
+
The `autoversion` namespace has two attributes:
|
35
|
+
|
36
|
+
* `create_tags BOOLEAN` enables auto-generation of SCM tags after `bump` tasks. Default `true`.
|
37
|
+
* `search_tags` enables detection of the current version from SCM tags. Default `true`.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
autoversion do |version|
|
41
|
+
version.create_tags true
|
42
|
+
version.search_tags true
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
## Adding Providers
|
47
|
+
|
48
|
+
SCM providers must extend the `Builderator::Control::Version::SCM` module, and must implement two methods in their singleton class:
|
49
|
+
|
50
|
+
* `self._history` Return an array of hashes with the following keys:
|
51
|
+
- `:id` SCM commit identity
|
52
|
+
- `:message` SCM commit message
|
53
|
+
- `:tags` `nil` or an array of semver strings
|
54
|
+
* `self.supported?` Return `true` if the provider supports the build environment (e.g. the `Git` provider checks that `pwd` is a git repository), else return `false`.
|
55
|
+
|
56
|
+
To enable a provider module, pass it to `SCM.register`. See [blob/auto-version/lib/builderator/control/version/git.rb] for an example.
|
57
|
+
|
58
|
+
## This looks like `thor-scmversion`
|
59
|
+
|
60
|
+
_Why aren't you using `thor-scmversion`?!_
|
61
|
+
|
62
|
+
Well yes, it's based upon `thor-scmversion`, which I've been using for a while. `thor-scmversion` provides a nice model to ensure correct versioning of automatically built modules, but the project is largely abandoned, lacks some key features required for Builderator, and has a very inefficient access path for reading Git SCM data.
|
63
|
+
|
64
|
+
This implementation adds `TYPE-prerelease` bump steps, improves semver matching regular-expressions, dramatically improves git-data access time for repositories with many tags (only reads from git-blobs once),
|
65
|
+
and de-couples core functionality for parsing and incrementing versions from Thor tasks and actual mutation of the repository.
|
data/lib/builderator.rb
CHANGED
@@ -0,0 +1,93 @@
|
|
1
|
+
require_relative './config/file'
|
2
|
+
require_relative './config/defaults'
|
3
|
+
|
4
|
+
module Builderator
|
5
|
+
##
|
6
|
+
# Global Configuration
|
7
|
+
##
|
8
|
+
module Config
|
9
|
+
class << self
|
10
|
+
## GLOBAL_DEFAULTS is the lowest-precedence layer, followed by dynamically
|
11
|
+
## defined instance-defaults.
|
12
|
+
def layers
|
13
|
+
@layers ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def all_layers
|
17
|
+
([GLOBAL_DEFAULTS, defaults] + layers + [overrides, argv])
|
18
|
+
end
|
19
|
+
|
20
|
+
def defaults
|
21
|
+
@defaults ||= File.new({}, :source => 'defaults')
|
22
|
+
end
|
23
|
+
|
24
|
+
def overrides
|
25
|
+
@overrides ||= File.new({}, :source => 'overrides')
|
26
|
+
end
|
27
|
+
|
28
|
+
def argv(options = {})
|
29
|
+
@argv ||= File.new(options, :source => 'argv')
|
30
|
+
end
|
31
|
+
|
32
|
+
def append(path)
|
33
|
+
layers << File.from_file(path) if ::File.exist?(path)
|
34
|
+
end
|
35
|
+
alias_method :load, :append
|
36
|
+
|
37
|
+
def append_json(path)
|
38
|
+
layers << File.from_json(path) if ::File.exist?(path)
|
39
|
+
end
|
40
|
+
alias_method :load_json, :append_json
|
41
|
+
|
42
|
+
def prepend(path)
|
43
|
+
layers.unshift(File.from_file(path)) if ::File.exist?(path)
|
44
|
+
end
|
45
|
+
|
46
|
+
def prepend_json(path)
|
47
|
+
layers.unshift(File.from_json(path)) if ::File.exist?(path)
|
48
|
+
end
|
49
|
+
|
50
|
+
def compile(max_iterations = 4)
|
51
|
+
compiled.unseal
|
52
|
+
compile_iterations = 0
|
53
|
+
|
54
|
+
## Automatically recompile while layers are dirty
|
55
|
+
loop do
|
56
|
+
fail "Re-compile iteration limit of #{max_iterations} has been exceeded" if compile_iterations >= max_iterations
|
57
|
+
|
58
|
+
## Reset flags before next iteration
|
59
|
+
compiled.clean
|
60
|
+
|
61
|
+
## Merge layers from lowest to highest
|
62
|
+
all_layers.each { |layer| compiled.merge(layer.compile) }
|
63
|
+
|
64
|
+
break unless dirty?
|
65
|
+
compile_iterations += 1
|
66
|
+
end
|
67
|
+
|
68
|
+
## Don't auto-populate keys anymore
|
69
|
+
compiled.seal
|
70
|
+
end
|
71
|
+
alias_method :recompile, :compile
|
72
|
+
|
73
|
+
def dirty?
|
74
|
+
all_layers.any?(&:dirty) || compiled.dirty
|
75
|
+
end
|
76
|
+
|
77
|
+
def compiled
|
78
|
+
@compiled ||= File.new
|
79
|
+
end
|
80
|
+
|
81
|
+
def fetch(key, *args)
|
82
|
+
compiled.send(key, *args)
|
83
|
+
end
|
84
|
+
alias_method :[], :fetch
|
85
|
+
|
86
|
+
def method_missing(method_name, *args)
|
87
|
+
return super unless compiled.respond_to?(method_name)
|
88
|
+
|
89
|
+
compiled.send(method_name, *args)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,287 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require_relative './rash'
|
5
|
+
|
6
|
+
module Builderator
|
7
|
+
module Config
|
8
|
+
##
|
9
|
+
# Shared Attribute Mixin
|
10
|
+
##
|
11
|
+
class Attributes
|
12
|
+
##
|
13
|
+
# DSL Definition
|
14
|
+
##
|
15
|
+
class << self
|
16
|
+
def attribute(attribute_name, default = nil, **options)
|
17
|
+
##
|
18
|
+
# Helpers for Array-type attributes
|
19
|
+
##
|
20
|
+
if options[:type] == :list
|
21
|
+
default = Array
|
22
|
+
|
23
|
+
## Add an appender DSL method
|
24
|
+
define_method(options[:singular]) do |*args|
|
25
|
+
append_if_valid(attribute_name, args.flatten, default, options)
|
26
|
+
end if options.include?(:singular)
|
27
|
+
end
|
28
|
+
|
29
|
+
## Getter/Setter
|
30
|
+
define_method(attribute_name) do |*arg|
|
31
|
+
arg.flatten!
|
32
|
+
arg = arg.first unless options[:type] == :list
|
33
|
+
|
34
|
+
set_or_return(attribute_name, arg, default, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
## Setter
|
38
|
+
define_method("#{attribute_name}=") do |arg|
|
39
|
+
set_if_valid(attribute_name, arg, options)
|
40
|
+
end unless options[:type] == :list
|
41
|
+
end
|
42
|
+
|
43
|
+
## Add a method the DSL
|
44
|
+
def define(method_name, &block)
|
45
|
+
define_method(method_name, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# A Namespace is a singleton sub-node of the attribute-set
|
50
|
+
#
|
51
|
+
# e.g. `namespace :chef ...` maps to `attributes[:chef]` and adds a
|
52
|
+
# method `chef(&block)` to the DSL which is used as follows:
|
53
|
+
#
|
54
|
+
# ```
|
55
|
+
# chef do
|
56
|
+
# run_list 'foo', 'bar'
|
57
|
+
# ...
|
58
|
+
# end
|
59
|
+
# ```
|
60
|
+
#
|
61
|
+
# Multiple calls to the DSL method will are safe and will
|
62
|
+
# update the same sub-node.
|
63
|
+
##
|
64
|
+
def namespace(namespace_name, &definition)
|
65
|
+
namespace_class = Namespace.create(namespace_name, &definition)
|
66
|
+
|
67
|
+
define_method(namespace_name) do |&block|
|
68
|
+
nodes[namespace_name] ||= namespace_class.new(
|
69
|
+
@attributes[namespace_name],
|
70
|
+
:name => namespace_name, &block).compile
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# A Collection is a named-set of items in a sub-node of the attribute-set.
|
76
|
+
#
|
77
|
+
# Like Namespaces, Collections map to a top-level key, but they also have
|
78
|
+
# multiple second-order keys:
|
79
|
+
#
|
80
|
+
# e.g. `collection :vagrant ...` adds a DSL method
|
81
|
+
# `vagrant(name = :default, &block)` which maps to
|
82
|
+
# `attributes[:vagrant][<name>]`
|
83
|
+
#
|
84
|
+
# Multiple entities can be added to the collection by calling the DSL method
|
85
|
+
# with unique `name` arguments. Multiple calls to the DSL method with the
|
86
|
+
# same name argument will update the existing entity in place
|
87
|
+
##
|
88
|
+
def collection(collection_name, &definition)
|
89
|
+
collection_class = Collection.create(collection_name, &definition)
|
90
|
+
|
91
|
+
define_method(collection_name) do |instance_name = nil, &block|
|
92
|
+
nodes[collection_name] ||= collection_class.new(
|
93
|
+
@attributes[collection_name])
|
94
|
+
|
95
|
+
return nodes[collection_name] if instance_name.nil?
|
96
|
+
nodes[collection_name].fetch(instance_name, &block).compile
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
extend Forwardable
|
102
|
+
include Enumerable
|
103
|
+
|
104
|
+
## Delegate enumerables to underlying storage structure
|
105
|
+
def_delegators :@attributes, :[], :fetch,
|
106
|
+
:keys, :values, :has?, :each,
|
107
|
+
:to_hash
|
108
|
+
|
109
|
+
def seal
|
110
|
+
attributes.seal
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
def unseal
|
115
|
+
attributes.unseal
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
attr_reader :attributes
|
120
|
+
attr_reader :nodes
|
121
|
+
attr_reader :dirty
|
122
|
+
|
123
|
+
def initialize(attributes = {}, &block)
|
124
|
+
@attributes = Rash.coerce(attributes)
|
125
|
+
@nodes = {}
|
126
|
+
@block = block
|
127
|
+
|
128
|
+
## Track change status for comsumers
|
129
|
+
@dirty = false
|
130
|
+
end
|
131
|
+
|
132
|
+
## Clear dirty state flag
|
133
|
+
def clean
|
134
|
+
@dirty = false
|
135
|
+
end
|
136
|
+
|
137
|
+
def compile
|
138
|
+
instance_eval(&@block) if @block
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
def merge(other)
|
143
|
+
attributes.merge!(other.attributes)
|
144
|
+
self
|
145
|
+
end
|
146
|
+
alias_method :includes, :merge
|
147
|
+
|
148
|
+
def to_json(*_)
|
149
|
+
JSON.pretty_generate(to_hash)
|
150
|
+
end
|
151
|
+
|
152
|
+
protected
|
153
|
+
|
154
|
+
def set_if_valid(key, arg, options = {})
|
155
|
+
## TODO: define validation interface
|
156
|
+
|
157
|
+
## Mutation helpers
|
158
|
+
|
159
|
+
# Input is a path relative to the working directory
|
160
|
+
arg = Util.relative_path(arg).to_s if options[:relative]
|
161
|
+
|
162
|
+
# Input is a path relative to the workspace
|
163
|
+
arg = Util.workspace(arg).to_s if options[:workspace]
|
164
|
+
|
165
|
+
## Unchanged
|
166
|
+
return if @attributes[key] == arg
|
167
|
+
|
168
|
+
@dirty = true ## A mutation has occured
|
169
|
+
@attributes[key] = arg
|
170
|
+
end
|
171
|
+
|
172
|
+
def append_if_valid(key, arg, default = Array, **options)
|
173
|
+
## TODO: define validation interface
|
174
|
+
|
175
|
+
attribute = set_or_return(key, nil, default, options)
|
176
|
+
arg.reject! { |item| attribute.include?(item) }
|
177
|
+
|
178
|
+
return if arg.empty?
|
179
|
+
|
180
|
+
@dirty = true ## A mutation has occured
|
181
|
+
attribute.push(*arg)
|
182
|
+
end
|
183
|
+
|
184
|
+
def set_or_return(key, arg = nil, default = nil, **options)
|
185
|
+
if arg.nil? || (arg.is_a?(Array) && arg.empty?)
|
186
|
+
return @attributes[key] if @attributes.has?(key)
|
187
|
+
|
188
|
+
## Default
|
189
|
+
return if default.is_a?(NilClass) ## No default
|
190
|
+
|
191
|
+
## Allow a default to be a static value, or instantiated
|
192
|
+
## at call-time from a class (e.g. Array or Hash)
|
193
|
+
default_value = default.is_a?(Class) ? default.new : default
|
194
|
+
return default_value if @attributes.sealed
|
195
|
+
|
196
|
+
return set_if_valid(key, default_value, options)
|
197
|
+
end
|
198
|
+
|
199
|
+
## Set value
|
200
|
+
set_if_valid(key, arg, options)
|
201
|
+
end
|
202
|
+
|
203
|
+
##
|
204
|
+
# Define a namespace for attributes
|
205
|
+
##
|
206
|
+
class Namespace < Attributes
|
207
|
+
class << self
|
208
|
+
attr_accessor :name
|
209
|
+
|
210
|
+
##
|
211
|
+
# Construct a new child-class to define the interface. The constructor
|
212
|
+
# accepts an attributes argument, which should be a sub-node of the root
|
213
|
+
# attribute-set.
|
214
|
+
##
|
215
|
+
def create(namespace_name, &definition)
|
216
|
+
space = Class.new(self)
|
217
|
+
space.name = namespace_name
|
218
|
+
|
219
|
+
## Define DSL interface
|
220
|
+
space.instance_eval(&definition) if definition
|
221
|
+
|
222
|
+
space
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
attr_reader :name
|
227
|
+
attr_reader :collection
|
228
|
+
|
229
|
+
def initialize(attributes, options = {}, &block)
|
230
|
+
super(attributes, &block)
|
231
|
+
|
232
|
+
@name = options.fetch(:name, self.class.name)
|
233
|
+
@collection = options[:collection]
|
234
|
+
end
|
235
|
+
|
236
|
+
def compile
|
237
|
+
@block.call(self) if @block
|
238
|
+
self
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
##
|
243
|
+
# Enumerable wrapper for collections
|
244
|
+
##
|
245
|
+
class Collection < Attributes
|
246
|
+
class << self
|
247
|
+
attr_accessor :name
|
248
|
+
attr_accessor :namespace_class
|
249
|
+
|
250
|
+
def create(collection_name, &definition)
|
251
|
+
collection = Class.new(self)
|
252
|
+
collection.name = collection_name
|
253
|
+
collection.namespace_class = Namespace.create(collection_name, &definition)
|
254
|
+
|
255
|
+
collection
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
## Allow a single instance to be selected
|
260
|
+
attr_reader :current
|
261
|
+
def use(instance_name)
|
262
|
+
@current = fetch(instance_name)
|
263
|
+
end
|
264
|
+
|
265
|
+
## Enumerable methods return namespace instances
|
266
|
+
def each(&block)
|
267
|
+
attributes.each_key do |instance_name|
|
268
|
+
block.call(instance_name, fetch(instance_name))
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def name
|
273
|
+
self.class.name
|
274
|
+
end
|
275
|
+
|
276
|
+
## Get namespace instances
|
277
|
+
def fetch(instance_name, &block)
|
278
|
+
self.class.namespace_class.new(
|
279
|
+
attributes[instance_name],
|
280
|
+
:collection => self,
|
281
|
+
:name => instance_name, &block)
|
282
|
+
end
|
283
|
+
alias_method :[], :fetch
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|