simple-service 0.1.1 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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