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