lunfardo 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2eedc2a9d1490bcc9b8b796fba86a8d1de87e748
4
+ data.tar.gz: a9dd417aaff5a9342f1f27d091c431ae18fa275f
5
+ SHA512:
6
+ metadata.gz: 5e28a670b94f828be87bbef17877df4e7f721f67ec81996120748b1e7857dbe0d8eb014ed2a5b12c941f547ae7f3dddcf46be577465abb380f7504836bc14c03
7
+ data.tar.gz: 7405789a3ded3bb148b97b268ed7ec60c582bda9fc00b6bdcbed8325035eb9d8de62e303dcf88fb53d3b3f0e89e210eabb1b6be472273cfbedb471730a58f30b
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --require spec-helper
2
+ --color
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in Lunfardo.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec', require: false
7
+ gem 'simplecov', require: false
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Martin Rubi
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,408 @@
1
+ # Lunfardo
2
+
3
+ Lunfardo is a framework to easily define simple (and not quite so simple) DSLs.
4
+ For example it can be used to define an object configuration DSL.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'lunfardo', '~> 0.1'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install lunfardo
21
+
22
+ ## Usage
23
+
24
+ Suppose you have the class
25
+
26
+ ```ruby
27
+ class SomeClass
28
+ def self.filename()
29
+ @filename
30
+ end
31
+
32
+ def self.set_filename(filename)
33
+ @filename = filename
34
+ end
35
+ end
36
+ ```
37
+
38
+ and you would like to make it configurable:
39
+
40
+ ```ruby
41
+ SomeClass.configure do
42
+ filename 'some_file.txt'
43
+ end
44
+ ```
45
+
46
+ To do that, define the DSL:
47
+
48
+ ```ruby
49
+ include 'lunfardo'
50
+
51
+ class SomeClassConfigurationDSL
52
+ include CabezaDeTermo::Lunfardo::Behaviour
53
+
54
+ on :filename do |name|
55
+ perform do |name|
56
+ context.set_filename(name)
57
+ end
58
+ end
59
+ end
60
+ ```
61
+
62
+ and then add the `configure` method to `SomeClass`:
63
+
64
+ ```ruby
65
+ class SomeClass
66
+ def self.filename()
67
+ @filename
68
+ end
69
+
70
+ def self.set_filename(filename)
71
+ @filename = filename
72
+ end
73
+
74
+ def self.configure(&block)
75
+ SomeClassConfigurationDSL.evaluate_on(self, &block)
76
+ end
77
+ end
78
+ ```
79
+
80
+ and that's it. This is the simpliest use, but it can be used for more complex DSLs too.
81
+
82
+ ## API
83
+
84
+ ### Accessing the context from the DSL
85
+
86
+ You can access the context from the DSL sending `context`:
87
+
88
+ ```ruby
89
+ include 'lunfardo'
90
+
91
+ class SomeClassConfigurationDSL
92
+ include CabezaDeTermo::Lunfardo::Behaviour
93
+
94
+ on :filename do |name|
95
+ perform do |name|
96
+ context.files << name
97
+ end
98
+ end
99
+ end
100
+
101
+ # to be used like
102
+
103
+ SomeClass.configure do
104
+ filename 'file_1.txt'
105
+ filename 'file_2.txt'
106
+ end
107
+ ```
108
+
109
+ ### Evaluating code at the begining of a configuration scope
110
+
111
+ You can evaluate code before evaluating the rest of the configuration:
112
+
113
+ ```ruby
114
+ include 'lunfardo'
115
+
116
+ class SomeClassConfigurationDSL
117
+ include CabezaDeTermo::Lunfardo::Behaviour
118
+
119
+ on :filenames do
120
+ before do
121
+ context.files = []
122
+ end
123
+
124
+ on :filename do |name|
125
+ perform do |name|
126
+ context.files << name
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ # to be used like
133
+
134
+ SomeClass.configure do
135
+ filenames do
136
+ filename 'file_1.txt'
137
+ filename 'file_2.txt'
138
+ end
139
+ end
140
+ ```
141
+
142
+ ### Evaluating code at the end of a configuration scope
143
+
144
+ You can evaluate code after evaluating the rest of the configuration:
145
+
146
+ ```ruby
147
+ include 'lunfardo'
148
+
149
+ class SomeClassConfigurationDSL
150
+ include CabezaDeTermo::Lunfardo::Behaviour
151
+
152
+ on :filenames do
153
+ attr_reader :files
154
+
155
+ before do
156
+ @files = []
157
+ end
158
+
159
+ on :filename do |name|
160
+ perform do |name|
161
+ outer.files << name
162
+ end
163
+ end
164
+
165
+ after do
166
+ context.files = @files.join(', ')
167
+ end
168
+ end
169
+ end
170
+
171
+ # to be used like
172
+
173
+ SomeClass.configure do
174
+ filenames do
175
+ filename 'file_1.txt'
176
+ filename 'file_2.txt'
177
+ end
178
+ end
179
+ ```
180
+
181
+ The `#after` scope will also return its evaluation:
182
+
183
+ ```ruby
184
+ include 'lunfardo'
185
+
186
+ class BloatedJoinDSL
187
+ include CabezaDeTermo::Lunfardo::Behaviour
188
+
189
+ on :filenames do
190
+ attr_reader :files
191
+
192
+ before do
193
+ @files = []
194
+ end
195
+
196
+ on :filename do |name|
197
+ perform do |name|
198
+ outer.files << name
199
+ end
200
+ end
201
+
202
+ after do
203
+ @files.join(', ')
204
+ end
205
+ end
206
+ end
207
+
208
+ # to be used like
209
+
210
+ files = BloatedJoinDSL.evaluate do
211
+ filenames do
212
+ filename 'file_1.txt'
213
+ filename 'file_2.txt'
214
+ end
215
+ end
216
+
217
+ files == ['file_1.txt', 'file_2.txt']
218
+ ```
219
+
220
+ ### Defining instance variables and methods in a configuration scope
221
+
222
+ In the previous example you can see that you can use instance variables and methods inside a configuration scope.
223
+
224
+ You can also define any method using `define`:
225
+
226
+ ```ruby
227
+ include 'lunfardo'
228
+
229
+ class BloatedJoinDSL
230
+ include CabezaDeTermo::Lunfardo::Behaviour
231
+
232
+ on :filenames do
233
+ attr_reader :files
234
+
235
+ define :extension do
236
+ '.txt'.freeze
237
+ end
238
+
239
+ before do
240
+ @files = []
241
+ end
242
+
243
+ on :filename do |name|
244
+ perform do |name|
245
+ outer.files << name + extension
246
+ end
247
+ end
248
+
249
+ after do
250
+ @files.join(', ')
251
+ end
252
+ end
253
+ end
254
+
255
+ # to be used like
256
+
257
+ files = BloatedJoinDSL.evaluate do
258
+ filenames do
259
+ filename 'file_1'
260
+ filename 'file_2'
261
+ end
262
+ end
263
+
264
+ files == ['file_1.txt', 'file_2.txt']
265
+ ```
266
+
267
+ ### Accessing the outer scope of a configuration scope
268
+
269
+ If you need to access the outer scope of your current scope, send `outer`
270
+
271
+ ```ruby
272
+ on :filename do |name|
273
+ perform do |name|
274
+ outer.files << name
275
+ end
276
+ end
277
+ ```
278
+
279
+ or if you need to reach an outer scope several layers above
280
+
281
+ ```ruby
282
+ on :filename do |name|
283
+ perform do |name|
284
+ outer(3).files << name
285
+ end
286
+ end
287
+ ```
288
+
289
+ ### Handling a block instead of evaluating it
290
+
291
+ Sometimes you won't want to evaluate a block but to handle it in a custom way.
292
+
293
+ In that case, you must declare in you scope definition (at the `#on` message) that you expect the `&block` parameter:
294
+
295
+ ```ruby
296
+ class ValidationsMessageLibraryDSL
297
+ include CabezaDeTermo::Lunfardo::Behaviour
298
+
299
+ dsl do
300
+ attr_reader :messages
301
+
302
+ before do
303
+ @messages = Hash[]
304
+ end
305
+
306
+ on :message_for do |validation_name, &message_block|
307
+ perform do |validation_name, &message_block|
308
+ outer.messages[validation_name] = message_block
309
+ end
310
+ end
311
+
312
+ after do
313
+ @messages
314
+ end
315
+ end
316
+ end
317
+
318
+ # to be used like
319
+
320
+ messages = ValidationsMessageLibraryDSL.evaluate do
321
+ message_for :presence do |validation|
322
+ "can not be left blank"
323
+ end
324
+
325
+ message_for :format do |validation|
326
+ "doesn't match expected format: #{validation.expected_value}"
327
+ end
328
+ end
329
+ ```
330
+
331
+ ### Defining recursive configurations
332
+
333
+ You can define a configuration using recursion like in the example:
334
+
335
+ ```ruby
336
+ class ParamsDSL
337
+ include CabezaDeTermo::Lunfardo::Behaviour
338
+
339
+ dsl do
340
+ on :params do
341
+ attr_reader :params
342
+
343
+ before do
344
+ @params = Hash[]
345
+ end
346
+
347
+ on :param do
348
+ attr_reader :name
349
+ attr_reader :params
350
+
351
+ before do |name, value = nil|
352
+ @name = name
353
+ @params = outer.params
354
+ end
355
+
356
+ perform do |name, value = nil|
357
+ @params[name] = value.nil? ? Hash[] : value
358
+ end
359
+
360
+ on :param do
361
+ attr_reader :name
362
+ attr_reader :params
363
+
364
+ before do |name, value = nil|
365
+ @name = name
366
+ @params = outer.params[outer.name]
367
+ end
368
+
369
+ perform do |name, value = nil|
370
+ @params[name] = value.nil? ? Hash[] : value
371
+ end
372
+
373
+ recursive :param, scope: self
374
+ end
375
+ end
376
+
377
+ after do
378
+ @params
379
+ end
380
+ end
381
+ end
382
+ end
383
+
384
+ # to be used like
385
+
386
+ params = ParamsDSL.evaluate do
387
+ params do
388
+ param :client do
389
+ param :name, 'Juan Salvo'
390
+ param :address do
391
+ param :city, 'Buenos Aires'
392
+ end
393
+ end
394
+ end
395
+ end
396
+
397
+ params == {:client => {:name=>"Juan Salvo ", :address=>{:city=>"Buenos Aires"}}}
398
+ ```
399
+
400
+ ## Contributing
401
+
402
+ Bug reports and pull requests are welcome on GitHub at https://github.com/cabeza-de-termo/lunfardo.
403
+
404
+
405
+ ## License
406
+
407
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
408
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,28 @@
1
+ module CabezaDeTermo
2
+ module Lunfardo
3
+ module Behaviour
4
+ def self.included(base_class)
5
+ base_class.extend ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ def dsl(&block)
10
+ @root_scope = new_root_scope(&block)
11
+ @root_scope.instance_eval(&block)
12
+ end
13
+
14
+ def new_root_scope(&block)
15
+ Lunfardo::Scope.new_scope_class(name: :root_scope, &block)
16
+ end
17
+
18
+ def evaluate(&block)
19
+ evaluate_on(nil, &block)
20
+ end
21
+
22
+ def evaluate_on(context, &block)
23
+ @root_scope.new(context, nil)._evaluate(&block)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ module CabezaDeTermo
2
+ module Lunfardo
3
+ class Error < RuntimeError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,146 @@
1
+ module CabezaDeTermo
2
+ module Lunfardo
3
+ class Scope
4
+ class << self
5
+ def new_scope_class(name:, &block)
6
+ Class.new(self) do
7
+ set_scope_name name
8
+ infere_block_as_parameter_from(block)
9
+ end
10
+ end
11
+
12
+ # Accessing
13
+
14
+ def set_scope_name(name)
15
+ @scope_name = name.to_sym
16
+ end
17
+
18
+ def scope_name()
19
+ @scope_name
20
+ end
21
+
22
+ def infere_block_as_parameter_from(block)
23
+ @block_as_parameter =
24
+ !block.nil? && !block.parameters.empty? &&
25
+ block.parameters.last[0] == :block
26
+ end
27
+
28
+ def block_as_parameter?()
29
+ @block_as_parameter
30
+ end
31
+
32
+ def scopes()
33
+ @scopes ||= Hash[]
34
+ end
35
+
36
+ # Defining the DSL
37
+
38
+ def define(method_name, &block)
39
+ define_method(method_name, &block)
40
+ end
41
+
42
+ def before(&block)
43
+ define(:before, &block)
44
+ end
45
+
46
+ def perform(*params, &block)
47
+ define(:perform, &block)
48
+ end
49
+
50
+ def on(method_name, block_param: false, &block)
51
+ scopes[method_name] = Scope.new_scope_class(name: method_name, &block)
52
+
53
+ scopes[method_name].instance_exec(&block)
54
+ end
55
+
56
+ def after(&block)
57
+ raise_error("#after method must receive a block argument") if block.nil?
58
+
59
+ define(:after, &block)
60
+ end
61
+
62
+ def recursive(scope_name, scope:)
63
+ scopes[scope_name] = scope
64
+ end
65
+
66
+ # Raising errors
67
+
68
+ def raise_error(message)
69
+ raise Lunfardo::Error.new(message)
70
+ end
71
+ end
72
+
73
+ # Initializing
74
+
75
+ def initialize(context, outer_scope)
76
+ @context = context
77
+ @outer_scope = outer_scope
78
+ end
79
+
80
+ # Accessing
81
+
82
+ def _scope_at(scope_name)
83
+ _scopes.fetch(scope_name.to_sym)
84
+ end
85
+
86
+ def _scope_name()
87
+ self.class.scope_name
88
+ end
89
+
90
+ def _block_as_parameter?()
91
+ self.class.block_as_parameter?
92
+ end
93
+
94
+ def _scopes()
95
+ self.class.scopes
96
+ end
97
+
98
+ # Asking
99
+
100
+ def _includes_scope?(scope_name)
101
+ _scopes.key?(scope_name.to_sym)
102
+ end
103
+
104
+ # Evaluating
105
+
106
+ def context()
107
+ @context
108
+ end
109
+
110
+ def outer(n = 1)
111
+ (1..n-1).inject(@outer_scope) do |outer_scope, i|
112
+ outer.outer
113
+ end
114
+ end
115
+
116
+ def _evaluate(*params, &block)
117
+ result = send(:before, *params, &block) if respond_to?(:before)
118
+
119
+ result = send(:perform, *params, &block) if respond_to?(:perform)
120
+
121
+ result = instance_exec(*params, &block) unless block.nil? || _block_as_parameter?
122
+
123
+ result = send(:after, *params, &block) if respond_to?(:after)
124
+
125
+ result
126
+ end
127
+
128
+ def method_missing(method_name, *params, &block)
129
+ return _raise_missing_scope_error(method_name) unless _includes_scope?(method_name)
130
+
131
+ _scope_at(method_name).new(@context, self)._evaluate(*params, &block)
132
+ end
133
+
134
+ # Raising errors
135
+
136
+ def _raise_missing_scope_error(method_name)
137
+ path = (_scope_path << method_name).join('.')
138
+ self.class.raise_error("The scope '#{path}' is not defined")
139
+ end
140
+
141
+ def _scope_path
142
+ outer.nil? ? [_scope_name] : outer._scope_path << _scope_name
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,5 @@
1
+ module CabezaDeTermo
2
+ module Lunfardo
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
data/lib/lunfardo.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'lunfardo/version'
2
+
3
+ require 'lunfardo/error'
4
+ require 'lunfardo/scope'
5
+ require 'lunfardo/behaviour'
data/lunfardo.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'lunfardo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "lunfardo"
8
+ spec.version = CabezaDeTermo::Lunfardo::VERSION
9
+ spec.authors = ["Martin Rubi"]
10
+ spec.email = ["martinrubi@gmail.com"]
11
+
12
+ spec.summary = %q{A framework to easily define simple DSLs}
13
+ spec.homepage = "https://github.com/cabeza-de-termo/lunfardo"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.11"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lunfardo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Martin Rubi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-04-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description:
42
+ email:
43
+ - martinrubi@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".rspec"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - lib/lunfardo.rb
55
+ - lib/lunfardo/behaviour.rb
56
+ - lib/lunfardo/error.rb
57
+ - lib/lunfardo/scope.rb
58
+ - lib/lunfardo/version.rb
59
+ - lunfardo.gemspec
60
+ homepage: https://github.com/cabeza-de-termo/lunfardo
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.6.3
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: A framework to easily define simple DSLs
84
+ test_files: []