builderator 0.3.15 → 1.0.0.pre.rc.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|