declarative-builder 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8a7a21a5da3c82bf9edcbfc53a0b493e3c886ad0
4
+ data.tar.gz: 62bb129cb86b262e81792e59bb83ec841ec8df7e
5
+ SHA512:
6
+ metadata.gz: 574cc0bb1eb623eb61c57b84d156470e400f864f4fc840a1c391826191399f4716a136c4e84de11f666c63397d8217447360efe1d5198c4202e1a5dd69b11e57
7
+ data.tar.gz: 67a36117a847c5069ac2131eadcb75e9a1116ae1e59a486df79917934dcbbe945b4af59bc8f32b348c9451fd0ee5b0212ed24cd7ecb1d2c869a85b75a6a59f8d
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,10 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ - 2.1
5
+ - 2.2
6
+ - 2.3.1
7
+ - jruby-19mode
8
+ - jruby
9
+ before_install:
10
+ - gem install bundler
@@ -0,0 +1,3 @@
1
+ # 0.1.0
2
+
3
+ * Introduce `Declarative::Builder` as a replacement for `Uber::Builder`.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in uber.gemspec
4
+ gemspec
5
+
6
+ gem "minitest-line"
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Nick Sutterer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,355 @@
1
+ # Uber
2
+
3
+ _Gem-authoring tools like class method inheritance in modules, dynamic options and more._
4
+
5
+ ## Installation
6
+
7
+ [![Gem Version](https://badge.fury.io/rb/uber.svg)](http://badge.fury.io/rb/uber)
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'uber'
13
+ ```
14
+
15
+ Uber runs with Ruby >= 1.9.3.
16
+
17
+ # Inheritable Class Attributes
18
+
19
+ If you want inherited class attributes, this is for you.
20
+ This is a mandatory mechanism for creating DSLs.
21
+
22
+ ```ruby
23
+ require 'uber/inheritable_attr'
24
+
25
+ class Song
26
+ extend Uber::InheritableAttr
27
+
28
+ inheritable_attr :properties
29
+ self.properties = [:title, :track] # initialize it before using it.
30
+ end
31
+ ```
32
+
33
+ Note that you have to initialize your class attribute with whatever you want - usually a hash or an array.
34
+
35
+ ```ruby
36
+ Song.properties #=> [:title, :track]
37
+ ```
38
+
39
+ A subclass of `Song` will have a `clone`d `properties` class attribute.
40
+
41
+ ```ruby
42
+ class Hit < Song
43
+ end
44
+
45
+ Hit.properties #=> [:title, :track]
46
+ ```
47
+
48
+ The cool thing about the inheritance is: you can work on the inherited attribute without any restrictions. It is a _copy_ of the original.
49
+
50
+ ```ruby
51
+ Hit.properties << :number
52
+
53
+ Hit.properties #=> [:title, :track, :number]
54
+ Song.properties #=> [:title, :track]
55
+ ```
56
+
57
+ It's similar to ActiveSupport's `class_attribute` but with a simpler implementation.
58
+ It is less dangerous. There are no restrictions for modifying the attribute. [compared to `class_attribute`](http://apidock.com/rails/v4.0.2/Class/class_attribute).
59
+
60
+ ## Uncloneable Values
61
+
62
+ `::inheritable_attr` will `clone` values to copy them to subclasses. Uber won't attempt to clone `Symbol`, `nil`, `true` and `false` per default.
63
+
64
+ If you assign any other unclonable value you need to tell Uber that.
65
+
66
+ ```ruby
67
+ class Song
68
+ extend Uber::InheritableAttr
69
+ inheritable_attr :properties, clone: false
70
+ ```
71
+
72
+ This won't `clone` but simply pass the value on to the subclass.
73
+
74
+
75
+ # Dynamic Options
76
+
77
+ Implements the pattern of defining configuration options and dynamically evaluating them at run-time.
78
+
79
+ Usually DSL methods accept a number of options that can either be static values, symbolized instance method names, or blocks (lambdas/Procs).
80
+
81
+ Here's an example from Cells.
82
+
83
+ ```ruby
84
+ cache :show, tags: lambda { Tag.last }, expires_in: 5.mins, ttl: :time_to_live
85
+ ```
86
+
87
+ Usually, when processing these options, you'd have to check every option for its type, evaluate the `tags:` lambda in a particular context, call the `#time_to_live` instance method, etc.
88
+
89
+ This is abstracted in `Uber::Options` and could be implemented like this.
90
+
91
+ ```ruby
92
+ require 'uber/options'
93
+
94
+ options = Uber::Options.new(tags: lambda { Tag.last },
95
+ expires_in: 5.mins,
96
+ ttl: :time_to_live)
97
+ ```
98
+
99
+ Just initialize `Options` with your actual options hash. While this usually happens on class level at compile-time, evaluating the hash happens at run-time.
100
+
101
+ ```ruby
102
+ class User < ActiveRecord::Base # this could be any Ruby class.
103
+ # .. lots of code
104
+
105
+ def time_to_live(*args)
106
+ "n/a"
107
+ end
108
+ end
109
+
110
+ user = User.find(1)
111
+
112
+ options.evaluate(user, *args) #=> {tags: "hot", expires_in: 300, ttl: "n/a"}
113
+ ```
114
+
115
+ ## Evaluating Dynamic Options
116
+
117
+ To evaluate the options to a real hash, the following happens:
118
+
119
+ * The `tags:` lambda is executed in `user` context (using `instance_exec`). This allows accessing instance variables or calling instance methods.
120
+ * Nothing is done with `expires_in`'s value, it is static.
121
+ * `user.time_to_live?` is called as the symbol `:time_to_live` indicates that this is an instance method.
122
+
123
+ The default behaviour is to treat `Proc`s, lambdas and symbolized `:method` names as dynamic options, everything else is considered static. Optional arguments from the `evaluate` call are passed in either as block or method arguments for dynamic options.
124
+
125
+ This is a pattern well-known from Rails and other frameworks.
126
+
127
+ ## Uber::Callable
128
+
129
+ A third way of providing a dynamic option is using a "callable" object. This saves you the unreadable lambda syntax and gives you more flexibility.
130
+
131
+ ```ruby
132
+ require 'uber/callable'
133
+ class Tags
134
+ include Uber::Callable
135
+
136
+ def call(context, *args)
137
+ [:comment]
138
+ end
139
+ end
140
+ ```
141
+
142
+ By including `Uber::Callable`, uber will invoke the `#call` method on the specified object.
143
+
144
+ Note how you simply pass an instance of the callable object into the hash instead of a lambda.
145
+
146
+ ```ruby
147
+ options = Uber::Options.new(tags: Tags.new)
148
+ ```
149
+
150
+ ## Option
151
+
152
+ `Uber::Option` implements the pattern of taking an option, such as a proc, instance method name, or static value, and evaluate it at runtime without knowing the option's implementation.
153
+
154
+ Creating `Option` instances via `::[]` usually happens on class-level in DSL methods.
155
+
156
+ ```ruby
157
+ with_proc = Uber::Option[ ->(options) { "proc: #{options.inspect}" } ]
158
+ with_static = Uber::Option[ "Static value" ]
159
+ with_method = Uber::Option[ :name_of_method ]
160
+
161
+ def name_of_method(options)
162
+ "method: #{options.inspect}"
163
+ end
164
+ ```
165
+
166
+ Use `#call` to evaluate the options at runtime.
167
+
168
+ ```ruby
169
+ with_proc.(1, 2) #=> "proc: [1, 2]"
170
+ with_static.(1, 2) #=> "Static value" # arguments are ignored
171
+ with_method.(self, 1, 2) #=> "method: [1, 2]" # first arg is context
172
+ ```
173
+
174
+ It's also possible to evaluate a callable object. It has to be marked with `Uber::Callable` beforehand.
175
+
176
+ ```ruby
177
+ class MyCallable
178
+ include Uber::Callable
179
+
180
+ def call(context, *args)
181
+ "callable: #{args.inspect}, #{context}"
182
+ end
183
+ end
184
+
185
+ with_callable = Uber::Option[ MyCallable.new ]
186
+ ```
187
+
188
+ The context is passed as first argument.
189
+
190
+ ```ruby
191
+ with_callable.(Object, 1, 2) #=> "callable: [1, 2] Object"
192
+ ```
193
+
194
+ You can also make blocks being `instance_exec`ed on the context, giving a unique API to all option types.
195
+
196
+ ```ruby
197
+ with_instance_proc = Uber::Option[ ->(options) { "proc: #{options.inspect} #{self}" }, instance_exec: true ]
198
+ ```
199
+
200
+ The first argument now becomes the context, exactly the way it works for the method and callable type.
201
+
202
+ ```ruby
203
+ with_instance_proc.(Object, 1, 2) #=> "proc [1, 2] Object"
204
+ ```
205
+
206
+
207
+ # Delegates
208
+
209
+ Using `::delegates` works exactly like the `Forwardable` module in Ruby, with one bonus: It creates the accessors in a module, allowing you to override and call `super` in a user module or class.
210
+
211
+ ```ruby
212
+ require 'uber/delegates'
213
+
214
+ class SongDecorator
215
+ def initialize(song)
216
+ @song = song
217
+ end
218
+ attr_reader :song
219
+
220
+ extend Uber::Delegates
221
+
222
+ delegates :song, :title, :id # delegate :title and :id to #song.
223
+
224
+ def title
225
+ super.downcase # this calls the original delegate #title.
226
+ end
227
+ end
228
+ ```
229
+
230
+ This creates readers `#title` and `#id` which are delegated to `#song`.
231
+
232
+ ```ruby
233
+ song = SongDecorator.new(Song.create(id: 1, title: "HELLOWEEN!"))
234
+
235
+ song.id #=> 1
236
+ song.title #=> "helloween!"
237
+ ```
238
+
239
+ Note how `#title` calls the original title and then downcases the string.
240
+
241
+ # Builder
242
+
243
+ Builders are good for polymorphically creating objects without having to know where that happens. You define a builder with conditions in one class, and that class takes care of creating the actual desired class.
244
+
245
+ ## Declarative Interface
246
+
247
+ Include `Uber::Builder` to leverage the `::builds` method for adding builders, and `::build!` to run those builders in a given context and with arbitrary options.
248
+
249
+
250
+ ```ruby
251
+ require "uber/builder"
252
+
253
+ class User
254
+ include Uber::Builder
255
+
256
+ builds do |options|
257
+ Admin if params[:admin]
258
+ end
259
+ end
260
+
261
+ class Admin
262
+ end
263
+ ```
264
+
265
+ Note that you can call `builds` as many times as you want per class.
266
+
267
+ Run the builders using `::build!`.
268
+
269
+ ```ruby
270
+ User.build!(User, {}) #=> User
271
+ User.build!(User, { admin: true }) #=> Admin
272
+ ```
273
+ The first argument is the context in which the builder blocks will be executed. This is also the default return value if all builders returned a falsey value.
274
+
275
+ All following arguments will be passed straight through to the procs.
276
+
277
+ Your API should communicate `User` as the only public class, since the builder hides details about computing the concrete class.
278
+
279
+ ### Builder: Procs
280
+
281
+ You may also use procs instead of blocks.
282
+
283
+ ```ruby
284
+ class User
285
+ include Uber::Builder
286
+
287
+ builds ->(options) do
288
+ return SignedIn if params[:current_user]
289
+ return Admin if params[:admin]
290
+ Anonymous
291
+ end
292
+ end
293
+ ```
294
+
295
+ Note that this allows `return`s in the block.
296
+
297
+ ## Builder: Direct API
298
+
299
+ In case you don't want the `builds` DSL, you can instantiate a `Builders` object yourself and add builders to it using `#<<`.
300
+
301
+ ```ruby
302
+ MyBuilders = Uber::Builder::Builders.new
303
+ MyBuilders << ->(options) do
304
+ return Admin if options[:admin]
305
+ end
306
+ ```
307
+
308
+ Note that you can call `Builders#<<` multiple times per instance.
309
+
310
+ Invoke the builder using `#call`.
311
+
312
+ ```ruby
313
+ MyBuilders.call(User, {}) #=> User
314
+ MyBuilders.call(User, { admin: true }) #=> Admin
315
+ ```
316
+
317
+ Again, the first object is the context/default return value, all other arguments are passed to the builder procs.
318
+
319
+ ## Builder: Contexts
320
+
321
+ Every proc is `instance_exec`ed in the context you pass into `build!` (or `call`), allowing you to define generic, shareable builders.
322
+
323
+ ```ruby
324
+ MyBuilders = Uber::Builder::Builders.new
325
+ MyBuilders << ->(options) do
326
+ return self::Admin if options[:admin] # note the self:: !
327
+ end
328
+
329
+ class User
330
+ class Admin
331
+ end
332
+ end
333
+
334
+ class Instructor
335
+ class Admin
336
+ end
337
+ end
338
+ ```
339
+
340
+ Now, depending on the context class, the builder will return different classes.
341
+
342
+ ```ruby
343
+ MyBuilders.call(User, {}) #=> User
344
+ MyBuilders.call(User, { admin: true }) #=> User::Admin
345
+ MyBuilders.call(Instructor, {}) #=> Instructor
346
+ MyBuilders.call(Instructor, { admin: true }) #=> Instructor::Admin
347
+ ```
348
+
349
+ Don't forget the `self::` when writing generic builders, and write tests.
350
+
351
+ # License
352
+
353
+ Copyright (c) 2014 by Nick Sutterer <apotonick@gmail.com>
354
+
355
+ Uber is released under the [MIT License](http://www.opensource.org/licenses/MIT).
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rake/testtask'
5
+
6
+ desc 'Test the representable gem.'
7
+ task :default => :test
8
+
9
+ Rake::TestTask.new(:test) do |test|
10
+ test.libs << 'test'
11
+ test.test_files = FileList['test/*_test.rb']
12
+ test.verbose = true
13
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path('../lib/declarative/builder/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.authors = ["Nick Sutterer"]
5
+ gem.email = ["apotonick@gmail.com"]
6
+ gem.description = %q{Generic builder pattern.}
7
+ gem.summary = %q{Generic builder pattern.}
8
+ gem.homepage = "https://github.com/apotonick/declarative-builder"
9
+ gem.license = "MIT"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "declarative-builder"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Declarative::Builder::VERSION
17
+
18
+ gem.add_dependency "declarative-option", "< 0.2.0"
19
+
20
+ gem.add_development_dependency "rake"
21
+ gem.add_development_dependency "minitest"
22
+ end
@@ -0,0 +1 @@
1
+ require "declarative/builder"
@@ -0,0 +1,44 @@
1
+ require "declarative/option"
2
+
3
+ module Declarative
4
+ module Builder
5
+ def self.included(base)
6
+ base.extend DSL
7
+ base.extend Build
8
+ end
9
+
10
+ class Builders < Array
11
+ def call(context, *args)
12
+ each do |block|
13
+ klass = block.(context, *args) and return klass # Declarative::Option#call()
14
+ end
15
+
16
+ context
17
+ end
18
+
19
+ def <<(proc)
20
+ super Declarative::Option( proc, instance_exec: true ) # lambdas are always instance_exec'ed.
21
+ end
22
+ end
23
+
24
+ module DSL
25
+ def builders
26
+ @builders ||= Builders.new
27
+ end
28
+
29
+ def builds(proc=nil, &block)
30
+ builders << (proc || block)
31
+ end
32
+ end
33
+
34
+ module Build
35
+ # Call this from your class to compute the concrete target class.
36
+ def build!(context, *args)
37
+ builders.(context, *args)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+
44
+ # Declarative::Builder(->(options) { options[:current_user] ? Bla : Blubb })
@@ -0,0 +1,5 @@
1
+ module Declarative
2
+ module Builder
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,147 @@
1
+ require "test_helper"
2
+ require "declarative/builder"
3
+
4
+ class BuilderTest < MiniTest::Spec
5
+ Evergreen = Struct.new(:title)
6
+ Hit = Struct.new(:title)
7
+
8
+ class Song
9
+ include Declarative::Builder
10
+
11
+ builds do |options|
12
+ if options[:evergreen]
13
+ Evergreen
14
+ elsif options[:hit]
15
+ Hit
16
+ end
17
+ end
18
+
19
+ def self.build(options)
20
+ build!(self, options).new
21
+ end
22
+ end
23
+
24
+ # building class if no block matches
25
+ it { Song.build({}).must_be_instance_of Song }
26
+
27
+ it { Song.build({evergreen: true}).must_be_instance_of Evergreen }
28
+ it { Song.build({hit: true}).must_be_instance_of Hit }
29
+
30
+ # test chained builds.
31
+ class Track
32
+ include Declarative::Builder
33
+
34
+ builds do |options|
35
+ Evergreen if options[:evergreen]
36
+ end
37
+
38
+ builds do |options|
39
+ Hit if options[:hit]
40
+ end
41
+
42
+ def self.build(options)
43
+ build!(self, options).new
44
+ end
45
+ end
46
+
47
+ it { Track.build({}).must_be_instance_of Track }
48
+ it { Track.build({evergreen: true}).must_be_instance_of Evergreen }
49
+ it { Track.build({hit: true}).must_be_instance_of Hit }
50
+
51
+
52
+ # test inheritance. builder do not inherit.
53
+ class Play < Song
54
+ end
55
+
56
+ it { Play.build({}).must_be_instance_of Play }
57
+ it { Play.build({evergreen: true}).must_be_instance_of Play }
58
+ it { Play.build({hit: true}).must_be_instance_of Play }
59
+
60
+ # test return from builds
61
+ class Boomerang
62
+ include Declarative::Builder
63
+
64
+ builds ->(options) do
65
+ return Song if options[:hit]
66
+ end
67
+
68
+ def self.build(options)
69
+ build!(self, options).new
70
+ end
71
+ end
72
+
73
+ it { Boomerang.build({}).must_be_instance_of Boomerang }
74
+ it { Boomerang.build({hit: true}).must_be_instance_of Song }
75
+ end
76
+
77
+
78
+ class BuilderScopeTest < MiniTest::Spec
79
+ def self.builder_method(options)
80
+ options[:from_builder_method] and return self
81
+ end
82
+
83
+ class Hit; end
84
+
85
+ class Song
86
+ class Hit
87
+ end
88
+
89
+ include Declarative::Builder
90
+
91
+ builds :builder_method # i get called first.
92
+ builds ->(options) do # and i second.
93
+ self::Hit
94
+ end
95
+
96
+ def self.build(context, options={})
97
+ build!(context, options)
98
+ end
99
+ end
100
+
101
+ it do
102
+ Song.build(self.class).must_equal BuilderScopeTest::Hit
103
+
104
+ # this runs BuilderScopeTest::builder_method and returns self.
105
+ Song.build(self.class, from_builder_method: true).must_equal BuilderScopeTest
106
+ end
107
+
108
+ class Evergreen
109
+ class Hit
110
+ end
111
+
112
+ include Declarative::Builder
113
+
114
+ class << self
115
+ attr_writer :builders
116
+ end
117
+ self.builders = Song.builders
118
+
119
+ def self.build(context, options={})
120
+ build!(context, options)
121
+ end
122
+
123
+ def self.builder_method(options)
124
+ options[:from_builder_method] and return self
125
+ end
126
+ end
127
+
128
+ it do
129
+ # running the "copied" block in Evergreen will reference the correct @context.
130
+ Evergreen.build(Evergreen).must_equal BuilderScopeTest::Evergreen::Hit
131
+
132
+ Evergreen.build(Evergreen, from_builder_method: true).must_equal BuilderScopeTest::Evergreen
133
+ end
134
+
135
+ #---
136
+ # Builders API
137
+ # Builders#call
138
+ # Builders#<<
139
+ A = Class.new
140
+ MyBuilders = Declarative::Builder::Builders.new
141
+ MyBuilders << ->(options) do
142
+ return Song if options[:hit]
143
+ end
144
+
145
+ it { MyBuilders.call(A, {}).new.must_be_instance_of A }
146
+ it { MyBuilders.call(A, { hit: true }).new.must_be_instance_of Song }
147
+ end
@@ -0,0 +1 @@
1
+ require "minitest/autorun"
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: declarative-builder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Sutterer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: declarative-option
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "<"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "<"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Generic builder pattern.
56
+ email:
57
+ - apotonick@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".travis.yml"
64
+ - CHANGES.md
65
+ - Gemfile
66
+ - LICENSE
67
+ - README.md
68
+ - Rakefile
69
+ - declarative-builder.gemspec
70
+ - lib/declarative-builder.rb
71
+ - lib/declarative/builder.rb
72
+ - lib/declarative/builder/version.rb
73
+ - test/builder_test.rb
74
+ - test/test_helper.rb
75
+ homepage: https://github.com/apotonick/declarative-builder
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubyforge_project:
95
+ rubygems_version: 2.6.3
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Generic builder pattern.
99
+ test_files:
100
+ - test/builder_test.rb
101
+ - test/test_helper.rb