rjr 0.18.2 → 0.19.1
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/Rakefile +2 -0
- data/bin/rjr-client +16 -9
- data/bin/rjr-server +2 -1
- data/examples/client.rb +21 -19
- data/examples/server.rb +1 -1
- data/examples/structured_server.rb +1 -0
- data/examples/tcp.rb +1 -0
- data/lib/rjr/common.rb +1 -226
- data/lib/rjr/core_ext.rb +63 -0
- data/lib/rjr/dispatcher.rb +75 -219
- data/lib/rjr/messages.rb +8 -0
- data/lib/rjr/messages/compressed.rb +264 -0
- data/lib/rjr/messages/notification.rb +95 -0
- data/lib/rjr/messages/request.rb +99 -0
- data/lib/rjr/messages/response.rb +128 -0
- data/lib/rjr/node.rb +100 -97
- data/lib/rjr/node_callback.rb +43 -0
- data/lib/rjr/nodes/amqp.rb +12 -11
- data/lib/rjr/nodes/easy.rb +4 -4
- data/lib/rjr/nodes/local.rb +13 -12
- data/lib/rjr/nodes/multi.rb +1 -1
- data/lib/rjr/nodes/tcp.rb +15 -13
- data/lib/rjr/nodes/template.rb +4 -4
- data/lib/rjr/nodes/unix.rb +15 -13
- data/lib/rjr/nodes/web.rb +15 -14
- data/lib/rjr/nodes/ws.rb +12 -11
- data/lib/rjr/request.rb +128 -0
- data/lib/rjr/result.rb +75 -0
- data/lib/rjr/util/args.rb +145 -0
- data/lib/rjr/{em_adapter.rb → util/em_adapter.rb} +0 -0
- data/lib/rjr/util/handles_methods.rb +115 -0
- data/lib/rjr/util/has_messages.rb +50 -0
- data/lib/rjr/{inspect.rb → util/inspect.rb} +1 -1
- data/lib/rjr/util/json_parser.rb +101 -0
- data/lib/rjr/util/logger.rb +128 -0
- data/lib/rjr/{thread_pool.rb → util/thread_pool.rb} +2 -0
- data/lib/rjr/version.rb +1 -1
- data/site/jrw.js +1 -1
- data/specs/args_spec.rb +144 -0
- data/specs/dispatcher_spec.rb +399 -211
- data/specs/em_adapter_spec.rb +31 -18
- data/specs/handles_methods_spec.rb +154 -0
- data/specs/has_messages_spec.rb +54 -0
- data/specs/inspect_spec.rb +1 -1
- data/specs/json_parser_spec.rb +169 -0
- data/specs/messages/notification_spec.rb +59 -0
- data/specs/messages/request_spec.rb +66 -0
- data/specs/messages/response_spec.rb +94 -0
- data/specs/node_callbacks_spec.rb +47 -0
- data/specs/node_spec.rb +465 -56
- data/specs/request_spec.rb +147 -0
- data/specs/result_spec.rb +144 -0
- data/specs/thread_pool_spec.rb +1 -1
- metadata +41 -11
- data/lib/rjr/errors.rb +0 -23
- data/lib/rjr/message.rb +0 -351
- data/lib/rjr/semaphore.rb +0 -58
- data/specs/message_spec.rb +0 -229
data/lib/rjr/nodes/web.rb
CHANGED
@@ -30,7 +30,7 @@ require 'thread'
|
|
30
30
|
require 'eventmachine'
|
31
31
|
|
32
32
|
require 'rjr/node'
|
33
|
-
require 'rjr/
|
33
|
+
require 'rjr/messages'
|
34
34
|
|
35
35
|
module RJR
|
36
36
|
module Nodes
|
@@ -57,7 +57,7 @@ class WebConnection < EventMachine::Connection
|
|
57
57
|
# XXX we still have to send a response back to client to satisfy
|
58
58
|
# the http standard, even if this is a notification. handle_message
|
59
59
|
# does not do this.
|
60
|
-
@rjr_node.send_msg "", self if
|
60
|
+
@rjr_node.send_msg "", self if Messages::Notification.is_notification_message?(msg)
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -87,13 +87,14 @@ end
|
|
87
87
|
# puts client.invoke('http://localhost:7777', 'hello', 'mo')
|
88
88
|
#
|
89
89
|
# @example Invoking json-rpc requests over http using curl
|
90
|
-
#
|
91
|
-
#
|
90
|
+
# sh> curl -X POST http://localhost:7777 -d '{"jsonrpc":"2.0","method":"hello","params":["mo"],"id":"123"}'
|
91
|
+
# > {"jsonrpc":"2.0","id":"123","result":"Hello mo!"}
|
92
92
|
#
|
93
93
|
class Web < RJR::Node
|
94
94
|
|
95
95
|
RJR_NODE_TYPE = :web
|
96
96
|
PERSISTENT_NODE = false
|
97
|
+
INDIRECT_NODE = false
|
97
98
|
|
98
99
|
public
|
99
100
|
|
@@ -113,7 +114,7 @@ class Web < RJR::Node
|
|
113
114
|
|
114
115
|
# Send data using specified http connection
|
115
116
|
#
|
116
|
-
# Implementation of
|
117
|
+
# Implementation of RJR::Node#send_msg
|
117
118
|
def send_msg(data, connection)
|
118
119
|
# we are assuming that since http connections
|
119
120
|
# are not persistant, we should be sending a
|
@@ -131,7 +132,7 @@ class Web < RJR::Node
|
|
131
132
|
|
132
133
|
# Instruct Node to start listening for and dispatching rpc requests
|
133
134
|
#
|
134
|
-
# Implementation of
|
135
|
+
# Implementation of RJR::Node#listen
|
135
136
|
def listen
|
136
137
|
@@em.schedule do
|
137
138
|
EventMachine::start_server(@host, @port, WebConnection, :rjr_node => self)
|
@@ -141,7 +142,7 @@ class Web < RJR::Node
|
|
141
142
|
|
142
143
|
# Instructs node to send rpc request, and wait for / return response
|
143
144
|
#
|
144
|
-
# Implementation of
|
145
|
+
# Implementation of RJR::Node#invoke
|
145
146
|
#
|
146
147
|
# Do not invoke directly from em event loop or callback as will block the message
|
147
148
|
# subscription used to receive responses
|
@@ -151,9 +152,9 @@ class Web < RJR::Node
|
|
151
152
|
# @param [String] rpc_method json-rpc method to invoke on destination
|
152
153
|
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
153
154
|
def invoke(uri, rpc_method, *args)
|
154
|
-
message =
|
155
|
-
|
156
|
-
|
155
|
+
message = Messages::Request.new :method => rpc_method,
|
156
|
+
:args => args,
|
157
|
+
:headers => @message_headers
|
157
158
|
cb = lambda { |http|
|
158
159
|
# TODO handle errors
|
159
160
|
handle_message(http.response, http)
|
@@ -177,7 +178,7 @@ class Web < RJR::Node
|
|
177
178
|
|
178
179
|
# Instructs node to send rpc notification (immadiately returns / no response is generated)
|
179
180
|
#
|
180
|
-
# Implementation of
|
181
|
+
# Implementation of RJR::Node#notify
|
181
182
|
#
|
182
183
|
# @param [String] uri location of node to send request to, should be
|
183
184
|
# in format of http://hostname:port
|
@@ -189,9 +190,9 @@ class Web < RJR::Node
|
|
189
190
|
published_c = ConditionVariable.new
|
190
191
|
|
191
192
|
invoked = false
|
192
|
-
message =
|
193
|
-
|
194
|
-
|
193
|
+
message = Messages::Notification.new :method => rpc_method,
|
194
|
+
:args => args,
|
195
|
+
:headers => @message_headers
|
195
196
|
cb = lambda { |arg| published_l.synchronize { invoked = true ; published_c.signal }}
|
196
197
|
@@em.schedule do
|
197
198
|
http = EventMachine::HttpRequest.new(uri).post :body => message.to_s,
|
data/lib/rjr/nodes/ws.rb
CHANGED
@@ -23,7 +23,7 @@ else
|
|
23
23
|
require 'thread'
|
24
24
|
|
25
25
|
require 'rjr/node'
|
26
|
-
require 'rjr/
|
26
|
+
require 'rjr/messages'
|
27
27
|
|
28
28
|
module RJR
|
29
29
|
module Nodes
|
@@ -55,6 +55,7 @@ module Nodes
|
|
55
55
|
class WS < RJR::Node
|
56
56
|
RJR_NODE_TYPE = :ws
|
57
57
|
PERSISTENT_NODE = true
|
58
|
+
INDIRECT_NODE = false
|
58
59
|
|
59
60
|
private
|
60
61
|
|
@@ -100,14 +101,14 @@ class WS < RJR::Node
|
|
100
101
|
|
101
102
|
# Send data using specified websocket safely
|
102
103
|
#
|
103
|
-
# Implementation of
|
104
|
+
# Implementation of RJR::Node#send_msg
|
104
105
|
def send_msg(data, ws)
|
105
106
|
@@em.schedule { ws.send(data) }
|
106
107
|
end
|
107
108
|
|
108
109
|
# Instruct Node to start listening for and dispatching rpc requests
|
109
110
|
#
|
110
|
-
# Implementation of
|
111
|
+
# Implementation of RJR::Node#listen
|
111
112
|
def listen
|
112
113
|
@@em.schedule do
|
113
114
|
EventMachine::WebSocket.run(:host => @host, :port => @port) do |ws|
|
@@ -122,7 +123,7 @@ class WS < RJR::Node
|
|
122
123
|
|
123
124
|
# Instructs node to send rpc request, and wait for / return response
|
124
125
|
#
|
125
|
-
# Implementation of
|
126
|
+
# Implementation of RJR::Node#invoke
|
126
127
|
#
|
127
128
|
# Do not invoke directly from em event loop or callback as will block the message
|
128
129
|
# subscription used to receive responses
|
@@ -132,9 +133,9 @@ class WS < RJR::Node
|
|
132
133
|
# @param [String] rpc_method json-rpc method to invoke on destination
|
133
134
|
# @param [Array] args array of arguments to convert to json and invoke remote method wtih
|
134
135
|
def invoke(uri, rpc_method, *args)
|
135
|
-
message =
|
136
|
-
|
137
|
-
|
136
|
+
message = Messages::Request.new :method => rpc_method,
|
137
|
+
:args => args,
|
138
|
+
:headers => @message_headers
|
138
139
|
|
139
140
|
@@em.schedule {
|
140
141
|
init_client(uri) do |c|
|
@@ -155,7 +156,7 @@ class WS < RJR::Node
|
|
155
156
|
|
156
157
|
# Instructs node to send rpc notification (immadiately returns / no response is generated)
|
157
158
|
#
|
158
|
-
# Implementation of
|
159
|
+
# Implementation of RJR::Node#notify
|
159
160
|
#
|
160
161
|
# @param [String] uri location of node to send notification to, should be
|
161
162
|
# in format of ws://hostname:port
|
@@ -167,9 +168,9 @@ class WS < RJR::Node
|
|
167
168
|
published_c = ConditionVariable.new
|
168
169
|
|
169
170
|
invoked = false
|
170
|
-
message =
|
171
|
-
|
172
|
-
|
171
|
+
message = Messages::Notification.new :method => rpc_method,
|
172
|
+
:args => args,
|
173
|
+
:headers => @message_headers
|
173
174
|
@@em.schedule {
|
174
175
|
init_client(uri) do |c|
|
175
176
|
c.stream { |msg| handle_message(msg.data, c) }
|
data/lib/rjr/request.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# RJR Request Representation
|
2
|
+
#
|
3
|
+
# Copyright (C) 2012-2014 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Licensed under the Apache License, Version 2.0
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
require 'rjr/result'
|
8
|
+
require 'rjr/core_ext'
|
9
|
+
require 'rjr/util/args'
|
10
|
+
require 'rjr/util/logger'
|
11
|
+
|
12
|
+
module RJR
|
13
|
+
|
14
|
+
# JSON-RPC request representation.
|
15
|
+
#
|
16
|
+
# Registered request handlers will be invoked in the context of
|
17
|
+
# instances of this class, meaning all member variables will be available
|
18
|
+
# for use in the handler.
|
19
|
+
class Request
|
20
|
+
# Result of the request operation, set by dispatcher
|
21
|
+
attr_accessor :result
|
22
|
+
|
23
|
+
# Method which request is for
|
24
|
+
attr_accessor :rjr_method
|
25
|
+
|
26
|
+
# Arguments be passed to method
|
27
|
+
attr_accessor :rjr_method_args
|
28
|
+
|
29
|
+
# Argument object encapsulating arguments
|
30
|
+
attr_accessor :rjr_args
|
31
|
+
|
32
|
+
# Headers which came w/ request
|
33
|
+
attr_accessor :rjr_headers
|
34
|
+
|
35
|
+
# Client IP which request came in on (only for direct nodes)
|
36
|
+
attr_accessor :rjr_client_ip
|
37
|
+
|
38
|
+
# Port which request came in on (only for direct nodes)
|
39
|
+
attr_accessor :rjr_client_port
|
40
|
+
|
41
|
+
# RJR callback which may be used to push data to client
|
42
|
+
attr_accessor :rjr_callback
|
43
|
+
|
44
|
+
# Node which the request came in on
|
45
|
+
attr_accessor :rjr_node
|
46
|
+
|
47
|
+
# Type of node which request came in on
|
48
|
+
attr_accessor :rjr_node_type
|
49
|
+
|
50
|
+
# ID of node which request came in on
|
51
|
+
attr_accessor :rjr_node_id
|
52
|
+
|
53
|
+
# Actual proc registered to handle request
|
54
|
+
attr_accessor :rjr_handler
|
55
|
+
|
56
|
+
# RJR Request initializer
|
57
|
+
#
|
58
|
+
# @param [Hash] args options to set on request,
|
59
|
+
# see Request accessors for valid keys
|
60
|
+
def initialize(args = {})
|
61
|
+
@rjr_method = args[:rjr_method] || args['rjr_method']
|
62
|
+
@rjr_method_args = args[:rjr_method_args] || args['rjr_method_args'] || []
|
63
|
+
@rjr_headers = args[:rjr_headers] || args['rjr_headers']
|
64
|
+
|
65
|
+
@rjr_client_ip = args[:rjr_client_ip]
|
66
|
+
@rjr_client_port = args[:rjr_client_port]
|
67
|
+
|
68
|
+
@rjr_callback = args[:rjr_callback]
|
69
|
+
@rjr_node = args[:rjr_node]
|
70
|
+
@rjr_node_id = args[:rjr_node_id] || args['rjr_node_id']
|
71
|
+
@rjr_node_type = args[:rjr_node_type] || args['rjr_node_type']
|
72
|
+
|
73
|
+
@rjr_handler = args[:rjr_handler]
|
74
|
+
|
75
|
+
@rjr_args = Arguments.new :args => @rjr_method_args
|
76
|
+
@result = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
# Invoke the request by calling the registered handler with the registered
|
80
|
+
# method parameters in the local scope
|
81
|
+
def handle
|
82
|
+
node_sig = "#{@rjr_node_id}(#{@rjr_node_type})"
|
83
|
+
method_sig = "#{@rjr_method}(#{@rjr_method_args.join(',')})"
|
84
|
+
|
85
|
+
RJR::Logger.info "#{node_sig}->#{method_sig}"
|
86
|
+
|
87
|
+
# TODO option to compare arity of handler to number
|
88
|
+
# of method_args passed in ?
|
89
|
+
retval = instance_exec(*@rjr_method_args, &@rjr_handler)
|
90
|
+
|
91
|
+
RJR::Logger.info \
|
92
|
+
"#{node_sig}<-#{method_sig}<-#{retval.nil? ? "nil" : retval}"
|
93
|
+
|
94
|
+
return retval
|
95
|
+
end
|
96
|
+
|
97
|
+
def request_json
|
98
|
+
{:request => { :rjr_method => @rjr_method,
|
99
|
+
:rjr_method_args => @rjr_method_args,
|
100
|
+
:rjr_headers => @rjr_headers,
|
101
|
+
:rjr_node_type => @rjr_node_type,
|
102
|
+
:rjr_node_id => @rjr_node_id }}
|
103
|
+
end
|
104
|
+
|
105
|
+
def result_json
|
106
|
+
return {} unless !!@result
|
107
|
+
{:result => { :result => @result.result,
|
108
|
+
:error_code => @result.error_code,
|
109
|
+
:error_msg => @result.error_msg,
|
110
|
+
:error_class => @result.error_class }}
|
111
|
+
end
|
112
|
+
|
113
|
+
# Convert request to json representation and return it
|
114
|
+
def to_json(*a)
|
115
|
+
{'json_class' => self.class.name,
|
116
|
+
'data' => request_json.merge(result_json)}.to_json(*a)
|
117
|
+
end
|
118
|
+
|
119
|
+
# Create new request from json representation
|
120
|
+
def self.json_create(o)
|
121
|
+
result = Result.new(o['data']['result'])
|
122
|
+
request = Request.new(o['data']['request'])
|
123
|
+
request.result = result
|
124
|
+
return request
|
125
|
+
end
|
126
|
+
|
127
|
+
end # class Request
|
128
|
+
end # module RJR
|
data/lib/rjr/result.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# RJR Result Representation
|
2
|
+
#
|
3
|
+
# Copyright (C) 2012-2014 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Licensed under the Apache License, Version 2.0
|
5
|
+
|
6
|
+
module RJR
|
7
|
+
|
8
|
+
# JSON-RPC Result Representation
|
9
|
+
class Result
|
10
|
+
# Boolean indicating if request was successfully invoked
|
11
|
+
attr_accessor :success
|
12
|
+
|
13
|
+
# Boolean indicating if request failed in some manner
|
14
|
+
attr_accessor :failed
|
15
|
+
|
16
|
+
# Return value of the json-rpc call if successful
|
17
|
+
attr_accessor :result
|
18
|
+
|
19
|
+
# Code corresponding to json-rpc error if problem occured during request invocation
|
20
|
+
attr_accessor :error_code
|
21
|
+
|
22
|
+
# Message corresponding to json-rpc error if problem occured during request invocation
|
23
|
+
attr_accessor :error_msg
|
24
|
+
|
25
|
+
# Class of error raised (if any) during request invocation (this is extra metadata beyond standard json-rpc)
|
26
|
+
attr_accessor :error_class
|
27
|
+
|
28
|
+
# RJR result intializer
|
29
|
+
# @param [Hash] args options to set on result
|
30
|
+
# @option args [Object] :result result of json-rpc method handler if successfully returned
|
31
|
+
# @option args [Integer] :error_code code corresponding to json-rpc error if problem occured during request invocation
|
32
|
+
# @option args [String] :error_msg message corresponding to json-rpc error if problem occured during request invocation
|
33
|
+
# @option args [Class] :error_class class of error raised (if any) during request invocation (this is extra metadata beyond standard json-rpc)
|
34
|
+
def initialize(args = {})
|
35
|
+
@result = args[:result] || args['result']
|
36
|
+
@error_code = args[:error_code] || args['error_code']
|
37
|
+
@error_msg = args[:error_msg] || args['error_msg']
|
38
|
+
@error_class = args[:error_class] || args['error_class']
|
39
|
+
|
40
|
+
@success = @error_code.nil?
|
41
|
+
@failed = !@error_code.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
# Compare Result against other result, returning true if both correspond
|
45
|
+
# to equivalent json-rpc results else false
|
46
|
+
def ==(other)
|
47
|
+
@success == other.success &&
|
48
|
+
@failed == other.failed &&
|
49
|
+
@result == other.result &&
|
50
|
+
@error_code == other.error_code &&
|
51
|
+
@error_msg == other.error_msg &&
|
52
|
+
@error_class == other.error_class
|
53
|
+
end
|
54
|
+
|
55
|
+
# Convert Response to human consumable string
|
56
|
+
def to_s
|
57
|
+
"#{@success} #{@result} #{@error_code} #{@error_msg} #{@error_class}"
|
58
|
+
end
|
59
|
+
|
60
|
+
######### Specific request types
|
61
|
+
|
62
|
+
# JSON-RPC -32600 / Invalid Request
|
63
|
+
def self.invalid_request
|
64
|
+
return Result.new(:error_code => -32600,
|
65
|
+
:error_msg => 'Invalid Request')
|
66
|
+
end
|
67
|
+
|
68
|
+
# JSON-RPC -32602 / Method not found
|
69
|
+
def self.method_not_found(name)
|
70
|
+
return Result.new(:error_code => -32602,
|
71
|
+
:error_msg => "Method '#{name}' not found")
|
72
|
+
end
|
73
|
+
|
74
|
+
end # class Result
|
75
|
+
end # module RJR
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# RJR JSON-RPC Argument Representation
|
2
|
+
#
|
3
|
+
# Copyright (C) 2014 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Licensed under the Apache License, Version 2.0
|
5
|
+
|
6
|
+
require 'forwardable'
|
7
|
+
|
8
|
+
module RJR
|
9
|
+
|
10
|
+
# Encapsulates a list of JSON-RPC method arguments as sent
|
11
|
+
# from the client to the server, in addition to providing
|
12
|
+
# various helper / utility methods.
|
13
|
+
class Arguments
|
14
|
+
include Enumerable
|
15
|
+
extend Forwardable
|
16
|
+
|
17
|
+
attr_accessor :args
|
18
|
+
|
19
|
+
def_delegators :@args, :each, :<<, :length, :[], :index
|
20
|
+
|
21
|
+
def initialize(args={})
|
22
|
+
@args = args[:args] || []
|
23
|
+
end
|
24
|
+
|
25
|
+
# Validate arguments against acceptable values.
|
26
|
+
#
|
27
|
+
# Raises error if value is found which is not on
|
28
|
+
# list of acceptable values.
|
29
|
+
#
|
30
|
+
# If acceptable values are hash's, keys are compared
|
31
|
+
# and on matches the values are used as the # of following
|
32
|
+
# arguments to skip in the validator
|
33
|
+
#
|
34
|
+
# *Note* args / acceptable params are converted to strings
|
35
|
+
# before comparison
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# args = Arguments.new :args => ['custom', 'another']
|
39
|
+
# args.validate! 'custom', 'another' #=> nil
|
40
|
+
# args.validate! 'something' #=> ArgumentError
|
41
|
+
#
|
42
|
+
# args = Arguments.new :args => ['with_id', 123, 'another']
|
43
|
+
# args.validate! :with_id => 1, :another => 0 #=> nil
|
44
|
+
# args.validate! :with_id => 1 #=> ArgumentError
|
45
|
+
def validate!(*acceptable)
|
46
|
+
i = 0
|
47
|
+
if acceptable.first.is_a?(Hash)
|
48
|
+
# clone acceptable hash, swap keys for string
|
49
|
+
acceptable = Hash[acceptable.first]
|
50
|
+
acceptable.keys.each { |k|
|
51
|
+
acceptable[k.to_s] = acceptable[k]
|
52
|
+
acceptable.delete(k) unless k.is_a?(String)
|
53
|
+
}
|
54
|
+
|
55
|
+
# compare acceptable against arguments, raising error if issue found
|
56
|
+
while(i < length) do
|
57
|
+
val = self[i]
|
58
|
+
passed = acceptable.has_key?(val)
|
59
|
+
raise ArgumentError, "#{val} not an acceptable arg" unless passed
|
60
|
+
skip = acceptable[val]
|
61
|
+
i += (skip + 1)
|
62
|
+
end
|
63
|
+
|
64
|
+
else
|
65
|
+
# clone acceptable array, swap values for string
|
66
|
+
acceptable = Array.new(acceptable).map { |a| a.to_s }
|
67
|
+
|
68
|
+
# compare acceptable against arguments, raising error if issue found
|
69
|
+
while(i < length) do
|
70
|
+
val = self[i].to_s
|
71
|
+
passed = acceptable.include?(val)
|
72
|
+
raise ArgumentError, "#{val} not an acceptable arg" unless passed
|
73
|
+
i += 1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
# Extract groups of values from argument list
|
81
|
+
#
|
82
|
+
# Groups are generated by comparing arguments to keys in the
|
83
|
+
# specified map and on matches extracting the # of following
|
84
|
+
# arguments specified by the map values
|
85
|
+
#
|
86
|
+
# Note arguments / keys are converted to strings before comparison
|
87
|
+
#
|
88
|
+
# @example
|
89
|
+
# args = Arguments.new :args => ['with_id', 123, 'custom', 'another']
|
90
|
+
# args.extract :with_id => 1, :custom => 0
|
91
|
+
# # => [['with_id', 123], ['custom']]
|
92
|
+
#
|
93
|
+
def extract(map)
|
94
|
+
# clone map hash, swap keys for string
|
95
|
+
map = Hash[map]
|
96
|
+
map.keys.each { |k|
|
97
|
+
map[k.to_s] = map[k]
|
98
|
+
map.delete(k) unless k.is_a?(String)
|
99
|
+
}
|
100
|
+
|
101
|
+
groups = []
|
102
|
+
i = 0
|
103
|
+
while(i < length) do
|
104
|
+
val = self[i]
|
105
|
+
i += 1
|
106
|
+
next unless !!map.has_key?(val)
|
107
|
+
|
108
|
+
num = map[val]
|
109
|
+
group = [val]
|
110
|
+
0.upto(num-1) do |j|
|
111
|
+
group << self[i]
|
112
|
+
i += 1
|
113
|
+
end
|
114
|
+
groups << group
|
115
|
+
end
|
116
|
+
|
117
|
+
groups
|
118
|
+
end
|
119
|
+
|
120
|
+
# Return boolean if tag appears in argument list.
|
121
|
+
# Simple wrapper around includes setting up the scope
|
122
|
+
# of argument 'specifiers'
|
123
|
+
#
|
124
|
+
# @example
|
125
|
+
# args = Arguments.new :args => ['foobar']
|
126
|
+
# args.specifies?('foobar') #=> true
|
127
|
+
# args.specifies?('barfoo') #=> false
|
128
|
+
def specifies?(tag)
|
129
|
+
include?(tag)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Return specifier corresponding given tag. The specifier
|
133
|
+
# is defined as the value appearing in the argument list
|
134
|
+
# immediately after the tag
|
135
|
+
#
|
136
|
+
# @example
|
137
|
+
# args = Argument.new :args => ['with_id', 123]
|
138
|
+
# args.specifier_for('with_id') #=> 123
|
139
|
+
# args.specifier_for('other') #=> nil
|
140
|
+
def specifier_for(tag)
|
141
|
+
return nil unless specifies?(tag)
|
142
|
+
self[index(tag) + 1]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|