docile 1.1.5 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4df64881f4215d866d038e00f55d5b1ca2a45cb2
4
- data.tar.gz: 26a8de33f8bf657873666a2754bf09f605334e9d
2
+ SHA256:
3
+ metadata.gz: d7dae1c209d04576cf98b4d080a44f659eaf7ed41dccf942bec2ff95c44db5e8
4
+ data.tar.gz: 3209a17a2588ba8c873fddd9b5c219ea6e7b73d3fcb3575997b347480abc622a
5
5
  SHA512:
6
- metadata.gz: 75669d4f578d70ba4c8be7dd6c19676041506e0f9408ba26ea0b1ad7931cf59636ccf36a4363ae709a1476464caba5ca1c429b07233dda7d797d5007f3c0af10
7
- data.tar.gz: e0e0e7364436ce92f4ab5efc52c2782e5da098d302652ca8698d74bcccc27c695b269c04ced3d224eec1a5b3833ba91c44232a2cc4c55fe061504246a715ef49
6
+ metadata.gz: 714bafc9545d9ccc0037fdb5c9fa8ff016a6f5f3df3c758ca16b225e371758cb3b00f2afb85763eef87576686192b129a53f1b21e2ee4f34089bdd6ad58ffd54
7
+ data.tar.gz: bb82951b411c16cd720bdf9c359f43f4e38c9fa71815d336ee45bc240eeb7d9e2de1922dacdaad84ebfffdfdb54a25bf023302a0fe487a1507d102d9e59dfcae
data/.gitignore CHANGED
@@ -6,4 +6,6 @@ pkg
6
6
  doc
7
7
  .yardoc
8
8
  coverage
9
- vendor
9
+ vendor
10
+ .ruby-gemset
11
+ .ruby-version
@@ -1,21 +1,33 @@
1
1
  language: ruby
2
- cache: bundler
2
+
3
+ # Apparently sudo is required to test on Rubinius and JRuby-head
4
+ sudo: required
5
+
6
+ # See https://docs.travis-ci.com/user/languages/ruby/#Rubinius
7
+ dist: trusty
8
+
3
9
  rvm:
10
+ # MRI
4
11
  - ruby-head
5
- - 2.1.2
6
- - 2.1.1
7
- - 2.1.0
8
- - 2.0.0
12
+ - 2.6
13
+ - 2.5
14
+ - 2.4
15
+ - 2.3
16
+ - 2.2
17
+ - 2.1
9
18
  - 1.9.3
10
- - 1.9.2
11
19
  - 1.8.7
12
20
  - ree
21
+ # JRuby
13
22
  - jruby-head
14
- - jruby-19mode
15
- - jruby-18mode
16
- - rbx-2
23
+ - jruby-9.1
24
+ - jruby-9.0
25
+ # Rubinius
26
+ - rubinius-3
27
+
17
28
  matrix:
18
29
  allow_failures:
19
30
  - rvm: ruby-head
20
31
  - rvm: jruby-head
32
+ - rvm: rubinius-3
21
33
  fast_finish: true
data/Gemfile CHANGED
@@ -1,4 +1,12 @@
1
- source 'https://rubygems.org'
1
+ require File.expand_path("on_what", File.dirname(__FILE__))
2
+ source "https://rubygems.org"
3
+
4
+ # Travis-only dependencies go here
5
+ if on_travis? && !on_1_8? && !on_rubinius?
6
+ group :test do
7
+ gem "codecov", ">= 0.0.9", :require => false
8
+ end
9
+ end
2
10
 
3
11
  # Specify gem's dependencies in docile.gemspec
4
12
  gemspec
data/HISTORY.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # HISTORY
2
2
 
3
+ ## [Unreleased changes](http://github.com/ms-ati/docile/compare/v1.3.2...master)
4
+
5
+ - ...
6
+
7
+ ## [v1.3.2 (Jun 12, 2019)](http://github.com/ms-ati/docile/compare/v1.3.1...v1.3.2)
8
+
9
+ - Special thanks (again!) to Taichi Ishitani (@taichi-ishitani):
10
+ - Fix for DSL object is replaced when #dsl_eval is nested (#33, PR #34)
11
+
12
+ ## [v1.3.1 (May 24, 2018)](http://github.com/ms-ati/docile/compare/v1.3.0...v1.3.1)
13
+
14
+ - Special thanks to Taichi Ishitani (@taichi-ishitani):
15
+ - Fix for when DSL object is also the block's context (#30)
16
+
17
+ ## [v1.3.0 (Feb 7, 2018)](http://github.com/ms-ati/docile/compare/v1.2.0...v1.3.0)
18
+
19
+ - Allow helper methods in block's context to call DSL methods
20
+ - Add SemVer release policy explicitly
21
+ - Standardize on double-quoted string literals
22
+ - Workaround some more Travis CI shenanigans
23
+
24
+ ## [v1.2.0 (Jan 11, 2018)](http://github.com/ms-ati/docile/compare/v1.1.5...v1.2.0)
25
+
26
+ - Special thanks to Christina Koller (@cmkoller)
27
+ - add DSL evaluation returning *return value of the block* (see `.dsl_eval_with_block_return`)
28
+ - add an example to README
29
+ - keep travis builds passing on old ruby versions
30
+
3
31
  ## [v1.1.5 (Jun 15, 2014)](http://github.com/ms-ati/docile/compare/v1.1.4...v1.1.5)
4
32
 
5
33
  - as much as possible, loosen version restrictions on development dependencies
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2012-2014 Marc Siegel
3
+ Copyright (c) 2012-2019 Marc Siegel
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,11 +1,15 @@
1
1
  # Docile
2
- [![Gem Version](https://badge.fury.io/rb/docile.png)](http://badge.fury.io/rb/docile)
3
- [![Build Status](https://travis-ci.org/ms-ati/docile.png)](https://travis-ci.org/ms-ati/docile)
4
- [![Dependency Status](https://gemnasium.com/ms-ati/docile.png)](https://gemnasium.com/ms-ati/docile)
5
- [![Code Climate](https://codeclimate.com/github/ms-ati/docile.png)](https://codeclimate.com/github/ms-ati/docile)
6
- [![Coverage Status](https://coveralls.io/repos/ms-ati/docile/badge.png)](https://coveralls.io/r/ms-ati/docile)
7
- [![Inline docs](http://inch-ci.org/github/ms-ati/docile.png)](http://inch-ci.org/github/ms-ati/docile)
8
- [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/ms-ati/docile/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/docile.svg)](https://rubygems.org/gems/docile)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/docile.svg)](https://rubygems.org/gems/docile)
5
+
6
+ [![Join the chat at https://gitter.im/ms-ati/docile](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ms-ati/docile?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](http://rubydoc.info/github/ms-ati/docile)
8
+ [![Docs Coverage](http://inch-ci.org/github/ms-ati/docile.png)](http://inch-ci.org/github/ms-ati/docile)
9
+
10
+ [![Build Status](https://img.shields.io/travis/ms-ati/docile/master.svg)](https://travis-ci.org/ms-ati/docile)
11
+ [![Code Coverage](https://img.shields.io/codecov/c/github/ms-ati/docile.svg)](https://codecov.io/github/ms-ati/docile)
12
+ [![Maintainability](https://api.codeclimate.com/v1/badges/79ca631bc123f7b83b34/maintainability)](https://codeclimate.com/github/ms-ati/docile/maintainability)
9
13
 
10
14
  Ruby makes it possible to create very expressive **Domain Specific
11
15
  Languages**, or **DSL**'s for short. However, it requires some deep knowledge and
@@ -20,7 +24,7 @@ coding a bit more docile...
20
24
 
21
25
  ## Usage
22
26
 
23
- ### Basic
27
+ ### Basic: Ruby [Array](http://ruby-doc.org/core-2.2.2/Array.html) as DSL
24
28
 
25
29
  Let's say that we want to make a DSL for modifying Array objects.
26
30
  Wouldn't it be great if we could just treat the methods of Array as a DSL?
@@ -45,7 +49,80 @@ end
45
49
 
46
50
  Easy!
47
51
 
48
- ### Advanced
52
+ ### Next step: Allow helper methods to call DSL methods
53
+
54
+ What if, in our use of the methods of Array as a DSL, we want to extract
55
+ helper methods which in turn call DSL methods?
56
+
57
+ ```ruby
58
+ def pop_sum_and_push(n)
59
+ sum = 0
60
+ n.times { sum += pop }
61
+ push sum
62
+ end
63
+
64
+ Docile.dsl_eval([]) do
65
+ push 5
66
+ push 6
67
+ pop_sum_and_push(2)
68
+ end
69
+ #=> [11]
70
+ ```
71
+
72
+ Without Docile, you may find this sort of code extraction to be more
73
+ challenging.
74
+
75
+ ### Wait! Can't I do that with just `instance_eval` or `instance_exec`?
76
+
77
+ Good question!
78
+
79
+ In short: **No**.
80
+
81
+ Not if you want the code in the block to be able to refer to anything
82
+ the block would normally have access to from the surrounding context.
83
+
84
+ Let's be very specific. Docile internally uses `instance_exec` (see [execution.rb#26](lib/docile/execution.rb#L26)), adding a small layer to support referencing *local variables*, *instance variables*, and *methods* from the _block's context_ **or** the target _object's context_, interchangeably. This is "**the hard part**", where most folks making a DSL in Ruby throw up their hands.
85
+
86
+ For example:
87
+
88
+ ```ruby
89
+ class ContextOfBlock
90
+ def example_of_contexts
91
+ @block_instance_var = 1
92
+ block_local_var = 2
93
+
94
+ with_array do
95
+ push @block_instance_var
96
+ push block_local_var
97
+ pop
98
+ push block_sees_this_method
99
+ end
100
+ end
101
+
102
+ def block_sees_this_method
103
+ 3
104
+ end
105
+
106
+ def with_array(&block)
107
+ {
108
+ docile: Docile.dsl_eval([], &block),
109
+ instance_eval: ([].instance_eval(&block) rescue $!),
110
+ instance_exec: ([].instance_exec(&block) rescue $!)
111
+ }
112
+ end
113
+ end
114
+
115
+ ContextOfBlock.new.example_of_contexts
116
+ #=> {
117
+ :docile=>[1, 3],
118
+ :instance_eval=>#<NameError: undefined local variable or method `block_sees_this_method' for [nil]:Array>,
119
+ :instance_exec=>#<NameError: undefined local variable or method `block_sees_this_method' for [nil]:Array>
120
+ }
121
+ ```
122
+
123
+ As you can see, it won't be possible to call methods or access instance variables defined in the block's context using just the raw `instance_eval` or `instance_exec` methods. And in fact, Docile goes further, making it easy to maintain this support even in multi-layered DSLs.
124
+
125
+ ### Build a Pizza
49
126
 
50
127
  Mutating (changing) an Array instance is fine, but what usually makes a good DSL is a [Builder Pattern][2].
51
128
 
@@ -83,7 +160,7 @@ PizzaBuilder.new.cheese.pepperoni.sauce(:extra).build
83
160
 
84
161
  Then implement your DSL like this:
85
162
 
86
- ``` ruby
163
+ ```ruby
87
164
  def pizza(&block)
88
165
  Docile.dsl_eval(PizzaBuilder.new, &block).build
89
166
  end
@@ -93,6 +170,38 @@ It's just that easy!
93
170
 
94
171
  [2]: http://stackoverflow.com/questions/328496/when-would-you-use-the-builder-pattern "Builder Pattern"
95
172
 
173
+ ### Multi-level and Recursive DSLs
174
+
175
+ Docile is a very easy way to write a multi-level DSL in Ruby, even for
176
+ a [recursive data structure such as a tree][4]:
177
+
178
+ ```ruby
179
+ Person = Struct.new(:name, :mother, :father)
180
+
181
+ person {
182
+ name 'John Smith'
183
+ mother {
184
+ name 'Mary Smith'
185
+ }
186
+ father {
187
+ name 'Tom Smith'
188
+ mother {
189
+ name 'Jane Smith'
190
+ }
191
+ }
192
+ }
193
+
194
+ #=> #<struct Person name="John Smith",
195
+ # mother=#<struct Person name="Mary Smith", mother=nil, father=nil>,
196
+ # father=#<struct Person name="Tom Smith",
197
+ # mother=#<struct Person name="Jane Smith", mother=nil, father=nil>,
198
+ # father=nil>>
199
+ ```
200
+
201
+ See the full [person tree example][4] for details.
202
+
203
+ [4]: https://gist.github.com/ms-ati/2bb17bdf10a430faba98
204
+
96
205
  ### Block parameters
97
206
 
98
207
  Parameters can be passed to the DSL block.
@@ -153,7 +262,7 @@ end
153
262
 
154
263
  [3]: http://www.sinatrarb.com "Sinatra"
155
264
 
156
- ### Functional-Style DSL Objects
265
+ ### Functional-Style Immutable DSL Objects
157
266
 
158
267
  Sometimes, you want to use an object as a DSL, but it doesn't quite fit the
159
268
  [imperative](http://en.wikipedia.org/wiki/Imperative_programming) pattern shown
@@ -192,6 +301,33 @@ end
192
301
 
193
302
  All set!
194
303
 
304
+ ### Accessing the block's return value
305
+
306
+ Sometimes you might want to access the return value of your provided block,
307
+ as opposed to the DSL object itself. In these cases, use
308
+ `dsl_eval_with_block_return`. It behaves exactly like `dsl_eval`, but returns
309
+ the output from executing the block, rather than the DSL object.
310
+
311
+ ```ruby
312
+ arr = []
313
+ with_array(arr) do
314
+ push "a"
315
+ push "b"
316
+ push "c"
317
+ length
318
+ end
319
+ #=> 3
320
+
321
+ arr
322
+ #=> ["a", "b", "c"]
323
+ ```
324
+
325
+ ```ruby
326
+ def with_array(arr=[], &block)
327
+ Docile.dsl_eval_with_block_return(arr, &block)
328
+ end
329
+ ```
330
+
195
331
  ## Features
196
332
 
197
333
  1. Method lookup falls back from the DSL object to the block's context
@@ -219,6 +355,10 @@ Works on [all ruby versions since 1.8.7](https://github.com/ms-ati/docile/blob/m
219
355
 
220
356
  Used by some pretty cool gems to implement their DSLs, notably including [SimpleCov](https://github.com/colszowka/simplecov). Keep an eye out for new gems using Docile at the [Ruby Toolbox](https://www.ruby-toolbox.com/projects/docile).
221
357
 
358
+ ## Release Policy
359
+
360
+ Docile releases follow [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
361
+
222
362
  ## Note on Patches/Pull Requests
223
363
 
224
364
  * Fork the project.
@@ -234,7 +374,7 @@ Used by some pretty cool gems to implement their DSLs, notably including [Simple
234
374
 
235
375
  ## Copyright & License
236
376
 
237
- Copyright (c) 2012-2014 Marc Siegel.
377
+ Copyright (c) 2012-2019 Marc Siegel.
238
378
 
239
379
  Licensed under the [MIT License](http://choosealicense.com/licenses/mit/), see [LICENSE](LICENSE) for details.
240
380
 
data/Rakefile CHANGED
@@ -1,7 +1,7 @@
1
- require 'rake/clean'
2
- require 'bundler/gem_tasks'
3
- require 'rspec/core/rake_task'
4
- require File.expand_path('on_what', File.dirname(__FILE__))
1
+ require "rake/clean"
2
+ require "bundler/gem_tasks"
3
+ require "rspec/core/rake_task"
4
+ require File.expand_path("on_what", File.dirname(__FILE__))
5
5
 
6
6
  # Default task for `rake` is to run rspec
7
7
  task :default => [:spec]
@@ -10,19 +10,19 @@ task :default => [:spec]
10
10
  RSpec::Core::RakeTask.new
11
11
 
12
12
  # Configure `rake clobber` to delete all generated files
13
- CLOBBER.include('pkg', 'doc', 'coverage')
13
+ CLOBBER.include("pkg", "doc", "coverage")
14
14
 
15
15
  # To limit needed compatibility with versions of dependencies, only configure
16
- # yard doc generation when *not* on Travis, JRuby, or 1.8
17
- if !on_travis? && !on_jruby? && !on_1_8?
18
- require 'github/markup'
19
- require 'redcarpet'
20
- require 'yard'
21
- require 'yard/rake/yardoc_task'
16
+ # yard doc generation when *not* on Travis, JRuby, or < 2.0
17
+ if !on_travis? && !on_jruby? && !on_less_than_2_0?
18
+ require "github/markup"
19
+ require "redcarpet"
20
+ require "yard"
21
+ require "yard/rake/yardoc_task"
22
22
 
23
23
  YARD::Rake::YardocTask.new do |t|
24
24
  OTHER_PATHS = %w()
25
- t.files = ['lib/**/*.rb', OTHER_PATHS]
25
+ t.files = ["lib/**/*.rb", OTHER_PATHS]
26
26
  t.options = %w(--markup-provider=redcarpet --markup=markdown --main=README.md)
27
27
  end
28
28
  end
@@ -1,43 +1,55 @@
1
- require File.expand_path('on_what', File.dirname(__FILE__))
2
- $:.push File.expand_path('../lib', __FILE__)
3
- require 'docile/version'
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require File.expand_path("on_what", File.dirname(__FILE__))
3
+ require "docile/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = 'docile'
6
+ s.name = "docile"
7
7
  s.version = Docile::VERSION
8
- s.author = 'Marc Siegel'
9
- s.email = 'marc@usainnov.com'
10
- s.homepage = 'https://ms-ati.github.io/docile/'
11
- s.summary = 'Docile keeps your Ruby DSLs tame and well-behaved'
12
- s.description = 'Docile turns any Ruby object into a DSL. Especially useful with the Builder pattern.'
13
- s.license = 'MIT'
8
+ s.author = "Marc Siegel"
9
+ s.email = "marc@usainnov.com"
10
+ s.homepage = "https://ms-ati.github.io/docile/"
11
+ s.summary = "Docile keeps your Ruby DSLs tame and well-behaved."
12
+ s.description = "Docile treats the methods of a given ruby object as a DSL " \
13
+ "(domain specific language) within a given block. \n\n" \
14
+ "Killer feature: you can also reference methods, instance " \
15
+ "variables, and local variables from the original (non-DSL) "\
16
+ "context within the block. \n\n" \
17
+ "Docile releases follow Semantic Versioning as defined at " \
18
+ "semver.org."
19
+ s.license = "MIT"
14
20
 
15
21
  # Files included in the gem
16
- s.files = `git ls-files`.split("\n")
17
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
- s.require_paths = %w(lib)
22
+ s.files = `git ls-files -z`.split("\x0").reject do |f|
23
+ f.match(%r{^(test|spec|features)/})
24
+ end
25
+ s.require_paths = ["lib"]
20
26
 
21
27
  # Specify oldest supported Ruby version
22
- s.required_ruby_version = '>= 1.8.7'
28
+ s.required_ruby_version = ">= 1.8.7"
29
+
30
+ # Run rspec tests from rake even on old Ruby versions
31
+ s.add_development_dependency "rake", "~> 10.5" if on_less_than_1_9_3? # Pin compatible rake on old rubies, see: https://github.com/travis-ci/travis.rb/issues/380
32
+ s.add_development_dependency "rake", "< 11.0" unless on_less_than_1_9_3? # See http://stackoverflow.com/questions/35893584/nomethoderror-undefined-method-last-comment-after-upgrading-to-rake-11
33
+ s.add_development_dependency "rspec", "~> 3.0"
34
+ s.add_development_dependency "rspec-expectations", "!= 3.8.3" # Workaround for RSpec's issue, see: https://github.com/rspec/rspec-expectations/issues/1111
23
35
 
24
- # Run rspec tests from rake
25
- s.add_development_dependency 'rake'
26
- s.add_development_dependency 'rspec', '~> 3.0.0'
36
+ # Run code coverage where possible - not on Rubinius
37
+ unless on_rubinius?
38
+ # Pin versions for Travis builds on 1.9
39
+ s.add_development_dependency "json", "< 2.0" if on_less_than_2_0?
27
40
 
28
- # NOTE: needed for Travis builds on 1.8, but can't yet reproduce failure locally
29
- s.add_development_dependency 'mime-types', '~> 1.25.1' if on_1_8?
41
+ # Pin versions for Travis builds on 1.8
42
+ s.add_development_dependency "mime-types" , "~> 1.25.1" if on_1_8?
43
+ s.add_development_dependency "rest-client", "~> 1.6.8" if on_1_8?
44
+ end
30
45
 
31
46
  # To limit needed compatibility with versions of dependencies, only configure
32
- # yard doc generation when *not* on Travis, JRuby, or 1.8
33
- if !on_travis? && !on_jruby? && !on_1_8?
47
+ # yard doc generation when *not* on Travis, JRuby, Rubinius, or < 2.0
48
+ if !on_travis? && !on_jruby? && !on_rubinius? && !on_less_than_2_0?
34
49
  # Github flavored markdown in YARD documentation
35
50
  # http://blog.nikosd.com/2011/11/github-flavored-markdown-in-yard.html
36
- s.add_development_dependency 'yard'
37
- s.add_development_dependency 'redcarpet'
38
- s.add_development_dependency 'github-markup'
51
+ s.add_development_dependency "yard"
52
+ s.add_development_dependency "redcarpet"
53
+ s.add_development_dependency "github-markup"
39
54
  end
40
-
41
- # Coveralls test coverage tool, basically hosted SimpleCov
42
- s.add_development_dependency 'coveralls'
43
55
  end
@@ -1,7 +1,7 @@
1
- require 'docile/version'
2
- require 'docile/execution'
3
- require 'docile/fallback_context_proxy'
4
- require 'docile/chaining_fallback_context_proxy'
1
+ require "docile/version"
2
+ require "docile/execution"
3
+ require "docile/fallback_context_proxy"
4
+ require "docile/chaining_fallback_context_proxy"
5
5
 
6
6
  # Docile keeps your Ruby DSLs tame and well-behaved.
7
7
  module Docile
@@ -45,6 +45,45 @@ module Docile
45
45
  end
46
46
  module_function :dsl_eval
47
47
 
48
+ # Execute a block in the context of an object whose methods represent the
49
+ # commands in a DSL, and return *the block's return value*.
50
+ #
51
+ # @note Use with an *imperative* DSL (commands modify the context object)
52
+ #
53
+ # Use this method to execute an *imperative* DSL, which means that:
54
+ #
55
+ # 1. Each command mutates the state of the DSL context object
56
+ # 2. The return value of each command is ignored
57
+ # 3. The final return value is the original context object
58
+ #
59
+ # @example Use a String as a DSL
60
+ # Docile.dsl_eval_with_block_return("Hello, world!") do
61
+ # reverse!
62
+ # upcase!
63
+ # first
64
+ # end
65
+ # #=> "!"
66
+ #
67
+ # @example Use an Array as a DSL
68
+ # Docile.dsl_eval_with_block_return([]) do
69
+ # push "a"
70
+ # push "b"
71
+ # pop
72
+ # push "c"
73
+ # length
74
+ # end
75
+ # #=> 2
76
+ #
77
+ # @param dsl [Object] context object whose methods make up the DSL
78
+ # @param args [Array] arguments to be passed to the block
79
+ # @param block [Proc] the block of DSL commands to be executed against the
80
+ # `dsl` context object
81
+ # @return [Object] the return value from executing the block
82
+ def dsl_eval_with_block_return(dsl, *args, &block)
83
+ exec_in_proxy_context(dsl, FallbackContextProxy, *args, &block)
84
+ end
85
+ module_function :dsl_eval_with_block_return
86
+
48
87
  # Execute a block in the context of an immutable object whose methods,
49
88
  # and the methods of their return values, represent the commands in a DSL.
50
89
  #
@@ -1,4 +1,4 @@
1
- require 'docile/fallback_context_proxy'
1
+ require "docile/fallback_context_proxy"
2
2
 
3
3
  module Docile
4
4
  # @api private
@@ -17,4 +17,4 @@ module Docile
17
17
  @__receiver__ = super(method, *args, &block)
18
18
  end
19
19
  end
20
- end
20
+ end
@@ -15,16 +15,22 @@ module Docile
15
15
  # @param block [Proc] the block of DSL commands to be executed
16
16
  # @return [Object] the return value of the block
17
17
  def exec_in_proxy_context(dsl, proxy_type, *args, &block)
18
- block_context = eval('self', block.binding)
18
+ block_context = eval("self", block.binding)
19
19
  proxy_context = proxy_type.new(dsl, block_context)
20
20
  begin
21
21
  block_context.instance_variables.each do |ivar|
22
22
  value_from_block = block_context.instance_variable_get(ivar)
23
23
  proxy_context.instance_variable_set(ivar, value_from_block)
24
24
  end
25
+
25
26
  proxy_context.instance_exec(*args, &block)
26
27
  ensure
28
+ if block_context.respond_to?(:__docile_undo_fallback__)
29
+ block_context.send(:__docile_undo_fallback__)
30
+ end
31
+
27
32
  block_context.instance_variables.each do |ivar|
33
+ next unless proxy_context.instance_variables.include?(ivar)
28
34
  value_from_dsl_proxy = proxy_context.instance_variable_get(ivar)
29
35
  block_context.instance_variable_set(ivar, value_from_dsl_proxy)
30
36
  end
@@ -32,4 +38,4 @@ module Docile
32
38
  end
33
39
  module_function :exec_in_proxy_context
34
40
  end
35
- end
41
+ end
@@ -1,4 +1,4 @@
1
- require 'set'
1
+ require "set"
2
2
 
3
3
  module Docile
4
4
  # @api private
@@ -17,10 +17,14 @@ module Docile
17
17
  # The set of methods which will **not** be proxied, but instead answered
18
18
  # by this object directly.
19
19
  NON_PROXIED_METHODS = Set[:__send__, :object_id, :__id__, :==, :equal?,
20
- :'!', :'!=', :instance_exec, :instance_variables,
20
+ :"!", :"!=", :instance_exec, :instance_variables,
21
21
  :instance_variable_get, :instance_variable_set,
22
22
  :remove_instance_variable]
23
23
 
24
+ # The set of methods which will **not** fallback from the block's context
25
+ # to the dsl object.
26
+ NON_FALLBACK_METHODS = Set[:class, :self, :respond_to?, :instance_of?]
27
+
24
28
  # The set of instance variables which are local to this object and hidden.
25
29
  # All other instance variables will be copied in and out of this object
26
30
  # from the scope in which this proxy was created.
@@ -38,6 +42,32 @@ module Docile
38
42
  def initialize(receiver, fallback)
39
43
  @__receiver__ = receiver
40
44
  @__fallback__ = fallback
45
+
46
+ # Enables calling DSL methods from helper methods in the block's context
47
+ unless fallback.respond_to?(:method_missing)
48
+ # NOTE: There's no {#define_singleton_method} on Ruby 1.8.x
49
+ singleton_class = (class << fallback; self; end)
50
+
51
+ # instrument {#method_missing} on the block's context to fallback to
52
+ # the DSL object. This allows helper methods in the block's context to
53
+ # contain calls to methods on the DSL object.
54
+ singleton_class.
55
+ send(:define_method, :method_missing) do |method, *args, &block|
56
+ m = method.to_sym
57
+ if !NON_FALLBACK_METHODS.include?(m) && !fallback.respond_to?(m) && receiver.respond_to?(m)
58
+ receiver.__send__(method.to_sym, *args, &block)
59
+ else
60
+ super(method, *args, &block)
61
+ end
62
+ end
63
+
64
+ # instrument a helper method to remove the above instrumentation
65
+ singleton_class.
66
+ send(:define_method, :__docile_undo_fallback__) do
67
+ singleton_class.send(:remove_method, :method_missing)
68
+ singleton_class.send(:remove_method, :__docile_undo_fallback__)
69
+ end
70
+ end
41
71
  end
42
72
 
43
73
  # @return [Array<Symbol>] Instance variable names, excluding
@@ -1,4 +1,4 @@
1
1
  module Docile
2
2
  # The current version of this library
3
- VERSION = '1.1.5'
3
+ VERSION = "1.3.2"
4
4
  end
data/on_what.rb CHANGED
@@ -2,13 +2,25 @@
2
2
  # between Rakefile, gemspec, and spec_helper. Not for use in actual library.
3
3
 
4
4
  def on_travis?
5
- ENV['CI'] == 'true'
5
+ ENV["CI"] == "true"
6
6
  end
7
7
 
8
8
  def on_jruby?
9
- (defined?(RUBY_ENGINE) && 'jruby' == RUBY_ENGINE)
9
+ defined?(RUBY_ENGINE) && "jruby" == RUBY_ENGINE
10
+ end
11
+
12
+ def on_rubinius?
13
+ defined?(RUBY_ENGINE) && "rbx" == RUBY_ENGINE
10
14
  end
11
15
 
12
16
  def on_1_8?
13
- RUBY_VERSION.start_with? '1.8'
14
- end
17
+ RUBY_VERSION.start_with? "1.8"
18
+ end
19
+
20
+ def on_less_than_1_9_3?
21
+ RUBY_VERSION < "1.9.3"
22
+ end
23
+
24
+ def on_less_than_2_0?
25
+ RUBY_VERSION < "2.0.0"
26
+ end
metadata CHANGED
@@ -1,59 +1,59 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docile
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc Siegel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-15 00:00:00.000000000 Z
11
+ date: 2019-06-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "<"
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '11.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "<"
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '11.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 3.0.0
33
+ version: '3.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 3.0.0
40
+ version: '3.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: yard
42
+ name: rspec-expectations
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "!="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 3.8.3
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "!="
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 3.8.3
55
55
  - !ruby/object:Gem::Dependency
56
- name: redcarpet
56
+ name: yard
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: github-markup
70
+ name: redcarpet
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: coveralls
84
+ name: github-markup
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -94,8 +94,10 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
- description: Docile turns any Ruby object into a DSL. Especially useful with the Builder
98
- pattern.
97
+ description: "Docile treats the methods of a given ruby object as a DSL (domain specific
98
+ language) within a given block. \n\nKiller feature: you can also reference methods,
99
+ instance variables, and local variables from the original (non-DSL) context within
100
+ the block. \n\nDocile releases follow Semantic Versioning as defined at semver.org."
99
101
  email: marc@usainnov.com
100
102
  executables: []
101
103
  extensions: []
@@ -103,8 +105,6 @@ extra_rdoc_files: []
103
105
  files:
104
106
  - ".gitignore"
105
107
  - ".rspec"
106
- - ".ruby-gemset"
107
- - ".ruby-version"
108
108
  - ".travis.yml"
109
109
  - ".yardopts"
110
110
  - Gemfile
@@ -119,8 +119,6 @@ files:
119
119
  - lib/docile/fallback_context_proxy.rb
120
120
  - lib/docile/version.rb
121
121
  - on_what.rb
122
- - spec/docile_spec.rb
123
- - spec/spec_helper.rb
124
122
  homepage: https://ms-ati.github.io/docile/
125
123
  licenses:
126
124
  - MIT
@@ -141,11 +139,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
139
  version: '0'
142
140
  requirements: []
143
141
  rubyforge_project:
144
- rubygems_version: 2.2.2
142
+ rubygems_version: 2.7.9
145
143
  signing_key:
146
144
  specification_version: 4
147
- summary: Docile keeps your Ruby DSLs tame and well-behaved
148
- test_files:
149
- - spec/docile_spec.rb
150
- - spec/spec_helper.rb
151
- has_rdoc:
145
+ summary: Docile keeps your Ruby DSLs tame and well-behaved.
146
+ test_files: []
@@ -1 +0,0 @@
1
- docile
@@ -1 +0,0 @@
1
- ruby-2.1.0
@@ -1,339 +0,0 @@
1
- require 'spec_helper'
2
- require 'singleton'
3
-
4
- describe Docile do
5
-
6
- describe '.dsl_eval' do
7
-
8
- context 'when DSL context object is an Array' do
9
- let(:array) { [] }
10
- let!(:result) { execute_dsl_against_array }
11
-
12
- def execute_dsl_against_array
13
- Docile.dsl_eval(array) do
14
- push 1
15
- push 2
16
- pop
17
- push 3
18
- end
19
- end
20
-
21
- it 'executes the block against the DSL context object' do
22
- expect(array).to eq([1, 3])
23
- end
24
-
25
- it 'returns the DSL object after executing block against it' do
26
- expect(result).to eq(array)
27
- end
28
-
29
- it "doesn't proxy #__id__" do
30
- Docile.dsl_eval(array) { expect(__id__).not_to eq(array.__id__) }
31
- end
32
-
33
- it "raises NoMethodError if the DSL object doesn't implement the method" do
34
- expect { Docile.dsl_eval(array) { no_such_method } }.to raise_error(NoMethodError)
35
- end
36
- end
37
-
38
- Pizza = Struct.new(:cheese, :pepperoni, :bacon, :sauce)
39
-
40
- class PizzaBuilder
41
- def cheese(v=true); @cheese = v; end
42
- def pepperoni(v=true); @pepperoni = v; end
43
- def bacon(v=true); @bacon = v; end
44
- def sauce(v=nil); @sauce = v; end
45
- def build
46
- Pizza.new(!!@cheese, !!@pepperoni, !!@bacon, @sauce)
47
- end
48
- end
49
-
50
- context 'when DSL context object is a Builder pattern' do
51
- let(:builder) { PizzaBuilder.new }
52
- let(:result) { execute_dsl_against_builder_and_call_build }
53
-
54
- def execute_dsl_against_builder_and_call_build
55
- @sauce = :extra
56
- Docile.dsl_eval(builder) do
57
- bacon
58
- cheese
59
- sauce @sauce
60
- end.build
61
- end
62
-
63
- it 'returns correctly built object' do
64
- expect(result).to eq(Pizza.new(true, false, true, :extra))
65
- end
66
- end
67
-
68
- class InnerDSL
69
- def initialize; @b = 'b'; end
70
- attr_accessor :b
71
- end
72
-
73
- class OuterDSL
74
- def initialize; @a = 'a'; end
75
- attr_accessor :a
76
-
77
- def inner(&block)
78
- Docile.dsl_eval(InnerDSL.new, &block)
79
- end
80
-
81
- def inner_with_params(param, &block)
82
- Docile.dsl_eval(InnerDSL.new, param, :foo, &block)
83
- end
84
- end
85
-
86
- def outer(&block)
87
- Docile.dsl_eval(OuterDSL.new, &block)
88
- end
89
-
90
- context 'when given parameters for the DSL block' do
91
- def parameterized(*args, &block)
92
- Docile.dsl_eval(OuterDSL.new, *args, &block)
93
- end
94
-
95
- it 'passes parameters to the block' do
96
- parameterized(1,2,3) do |x,y,z|
97
- expect(x).to eq(1)
98
- expect(y).to eq(2)
99
- expect(z).to eq(3)
100
- end
101
- end
102
-
103
- it 'finds parameters before methods' do
104
- parameterized(1) { |a| expect(a).to eq(1) }
105
- end
106
-
107
- it 'find outer dsl parameters in inner dsl scope' do
108
- parameterized(1,2,3) do |a,b,c|
109
- inner_with_params(c) do |d,e|
110
- expect(a).to eq(1)
111
- expect(b).to eq(2)
112
- expect(c).to eq(3)
113
- expect(d).to eq(c)
114
- expect(e).to eq(:foo)
115
- end
116
- end
117
- end
118
- end
119
-
120
- class DSLWithNoMethod
121
- def initialize(b); @b = b; end
122
- attr_accessor :b
123
- def push_element
124
- @b.push 1
125
- end
126
- end
127
-
128
- context 'when DSL have NoMethod error inside' do
129
- it 'raise error from nil' do
130
- Docile.dsl_eval(DSLWithNoMethod.new(nil)) do
131
- expect { push_element }.to raise_error(NoMethodError, /undefined method `push' (for|on) nil:NilClass/)
132
- end
133
- end
134
- end
135
-
136
- context 'when DSL blocks are nested' do
137
-
138
- context 'method lookup' do
139
- it 'finds method of outer dsl in outer dsl scope' do
140
- outer { expect(a).to eq('a') }
141
- end
142
-
143
- it 'finds method of inner dsl in inner dsl scope' do
144
- outer { inner { expect(b).to eq('b') } }
145
- end
146
-
147
- it 'finds method of outer dsl in inner dsl scope' do
148
- outer { inner { expect(a).to eq('a') } }
149
- end
150
-
151
- it "finds method of block's context in outer dsl scope" do
152
- def c; 'c'; end
153
- outer { expect(c).to eq('c') }
154
- end
155
-
156
- it "finds method of block's context in inner dsl scope" do
157
- def c; 'c'; end
158
- outer { inner { expect(c).to eq('c') } }
159
- end
160
-
161
- it 'finds method of outer dsl in preference to block context' do
162
- def a; 'not a'; end
163
- outer { expect(a).to eq('a') }
164
- outer { inner { expect(a).to eq('a') } }
165
- end
166
- end
167
-
168
- context 'local variable lookup' do
169
- it 'finds local variable from block context in outer dsl scope' do
170
- foo = 'foo'
171
- outer { expect(foo).to eq('foo') }
172
- end
173
-
174
- it 'finds local variable from block definition in inner dsl scope' do
175
- bar = 'bar'
176
- outer { inner { expect(bar).to eq('bar') } }
177
- end
178
- end
179
-
180
- context 'instance variable lookup' do
181
- it 'finds instance variable from block definition in outer dsl scope' do
182
- @iv1 = 'iv1'; outer { expect(@iv1).to eq('iv1') }
183
- end
184
-
185
- it "proxies instance variable assignments in block in outer dsl scope back into block's context" do
186
- @iv1 = 'foo'; outer { @iv1 = 'bar' }; expect(@iv1).to eq('bar')
187
- end
188
-
189
- it 'finds instance variable from block definition in inner dsl scope' do
190
- @iv2 = 'iv2'; outer { inner { expect(@iv2).to eq('iv2') } }
191
- end
192
-
193
- it "proxies instance variable assignments in block in inner dsl scope back into block's context" do
194
- @iv2 = 'foo'; outer { inner { @iv2 = 'bar' } }; expect(@iv2).to eq('bar')
195
- end
196
- end
197
-
198
- end
199
-
200
- context 'when DSL context object is a Dispatch pattern' do
201
- class DispatchScope
202
- def params
203
- { :a => 1, :b => 2, :c => 3 }
204
- end
205
- end
206
-
207
- class MessageDispatch
208
- include Singleton
209
-
210
- def initialize
211
- @responders = {}
212
- end
213
-
214
- def add_responder path, &block
215
- @responders[path] = block
216
- end
217
-
218
- def dispatch path, request
219
- Docile.dsl_eval(DispatchScope.new, request, &@responders[path])
220
- end
221
- end
222
-
223
- def respond(path, &block)
224
- MessageDispatch.instance.add_responder(path, &block)
225
- end
226
-
227
- def send_request(path, request)
228
- MessageDispatch.instance.dispatch(path, request)
229
- end
230
-
231
- it 'dispatches correctly' do
232
- @first = @second = nil
233
-
234
- respond '/path' do |request|
235
- @first = request
236
- end
237
-
238
- respond '/new_bike' do |bike|
239
- @second = "Got a new #{bike}"
240
- end
241
-
242
- def x(y) ; "Got a #{y}"; end
243
- respond '/third' do |third|
244
- expect(x(third)).to eq('Got a third thing')
245
- end
246
-
247
- fourth = nil
248
- respond '/params' do |arg|
249
- fourth = params[arg]
250
- end
251
-
252
- send_request '/path', 1
253
- send_request '/new_bike', 'ten speed'
254
- send_request '/third', 'third thing'
255
- send_request '/params', :b
256
-
257
- expect(@first).to eq(1)
258
- expect(@second).to eq('Got a new ten speed')
259
- expect(fourth).to eq(2)
260
- end
261
-
262
- end
263
-
264
- end
265
-
266
- describe '.dsl_eval_immutable' do
267
-
268
- context 'when DSL context object is a frozen String' do
269
- let(:original) { "I'm immutable!".freeze }
270
- let!(:result) { execute_non_mutating_dsl_against_string }
271
-
272
- def execute_non_mutating_dsl_against_string
273
- Docile.dsl_eval_immutable(original) do
274
- reverse
275
- upcase
276
- end
277
- end
278
-
279
- it "doesn't modify the original string" do
280
- expect(original).to eq("I'm immutable!")
281
- end
282
-
283
- it 'chains the commands in the block against the DSL context object' do
284
- expect(result).to eq("!ELBATUMMI M'I")
285
- end
286
- end
287
-
288
- context 'when DSL context object is a number' do
289
- let(:original) { 84.5 }
290
- let!(:result) { execute_non_mutating_dsl_against_number }
291
-
292
- def execute_non_mutating_dsl_against_number
293
- Docile.dsl_eval_immutable(original) do
294
- fdiv(2)
295
- floor
296
- end
297
- end
298
-
299
- it 'chains the commands in the block against the DSL context object' do
300
- expect(result).to eq(42)
301
- end
302
- end
303
- end
304
-
305
- end
306
-
307
- describe Docile::FallbackContextProxy do
308
-
309
- describe '#instance_variables' do
310
- subject { create_fcp_and_set_one_instance_variable.instance_variables }
311
- let(:expected_type_of_names) { type_of_ivar_names_on_this_ruby }
312
- let(:actual_type_of_names) { subject.first.class }
313
- let(:excluded) { Docile::FallbackContextProxy::NON_PROXIED_INSTANCE_VARIABLES }
314
-
315
- def create_fcp_and_set_one_instance_variable
316
- fcp = Docile::FallbackContextProxy.new(nil, nil)
317
- fcp.instance_variable_set(:@foo, 'foo')
318
- fcp
319
- end
320
-
321
- def type_of_ivar_names_on_this_ruby
322
- @a = 1
323
- instance_variables.first.class
324
- end
325
-
326
- it 'returns proxied instance variables' do
327
- expect(subject.map(&:to_sym)).to include(:@foo)
328
- end
329
-
330
- it "doesn't return non-proxied instance variables" do
331
- expect(subject.map(&:to_sym)).not_to include(*excluded)
332
- end
333
-
334
- it 'preserves the type (String or Symbol) of names on this ruby version' do
335
- expect(actual_type_of_names).to eq(expected_type_of_names)
336
- end
337
- end
338
-
339
- end
@@ -1,30 +0,0 @@
1
- require File.expand_path('on_what', File.dirname(File.dirname(__FILE__)))
2
-
3
- begin
4
- require 'simplecov'
5
- require 'coveralls'
6
-
7
- # On Ruby 1.9+ use SimpleCov and publish to Coveralls.io
8
- if !on_1_8?
9
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
10
- SimpleCov::Formatter::HTMLFormatter,
11
- Coveralls::SimpleCov::Formatter
12
- ]
13
- SimpleCov.start do
14
- add_filter '/spec/' # exclude test code
15
- add_filter '/vendor/' # exclude gems which are vendored on Travis CI
16
- end
17
-
18
- # Remove Docile, which was required by SimpleCov, to require again later
19
- Object.send(:remove_const, :Docile)
20
- $LOADED_FEATURES.reject! { |f| f =~ /\/docile\// }
21
- end
22
- rescue LoadError
23
- warn 'warning: simplecov/coveralls gems not found; skipping coverage'
24
- end
25
-
26
- lib_dir = File.join(File.dirname(File.dirname(__FILE__)), 'lib')
27
- $LOAD_PATH.unshift lib_dir unless $LOAD_PATH.include? lib_dir
28
-
29
- # Require Docile again, now with coverage enabled on 1.9+
30
- require 'docile'