trailblazer-context 0.1.1 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: eb81fa6ece2d3615f46a7b72a066a8316c48065a
4
- data.tar.gz: d720c68f9154786ef561e97f67e29ff15e58be6c
2
+ SHA256:
3
+ metadata.gz: 0f6c3c7ff9c8b061c5ac0d5aca954f03cd1b5ff0bf286aa787ede12f1e187890
4
+ data.tar.gz: 0fdc45ffab9316c7799f2ce59f54c6409305cf91fa01349d30d026eac998c188
5
5
  SHA512:
6
- metadata.gz: 720a50aab05ec4de810b88227c7405f5bffaef9e2685419849f09cd56159e5e216c0ae5959b4d6502e2edabc7559bdfcbe5a01b61f9a141bfc01508f407e39a6
7
- data.tar.gz: 2aa161d5f6de1b93210f3e1f5f9c187f0dd8b44cc0bf2d335dbc36c6479d6891d1a4ac002dd729d3b824a5fd04304fe952b0038fa16e4105ac242ec584905020
6
+ metadata.gz: 2c9f349028afca126e1961e50ebb0ff661a5fe5a0e2573108c9c79f762694cdb03c6914beca4eec4c5b3b25aecae03d4706c47c84854d0ebb57b6e8a42ec279f
7
+ data.tar.gz: 1fba9db52f9e3680cccb3b89624c8a159bbd144fbd0b86b3a71972ea1feff96978b82867c976a568aec709b09d0fe859d173a1a813fa5747cb42d179bf6c7520
data/.gitignore CHANGED
@@ -9,6 +9,7 @@
9
9
  /test/tmp/
10
10
  /test/version_tmp/
11
11
  /tmp/
12
+ Gemfile.lock
12
13
 
13
14
  # Used by dotenv library to load environment variables.
14
15
  # .env
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ before_install: gem install bundler
3
+ cache: bundler
4
+ rvm:
5
+ - ruby-head
6
+ - 2.7
7
+ - 2.6
8
+ - 2.5
9
+ - 2.4
10
+ jobs:
11
+ allow_failures:
12
+ - rvm: ruby-head
data/CHANGES.md CHANGED
@@ -1,3 +1,29 @@
1
+ # 0.3.0
2
+ * Add support for ruby 2.7
3
+ * Drop support for ruby 2.0
4
+
5
+ # 0.2.0
6
+
7
+ * Added `Context::IndifferentAccess`.
8
+ * Added `Context::Aliasing`.
9
+ * `Context.for_circuit` is not the authorative builder for creating a context.
10
+
11
+ # 0.1.5
12
+
13
+ * `Context.build` allows quickly building a Context without requiring the circuit interface.
14
+
15
+ # 0.1.4
16
+
17
+ * Fix the `IndifferentAccess` name lookup. Since we can't convert all keys to symbols internally (not every options structure has `collect`) we need to have a lookup chain.
18
+
19
+ # 0.1.3
20
+
21
+ * Introduce `Context::IndifferentAccess` which converts all keys to symbol. This, in turn, allows to use both string and symbol keys everywhere. Currently, the implementation is set via the global method `Context.implementation` and defaults to the new `IndifferentAccess`.
22
+
23
+ # 0.1.2
24
+
25
+ * More meaningful error message: "No :exec_context given.".
26
+
1
27
  # 0.1.1
2
28
 
3
29
  * `Option.( *args, &block )` now passes through the block to the option call.
data/Gemfile CHANGED
@@ -1,5 +1,5 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
  gemspec
3
3
 
4
- gem "minitest-line"
5
4
  gem "benchmark-ips"
5
+ gem "minitest-line"
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017 TRAILBLAZER GmbH
3
+ Copyright (c) 2017-2020 TRAILBLAZER GmbH
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/Rakefile CHANGED
@@ -4,7 +4,7 @@ require "rake/testtask"
4
4
  Rake::TestTask.new(:test) do |t|
5
5
  t.libs << "test"
6
6
  t.libs << "lib"
7
- t.test_files = FileList['test/*_test.rb']
7
+ t.test_files = FileList["test/*_test.rb"]
8
8
  end
9
9
 
10
- task :default => :test
10
+ task default: %i[test]
@@ -1,2 +1,2 @@
1
+ require "trailblazer/context/version"
1
2
  require "trailblazer/context"
2
- require "trailblazer/option"
@@ -1,5 +1,6 @@
1
1
  # @private
2
- class Trailblazer::Context::ContainerChain # used to be called Resolver.
2
+ # used to be called Resolver.
3
+ class Trailblazer::Context::ContainerChain
3
4
  # Keeps a list of containers. When looking up a key/value, containers are traversed in
4
5
  # the order they were added until key is found.
5
6
  #
@@ -28,11 +29,12 @@ class Trailblazer::Context::ContainerChain # used to be called Resolver.
28
29
 
29
30
  def keys
30
31
  @containers.collect(&:keys).flatten
31
- end
32
+ end
32
33
 
33
34
  # @private
34
35
  def to_hash
35
- return @to_hash.(@containers) if @to_hash # FIXME: introduce pattern matching so we can have different "transformers" for each container type.
36
+ # FIXME: introduce pattern matching so we can have different "transformers" for each container type.
37
+ return @to_hash.(@containers) if @to_hash
36
38
  @containers.each_with_object({}) { |container, hash| hash.merge!(container.to_hash) }
37
39
  end
38
40
  end
@@ -1,3 +1,4 @@
1
+ require "trailblazer/option"
1
2
  # TODO: mark/make all but mutable_options as frozen.
2
3
  # The idea of Skill is to have a generic, ordered read/write interface that
3
4
  # collects mutable runtime-computed data while providing access to compile-time
@@ -11,10 +12,36 @@ module Trailblazer
11
12
  # activity (aka wrapped_options).
12
13
 
13
14
  # only public creator: Build
14
- class Context # :data object:
15
- def initialize(wrapped_options, mutable_options)
16
- @wrapped_options, @mutable_options = wrapped_options, mutable_options
17
- # TODO: wrapped_options should be optimized for lookups here since it could also be a Context instance, but should be a ContainerChain.
15
+ # :data object:
16
+ class Context
17
+ # NOTE: In the future, we might look up the Context to use in the ctx.
18
+ # The demanding signature is for forward-compat.
19
+ # @private
20
+ def self.for(wrapped_options, (ctx, flow_options), **circuit_options) # TODO: remove
21
+ implementation.build(wrapped_options, {}, [ctx, flow_options], circuit_options)
22
+ end
23
+
24
+ def self.for_circuit(wrapped_options, mutable_options, (ctx, flow_options), **circuit_options)
25
+ context_class = flow_options[:context_class] || implementation # Context::IndifferentAccess
26
+
27
+ context_class.build(wrapped_options, mutable_options, [ctx, flow_options], circuit_options)
28
+ end
29
+
30
+ # @public
31
+ def self.build(wrapped_options, *)
32
+ new(wrapped_options)
33
+ end
34
+
35
+ # I hate globals, but currently this is the only easy way for setting the implementation.
36
+ def self.implementation
37
+ IndifferentAccess
38
+ end
39
+
40
+ def initialize(wrapped_options, mutable_options, *)
41
+ @wrapped_options = wrapped_options
42
+ @mutable_options = mutable_options
43
+ # TODO: wrapped_options should be optimized for lookups here since
44
+ # it could also be a Context instance, but should be a ContainerChain.
18
45
  end
19
46
 
20
47
  def [](name)
@@ -39,26 +66,22 @@ module Trailblazer
39
66
  end
40
67
 
41
68
  # @private
42
- #
43
- # This method might be removed.
44
69
  def merge(hash)
45
70
  original, mutable_options = decompose
46
71
 
47
- ctx = Trailblazer::Context( original, mutable_options.merge(hash) )
72
+ self.class.new(original, mutable_options.merge(hash))
48
73
  end
49
74
 
50
75
  # Return the Context's two components. Used when computing the new output for
51
76
  # the next activity.
52
77
  def decompose
53
- [ @wrapped_options, @mutable_options ]
78
+ [@wrapped_options, @mutable_options]
54
79
  end
55
80
 
56
81
  def keys
57
82
  @mutable_options.keys + @wrapped_options.keys # FIXME.
58
83
  end
59
84
 
60
-
61
-
62
85
  # TODO: maybe we shouldn't allow to_hash from context?
63
86
  # TODO: massive performance bottleneck. also, we could already "know" here what keys the
64
87
  # transformation wants.
@@ -66,14 +89,16 @@ module Trailblazer
66
89
  def to_hash
67
90
  {}.tap do |hash|
68
91
  # the "key" here is to call to_hash on all containers.
69
- [ @wrapped_options.to_hash, @mutable_options.to_hash ].each do |options|
92
+ [@wrapped_options.to_hash, @mutable_options.to_hash].each do |options|
70
93
  options.each { |k, v| hash[k.to_sym] = v }
71
94
  end
72
95
  end
73
96
  end
74
97
  end
75
98
 
76
- def self.Context(wrapped_options, mutable_options={})
99
+ def self.Context(wrapped_options, mutable_options = {})
77
100
  Context.new(wrapped_options, mutable_options)
78
101
  end
79
- end # Trailblazer
102
+ end
103
+
104
+ require "trailblazer/context/indifferent_access"
@@ -0,0 +1,38 @@
1
+ module Trailblazer
2
+ class Context
3
+ module Aliasing
4
+ def initialize(wrapped_options, mutable_options, context_alias: {}, **)
5
+ super(wrapped_options, mutable_options)
6
+
7
+ @aliases = context_alias.invert
8
+ end
9
+
10
+ def [](key)
11
+ return super unless (aka = @aliases[key]) # yepp, nil/false won't work
12
+
13
+ super(aka)
14
+ end
15
+
16
+ def key?(key)
17
+ return super unless (aka = @aliases[key]) # yepp, nil/false won't work
18
+
19
+ super(aka)
20
+ end
21
+
22
+ # @private ?
23
+ def merge(hash)
24
+ original, mutable_options = decompose
25
+
26
+ self.class.new(
27
+ original,
28
+ mutable_options.merge(hash),
29
+ context_alias: @aliases.invert # DISCUSS: maybe we can speed up by remembering the original options?
30
+ )
31
+ end
32
+
33
+ def to_hash
34
+ super.merge(Hash[@aliases.collect { |aka, k| key?(k) ? [aka, self[k]] : nil }.compact]) # FIXME: performance!
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,38 @@
1
+ require "trailblazer/context/aliasing"
2
+
3
+ module Trailblazer
4
+ class Context
5
+ class IndifferentAccess < Context
6
+ module InstanceMethods
7
+ def [](name)
8
+ # TODO: well...
9
+ @mutable_options.key?(name.to_sym) and return @mutable_options[name.to_sym]
10
+ @mutable_options.key?(name.to_s) and return @mutable_options[name.to_s]
11
+ @wrapped_options.key?(name.to_sym) and return @wrapped_options[name.to_sym]
12
+ @wrapped_options[name.to_s]
13
+ end
14
+
15
+ def self.build(wrapped_options, mutable_options, (_ctx, flow_options), **)
16
+ new(wrapped_options, mutable_options, flow_options)
17
+ end
18
+ end
19
+ include InstanceMethods
20
+
21
+ def key?(name)
22
+ super(name.to_sym) || super(name.to_s)
23
+ end
24
+
25
+ include Aliasing # FIXME
26
+
27
+
28
+ class << self
29
+ # This also builds IndifferentAccess::Aliasing.
30
+ # The {#build} method is designed to take all args from {for_circuit} and then
31
+ # translate that to the constructor.
32
+ def build(wrapped_options, mutable_options, (_ctx, flow_options), **)
33
+ new(wrapped_options, mutable_options, **flow_options)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
2
  class Context
3
- VERSION = "0.1.1"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
@@ -1,22 +1,5 @@
1
1
  module Trailblazer
2
- # @note This might go to trailblazer-args along with `Context` at some point.
3
- def self.Option(proc)
4
- Option.build(Option, proc)
5
- end
6
-
7
2
  class Option
8
- # Generic builder for a callable "option".
9
- # @param call_implementation [Class, Module] implements the process of calling the proc
10
- # while passing arguments/options to it in a specific style (e.g. kw args, step interface).
11
- # @return [Proc] when called, this proc will evaluate its option (at run-time).
12
- def self.build(call_implementation, proc)
13
- if proc.is_a? Symbol
14
- ->(*args, &block) { call_implementation.evaluate_method(proc, *args, &block) }
15
- else
16
- ->(*args, &block) { call_implementation.evaluate_callable(proc, *args, &block) }
17
- end
18
- end
19
-
20
3
  # A call implementation invoking `proc.(*args)` and plainly forwarding all arguments.
21
4
  # Override this for your own step strategy (see KW#call!).
22
5
  # @private
@@ -27,16 +10,28 @@ module Trailblazer
27
10
  # Note that both #evaluate_callable and #evaluate_method drop most of the args.
28
11
  # If you need those, override this class.
29
12
  # @private
30
- def self.evaluate_callable(proc, *args, **flow_options, &block)
13
+ def self.evaluate_callable(proc, *args, **, &block)
31
14
  call!(proc, *args, &block)
32
15
  end
33
16
 
34
17
  # Make the context's instance method a "lambda" and reuse #call!.
35
18
  # @private
36
- def self.evaluate_method(proc, *args, exec_context:raise, **flow_options, &block)
19
+ def self.evaluate_method(proc, *args, exec_context: raise("No :exec_context given."), **, &block)
37
20
  call!(exec_context.method(proc), *args, &block)
38
21
  end
39
22
 
23
+ # Generic builder for a callable "option".
24
+ # @param call_implementation [Class, Module] implements the process of calling the proc
25
+ # while passing arguments/options to it in a specific style (e.g. kw args, step interface).
26
+ # @return [Proc] when called, this proc will evaluate its option (at run-time).
27
+ def self.build(call_implementation, proc)
28
+ if proc.is_a? Symbol
29
+ ->(*args, &block) { call_implementation.evaluate_method(proc, *args, &block) }
30
+ else
31
+ ->(*args, &block) { call_implementation.evaluate_callable(proc, *args, &block) }
32
+ end
33
+ end
34
+
40
35
  # Returns a {Proc} that, when called, invokes the `proc` argument with keyword arguments.
41
36
  # This is known as "step (call) interface".
42
37
  #
@@ -75,4 +70,8 @@ module Trailblazer
75
70
  end
76
71
  end
77
72
  end
73
+ # @note This might go to trailblazer-args along with `Context` at some point.
74
+ def self.Option(proc)
75
+ Option.build(Option, proc)
76
+ end
78
77
  end
@@ -0,0 +1,211 @@
1
+ require "test_helper"
2
+ require "trailblazer/container_chain"
3
+
4
+ class ArgsTest < Minitest::Spec
5
+ let(:immutable) { {repository: "User"} }
6
+
7
+ let(:ctx) { Trailblazer::Context(immutable) }
8
+
9
+ it do
10
+ ctx = Trailblazer::Context(immutable)
11
+
12
+ # it { }
13
+ #-
14
+ # options[] and options[]=
15
+ ctx[:model] = Module
16
+ ctx[:contract] = Integer
17
+ _(ctx[:model]) .must_equal Module
18
+ _(ctx[:contract]).must_equal Integer
19
+
20
+ # it { }
21
+ _(immutable.inspect).must_equal %({:repository=>\"User\"})
22
+ end
23
+
24
+ it "allows false/nil values" do
25
+ ctx["x"] = false
26
+ _(ctx["x"]).must_equal false
27
+
28
+ ctx["x"] = nil
29
+ assert_nil ctx["x"]
30
+ end
31
+
32
+ #- #to_hash
33
+ it do
34
+ ctx = Trailblazer::Context(immutable)
35
+
36
+ # it { }
37
+ _(ctx.to_hash).must_equal(repository: "User")
38
+
39
+ # last added has precedence.
40
+ # only symbol keys.
41
+ # it { }
42
+ ctx[:a] = Symbol
43
+ ctx["a"] = String
44
+
45
+ _(ctx.to_hash).must_equal(repository: "User", a: String)
46
+ end
47
+
48
+ describe "#merge" do
49
+ it do
50
+ ctx = Trailblazer::Context(immutable)
51
+
52
+ merged = ctx.merge(current_user: Module)
53
+
54
+ _(merged.to_hash).must_equal(repository: "User", current_user: Module)
55
+ _(ctx.to_hash).must_equal(repository: "User")
56
+ end
57
+ end
58
+
59
+ #-
60
+ it do
61
+ immutable = {repository: "User", model: Module, current_user: Class}
62
+
63
+ Trailblazer::Context(immutable) do |_original, mutable|
64
+ mutable
65
+ end
66
+ end
67
+ end
68
+
69
+ class ContextWithIndifferentAccessTest < Minitest::Spec
70
+ it do
71
+ flow_options = {}
72
+ circuit_options = {}
73
+
74
+ immutable = {model: Object, "policy" => Hash}
75
+
76
+ ctx = Trailblazer::Context.for_circuit(immutable, {}, [immutable, flow_options], **circuit_options)
77
+
78
+ _(ctx[:model]).must_equal Object
79
+ _(ctx["model"]).must_equal Object
80
+ _(ctx[:policy]).must_equal Hash
81
+ _(ctx["policy"]).must_equal Hash
82
+
83
+ ctx["contract.default"] = Module
84
+ _(ctx["contract.default"]).must_equal Module
85
+ _(ctx[:"contract.default"]).must_equal Module
86
+
87
+ # key?
88
+ _(ctx.key?("____contract.default")).must_equal false
89
+ _(ctx.key?("contract.default")).must_equal true
90
+ _(ctx.key?(:"contract.default")).must_equal true
91
+
92
+ # context in context
93
+ ctx2 = Trailblazer::Context.for_circuit(ctx, {}, [ctx, flow_options], **circuit_options)
94
+
95
+ _(ctx2[:model]).must_equal Object
96
+ _(ctx2["model"]).must_equal Object
97
+
98
+ ctx2["contract.default"] = Class
99
+ _(ctx2["contract.default"]).must_equal Class
100
+ _(ctx2[:"contract.default"]).must_equal Class
101
+
102
+ # key?
103
+ _(ctx2.key?("contract.default")).must_equal true
104
+ _(ctx2.key?(:"contract.default")).must_equal true
105
+ _(ctx2.key?("model")).must_equal true
106
+
107
+ # wrapped ctx doesn't change
108
+ _(ctx["contract.default"]).must_equal Module
109
+ _(ctx[:"contract.default"]).must_equal Module
110
+
111
+
112
+ ctx3 = ctx.merge("result" => false)
113
+
114
+ _(ctx3["contract.default"]).must_equal Module
115
+ _(ctx3[:"contract.default"]).must_equal Module
116
+ _(ctx3["result"]).must_equal false
117
+ _(ctx3[:result]).must_equal false
118
+ _(ctx3.key?("result")).must_equal true
119
+ _(ctx3.key?(:result)).must_equal true
120
+ end
121
+
122
+ it "Aliasable" do
123
+ flow_options = {context_alias: {"contract.default" => :contract, "result.default"=>:result, "trace.stack" => :stack}}
124
+ circuit_options = {}
125
+
126
+ immutable = {model: Object, "policy" => Hash}
127
+
128
+ ctx = Trailblazer::Context.for_circuit(immutable, {}, [immutable, flow_options], **circuit_options)
129
+
130
+ _(ctx[:model]).must_equal Object
131
+ _(ctx["model"]).must_equal Object
132
+ _(ctx[:policy]).must_equal Hash
133
+ _(ctx["policy"]).must_equal Hash
134
+
135
+ ctx["contract.default"] = Module
136
+ _(ctx["contract.default"]).must_equal Module
137
+ _(ctx[:"contract.default"]).must_equal Module
138
+
139
+ # alias
140
+ assert_nil ctx[:result]
141
+ assert_nil ctx["result"]
142
+
143
+ _(ctx[:contract]).must_equal Module
144
+
145
+ assert_nil ctx[:stack]
146
+
147
+ # Set an aliased property via setter
148
+ ctx["trace.stack"] = Object
149
+ _(ctx[:stack]).must_equal Object
150
+ _(ctx["trace.stack"]).must_equal Object
151
+
152
+ # key?
153
+ _(ctx.key?("____contract.default")).must_equal false
154
+ _(ctx.key?("contract.default")).must_equal true
155
+ _(ctx.key?(:"contract.default")).must_equal true
156
+ _(ctx.key?(:contract)).must_equal true
157
+ _(ctx.key?(:result)).must_equal false
158
+ _(ctx.key?(:stack)).must_equal true
159
+ _(ctx.key?("trace.stack")).must_equal true
160
+ _(ctx.key?(:"trace.stack")).must_equal true
161
+
162
+ # to_hash
163
+ _(ctx.to_hash).must_equal(:model=>Object, :policy=>Hash, :"contract.default"=>Module, :"trace.stack"=>Object, :contract=>Module, :stack=>Object)
164
+
165
+ # context in context
166
+ ctx2 = Trailblazer::Context.for_circuit(ctx, {}, [ctx, flow_options], **circuit_options)
167
+
168
+ _(ctx2.key?("____contract.default")).must_equal false
169
+ _(ctx2.key?("contract.default")).must_equal true
170
+ _(ctx2.key?(:"contract.default")).must_equal true
171
+ _(ctx2.key?(:contract)).must_equal true
172
+ _(ctx2.key?(:result)).must_equal false
173
+ _(ctx2.key?("result.default")).must_equal false
174
+ _(ctx2.key?(:stack)).must_equal true
175
+ _(ctx2.key?("trace.stack")).must_equal true
176
+ _(ctx2.key?(:"trace.stack")).must_equal true
177
+
178
+ # Set aliased in new context via setter
179
+ ctx2["result.default"] = Class
180
+
181
+ _(ctx2[:result]).must_equal Class
182
+ _(ctx2[:"result.default"]).must_equal Class
183
+
184
+ _(ctx2.key?("result.default")).must_equal true
185
+ _(ctx2.key?(:"result.default")).must_equal true
186
+ _(ctx2.key?(:result)).must_equal true
187
+
188
+ # todo: TEST flow_options={context_class: SomethingElse}
189
+ end
190
+
191
+ it ".build provides default args" do
192
+ immutable = {model: Object, "policy.default" => Hash}
193
+
194
+ # {Aliasing#initialize}
195
+ ctx = Trailblazer::Context::IndifferentAccess.new(immutable, {}, context_alias: {"policy.default" => :policy})
196
+
197
+ _(ctx[:model]).must_equal Object
198
+ _(ctx["model"]).must_equal Object
199
+ _(ctx[:policy]).must_equal Hash
200
+
201
+ ctx2 = ctx.merge(result: :success)
202
+
203
+
204
+ _(ctx2[:model]).must_equal Object
205
+ _(ctx2["model"]).must_equal Object
206
+ _(ctx2[:policy]).must_equal Hash
207
+ _(ctx2[:result]).must_equal :success
208
+ end
209
+ end
210
+
211
+ # TODO: test overriding Context.implementation.
@@ -0,0 +1,186 @@
1
+ require "test_helper"
2
+
3
+ class OptionTest < Minitest::Spec
4
+ def assert_result(result, block = nil)
5
+ _(result).must_equal([{a: 1}, 2, {b: 3}, block])
6
+
7
+ _(positional.inspect).must_equal %({:a=>1})
8
+ _(keywords.inspect).must_equal %({:a=>2, :b=>3})
9
+ end
10
+
11
+ describe "positional and kws" do
12
+ class Step
13
+ def with_positional_and_keywords(options, a: nil, **more_options, &block)
14
+ [options, a, more_options, block]
15
+ end
16
+ end
17
+
18
+ WITH_POSITIONAL_AND_KEYWORDS = ->(options, a: nil, **more_options, &block) do
19
+ [options, a, more_options, block]
20
+ end
21
+
22
+ class WithPositionalAndKeywords
23
+ def self.call(options, a: nil, **more_options, &block)
24
+ [options, a, more_options, block]
25
+ end
26
+ end
27
+
28
+ let(:positional) { {a: 1} }
29
+ let(:keywords) { {a: 2, b: 3} }
30
+
31
+ let(:block) { ->(*) { snippet } }
32
+
33
+ describe ":method" do
34
+ let(:option) { Trailblazer::Option(:with_positional_and_keywords) }
35
+
36
+ it "passes through all args" do
37
+ step = Step.new
38
+
39
+ # positional = { a: 1 }
40
+ # keywords = { a: 2, b: 3 }
41
+ assert_result option.(positional, keywords, exec_context: step)
42
+ end
43
+
44
+ it "allows passing a block, too" do
45
+ step = Step.new
46
+
47
+ assert_result option.(positional, keywords, {exec_context: step}, &block), block
48
+ end
49
+ end
50
+
51
+ describe "lambda" do
52
+ let(:option) { Trailblazer::Option(WITH_POSITIONAL_AND_KEYWORDS) }
53
+
54
+ it "-> {} lambda" do
55
+ assert_result option.(positional, keywords, {})
56
+ end
57
+
58
+ it "allows passing a block, too" do
59
+ assert_result option.(positional, keywords, {}, &block), block
60
+ end
61
+
62
+ it "doesn't mind :exec_context" do
63
+ assert_result option.(positional, keywords, exec_context: "bogus")
64
+ end
65
+ end
66
+
67
+ describe "Callable" do
68
+ let(:option) { Trailblazer::Option(WithPositionalAndKeywords) }
69
+
70
+ it "passes through all args" do
71
+ assert_result option.(positional, keywords, exec_context: nil)
72
+ end
73
+
74
+ it "allows passing a block, too" do
75
+ assert_result option.(positional, keywords, {exec_context: nil}, &block), block
76
+ end
77
+ end
78
+ end
79
+
80
+ describe "positionals" do
81
+ def assert_result_pos(result)
82
+ _(result).must_equal([1, 2, [3, 4]])
83
+ _(positionals).must_equal [1, 2, 3, 4]
84
+ end
85
+
86
+ class Step
87
+ def with_positionals(a, b, *args)
88
+ [a, b, args]
89
+ end
90
+ end
91
+
92
+ WITH_POSITIONALS = ->(a, b, *args) do
93
+ [a, b, args]
94
+ end
95
+
96
+ class WithPositionals
97
+ def self.call(a, b, *args)
98
+ [a, b, args]
99
+ end
100
+ end
101
+
102
+ let(:positionals) { [1, 2, 3, 4] }
103
+
104
+ it ":method" do
105
+ step = Step.new
106
+
107
+ option = Trailblazer::Option(:with_positionals)
108
+
109
+ assert_result_pos option.(*positionals, exec_context: step)
110
+ end
111
+
112
+ it "-> {} lambda" do
113
+ option = Trailblazer::Option(WITH_POSITIONALS)
114
+
115
+ assert_result_pos option.(*positionals, exec_context: "something")
116
+ end
117
+
118
+ it "callable" do
119
+ option = Trailblazer::Option(WithPositionals)
120
+
121
+ assert_result_pos option.(*positionals, exec_context: "something")
122
+ end
123
+ end
124
+
125
+ describe "Option::KW" do
126
+ def assert_result_kws(result)
127
+ _(result).must_equal([{a: 1, b: 2, c: 3}, 1, 2, {c: 3}])
128
+ end
129
+
130
+ class Step
131
+ def with_kws(options, a: nil, b: nil, **rest)
132
+ [options, a, b, rest]
133
+ end
134
+ end
135
+
136
+ module Task
137
+ def self.with_kws(options, a: nil, b: nil, **rest)
138
+ [options, a, b, rest]
139
+ end
140
+ end
141
+
142
+ WITH_KWS = ->(options, a: nil, b: nil, **rest) do
143
+ [options, a, b, rest]
144
+ end
145
+
146
+ class WithKWs
147
+ def self.call(options, a: nil, b: nil, **rest)
148
+ [options, a, b, rest]
149
+ end
150
+ end
151
+
152
+ let(:options) { {a: 1, b: 2, c: 3} }
153
+
154
+ it ":method" do
155
+ step = Step.new
156
+
157
+ option = Trailblazer::Option::KW(:with_kws)
158
+
159
+ assert_result_kws option.(options, exec_context: step)
160
+ end
161
+
162
+ it "Method instance" do
163
+ option = Trailblazer::Option::KW(Task.method(:with_kws))
164
+
165
+ assert_result_kws option.(options, {})
166
+ end
167
+
168
+ it "-> {} lambda" do
169
+ option = Trailblazer::Option::KW(WITH_KWS)
170
+
171
+ assert_result_kws option.(options, {})
172
+ end
173
+
174
+ it "lambda ignores :exec_context" do
175
+ option = Trailblazer::Option::KW(WITH_KWS)
176
+
177
+ assert_result_kws option.(options, exec_context: "something")
178
+ end
179
+
180
+ it "callable" do
181
+ option = Trailblazer::Option::KW(WithKWs)
182
+
183
+ assert_result_kws option.(options, {})
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,3 @@
1
+ require "trailblazer-context"
2
+
3
+ require "minitest/autorun"
@@ -1,6 +1,6 @@
1
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path("lib", __dir__)
2
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
- require 'trailblazer/context/version'
3
+ require "trailblazer/context/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "trailblazer-context"
@@ -8,21 +8,22 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Nick Sutterer"]
9
9
  spec.email = ["apotonick@gmail.com"]
10
10
 
11
- spec.summary = %q{Argument-specific data structures for Trailblazer.}
12
- spec.description = %q{Argument-specific data structures for Trailblazer such as Context, Option and ContainerChain.}
11
+ spec.summary = "Argument-specific data structures for Trailblazer."
12
+ spec.description = "Argument-specific data structures for Trailblazer such as Context, Option and ContainerChain."
13
13
  spec.homepage = "http://trailblazer.to/gems/workflow"
14
14
  spec.licenses = ["MIT"]
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
- f.match(%r{^(test|spec|features)/})
17
+ f.match(%r(^test/))
18
18
  end
19
- spec.bindir = "exe"
20
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.test_files = `git ls-files -z test`.split("\x0")
20
+
21
21
  spec.require_paths = ["lib"]
22
22
 
23
- spec.add_development_dependency "bundler", "~> 1.14"
24
- spec.add_development_dependency "rake", "~> 10.0"
25
- spec.add_development_dependency "minitest", "~> 5.0"
23
+ spec.add_development_dependency "bundler"
24
+ spec.add_development_dependency "minitest"
25
+ spec.add_development_dependency "rake"
26
26
 
27
- spec.required_ruby_version = '>= 2.0.0'
27
+ # maybe we could remove this?
28
+ spec.required_ruby_version = ">= 2.1.0"
28
29
  end
metadata CHANGED
@@ -1,57 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trailblazer-context
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Sutterer
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2017-12-14 00:00:00.000000000 Z
11
+ date: 2020-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.14'
19
+ version: '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: '1.14'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: minitest
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '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: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: minitest
42
+ name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '5.0'
47
+ version: '0'
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: '5.0'
54
+ version: '0'
55
55
  description: Argument-specific data structures for Trailblazer such as Context, Option
56
56
  and ContainerChain.
57
57
  email:
@@ -61,6 +61,7 @@ extensions: []
61
61
  extra_rdoc_files: []
62
62
  files:
63
63
  - ".gitignore"
64
+ - ".travis.yml"
64
65
  - CHANGES.md
65
66
  - Gemfile
66
67
  - LICENSE
@@ -69,8 +70,13 @@ files:
69
70
  - lib/trailblazer-context.rb
70
71
  - lib/trailblazer/container_chain.rb
71
72
  - lib/trailblazer/context.rb
73
+ - lib/trailblazer/context/aliasing.rb
74
+ - lib/trailblazer/context/indifferent_access.rb
72
75
  - lib/trailblazer/context/version.rb
73
76
  - lib/trailblazer/option.rb
77
+ - test/context_test.rb
78
+ - test/option_test.rb
79
+ - test/test_helper.rb
74
80
  - trailblazer-context.gemspec
75
81
  homepage: http://trailblazer.to/gems/workflow
76
82
  licenses:
@@ -84,16 +90,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
84
90
  requirements:
85
91
  - - ">="
86
92
  - !ruby/object:Gem::Version
87
- version: 2.0.0
93
+ version: 2.1.0
88
94
  required_rubygems_version: !ruby/object:Gem::Requirement
89
95
  requirements:
90
96
  - - ">="
91
97
  - !ruby/object:Gem::Version
92
98
  version: '0'
93
99
  requirements: []
94
- rubyforge_project:
95
- rubygems_version: 2.6.8
100
+ rubygems_version: 3.0.3
96
101
  signing_key:
97
102
  specification_version: 4
98
103
  summary: Argument-specific data structures for Trailblazer.
99
- test_files: []
104
+ test_files:
105
+ - test/context_test.rb
106
+ - test/option_test.rb
107
+ - test/test_helper.rb