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.
- checksums.yaml +4 -4
- data/.gitignore +2 -2
- data/.rubocop.yml +7 -0
- data/.tm_properties +1 -1
- data/Makefile +8 -0
- data/README.md +68 -1
- data/TODO.txt +3 -0
- data/VERSION +1 -1
- data/doc/Simple.html +117 -0
- data/doc/Simple/Service.html +865 -0
- data/doc/Simple/Service/Action.html +923 -0
- data/doc/Simple/Service/Action/Comment.html +451 -0
- data/doc/Simple/Service/Action/Comment/Extractor.html +347 -0
- data/doc/Simple/Service/Action/MethodReflection.html +285 -0
- data/doc/Simple/Service/Action/Parameter.html +816 -0
- data/doc/Simple/Service/ArgumentError.html +128 -0
- data/doc/Simple/Service/ClassMethods.html +187 -0
- data/doc/Simple/Service/Context.html +379 -0
- data/doc/Simple/Service/ContextMissingError.html +124 -0
- data/doc/Simple/Service/ContextReadOnlyError.html +206 -0
- data/doc/Simple/Service/ExtraArguments.html +428 -0
- data/doc/Simple/Service/GemHelper.html +190 -0
- data/doc/Simple/Service/MissingArguments.html +426 -0
- data/doc/Simple/Service/NoSuchAction.html +433 -0
- data/doc/_index.html +274 -0
- data/doc/class_list.html +51 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +58 -0
- data/doc/css/style.css +496 -0
- data/doc/file.README.html +146 -0
- data/doc/file.TODO.html +70 -0
- data/doc/file_list.html +61 -0
- data/doc/frames.html +17 -0
- data/doc/index.html +146 -0
- data/doc/js/app.js +303 -0
- data/doc/js/full_list.js +216 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +483 -0
- data/doc/top-level-namespace.html +110 -0
- data/lib/simple/service.rb +161 -56
- data/lib/simple/service/action.rb +104 -107
- data/lib/simple/service/action/comment.rb +3 -1
- data/lib/simple/service/action/method_reflection.rb +3 -3
- data/lib/simple/service/action/parameter.rb +3 -7
- data/lib/simple/service/context.rb +69 -27
- data/lib/simple/service/errors.rb +54 -0
- data/lib/simple/service/version.rb +7 -2
- data/scripts/stats +2 -1
- data/spec/simple/service/action_invoke3_spec.rb +266 -0
- data/spec/simple/service/action_invoke_spec.rb +237 -0
- data/spec/simple/service/action_spec.rb +51 -0
- data/spec/simple/service/context_spec.rb +39 -7
- data/spec/simple/service/service_spec.rb +158 -0
- data/spec/simple/service/version_spec.rb +7 -0
- data/spec/spec_helper.rb +15 -2
- data/spec/support/spec_services.rb +58 -0
- metadata +48 -6
- data/lib/simple.rb +0 -2
- data/spec/support/004_simplecov.rb +0 -13
@@ -0,0 +1,110 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>
|
7
|
+
Top Level Namespace
|
8
|
+
|
9
|
+
— Documentation by YARD 0.9.20
|
10
|
+
|
11
|
+
</title>
|
12
|
+
|
13
|
+
<link rel="stylesheet" href="css/style.css" type="text/css" charset="utf-8" />
|
14
|
+
|
15
|
+
<link rel="stylesheet" href="css/common.css" type="text/css" charset="utf-8" />
|
16
|
+
|
17
|
+
<script type="text/javascript" charset="utf-8">
|
18
|
+
pathId = "";
|
19
|
+
relpath = '';
|
20
|
+
</script>
|
21
|
+
|
22
|
+
|
23
|
+
<script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
|
24
|
+
|
25
|
+
<script type="text/javascript" charset="utf-8" src="js/app.js"></script>
|
26
|
+
|
27
|
+
|
28
|
+
</head>
|
29
|
+
<body>
|
30
|
+
<div class="nav_wrap">
|
31
|
+
<iframe id="nav" src="class_list.html?1"></iframe>
|
32
|
+
<div id="resizer"></div>
|
33
|
+
</div>
|
34
|
+
|
35
|
+
<div id="main" tabindex="-1">
|
36
|
+
<div id="header">
|
37
|
+
<div id="menu">
|
38
|
+
|
39
|
+
<a href="_index.html">Index</a> »
|
40
|
+
|
41
|
+
|
42
|
+
<span class="title">Top Level Namespace</span>
|
43
|
+
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<div id="search">
|
47
|
+
|
48
|
+
<a class="full_list_link" id="class_list_link"
|
49
|
+
href="class_list.html">
|
50
|
+
|
51
|
+
<svg width="24" height="24">
|
52
|
+
<rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
|
53
|
+
<rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
|
54
|
+
<rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
|
55
|
+
</svg>
|
56
|
+
</a>
|
57
|
+
|
58
|
+
</div>
|
59
|
+
<div class="clear"></div>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<div id="content"><h1>Top Level Namespace
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
</h1>
|
67
|
+
<div class="box_info">
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
</div>
|
80
|
+
|
81
|
+
<h2>Defined Under Namespace</h2>
|
82
|
+
<p class="children">
|
83
|
+
|
84
|
+
|
85
|
+
<strong class="modules">Modules:</strong> <span class='object_link'><a href="Simple.html" title="Simple (module)">Simple</a></span>
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
</p>
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
</div>
|
101
|
+
|
102
|
+
<div id="footer">
|
103
|
+
Generated on Wed Dec 4 22:57:12 2019 by
|
104
|
+
<a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
|
105
|
+
0.9.20 (ruby-2.5.1).
|
106
|
+
</div>
|
107
|
+
|
108
|
+
</div>
|
109
|
+
</body>
|
110
|
+
</html>
|
data/lib/simple/service.rb
CHANGED
@@ -1,89 +1,194 @@
|
|
1
|
-
module Simple
|
2
|
-
class ArgumentError < ::ArgumentError
|
3
|
-
end
|
1
|
+
module Simple # @private
|
4
2
|
end
|
5
3
|
|
4
|
+
require "expectation"
|
5
|
+
require "logger"
|
6
|
+
|
7
|
+
require_relative "service/errors"
|
6
8
|
require_relative "service/action"
|
7
9
|
require_relative "service/context"
|
10
|
+
require_relative "service/version"
|
8
11
|
|
9
|
-
# The Simple::Service
|
12
|
+
# <b>The Simple::Service interface</b>
|
13
|
+
#
|
14
|
+
# This module implements the main API of the Simple::Service ruby gem.
|
15
|
+
#
|
16
|
+
# 1. <em>Marking a service module:</em> To turn a target module as a service module one must include <tt>Simple::Service</tt>
|
17
|
+
# into the target. This serves as a marker that this module is actually intended
|
18
|
+
# to provide one or more services. Example:
|
19
|
+
#
|
20
|
+
# module GodMode
|
21
|
+
# include Simple::Service
|
22
|
+
#
|
23
|
+
# # Build a universe.
|
24
|
+
# #
|
25
|
+
# # This comment will become part of the full description of the
|
26
|
+
# # "build_universe" service
|
27
|
+
# def build_universe(name, c: , pi: 3.14, e: 2.781)
|
28
|
+
# # at this point I realize that *I* am not God.
|
29
|
+
#
|
30
|
+
# 42 # Best try approach
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# 2. <em>Discover services:</em> To discover services in a service module use the #actions method. This returns a Hash
|
35
|
+
# of actions.
|
10
36
|
#
|
11
|
-
#
|
12
|
-
# Simple::Service
|
37
|
+
# Simple::Service.actions(GodMode)
|
38
|
+
# => {:build_universe=>#<Simple::Service::Action...>, ...}
|
39
|
+
#
|
40
|
+
# TODO: why a Hash? It feels much better if Simple::Service.actions returns an array of names.
|
41
|
+
#
|
42
|
+
#
|
43
|
+
# 3. <em>Invoke a service:</em> run <tt>Simple::Service.invoke3</tt> or <tt>Simple::Service.invoke</tt>. You must set a context first.
|
44
|
+
#
|
45
|
+
# Simple::Service.with_context do
|
46
|
+
# Simple::Service.invoke3(GodMode, :build_universe, "TestWorld", c: 1e9)
|
47
|
+
# end
|
48
|
+
# => 42
|
13
49
|
#
|
14
|
-
# This serves as a marker that this module is actually intended
|
15
|
-
# to be used as a service.
|
16
50
|
module Simple::Service
|
17
|
-
|
18
|
-
|
51
|
+
module ServiceExpectations
|
52
|
+
def expect!(*args, &block)
|
53
|
+
Expectation.expect!(*args, &block)
|
54
|
+
rescue ::Expectation::Error => e
|
55
|
+
raise ArgumentError, e.to_s
|
56
|
+
end
|
19
57
|
end
|
20
58
|
|
21
|
-
|
22
|
-
|
23
|
-
|
59
|
+
def self.included(klass) # @private
|
60
|
+
klass.extend ClassMethods
|
61
|
+
klass.include ServiceExpectations
|
24
62
|
end
|
25
63
|
|
26
|
-
|
27
|
-
|
28
|
-
def self.with_context(ctx, &block)
|
29
|
-
expect! ctx => [Simple::Service::Context, nil]
|
30
|
-
_ = block
|
31
|
-
|
32
|
-
old_ctx = Thread.current[:"Simple::Service.context"]
|
33
|
-
Thread.current[:"Simple::Service.context"] = ctx
|
34
|
-
yield
|
35
|
-
ensure
|
36
|
-
Thread.current[:"Simple::Service.context"] = old_ctx
|
64
|
+
def self.logger
|
65
|
+
@logger ||= ::Logger.new(STDOUT)
|
37
66
|
end
|
38
67
|
|
39
|
-
def self.
|
40
|
-
|
41
|
-
actions[name] || begin
|
42
|
-
action_names = actions.keys.sort
|
43
|
-
informal = "service #{service} has these actions: #{action_names.map(&:inspect).join(", ")}"
|
44
|
-
raise "No such action #{name.inspect}; #{informal}"
|
45
|
-
end
|
68
|
+
def self.logger=(logger)
|
69
|
+
@logger = logger
|
46
70
|
end
|
47
71
|
|
72
|
+
# returns true if the passed in object is a service module.
|
73
|
+
#
|
74
|
+
# A service must be a module, and it must include the Simple::Service module.
|
48
75
|
def self.service?(service)
|
49
|
-
|
76
|
+
verify_service! service
|
77
|
+
true
|
78
|
+
rescue ::ArgumentError
|
79
|
+
false
|
50
80
|
end
|
51
81
|
|
82
|
+
# Raises an error if the passed in object is not a service
|
83
|
+
def self.verify_service!(service) # @private
|
84
|
+
raise ::ArgumentError, "#{service.inspect} must be a Simple::Service, but is not even a Module" unless service.is_a?(Module)
|
85
|
+
raise ::ArgumentError, "#{service.inspect} must be a Simple::Service, did you 'include Simple::Service'" unless service.include?(self)
|
86
|
+
end
|
87
|
+
|
88
|
+
# returns a Hash with all actions in the +service+ module
|
52
89
|
def self.actions(service)
|
53
|
-
|
90
|
+
verify_service!(service)
|
54
91
|
|
55
92
|
service.__simple_service_actions__
|
56
93
|
end
|
57
94
|
|
58
|
-
|
59
|
-
|
60
|
-
|
95
|
+
# returns the action with the given name.
|
96
|
+
def self.action(service, name)
|
97
|
+
actions = self.actions(service)
|
98
|
+
actions[name] || begin
|
99
|
+
raise ::Simple::Service::NoSuchAction.new(service, name)
|
61
100
|
end
|
62
101
|
end
|
63
102
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
103
|
+
# invokes an action with a given +name+ in a service with +args+ and +flags+.
|
104
|
+
#
|
105
|
+
# This is a helper method which one can use to easily call an action from
|
106
|
+
# ruby source code.
|
107
|
+
#
|
108
|
+
# As the main purpose of this module is to call services with outside data,
|
109
|
+
# the +.invoke+ action is usually preferred.
|
110
|
+
#
|
111
|
+
# *Note:* You cannot call this method if the context is not set.
|
112
|
+
def self.invoke3(service, name, *args, **flags)
|
113
|
+
flags = flags.each_with_object({}) { |(k, v), hsh| hsh[k.to_s] = v }
|
114
|
+
invoke service, name, args: args, flags: flags
|
69
115
|
end
|
70
116
|
|
71
|
-
#
|
72
|
-
#
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
117
|
+
# invokes an action with a given +name+.
|
118
|
+
#
|
119
|
+
# This is the general form of invoking a service. It accepts the following
|
120
|
+
# arguments:
|
121
|
+
#
|
122
|
+
# - args: an Array of positional arguments OR a Hash of named arguments.
|
123
|
+
# - flags: a Hash of flags.
|
124
|
+
#
|
125
|
+
# Note that the keys in both the +flags+ and the +args+ Hash must be strings.
|
126
|
+
#
|
127
|
+
# The service is being called with a parameters built out of those like this:
|
128
|
+
#
|
129
|
+
# - The service's positional arguments are being built from the +args+ array
|
130
|
+
# parameter or from the +named_args+ hash parameter.
|
131
|
+
# - The service's keyword arguments are being built from the +named_args+
|
132
|
+
# and +flags+ arguments.
|
133
|
+
#
|
134
|
+
# In other words:
|
135
|
+
#
|
136
|
+
# 1. You cannot set both +args+ and +named_args+ at the same time.
|
137
|
+
# 2. The +flags+ arguments are only being used to determine the
|
138
|
+
# service's keyword parameters.
|
139
|
+
#
|
140
|
+
# So, if the service X implements an action "def foo(bar, baz:)", the following would
|
141
|
+
# all invoke that service:
|
142
|
+
#
|
143
|
+
# - +Service.invoke3(X, :foo, "bar-value", baz: "baz-value")+, or
|
144
|
+
# - +Service.invoke3(X, :foo, bar: "bar-value", baz: "baz-value")+, or
|
145
|
+
# - +Service.invoke(X, :foo, args: ["bar-value"], flags: { "baz" => "baz-value" })+, or
|
146
|
+
# - +Service.invoke(X, :foo, args: { "bar" => "bar-value", "baz" => "baz-value" })+.
|
147
|
+
#
|
148
|
+
# (see spec/service_spec.rb)
|
149
|
+
#
|
150
|
+
# When there are not enough positional arguments to match the number of required
|
151
|
+
# positional arguments of the method we raise an ArgumentError.
|
152
|
+
#
|
153
|
+
# When there are more positional arguments provided than the number accepted
|
154
|
+
# by the method we raise an ArgumentError.
|
155
|
+
#
|
156
|
+
# Entries in the +named_args+ Hash that are not defined in the action itself are ignored.
|
157
|
+
|
158
|
+
# <b>Note:</b> You cannot call this method if the context is not set.
|
159
|
+
def self.invoke(service, name, args: {}, flags: {})
|
160
|
+
raise ContextMissingError, "Need to set context before calling ::Simple::Service.invoke3" unless context
|
161
|
+
|
162
|
+
expect! args => [Hash, Array], flags: Hash
|
163
|
+
args.keys.each { |key| expect! key => String } if args.is_a?(Hash)
|
164
|
+
flags.keys.each { |key| expect! key => String }
|
165
|
+
|
166
|
+
action(service, name).invoke(args: args, flags: flags)
|
82
167
|
end
|
83
168
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
169
|
+
module ClassMethods # @private
|
170
|
+
# returns a Hash of actions provided by the service module.
|
171
|
+
def __simple_service_actions__
|
172
|
+
@__simple_service_actions__ ||= Action.enumerate(service: self)
|
173
|
+
end
|
88
174
|
end
|
175
|
+
|
176
|
+
# # Resolves a service by name. Returns nil if the name does not refer to a service,
|
177
|
+
# # or the service module otherwise.
|
178
|
+
# def self.resolve(str)
|
179
|
+
# return unless str =~ /^[A-Z][A-Za-z0-9_]*(::[A-Z][A-Za-z0-9_]*)*$/
|
180
|
+
#
|
181
|
+
# service = resolve_constant(str)
|
182
|
+
#
|
183
|
+
# return unless service.is_a?(Module)
|
184
|
+
# return unless service.include?(::Simple::Service)
|
185
|
+
#
|
186
|
+
# service
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# def self.resolve_constant(str)
|
190
|
+
# const_get(str)
|
191
|
+
# rescue NameError
|
192
|
+
# nil
|
193
|
+
# end
|
89
194
|
end
|
@@ -1,8 +1,3 @@
|
|
1
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
2
|
-
# rubocop:disable Metrics/AbcSize
|
3
|
-
# rubocop:disable Metrics/MethodLength
|
4
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
5
|
-
|
6
1
|
module Simple::Service
|
7
2
|
class Action
|
8
3
|
end
|
@@ -12,14 +7,17 @@ require_relative "./action/comment"
|
|
12
7
|
require_relative "./action/parameter"
|
13
8
|
|
14
9
|
module Simple::Service
|
15
|
-
|
16
|
-
|
10
|
+
# rubocop:disable Metrics/AbcSize
|
11
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
12
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
13
|
+
# rubocop:disable Metrics/ClassLength
|
17
14
|
|
18
|
-
|
19
|
-
|
15
|
+
class Action
|
16
|
+
IDENTIFIER_PATTERN = "[a-z][a-z0-9_]*" # @private
|
17
|
+
IDENTIFIER_REGEXP = Regexp.compile("\\A#{IDENTIFIER_PATTERN}\\z") # @private
|
20
18
|
|
21
19
|
# determines all services provided by the +service+ service module.
|
22
|
-
def self.enumerate(service:) #
|
20
|
+
def self.enumerate(service:) # @private
|
23
21
|
service.public_instance_methods(false)
|
24
22
|
.grep(IDENTIFIER_REGEXP)
|
25
23
|
.each_with_object({}) { |name, hsh| hsh[name] = Action.new(service, name) }
|
@@ -28,12 +26,20 @@ module Simple::Service
|
|
28
26
|
attr_reader :service
|
29
27
|
attr_reader :name
|
30
28
|
|
29
|
+
def full_name
|
30
|
+
"#{service.name}##{name}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s # @private
|
34
|
+
full_name
|
35
|
+
end
|
36
|
+
|
31
37
|
# returns an Array of Parameter structures.
|
32
38
|
def parameters
|
33
39
|
@parameters ||= Parameter.reflect_on_method(service: service, name: name)
|
34
40
|
end
|
35
41
|
|
36
|
-
def initialize(service, name)
|
42
|
+
def initialize(service, name) # @private
|
37
43
|
@service = service
|
38
44
|
@name = name
|
39
45
|
|
@@ -63,131 +69,122 @@ module Simple::Service
|
|
63
69
|
@service.instance_method(name).source_location
|
64
70
|
end
|
65
71
|
|
66
|
-
#
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
# necessary to be able to apply default value-based type conversions. (On
|
74
|
-
# the downside this also means we convert an array to a hash and then back
|
75
|
-
# into an array. This, however, should only be an issue for CLI based action
|
76
|
-
# invocations, because any other use case (that I can think of) should allow
|
77
|
-
# us to provide arguments as a Hash.
|
78
|
-
if args.is_a?(Array)
|
79
|
-
args = convert_argument_array_to_hash(args)
|
80
|
-
end
|
72
|
+
# invokes an action with a given +name+ in a service with a Hash of arguments.
|
73
|
+
#
|
74
|
+
# You cannot call this method if the context is not set.
|
75
|
+
def invoke(args:, flags:)
|
76
|
+
args = convert_argument_array_to_hash(args) if args.is_a?(Array)
|
77
|
+
|
78
|
+
verify_required_args!(args, flags)
|
81
79
|
|
82
|
-
|
83
|
-
|
80
|
+
positionals = build_positional_arguments(args, flags)
|
81
|
+
keywords = build_keyword_arguments(args.merge(flags))
|
84
82
|
|
85
83
|
service_instance = Object.new
|
86
84
|
service_instance.extend service
|
87
|
-
|
85
|
+
|
86
|
+
if keywords.empty?
|
87
|
+
service_instance.public_send(@name, *positionals)
|
88
|
+
else
|
89
|
+
# calling this with an empty keywords Hash still raises an ArgumentError
|
90
|
+
# if the target method does not accept arguments.
|
91
|
+
service_instance.public_send(@name, *positionals, **keywords)
|
92
|
+
end
|
88
93
|
end
|
89
94
|
|
90
95
|
private
|
91
96
|
|
92
|
-
|
93
|
-
|
94
|
-
|
97
|
+
# returns an error if the keywords hash does not define all required keyword arguments.
|
98
|
+
def verify_required_args!(args, flags) # @private
|
99
|
+
@required_names ||= parameters.select(&:required?).map(&:name).map(&:to_s)
|
95
100
|
|
96
|
-
|
97
|
-
|
98
|
-
missing_key!(name)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
101
|
+
missing_parameters = @required_names - args.keys - flags.keys
|
102
|
+
return if missing_parameters.empty?
|
102
103
|
|
103
|
-
|
104
|
-
|
104
|
+
raise ::Simple::Service::MissingArguments.new(self, missing_parameters)
|
105
|
+
end
|
105
106
|
|
106
|
-
|
107
|
-
|
107
|
+
# Enumerating all parameters it puts all named parameters into a Hash
|
108
|
+
# of keyword arguments.
|
109
|
+
def build_keyword_arguments(args)
|
110
|
+
@keyword_names ||= parameters.select(&:keyword?).map(&:name).map(&:to_s)
|
108
111
|
|
109
|
-
|
110
|
-
|
111
|
-
end
|
112
|
-
end
|
112
|
+
keys = @keyword_names & args.keys
|
113
|
+
values = args.fetch_values(*keys)
|
113
114
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
args = []
|
119
|
-
keyword_args = {}
|
120
|
-
|
121
|
-
parameters.each do |parameter|
|
122
|
-
if parameter.keyword?
|
123
|
-
if I.key?(params_hsh, parameter.name)
|
124
|
-
keyword_args[parameter.name] = I.fetch(params_hsh, parameter.name)
|
125
|
-
end
|
126
|
-
else
|
127
|
-
if parameter.variadic?
|
128
|
-
if I.key?(args_hsh, parameter.name)
|
129
|
-
args.concat(Array(I.fetch(args_hsh, parameter.name)))
|
130
|
-
end
|
131
|
-
else
|
132
|
-
if !parameter.optional? || I.key?(args_hsh, parameter.name)
|
133
|
-
args << I.fetch(args_hsh, parameter.name)
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
end
|
115
|
+
# Note that +keys+ now only contains names of keyword arguments that actually exist.
|
116
|
+
# This is therefore not a way to DOS this process.
|
117
|
+
Hash[keys.map(&:to_sym).zip(values)]
|
118
|
+
end
|
138
119
|
|
139
|
-
|
140
|
-
|
141
|
-
end
|
120
|
+
def variadic_parameter
|
121
|
+
return @variadic_parameter if defined? @variadic_parameter
|
142
122
|
|
143
|
-
|
123
|
+
@variadic_parameter = parameters.detect(&:variadic?)
|
144
124
|
end
|
145
125
|
|
146
|
-
def
|
147
|
-
|
148
|
-
|
149
|
-
# in +ary+ they will be assigned to the variadic arguments array, which
|
150
|
-
# - if a variadic parameter is defined in this action - will be added to
|
151
|
-
# the hash as well.
|
152
|
-
hsh = {}
|
153
|
-
variadic_parameter_name = nil
|
154
|
-
|
155
|
-
parameters.each do |parameter|
|
156
|
-
next if parameter.keyword?
|
157
|
-
parameter_name = parameter.name
|
158
|
-
|
159
|
-
if parameter.variadic?
|
160
|
-
variadic_parameter_name = parameter_name
|
161
|
-
next
|
162
|
-
end
|
126
|
+
def positional_names
|
127
|
+
@positional_names ||= parameters.select(&:positional?).map(&:name).map(&:to_s)
|
128
|
+
end
|
163
129
|
|
164
|
-
|
165
|
-
|
130
|
+
# Enumerating all parameters it collects all positional parameters into
|
131
|
+
# an Array.
|
132
|
+
def build_positional_arguments(args, flags)
|
133
|
+
positionals = positional_names.each_with_object([]) do |parameter_name, ary|
|
134
|
+
if args.key?(parameter_name)
|
135
|
+
ary << args[parameter_name]
|
136
|
+
elsif flags.key?(parameter_name)
|
137
|
+
ary << flags[parameter_name]
|
166
138
|
end
|
139
|
+
end
|
167
140
|
|
168
|
-
|
141
|
+
# A variadic parameter is appended to the positionals array.
|
142
|
+
# It is always optional - but if it exists it must be an Array.
|
143
|
+
if variadic_parameter
|
144
|
+
value = if args.key?(variadic_parameter.name)
|
145
|
+
args[variadic_parameter.name]
|
146
|
+
elsif flags.key?(variadic_parameter.name)
|
147
|
+
flags[variadic_parameter.name]
|
148
|
+
end
|
169
149
|
|
170
|
-
|
150
|
+
positionals.concat(value) if value
|
171
151
|
end
|
172
152
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
153
|
+
positionals
|
154
|
+
end
|
155
|
+
|
156
|
+
def convert_argument_array_to_hash(ary)
|
157
|
+
expect! ary => Array
|
158
|
+
|
159
|
+
# +ary* might contain more, less, or the exact number of positional
|
160
|
+
# arguments. If the number is less, we return a hash with only whart
|
161
|
+
# exists in ary - the action might define default values after all.
|
162
|
+
#
|
163
|
+
# If it contains more the action better supports a variadic parameter;
|
164
|
+
# we otherwise raise a ExtraArguments exception.
|
165
|
+
case ary.length <=> positional_names.length
|
166
|
+
when 1 # i.e. ary.length > positional_names.length
|
167
|
+
extra_arguments = ary[positional_names.length..-1]
|
168
|
+
ary = ary[0..positional_names.length]
|
169
|
+
|
170
|
+
if !extra_arguments.empty? && !variadic_parameter
|
171
|
+
raise ::Simple::Service::ExtraArguments.new(self, extra_arguments)
|
177
172
|
end
|
178
173
|
|
179
|
-
|
174
|
+
existing_positional_names = positional_names
|
175
|
+
when 0 # i.e. ary.length == positional_names.length
|
176
|
+
existing_positional_names = positional_names
|
177
|
+
when -1 # i.e. ary.length < positional_names.length
|
178
|
+
existing_positional_names = positional_names[0, ary.length]
|
180
179
|
end
|
181
180
|
|
182
|
-
|
183
|
-
|
181
|
+
# Build a hash with the existing_positional_names and the values from the array.
|
182
|
+
hsh = Hash[existing_positional_names.zip(ary)]
|
184
183
|
|
185
|
-
|
186
|
-
|
187
|
-
end
|
184
|
+
# Add the variadic_parameter, if any.
|
185
|
+
hsh[variadic_parameter.name] = extra_arguments if variadic_parameter
|
188
186
|
|
189
|
-
|
190
|
-
full_name
|
187
|
+
hsh
|
191
188
|
end
|
192
189
|
end
|
193
190
|
end
|