dsl_factory 0.1.0
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 +7 -0
- data/.gitignore +8 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +38 -0
- data/LICENSE.txt +21 -0
- data/README.md +264 -0
- data/Rakefile +12 -0
- data/dsl_factory.gemspec +32 -0
- data/lib/dsl_factory/generator.rb +120 -0
- data/lib/dsl_factory/version.rb +5 -0
- data/lib/dsl_factory.rb +17 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9110073c0a21c89089537a09c4f21394b30fffa76aeaec2291c9918d58051479
|
4
|
+
data.tar.gz: 06d23515ae9d3a03da213d7e6642ffe19cb7deba898e05526af11131070e644b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 37b94876fd1631ab2247da8688cf5d8ded60282bcd204bec006a3017d7e843015b926aceb4a9237a0c85dc9fdfaa4641a3256709e23950dae1f4fc5103f926f3
|
7
|
+
data.tar.gz: 2844d3bd1abd55906099d5a309276a8dfd180e10a7bfe6b48422c1a931f26cb79cdf8ac1b593e355e4851cfe809f6288463f183da8f029cf072b8aad0f7c1c16
|
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
dsl_factory (0.1.0)
|
5
|
+
activesupport (>= 5.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (7.0.2.2)
|
11
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
12
|
+
i18n (>= 1.6, < 2)
|
13
|
+
minitest (>= 5.1)
|
14
|
+
tzinfo (~> 2.0)
|
15
|
+
coderay (1.1.3)
|
16
|
+
concurrent-ruby (1.1.9)
|
17
|
+
i18n (1.10.0)
|
18
|
+
concurrent-ruby (~> 1.0)
|
19
|
+
method_source (1.0.0)
|
20
|
+
minitest (5.15.0)
|
21
|
+
pry (0.14.1)
|
22
|
+
coderay (~> 1.1)
|
23
|
+
method_source (~> 1.0)
|
24
|
+
rake (13.0.6)
|
25
|
+
tzinfo (2.0.4)
|
26
|
+
concurrent-ruby (~> 1.0)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
dsl_factory!
|
33
|
+
minitest (~> 5.0)
|
34
|
+
pry
|
35
|
+
rake (~> 13.0)
|
36
|
+
|
37
|
+
BUNDLED WITH
|
38
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Tom Rothe
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,264 @@
|
|
1
|
+
# DSL Factory
|
2
|
+
|
3
|
+
A small DSL to generate DSLs.
|
4
|
+
Define DSLs quickly and avoid the [boilerplate to write getters and setters](https://gist.github.com/motine/28da503ba0075e9d64d3f3b1faab9014). Oh, and it does validation too.
|
5
|
+
|
6
|
+
## Example
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
# add this to your Gemfile: gem 'dsl_factory'
|
10
|
+
|
11
|
+
# define the DSL
|
12
|
+
LakeDsl = DslFactory.define_dsl do
|
13
|
+
string :lake_name
|
14
|
+
numeric :max_depth
|
15
|
+
array :fishes, String
|
16
|
+
end
|
17
|
+
|
18
|
+
# use it in any class
|
19
|
+
class LakeSuperior
|
20
|
+
extend LakeDsl
|
21
|
+
|
22
|
+
lake_name 'Lake Superior'
|
23
|
+
max_depth 406
|
24
|
+
fish 'trout'
|
25
|
+
fish 'northern pike'
|
26
|
+
end
|
27
|
+
|
28
|
+
# and you can access the values
|
29
|
+
LakeSuperior.lake_name # => "Lake Superior"
|
30
|
+
LakeSuperior.fishes # => ["trout", "northern pike"]
|
31
|
+
```
|
32
|
+
|
33
|
+
This gem came about during my time at [Netskin GmbH](https://www.netskin.com). Check it out, we do great (Rails) work there.
|
34
|
+
|
35
|
+
# Usage
|
36
|
+
|
37
|
+
## Definition
|
38
|
+
|
39
|
+
**Basic Types**
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
# the following data types are available
|
43
|
+
# if a value is given which does not fit the type a `DslFactory::ValidationError` is raised
|
44
|
+
LakeDsl = DslFactory.define_dsl do
|
45
|
+
string :lake_name
|
46
|
+
symbol :group
|
47
|
+
numeric :max_depth
|
48
|
+
boolean :protected_habitat
|
49
|
+
callable :current_temperature_handler
|
50
|
+
any :continent # does not validate the given contents later
|
51
|
+
end
|
52
|
+
|
53
|
+
# now we can use the definition
|
54
|
+
class LakeSuperior
|
55
|
+
extend LakeDsl
|
56
|
+
|
57
|
+
lake_name 'Lake Superior'
|
58
|
+
group :great_lakes
|
59
|
+
max_depth 406
|
60
|
+
protected_habitat true
|
61
|
+
current_temperature_handler ->() { self.temperature = FetchService.receive_temperature } # the proc is only saved, DslFactory will not call it
|
62
|
+
continent Continent::NorthAmerica
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
**Arrays**
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
BookDsl = DslFactory.define_dsl do
|
70
|
+
array :authors # we must use the plural!
|
71
|
+
array :publishers, String # validates that the item is of the given (Ruby) class
|
72
|
+
# see below for nested DSLs
|
73
|
+
end
|
74
|
+
|
75
|
+
class SuperBook
|
76
|
+
extend BookDsl
|
77
|
+
authors ['Manfred', 'Dieter'] # we can use the plural form to set the whole array
|
78
|
+
author 'Heinz' # or the singular form to add an item
|
79
|
+
|
80
|
+
publisher 'abc'
|
81
|
+
end
|
82
|
+
|
83
|
+
SuperBook.authors # => ['Manfred', 'Dieter', 'Heinz']
|
84
|
+
SuperBook.publishers # => ['abc']
|
85
|
+
```
|
86
|
+
|
87
|
+
**Hashes**
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
GeographyDsl = DslFactory.define_dsl do
|
91
|
+
hash :capital_for_countries # we must use the plural!
|
92
|
+
hash :country_sizes, String, Numeric # validate key and value (key must be of String class, value of Symbol)
|
93
|
+
# see below for nested DSLs
|
94
|
+
end
|
95
|
+
|
96
|
+
class World
|
97
|
+
extend GeographyDsl
|
98
|
+
capital_for_country 'Berlin', 'Germany' # here we use the signular
|
99
|
+
capital_for_country 'Copenhagen', 'Denmark'
|
100
|
+
|
101
|
+
country_size 'Germany', 357_022
|
102
|
+
end
|
103
|
+
|
104
|
+
SmartyPants.capital_for_countries # => { 'Berlin' => 'Germany', 'Copenhagen' => 'Denmark'}
|
105
|
+
SmartyPants.country_sizes # => { 'Germany' => 357022 }
|
106
|
+
```
|
107
|
+
|
108
|
+
**Nested DSLs**
|
109
|
+
```ruby
|
110
|
+
PersonDsl = DslFactory.define_dsl do
|
111
|
+
array :parents do
|
112
|
+
string :name
|
113
|
+
numeric :age
|
114
|
+
end
|
115
|
+
|
116
|
+
hash :citizenships, String do # validate key as String
|
117
|
+
symbol :status
|
118
|
+
any :expiry
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class Sabine
|
123
|
+
extend PersonDsl
|
124
|
+
# note that nested DSLs can only be used via the singular form
|
125
|
+
|
126
|
+
parent do
|
127
|
+
name 'Karla'
|
128
|
+
age 88
|
129
|
+
end
|
130
|
+
|
131
|
+
citizenship 'Germany' do
|
132
|
+
status :revoked
|
133
|
+
expiry Time.new(2000)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
Sabine.parents.first.name # => 'Karla'
|
138
|
+
Sabine.citizenships['Germany'].status # => :revoked
|
139
|
+
```
|
140
|
+
|
141
|
+
**Callbacks**
|
142
|
+
|
143
|
+
Sometimes we might want to do something when the DSL method is called.
|
144
|
+
This can be achived via callbacks.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
ButtonDsl = DslFactory.define_dsl do
|
148
|
+
# all types outlined above support callbacks
|
149
|
+
any :trigger, callback: ->(value) { puts "#{value} was triggered" }
|
150
|
+
any :snicker, callback: ->(value) { arg1, arg2 = value; puts "snicker: #{arg1} & #{arg2}" } # we can pass arguments via the value
|
151
|
+
any :clicker, callback: ->(value) { self.do_the_click }
|
152
|
+
|
153
|
+
# for arrays the callback always receives an array (even if it was used in singular form)
|
154
|
+
# for hashes the callback receives two arguments: key, value
|
155
|
+
end
|
156
|
+
|
157
|
+
class Sabine
|
158
|
+
extend ButtonDsl
|
159
|
+
# if we want to call a method of the class, we need to define it before the first usage of the DSL method
|
160
|
+
def self.do_the_click
|
161
|
+
puts 'Click!'
|
162
|
+
end
|
163
|
+
|
164
|
+
trigger 'abc' # -> abc was triggered
|
165
|
+
snicker ['haha', 'hihi'] # -> snicker: haha & hihi
|
166
|
+
clicker nil # make sure to alway pass a value, -> Click!
|
167
|
+
end
|
168
|
+
```
|
169
|
+
|
170
|
+
## Use of the definition
|
171
|
+
|
172
|
+
Usually we **extend a class** like so:
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
class LakeSuperior
|
176
|
+
extend LakeDsl
|
177
|
+
lake_name 'Lake Superior'
|
178
|
+
end
|
179
|
+
|
180
|
+
LakeSuperior.lake_name # => 'Lake Superior'
|
181
|
+
```
|
182
|
+
|
183
|
+
However we can also the **DSL in a variable**:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
@config = Module.new.extend(LakeDsl)
|
187
|
+
@config.lake_name 'Müritz'
|
188
|
+
@config.lake_name # => 'Müritz'
|
189
|
+
|
190
|
+
# or with the configuration pattern
|
191
|
+
@config = Module.new.extend(LakeDsl).tap do |c|
|
192
|
+
c.lake_name 'Summter See'
|
193
|
+
end
|
194
|
+
@config.lake_name # => 'Summter See'
|
195
|
+
|
196
|
+
# or even without any prefix
|
197
|
+
@config = Module.new.extend(LakeDsl)
|
198
|
+
@config.instance_exec do
|
199
|
+
lake_name 'Mühlenbecker See'
|
200
|
+
end
|
201
|
+
@config.lake_name # => 'Mühlenbecker See'
|
202
|
+
|
203
|
+
```
|
204
|
+
|
205
|
+
# Development
|
206
|
+
|
207
|
+
```bash
|
208
|
+
docker run --rm -ti -v (pwd):/app -w /app ruby:2.7 bash
|
209
|
+
bundle install
|
210
|
+
rake test # run the tests
|
211
|
+
pry # require_relative 'lib/dsl_factory.rb'
|
212
|
+
|
213
|
+
# to release a new version, update the version number in `version.rb`, then run
|
214
|
+
bundle exec rake release
|
215
|
+
```
|
216
|
+
|
217
|
+
## License
|
218
|
+
|
219
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
220
|
+
|
221
|
+
# Alternatives
|
222
|
+
|
223
|
+
_Download counts are from 01.03.2022._
|
224
|
+
|
225
|
+
- [dslh](https://github.com/kumogata/dslh) (178.000 downloads)
|
226
|
+
Allows to define a Hash as a DSL.
|
227
|
+
- [dsl_maker](https://rubygems.org/gems/dsl_maker) (80.700 downloads)
|
228
|
+
allows defining DSLs with structs.
|
229
|
+
- [genki-dsl_accessor](https://rubygems.org/gems/genki-dsl_accessor) (5.600 downloads)
|
230
|
+
allows defining hybrid accessors for class.
|
231
|
+
- [configuration_dsl](https://rubygems.org/gems/configuration_dsl) (4.100 downloads)
|
232
|
+
nice way to define configuration DSLs. quite outdated.
|
233
|
+
- [blockenspiel](https://github.com/dazuma/blockenspiel) (2.869.000 downloads)
|
234
|
+
allows to nicely define configuration blocks. does not facilitate assigning variables.
|
235
|
+
- [dsl_accessors](https://rubygems.org/gems/dsl_accessors) (3.700 downloads)
|
236
|
+
provides helpers to facilitate variable setting via DSL. exactly what we need, but unfortunately quite outdated.
|
237
|
+
- [open_dsl](https://rubygems.org/gems/open_dsl) (17.000 downloads)
|
238
|
+
dynamically defines OpenStructs to collect data for DSLs. really nice idea and quite close to what we need. unfortunately a little outdated.
|
239
|
+
- [alki-dsl](https://rubygems.org/gems/alki-dsl) (12.900 downloads)
|
240
|
+
allows to define DSL methods nicely. does not assist in variable getting/setting.
|
241
|
+
|
242
|
+
**Honerable Mentions**
|
243
|
+
|
244
|
+
- [cleanroom](https://rubygems.org/gems/cleanroom) (5.560.000 downloads)
|
245
|
+
allows to safely define DSLs. does not really facilitate the data assignments.
|
246
|
+
- [dsl_block](https://rubygems.org/gems/dsl_block) 4.900 downloads)
|
247
|
+
allows you to use classes to define blocks with commands. does not really facilitate setting/getting variables.
|
248
|
+
- [declarative](https://rubygems.org/gems/declarative) (110.990.000 downloads)
|
249
|
+
define declarative schemas.
|
250
|
+
- [dslkit](https://rubygems.org/gems/dslkit) (54.500 downloads)
|
251
|
+
allows defining DSL to be read from files (maybe). documentation hard to find. outdated.
|
252
|
+
- [dsltasks](https://rubygems.org/gems/dsltasks) (4.100 downloads)
|
253
|
+
allows to define hierarchical DSLs. does not facilitate variable assignments.
|
254
|
+
- [opendsl](https://rubygems.org/gems/opendsl) (8.800 downloads)
|
255
|
+
allows to simply define DSLs. does not help assigning variables. outdated.
|
256
|
+
|
257
|
+
**Not Applicable**
|
258
|
+
|
259
|
+
- [dsl](https://rubygems.org/gems/dsl) defines delegators. i am not sure when this is useful.
|
260
|
+
- [dsl_eval](https://rubygems.org/gems/dsl_eval) only defines an alias for instance_eval.
|
261
|
+
- [instant_dsl](https://rubygems.org/gems/instant_dsl) could not find repo/docs. quite outdated.
|
262
|
+
- [dsl_companion](https://rubygems.org/gems/dsl_companion) no documentation.
|
263
|
+
- [def_dsl](https://rubygems.org/gems/def_dsl) no documentation.
|
264
|
+
- [dslr](https://rubygems.org/gems/dslr) no documentation.
|
data/Rakefile
ADDED
data/dsl_factory.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/dsl_factory/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "dsl_factory"
|
7
|
+
spec.version = DslFactory::VERSION
|
8
|
+
spec.authors = ["Tom Rothe"]
|
9
|
+
spec.email = ["info@tomrothe.de"]
|
10
|
+
|
11
|
+
spec.summary = "A small DSL to generate DSLs"
|
12
|
+
spec.description = "Define DSLs quickly and avoid the boilerplate write getters and setters. Oh, and it does validation too."
|
13
|
+
spec.homepage = "https://github.com/motine/dsl_factory"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
16
|
+
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
18
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
19
|
+
spec.metadata["changelog_uri"] = 'https://github.com/motine/dsl_factory/blob/master/CHANGELOG.md'
|
20
|
+
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
25
|
+
end
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_dependency "activesupport", ">= 5.0"
|
29
|
+
|
30
|
+
# For more information and examples about making a new gem, checkout our
|
31
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
32
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
class DslFactory::Generator
|
2
|
+
attr_reader :definition
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@definition = Module.new do
|
6
|
+
def get_dsl_value(name)
|
7
|
+
@dsl_values ||= {}
|
8
|
+
@dsl_values[name]
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_dsl_value(name, value, &block)
|
12
|
+
@dsl_values ||= {}
|
13
|
+
@dsl_values[name] = value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# for more information & examples, please see README.md
|
19
|
+
#
|
20
|
+
# if a `callback` is given it will be called when the extended class uses this attribute (after setting the value).
|
21
|
+
# the callback block will be called with the value as first parameter (if you need multiple parameter pass a list).
|
22
|
+
# for examples, please see `README.md``.
|
23
|
+
#
|
24
|
+
# if a validation is given the block will be called with the value as first parameter.
|
25
|
+
# if the block returns false a `DslFactory::ValidationError` will be raised.
|
26
|
+
def any(name, callback: nil, validation: nil)
|
27
|
+
@definition.define_method(name) do |value = :not_given, &block|
|
28
|
+
return get_dsl_value(name) if value == :not_given
|
29
|
+
raise DslFactory::ValidationError, "#{name} is not valid" if validation && !validation.call(value)
|
30
|
+
set_dsl_value(name, value)
|
31
|
+
self.instance_exec(value, &callback) if callback
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# for more information & examples, please see README.md
|
36
|
+
def string(name, **kwargs)
|
37
|
+
any(name, validation: ->(value) { value.is_a?(String) }, **kwargs)
|
38
|
+
end
|
39
|
+
|
40
|
+
# for more information & examples, please see README.md
|
41
|
+
def symbol(name, **kwargs)
|
42
|
+
any(name, validation: ->(value) { value.is_a?(Symbol) }, **kwargs)
|
43
|
+
end
|
44
|
+
|
45
|
+
# for more information & examples, please see README.md
|
46
|
+
def numeric(name, **kwargs)
|
47
|
+
any(name, validation: ->(value) { value.is_a?(Numeric) }, **kwargs)
|
48
|
+
end
|
49
|
+
|
50
|
+
# for more information & examples, please see README.md
|
51
|
+
def boolean(name, **kwargs)
|
52
|
+
any(name, validation: ->(value) { value.is_a?(TrueClass) || value.is_a?(FalseClass) }, **kwargs)
|
53
|
+
end
|
54
|
+
|
55
|
+
# for more information & examples, please see README.md
|
56
|
+
def callable(name, **kwargs)
|
57
|
+
any(name, validation: ->(value) { value.respond_to?(:call) }, **kwargs)
|
58
|
+
end
|
59
|
+
|
60
|
+
# for more information & examples, please see README.md
|
61
|
+
def array(plural_name, item_class=nil, callback: nil, &definition_block)
|
62
|
+
singular_name = build_singular_name!(plural_name)
|
63
|
+
raise DslFactory::DefinitionError, "#{plural_name} item_class can not given at the same time as a block" if item_class && definition_block
|
64
|
+
|
65
|
+
any(plural_name, callback: callback, validation: ->(value) { value.is_a?(Array) && (item_class.nil? || value.all? { |item| item.is_a?(item_class) }) })
|
66
|
+
|
67
|
+
@definition.define_method(singular_name) do |value = :not_given, &block|
|
68
|
+
old_value = get_dsl_value(plural_name)
|
69
|
+
return old_value if value == :not_given && !block
|
70
|
+
|
71
|
+
if block # we deal with a sub-DSL
|
72
|
+
value_object = Class.new
|
73
|
+
value_object.extend(DslFactory.define_dsl(&definition_block))
|
74
|
+
value_object.instance_eval(&block)
|
75
|
+
value = value_object
|
76
|
+
end
|
77
|
+
raise DslFactory::ValidationError, "#{singular_name} is not valid" if item_class && !value.is_a?(item_class)
|
78
|
+
new_value = old_value || []
|
79
|
+
new_value << value
|
80
|
+
set_dsl_value(plural_name, new_value)
|
81
|
+
self.instance_exec([value], &callback) if callback # wrap the item in a list
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# for more information & examples, please see README.md
|
86
|
+
def hash(plural_name, key_class=nil, value_class=nil, callback: nil, &definition_block)
|
87
|
+
singular_name = build_singular_name!(plural_name)
|
88
|
+
raise DslFactory::DefinitionError, "#{plural_name} value_class can not given at the same time as a block" if value_class && definition_block
|
89
|
+
|
90
|
+
@definition.define_method(plural_name) do |value = nil|
|
91
|
+
raise DslFactory::DefinitionError, "hashes do not support setting values with the plural form, please use #{singular_name} to define items" if value
|
92
|
+
get_dsl_value(plural_name)
|
93
|
+
end
|
94
|
+
|
95
|
+
@definition.define_method(singular_name) do |key, value = nil, &block|
|
96
|
+
raise DslFactory::ValidationError, "value can not be given at the same time as a block for a hash" if value && block
|
97
|
+
|
98
|
+
if block # we deal with a sub-DSL
|
99
|
+
value_object = Class.new
|
100
|
+
value_object.extend(DslFactory.define_dsl(&definition_block))
|
101
|
+
value_object.instance_eval(&block)
|
102
|
+
value = value_object
|
103
|
+
end
|
104
|
+
raise DslFactory::ValidationError, "#{singular_name}'s key is not a #{key_class}" if key_class && !key.is_a?(key_class)
|
105
|
+
raise DslFactory::ValidationError, "#{singular_name}'s value is not a #{value_class}" if value_class && !value.is_a?(value_class)
|
106
|
+
new_value = get_dsl_value(plural_name) || {}
|
107
|
+
new_value[key] = value
|
108
|
+
set_dsl_value(plural_name, new_value)
|
109
|
+
self.instance_exec(key, value, &callback) if callback
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
|
115
|
+
def build_singular_name!(plural_name)
|
116
|
+
singular_name = ActiveSupport::Inflector.singularize(plural_name).to_sym
|
117
|
+
raise DslFactory::DefinitionError, "can not singularize #{plural_name}, please make sure to provide the plural form and that ActiveSupport::Inflector can singularize" if singular_name == plural_name
|
118
|
+
singular_name
|
119
|
+
end
|
120
|
+
end
|
data/lib/dsl_factory.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "dsl_factory/version"
|
2
|
+
require_relative "dsl_factory/generator"
|
3
|
+
|
4
|
+
require 'active_support/inflector'
|
5
|
+
|
6
|
+
module DslFactory
|
7
|
+
class DefinitionError < StandardError; end
|
8
|
+
class ValidationError < StandardError; end
|
9
|
+
|
10
|
+
def define_dsl(&block)
|
11
|
+
generator = DslFactory::Generator.new
|
12
|
+
generator.instance_eval(&block)
|
13
|
+
return generator.definition
|
14
|
+
end
|
15
|
+
|
16
|
+
module_function :define_dsl
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dsl_factory
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Rothe
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-03-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
description: Define DSLs quickly and avoid the boilerplate write getters and setters.
|
28
|
+
Oh, and it does validation too.
|
29
|
+
email:
|
30
|
+
- info@tomrothe.de
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- ".gitignore"
|
36
|
+
- CHANGELOG.md
|
37
|
+
- Gemfile
|
38
|
+
- Gemfile.lock
|
39
|
+
- LICENSE.txt
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- dsl_factory.gemspec
|
43
|
+
- lib/dsl_factory.rb
|
44
|
+
- lib/dsl_factory/generator.rb
|
45
|
+
- lib/dsl_factory/version.rb
|
46
|
+
homepage: https://github.com/motine/dsl_factory
|
47
|
+
licenses:
|
48
|
+
- MIT
|
49
|
+
metadata:
|
50
|
+
homepage_uri: https://github.com/motine/dsl_factory
|
51
|
+
source_code_uri: https://github.com/motine/dsl_factory
|
52
|
+
changelog_uri: https://github.com/motine/dsl_factory/blob/master/CHANGELOG.md
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.5.0
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubygems_version: 3.2.15
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: A small DSL to generate DSLs
|
72
|
+
test_files: []
|