simple-service 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # returns the comment for an action
2
- class ::Simple::Service::Action::Comment
2
+ class ::Simple::Service::Action::Comment # :nodoc:
3
3
  attr_reader :short
4
4
  attr_reader :full
5
5
 
@@ -16,7 +16,7 @@ module ::Simple::Service::Action::MethodReflection # :nodoc:
16
16
  method = service.instance_method(method_id)
17
17
  parameters = method.parameters
18
18
 
19
- # method parameters with a :key mode are optional keyword arguments. We only
19
+ # method parameters with a :key mode are optional named arguments. We only
20
20
  # support defaults for those - if there are none we abort here already.
21
21
  keys = parameters.map { |mode, name| name if mode == :key }.compact
22
22
  return parameters if keys.empty?
@@ -59,7 +59,7 @@ module ::Simple::Service::Action::MethodReflection # :nodoc:
59
59
  # values for these arguments doesn't matter at all.
60
60
  args = method.parameters.select { |mode, _name| mode == :req }
61
61
 
62
- # Add a hash with all required keyword arguments
62
+ # Add a hash with all required named arguments
63
63
  required_keyword_args = method.parameters.each_with_object({}) do |(mode, name), hsh|
64
64
  hsh[name] = :anything if mode == :keyreq
65
65
  end
@@ -7,10 +7,10 @@ class ::Simple::Service::Action::Parameter
7
7
  end
8
8
 
9
9
  def keyword?
10
- [:key, :keyreq].include? @kind
10
+ [:keyreq, :key].include? @kind
11
11
  end
12
12
 
13
- def anonymous?
13
+ def positional?
14
14
  [:req, :opt].include? @kind
15
15
  end
16
16
 
@@ -22,10 +22,6 @@ class ::Simple::Service::Action::Parameter
22
22
  @kind == :rest
23
23
  end
24
24
 
25
- def optional?
26
- !required?
27
- end
28
-
29
25
  attr_reader :name
30
26
  attr_reader :kind
31
27
 
@@ -1,39 +1,65 @@
1
1
  module Simple::Service
2
- class Context
3
- class ReadOnlyError < RuntimeError
4
- def initialize(key)
5
- super "Cannot overwrite existing context setting #{key.inspect}"
6
- end
7
- end
2
+ # Returns the current context.
3
+ def self.context
4
+ Thread.current[:"Simple::Service.context"]
5
+ end
8
6
 
9
- def initialize
10
- @hsh = {}
11
- end
7
+ # yields a block with a given context, and restores the previous context
8
+ # object afterwards.
9
+ def self.with_context(ctx = nil, &block)
10
+ old_ctx = Thread.current[:"Simple::Service.context"]
11
+ new_ctx = old_ctx ? old_ctx.merge(ctx) : Context.new(ctx)
12
12
 
13
- private
13
+ Thread.current[:"Simple::Service.context"] = new_ctx
14
14
 
15
- def [](key)
16
- @hsh[key]
17
- end
15
+ block.call
16
+ ensure
17
+ Thread.current[:"Simple::Service.context"] = old_ctx
18
+ end
19
+ end
18
20
 
19
- def []=(key, value)
20
- existing_value = @hsh[key]
21
+ module Simple::Service
22
+ # A context object
23
+ #
24
+ # Each service executes with a current context. The system manages a stack of
25
+ # contexts; whenever a service execution is done the current context is reverted
26
+ # to its previous value.
27
+ #
28
+ # A context object can store a large number of values; the only way to set or
29
+ # access a value is via getters and setters. These are implemented via
30
+ # +method_missing(..)+.
31
+ #
32
+ # Also, once a value is set in the context it is not possible to change or
33
+ # unset it.
34
+ class Context
35
+ def initialize(hsh = {}) # :nodoc:
36
+ @hsh = hsh
37
+ end
21
38
 
22
- unless existing_value.nil? || existing_value == value
23
- raise ReadOnlyError, key
24
- end
39
+ # returns a new Context object, which merges the values in the +overlay+
40
+ # argument (which must be a Hash or nil) with the values in this context.
41
+ #
42
+ # The overlay is allowed to change values in the current context.
43
+ #
44
+ # It does not change this context.
45
+ def merge(overlay)
46
+ expect! overlay => [Hash, nil]
25
47
 
26
- @hsh[key] = value
48
+ overlay ||= {}
49
+ new_context_hsh = @hsh.merge(overlay)
50
+ ::Simple::Service::Context.new(new_context_hsh)
27
51
  end
28
52
 
29
- IDENTIFIER_PATTERN = "[a-z][a-z0-9_]*"
30
- IDENTIFIER_REGEXP = Regexp.compile("\\A#{IDENTIFIER_PATTERN}\\z")
31
- ASSIGNMENT_REGEXP = Regexp.compile("\\A(#{IDENTIFIER_PATTERN})=\\z")
53
+ private
54
+
55
+ IDENTIFIER_PATTERN = "[a-z][a-z0-9_]*" # :nodoc:
56
+ IDENTIFIER_REGEXP = Regexp.compile("\\A#{IDENTIFIER_PATTERN}\\z") # :nodoc:
57
+ ASSIGNMENT_REGEXP = Regexp.compile("\\A(#{IDENTIFIER_PATTERN})=\\z") # :nodoc:
32
58
 
33
59
  def method_missing(sym, *args, &block)
34
- if block
35
- super
36
- elsif args.count == 0 && sym =~ IDENTIFIER_REGEXP
60
+ raise ArgumentError, "Block given" if block
61
+
62
+ if args.count == 0 && sym =~ IDENTIFIER_REGEXP
37
63
  self[sym]
38
64
  elsif args.count == 1 && sym =~ ASSIGNMENT_REGEXP
39
65
  self[$1.to_sym] = args.first
@@ -43,10 +69,26 @@ module Simple::Service
43
69
  end
44
70
 
45
71
  def respond_to_missing?(sym, include_private = false)
46
- return true if IDENTIFIER_REGEXP.maptch?(sym)
47
- return true if ASSIGNMENT_REGEXP.maptch?(sym)
72
+ # :nocov:
73
+ return true if IDENTIFIER_REGEXP.match?(sym)
74
+ return true if ASSIGNMENT_REGEXP.match?(sym)
48
75
 
49
76
  super
77
+ # :nocov:
78
+ end
79
+
80
+ def [](key)
81
+ @hsh[key]
82
+ end
83
+
84
+ def []=(key, value)
85
+ existing_value = @hsh[key]
86
+
87
+ unless existing_value.nil? || existing_value == value
88
+ raise ::Simple::Service::ContextReadOnlyError, key
89
+ end
90
+
91
+ @hsh[key] = value
50
92
  end
51
93
  end
52
94
  end
@@ -0,0 +1,54 @@
1
+ module Simple::Service
2
+ # Will be raised by ::Simple::Service.action.
3
+ class NoSuchAction < ::ArgumentError
4
+ attr_reader :service, :name
5
+ def initialize(service, name)
6
+ @service, @name = service, name
7
+ end
8
+
9
+ def to_s
10
+ action_names = ::Simple::Service.actions(service).keys.sort
11
+ informal = "service #{service} has these actions: #{action_names.map(&:inspect).join(", ")}"
12
+ "No such action #{name.inspect}; #{informal}"
13
+ end
14
+ end
15
+
16
+ class ArgumentError < ::ArgumentError
17
+ end
18
+
19
+ class MissingArguments < ArgumentError
20
+ attr_reader :action
21
+ attr_reader :parameters
22
+
23
+ def initialize(action, parameters)
24
+ @action, @parameters = action, parameters
25
+ end
26
+
27
+ def to_s
28
+ "#{action}: missing argument(s): #{parameters.map(&:to_s).join(", ")}"
29
+ end
30
+ end
31
+
32
+ class ExtraArguments < ArgumentError
33
+ attr_reader :action
34
+ attr_reader :arguments
35
+
36
+ def initialize(action, arguments)
37
+ @action, @arguments = action, arguments
38
+ end
39
+
40
+ def to_s
41
+ str = @arguments.map(&:inspect).join(", ")
42
+ "#{action}: extra argument(s) #{str}"
43
+ end
44
+ end
45
+
46
+ class ContextMissingError < ::StandardError
47
+ end
48
+
49
+ class ContextReadOnlyError < ::StandardError
50
+ def initialize(key)
51
+ super "Cannot overwrite existing context setting #{key.inspect}"
52
+ end
53
+ end
54
+ end
@@ -1,5 +1,8 @@
1
- class Simple::Service
2
- module GemHelper
1
+ module Simple # :nodoc:
2
+ end
3
+
4
+ module Simple::Service
5
+ module GemHelper # :nodoc:
3
6
  extend self
4
7
 
5
8
  def version(name)
@@ -14,9 +17,11 @@ class Simple::Service
14
17
  def unreleased?(spec)
15
18
  return false unless defined?(Bundler::Source::Gemspec)
16
19
  return true if spec.source.is_a?(::Bundler::Source::Gemspec)
20
+ # :nocov:
17
21
  return true if spec.source.is_a?(::Bundler::Source::Path)
18
22
 
19
23
  false
24
+ # :nocov:
20
25
  end
21
26
  end
22
27
 
data/scripts/stats CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/bin/bash
2
2
 
3
- cloc $(find lib/ -name *rb) | grep -E 'Language|Ruby' | sed 's-Language- -'
3
+ # cloc $(find lib/ -name *rb) | grep -E 'Language|Ruby' | sed 's-Language- -'
4
+ cloc --by-file --docstring-as-code --quiet --hide-rate $(find lib/ -name *rb)
4
5
  printf "\n"
@@ -0,0 +1,166 @@
1
+ # rubocop:disable Style/WordArray
2
+
3
+ require "spec_helper"
4
+
5
+ describe "Simple::Service.invoke2" do
6
+ # the context to use in the around hook below. By default this is nil -
7
+ # which gives us an empty context.
8
+ let(:context) { nil }
9
+
10
+ around do |example|
11
+ ::Simple::Service.with_context(context) { example.run }
12
+ end
13
+
14
+ let(:service) { InvokeTestService }
15
+ let(:action) { nil }
16
+
17
+ # a shortcut
18
+ def invoke2!(**hsh)
19
+ @actual = ::Simple::Service.invoke2(service, action, args: hsh)
20
+ # rescue ::StandardError => e
21
+ rescue ::Simple::Service::ArgumentError => e
22
+ @actual = e
23
+ end
24
+
25
+ attr_reader :actual
26
+
27
+ context "calling an action w/o parameters" do
28
+ # reminder: this is the definition of no_params
29
+ #
30
+ # def no_params
31
+ # "service2 return"
32
+ # end
33
+
34
+ let(:action) { :no_params }
35
+
36
+ context "calling without args" do
37
+ it "runs the action" do
38
+ invoke2!
39
+ expect(actual).to eq("service2 return")
40
+ end
41
+ end
42
+
43
+ context "calling with extra named args" do
44
+ it "ignores extra args" do
45
+ invoke2!(foo: "foo", bar: "bar")
46
+ expect(actual).to eq("service2 return")
47
+ end
48
+ end
49
+ end
50
+
51
+ context "calling an action w/positional parameters" do
52
+ # reminder: this is the definition of positional_params
53
+ #
54
+ # def positional_params(a, b, c = "speed-of-light", e = 2.781)
55
+ # [a, b, c, e]
56
+ # end
57
+
58
+ let(:action) { :positional_params }
59
+
60
+ context "without args" do
61
+ it "raises MissingArguments" do
62
+ invoke2!
63
+ expect(actual).to be_a(::Simple::Service::MissingArguments)
64
+ expect(actual.to_s).to match(/\ba, b\b/)
65
+ end
66
+ end
67
+
68
+ context "with the required number of args" do
69
+ it "runs" do
70
+ invoke2!(a: "foo", b: "bar")
71
+ expect(actual).to eq(["foo", "bar", "speed-of-light", 2.781])
72
+ end
73
+ end
74
+
75
+ context "with the allowed number of args" do
76
+ it "runs" do
77
+ invoke2!(a: "foo", b: "bar", c: "baz", e: "number4")
78
+ expect(actual).to eq(%w[foo bar baz number4])
79
+ end
80
+ end
81
+
82
+ context "calling with extra named args" do
83
+ it "ignores extra args" do
84
+ invoke2!(a: "foo", b: "bar", c: "baz", e: "number4", extra3: 3)
85
+ expect(actual).to eq(%w[foo bar baz number4])
86
+ end
87
+ end
88
+ end
89
+
90
+ context "calling an action w/named parameters" do
91
+ # reminder: this is the definition of named_params
92
+ #
93
+ # def named_params(a:, b:, c: "speed-of-light", e: 2.781)
94
+ # [a, b, c, e]
95
+ # end
96
+
97
+ let(:action) { :named_params }
98
+
99
+ context "without args" do
100
+ it "raises MissingArguments" do
101
+ invoke2!
102
+ expect(actual).to be_a(::Simple::Service::MissingArguments)
103
+ expect(actual.to_s).to match(/\ba, b\b/)
104
+ end
105
+ end
106
+
107
+ context "with the required number of args" do
108
+ it "runs" do
109
+ invoke2!(a: "foo", b: "bar")
110
+ expect(actual).to eq(["foo", "bar", "speed-of-light", 2.781])
111
+ end
112
+ end
113
+
114
+ context "with the allowed number of args" do
115
+ it "runs" do
116
+ invoke2!(a: "foo", b: "bar", c: "baz", e: "number4")
117
+ expect(actual).to eq(%w[foo bar baz number4])
118
+ end
119
+ end
120
+
121
+ context "with extra named args" do
122
+ it "ignores extra args" do
123
+ invoke2!(a: "foo", b: "bar", c: "baz", extra3: 3)
124
+ expect(actual).to eq(["foo", "bar", "baz", 2.781])
125
+ end
126
+ end
127
+ end
128
+
129
+ context "calling an action w/mixed and optional parameters" do
130
+ # reminder: this is the definition of named_params
131
+ #
132
+ # def mixed_optional_params(a, b = "default-b", c = "speed-of-light", e: 2.781)
133
+ # [a, b, c, e]
134
+ # end
135
+
136
+ let(:action) { :mixed_optional_params }
137
+
138
+ context "without args" do
139
+ it "raises MissingArguments" do
140
+ invoke2!
141
+ expect(actual).to be_a(::Simple::Service::MissingArguments)
142
+ end
143
+ end
144
+
145
+ context "with the required number of args" do
146
+ it "runs" do
147
+ invoke2!(a: "foo")
148
+ expect(actual).to eq(["foo", "default-b", "speed-of-light", 2.781])
149
+ end
150
+ end
151
+
152
+ context "with the allowed number of args" do
153
+ it "runs" do
154
+ invoke2!(a: "foo", b: "bar", c: "baz", e: "number4")
155
+ expect(actual).to eq(%w[foo bar baz number4])
156
+ end
157
+ end
158
+
159
+ context "with extra named args" do
160
+ it "ignores extra args" do
161
+ invoke2!(a: "foo", b: "bar", c: "baz", e: "number4", extra3: 3)
162
+ expect(actual).to eq(["foo", "bar", "baz", "number4"])
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,266 @@
1
+ # rubocop:disable Style/WordArray
2
+
3
+ require "spec_helper"
4
+
5
+ describe "Simple::Service.invoke" do
6
+ # the context to use in the around hook below. By default this is nil -
7
+ # which gives us an empty context.
8
+ let(:context) { nil }
9
+
10
+ around do |example|
11
+ ::Simple::Service.with_context(context) { example.run }
12
+ end
13
+
14
+ let(:service) { InvokeTestService }
15
+ let(:action) { nil }
16
+
17
+ # a shortcut
18
+ def invoke!(*args, **keywords)
19
+ @actual = ::Simple::Service.invoke(service, action, *args, **keywords)
20
+ # rescue ::StandardError => e
21
+ rescue ::Simple::Service::ArgumentError => e
22
+ @actual = e
23
+ end
24
+
25
+ attr_reader :actual
26
+
27
+ # when calling #invoke using positional arguments they will be matched against
28
+ # positional arguments of the invoked method - but they will not be matched
29
+ # against named arguments.
30
+ #
31
+ # When there are not enough positional arguments to match the number of required
32
+ # positional arguments of the method we raise an ArgumentError.
33
+ #
34
+ # When there are more positional arguments provided than the number accepted
35
+ # by the method we raise an ArgumentError.
36
+
37
+ context "calling an action w/o parameters" do
38
+ # reminder: this is the definition of no_params
39
+ #
40
+ # def no_params
41
+ # "service2 return"
42
+ # end
43
+
44
+ let(:action) { :no_params }
45
+
46
+ context "calling without args" do
47
+ it "runs the action" do
48
+ invoke!
49
+ expect(actual).to eq("service2 return")
50
+ end
51
+ end
52
+
53
+ context "calling with extra positional args" do
54
+ it "raises ExtraArguments" do
55
+ invoke!("foo", "bar")
56
+ expect(actual).to be_a(::Simple::Service::ExtraArguments)
57
+ expect(actual.to_s).to match(/"foo", "bar"/)
58
+ end
59
+ end
60
+
61
+ context "calling with extra named args" do
62
+ it "ignores extra args" do
63
+ invoke!(foo: "foo", bar: "bar")
64
+ expect(actual).to eq("service2 return")
65
+ end
66
+ end
67
+
68
+ context "calling with an additional hash arg" do
69
+ xit "ignores extra args" do
70
+ args = []
71
+ args.push foo: "foo", bar: "bar"
72
+ invoke!(*args)
73
+
74
+ expect(actual).to be_a(::Simple::Service::ExtraArguments)
75
+ end
76
+ end
77
+ end
78
+
79
+ context "calling an action w/positional parameters" do
80
+ # reminder: this is the definition of positional_params
81
+ #
82
+ # def positional_params(a, b, c = "speed-of-light", e = 2.781)
83
+ # [a, b, c, e]
84
+ # end
85
+
86
+ let(:action) { :positional_params }
87
+
88
+ context "without args" do
89
+ it "raises MissingArguments" do
90
+ invoke!
91
+ expect(actual).to be_a(::Simple::Service::MissingArguments)
92
+ end
93
+ end
94
+
95
+ context "with the required number of args" do
96
+ it "runs" do
97
+ invoke!("foo", "bar")
98
+ expect(actual).to eq(["foo", "bar", "speed-of-light", 2.781])
99
+ end
100
+ end
101
+
102
+ context "with the allowed number of args" do
103
+ it "runs" do
104
+ invoke!("foo", "bar", "baz", "number4")
105
+ expect(actual).to eq(%w[foo bar baz number4])
106
+ end
107
+ end
108
+
109
+ context "with more than the allowed number of args" do
110
+ it "raises ExtraArguments" do
111
+ invoke!("foo", "bar", "baz", "number4", "extra")
112
+ expect(actual).to be_a(::Simple::Service::ExtraArguments)
113
+ end
114
+ end
115
+
116
+ context "calling with extra named args" do
117
+ it "ignores extra args" do
118
+ invoke!("foo", "bar", "baz", extra3: 3)
119
+ expect(actual).to eq(["foo", "bar", "baz", 2.781])
120
+ end
121
+ end
122
+
123
+ context "calling with an additional hash arg" do
124
+ xit "raises ExtraArguments" do
125
+ args = ["foo", "bar", "baz", extra3: 3]
126
+ invoke!(*args)
127
+ expect(actual).to be_a(::Simple::Service::ExtraArguments)
128
+ end
129
+ end
130
+ end
131
+
132
+ context "calling an action w/named parameters" do
133
+ # reminder: this is the definition of named_params
134
+ #
135
+ # def named_params(a:, b:, c: "speed-of-light", e: 2.781)
136
+ # [a, b, c, e]
137
+ # end
138
+
139
+ let(:action) { :named_params }
140
+
141
+ context "without args" do
142
+ it "raises MissingArguments" do
143
+ invoke!
144
+ expect(actual).to be_a(::Simple::Service::MissingArguments)
145
+ end
146
+ end
147
+
148
+ context "with the required number of args" do
149
+ it "runs" do
150
+ invoke!(a: "foo", b: "bar")
151
+ expect(actual).to eq(["foo", "bar", "speed-of-light", 2.781])
152
+ end
153
+ end
154
+
155
+ context "with the allowed number of args" do
156
+ it "runs" do
157
+ invoke!(a: "foo", b: "bar", c: "baz", e: "number4")
158
+ expect(actual).to eq(%w[foo bar baz number4])
159
+ end
160
+ end
161
+
162
+ context "with more than the allowed number of args" do
163
+ it "runs" do
164
+ invoke!("foo", "bar", "baz", "number4", "extra")
165
+ expect(actual).to be_a(::Simple::Service::ExtraArguments)
166
+ end
167
+ end
168
+
169
+ context "with extra named args" do
170
+ it "ignores extra args" do
171
+ invoke!(a: "foo", b: "bar", c: "baz", extra3: 3)
172
+ expect(actual).to eq(["foo", "bar", "baz", 2.781])
173
+ end
174
+ end
175
+ end
176
+
177
+ context "calling an action w/mixed and optional parameters" do
178
+ # reminder: this is the definition of named_params
179
+ #
180
+ # def mixed_optional_params(a, b = "default-b", c = "speed-of-light", e: 2.781)
181
+ # [a, b, c, e]
182
+ # end
183
+
184
+ let(:action) { :mixed_optional_params }
185
+
186
+ context "without args" do
187
+ it "raises MissingArguments" do
188
+ invoke!
189
+ expect(actual).to be_a(::Simple::Service::MissingArguments)
190
+ end
191
+ end
192
+
193
+ context "with the required number of args" do
194
+ it "runs" do
195
+ invoke!("foo")
196
+ expect(actual).to eq(["foo", "default-b", "speed-of-light", 2.781])
197
+ end
198
+ end
199
+
200
+ context "with the allowed number of args" do
201
+ it "runs" do
202
+ invoke!("foo", "bar", "baz", e: "number4")
203
+ expect(actual).to eq(%w[foo bar baz number4])
204
+ end
205
+ end
206
+
207
+ context "with more than the allowed number of args" do
208
+ it "runs" do
209
+ invoke!("foo", "bar", "baz", "extra", e: "number4")
210
+ expect(actual).to be_a(::Simple::Service::ExtraArguments)
211
+ end
212
+ end
213
+
214
+ context "with extra named args" do
215
+ it "ignores extra args" do
216
+ invoke!("foo", "bar", "baz", e: "number4", extra3: 3)
217
+ expect(actual).to eq(["foo", "bar", "baz", "number4"])
218
+ end
219
+ end
220
+ end
221
+
222
+ context "calling an action w/mixed and variadic parameters" do
223
+ # reminder: this is the definition of variadic_params
224
+ #
225
+ # def variadic_params(a, b = "queen bee", *args, e: 2.781)
226
+ # [a, b, args, e]
227
+ # end
228
+
229
+ let(:action) { :variadic_params }
230
+
231
+ context "without args" do
232
+ it "raises MissingArguments" do
233
+ invoke!
234
+ expect(actual).to be_a(::Simple::Service::MissingArguments)
235
+ end
236
+ end
237
+
238
+ context "with the required number of args" do
239
+ it "runs" do
240
+ invoke!("foo")
241
+ expect(actual).to eq(["foo", "queen bee", [], 2.781])
242
+ end
243
+ end
244
+
245
+ context "with the allowed number of args" do
246
+ it "runs" do
247
+ invoke!("foo", "bar", "baz", e: "number4")
248
+ expect(actual).to eq(["foo", "bar", ["baz"], "number4"])
249
+ end
250
+ end
251
+
252
+ context "with more than the allowed number of args" do
253
+ it "runs" do
254
+ invoke!("foo", "bar", "baz", "extra", e: "number4")
255
+ expect(actual).to eq(["foo", "bar", ["baz", "extra"], "number4"])
256
+ end
257
+ end
258
+
259
+ context "with extra named args" do
260
+ it "ignores extra args" do
261
+ invoke!("foo", "bar", "baz", e: "number4", extra3: 3)
262
+ expect(actual).to eq(["foo", "bar", ["baz"], "number4"])
263
+ end
264
+ end
265
+ end
266
+ end