simple-service 0.1.1 → 0.1.6

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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -2
  3. data/.rubocop.yml +7 -0
  4. data/.tm_properties +1 -1
  5. data/Makefile +8 -0
  6. data/README.md +68 -1
  7. data/TODO.txt +3 -0
  8. data/VERSION +1 -1
  9. data/doc/Simple.html +117 -0
  10. data/doc/Simple/Service.html +865 -0
  11. data/doc/Simple/Service/Action.html +923 -0
  12. data/doc/Simple/Service/Action/Comment.html +451 -0
  13. data/doc/Simple/Service/Action/Comment/Extractor.html +347 -0
  14. data/doc/Simple/Service/Action/MethodReflection.html +285 -0
  15. data/doc/Simple/Service/Action/Parameter.html +816 -0
  16. data/doc/Simple/Service/ArgumentError.html +128 -0
  17. data/doc/Simple/Service/ClassMethods.html +187 -0
  18. data/doc/Simple/Service/Context.html +379 -0
  19. data/doc/Simple/Service/ContextMissingError.html +124 -0
  20. data/doc/Simple/Service/ContextReadOnlyError.html +206 -0
  21. data/doc/Simple/Service/ExtraArguments.html +428 -0
  22. data/doc/Simple/Service/GemHelper.html +190 -0
  23. data/doc/Simple/Service/MissingArguments.html +426 -0
  24. data/doc/Simple/Service/NoSuchAction.html +433 -0
  25. data/doc/_index.html +274 -0
  26. data/doc/class_list.html +51 -0
  27. data/doc/css/common.css +1 -0
  28. data/doc/css/full_list.css +58 -0
  29. data/doc/css/style.css +496 -0
  30. data/doc/file.README.html +146 -0
  31. data/doc/file.TODO.html +70 -0
  32. data/doc/file_list.html +61 -0
  33. data/doc/frames.html +17 -0
  34. data/doc/index.html +146 -0
  35. data/doc/js/app.js +303 -0
  36. data/doc/js/full_list.js +216 -0
  37. data/doc/js/jquery.js +4 -0
  38. data/doc/method_list.html +483 -0
  39. data/doc/top-level-namespace.html +110 -0
  40. data/lib/simple/service.rb +161 -56
  41. data/lib/simple/service/action.rb +104 -107
  42. data/lib/simple/service/action/comment.rb +3 -1
  43. data/lib/simple/service/action/method_reflection.rb +3 -3
  44. data/lib/simple/service/action/parameter.rb +3 -7
  45. data/lib/simple/service/context.rb +69 -27
  46. data/lib/simple/service/errors.rb +54 -0
  47. data/lib/simple/service/version.rb +7 -2
  48. data/scripts/stats +2 -1
  49. data/spec/simple/service/action_invoke3_spec.rb +266 -0
  50. data/spec/simple/service/action_invoke_spec.rb +237 -0
  51. data/spec/simple/service/action_spec.rb +51 -0
  52. data/spec/simple/service/context_spec.rb +39 -7
  53. data/spec/simple/service/service_spec.rb +158 -0
  54. data/spec/simple/service/version_spec.rb +7 -0
  55. data/spec/spec_helper.rb +15 -2
  56. data/spec/support/spec_services.rb +58 -0
  57. metadata +48 -6
  58. data/lib/simple.rb +0 -2
  59. data/spec/support/004_simplecov.rb +0 -13
@@ -1,5 +1,7 @@
1
+ # rubocop:disable Metrics/AbcSize
2
+
1
3
  # returns the comment for an action
2
- class ::Simple::Service::Action::Comment
4
+ class ::Simple::Service::Action::Comment # @private
3
5
  attr_reader :short
4
6
  attr_reader :full
5
7
 
@@ -1,6 +1,6 @@
1
1
  # rubocop:disable Metrics/AbcSize
2
2
 
3
- module ::Simple::Service::Action::MethodReflection # :nodoc:
3
+ module ::Simple::Service::Action::MethodReflection # @private
4
4
  extend self
5
5
 
6
6
  #
@@ -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
 
@@ -40,7 +36,7 @@ class ::Simple::Service::Action::Parameter
40
36
  expect! default_value.length => [0, 1]
41
37
 
42
38
  @kind = kind
43
- @name = name
39
+ @name = name.to_s
44
40
  @default_value = default_value[0]
45
41
  end
46
42
  end
@@ -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 = nil) # @private
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_]*" # @private
56
+ IDENTIFIER_REGEXP = Regexp.compile("\\A#{IDENTIFIER_PATTERN}\\z") # @private
57
+ ASSIGNMENT_REGEXP = Regexp.compile("\\A(#{IDENTIFIER_PATTERN})=\\z") # @private
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 # @private
2
+ end
3
+
4
+ module Simple::Service
5
+ module GemHelper # @private
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,266 @@
1
+ # rubocop:disable Style/WordArray
2
+
3
+ require "spec_helper"
4
+
5
+ describe "Simple::Service.invoke3" 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 invoke3!(*args, **keywords)
19
+ @actual = ::Simple::Service.invoke3(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 #invoke3 using positional arguments they will be matched against
28
+ # positional arguments of the invoke3d 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
+ invoke3!
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
+ invoke3!("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
+ invoke3!(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
+ invoke3!(*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
+ invoke3!
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
+ invoke3!("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
+ invoke3!("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
+ invoke3!("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
+ invoke3!("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
+ invoke3!(*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
+ invoke3!
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
+ invoke3!(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
+ invoke3!(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
+ invoke3!("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
+ invoke3!(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
+ invoke3!
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
+ invoke3!("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
+ invoke3!("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
+ invoke3!("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
+ invoke3!("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
+ invoke3!
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
+ invoke3!("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
+ invoke3!("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
+ invoke3!("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
+ invoke3!("foo", "bar", "baz", e: "number4", extra3: 3)
262
+ expect(actual).to eq(["foo", "bar", ["baz"], "number4"])
263
+ end
264
+ end
265
+ end
266
+ end