docile 1.1.5 → 1.3.2

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 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'