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
File without changes
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# RJR HandlesMethods Mixin
|
2
|
+
#
|
3
|
+
# Copyright (C) 2014 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Licensed under the Apache License, Version 2.0
|
5
|
+
|
6
|
+
module RJR
|
7
|
+
|
8
|
+
# Mixin adding methods allowing developer to specify JSON-RPC
|
9
|
+
# methods which to dispatch to.
|
10
|
+
#
|
11
|
+
# @example Defining a structured JSON-RPC method handler
|
12
|
+
# class MyMethodHandler
|
13
|
+
# include RJR::HandlesMethods
|
14
|
+
#
|
15
|
+
# jr_method :do_something
|
16
|
+
#
|
17
|
+
# def handle(*params)
|
18
|
+
# 'return value'
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# node = RJR::Nodes::TCP.new :host => '0.0.0.0', :port => 8888
|
23
|
+
# MyMethodHandler.dispatch_to(node.dispatcher)
|
24
|
+
# node.listen.join
|
25
|
+
#
|
26
|
+
# # clients can now invoke the 'do_something' json-rpc method by
|
27
|
+
# # issuing requests to the target host / port
|
28
|
+
#
|
29
|
+
module HandlesMethods
|
30
|
+
def self.included(base)
|
31
|
+
base.extend(ClassMethods)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Override w/ custom handler logic
|
35
|
+
def handle
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
attr_accessor :jr_handlers
|
40
|
+
|
41
|
+
# Return the handler method matching the argument set
|
42
|
+
def extract_handler_method(args)
|
43
|
+
handler = nil
|
44
|
+
|
45
|
+
if method_defined?(args.last)
|
46
|
+
handler = args.last
|
47
|
+
args.delete_at(-1)
|
48
|
+
|
49
|
+
else
|
50
|
+
handler = :handle
|
51
|
+
end
|
52
|
+
|
53
|
+
[handler, args]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return bool indicating if handler exists for the specified method
|
57
|
+
def has_handler_for?(handler_method)
|
58
|
+
@jr_handlers ||= {}
|
59
|
+
@jr_handlers.has_key?(handler_method)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns handler for specified method
|
63
|
+
def handler_for(handler_method)
|
64
|
+
@jr_handlers[handler_method]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Create handler for specified method.
|
68
|
+
#
|
69
|
+
# Creates a proc that gets evaluated via instance_exec in request
|
70
|
+
def create_handler_for(handler_method)
|
71
|
+
@jr_handlers ||= {}
|
72
|
+
handler_class = self
|
73
|
+
|
74
|
+
@jr_handlers[handler_method] = proc { |*args|
|
75
|
+
# instantiate new handler instance
|
76
|
+
jr_instance = handler_class.new
|
77
|
+
|
78
|
+
# setup scope to include request variables
|
79
|
+
instance_variables.each { |iv|
|
80
|
+
jr_instance.instance_variable_set(iv, instance_variable_get(iv))
|
81
|
+
}
|
82
|
+
|
83
|
+
# invoke handler method
|
84
|
+
jr_instance.method(handler_method).call *args
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Register one or more json-rpc methods.
|
89
|
+
#
|
90
|
+
# Invoke w/ list of method signatures to match in dispatcher
|
91
|
+
# w/ optional id of local method to dispatch to. If no method
|
92
|
+
# specified, the :handle method will be used
|
93
|
+
def jr_method(*args)
|
94
|
+
@jr_method_args ||= []
|
95
|
+
@jr_method_args << args
|
96
|
+
end
|
97
|
+
|
98
|
+
# Register locally stored methods w/ the specified dispatcher
|
99
|
+
def dispatch_to(dispatcher)
|
100
|
+
@jr_method_args.each { |args|
|
101
|
+
# copy args so original is preserved
|
102
|
+
handler_method, jr_methods =
|
103
|
+
extract_handler_method(Array.new(args))
|
104
|
+
jr_methods.map! { |m| m.to_s }
|
105
|
+
|
106
|
+
handler = has_handler_for?(handler_method) ?
|
107
|
+
handler_for(handler_method) :
|
108
|
+
create_handler_for(handler_method)
|
109
|
+
|
110
|
+
dispatcher.handle jr_methods, handler
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end # module ClassMethods
|
114
|
+
end # module HandlesMethods
|
115
|
+
end # module RJR
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# RJR HasMessages Mixin
|
2
|
+
#
|
3
|
+
# Copyright (C) 2014 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Licensed under the Apache License, Version 2.0
|
5
|
+
|
6
|
+
module RJR
|
7
|
+
|
8
|
+
# Mixin adding methods allowing developer to define performatted
|
9
|
+
# messages on a class. After they are defined they may be retrieved,
|
10
|
+
# manipulated, and sent to the server at any time
|
11
|
+
module HasMessages
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(ClassMethods)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Wrapper around HasMessages#message
|
17
|
+
def define_message(name, &bl)
|
18
|
+
self.class.message(name, bl.call)
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
# Mechanism to register / retrieve preformatted message
|
23
|
+
#
|
24
|
+
# @param [Symbol] id id of message to get / set
|
25
|
+
# @param [String] msg optional preformatted message to store
|
26
|
+
# @return [String] json rpc message
|
27
|
+
def message(id, msg=nil)
|
28
|
+
@rjr_messages ||= {}
|
29
|
+
@rjr_messages[id] = msg unless msg.nil?
|
30
|
+
@rjr_messages[id]
|
31
|
+
end
|
32
|
+
|
33
|
+
# Clear preformatted messages
|
34
|
+
def clear_messages
|
35
|
+
@rjr_messages = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return random message from registry.
|
39
|
+
#
|
40
|
+
# Optionally specify the transport which the message must accept.
|
41
|
+
# TODO turn this into a generic selection callback
|
42
|
+
def rand_message(transport = nil)
|
43
|
+
@rjr_messages ||= {}
|
44
|
+
messages = @rjr_messages.select { |mid,m| m[:transports].nil? || transport.nil? ||
|
45
|
+
m[:transports].include?(transport) }
|
46
|
+
messages[messages.keys[rand(messages.keys.size)]]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end # module HasMessages
|
50
|
+
end # module RJR
|
@@ -51,7 +51,7 @@ def select_stats(dispatcher, *filter)
|
|
51
51
|
end
|
52
52
|
|
53
53
|
# Add inspection methods to specified dispatcher
|
54
|
-
def
|
54
|
+
def dispatch_rjr_util_inspect(dispatcher)
|
55
55
|
# Retrieve all the dispatches this node served matching the specified criteri
|
56
56
|
dispatcher.handle "rjr::dispatches" do |filter|
|
57
57
|
select_stats(dispatcher, *filter)
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# RJR JSON Parser
|
2
|
+
#
|
3
|
+
# Copyright (C) 2014 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Licensed under the Apache License, Version 2.0
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
require 'rjr/common'
|
8
|
+
|
9
|
+
module RJR
|
10
|
+
|
11
|
+
# Provides utilities / helpers to parse json in a sane/safe manner
|
12
|
+
class JSONParser
|
13
|
+
|
14
|
+
# Extract and return a single json message from a data string.
|
15
|
+
#
|
16
|
+
# Returns the message and remaining portion of the data string,
|
17
|
+
# if message is found, else nil
|
18
|
+
#
|
19
|
+
# TODO efficiency can probably be optimized in the case closing '}'
|
20
|
+
# hasn't arrived yet
|
21
|
+
#
|
22
|
+
# FIXME if uneven brackets appears in string data (such as in params)
|
23
|
+
# this will break, detect when in string and ignore in counts
|
24
|
+
def self.extract_json_from(data)
|
25
|
+
return nil if data.nil? || data.empty?
|
26
|
+
start = 0
|
27
|
+
start += 1 until start == data.length || data[start].chr == '{'
|
28
|
+
on = mi = 0
|
29
|
+
start.upto(data.length - 1).each { |i|
|
30
|
+
if data[i].chr == '{'
|
31
|
+
on += 1
|
32
|
+
elsif data[i].chr == '}'
|
33
|
+
on -= 1
|
34
|
+
end
|
35
|
+
|
36
|
+
if on == 0
|
37
|
+
mi = i
|
38
|
+
break
|
39
|
+
end
|
40
|
+
}
|
41
|
+
|
42
|
+
return nil if mi == 0
|
43
|
+
return data[start..mi], data[(mi+1)..-1]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return bool indicating if json class is invalid in context
|
47
|
+
# of rjr & cannot be parsed.
|
48
|
+
#
|
49
|
+
# An invalid class is one not on whitelist if enabled or one
|
50
|
+
# not in ruby class heirachy.
|
51
|
+
#
|
52
|
+
# Implements a safe mechanism which to validate json data
|
53
|
+
# to parse
|
54
|
+
def self.invalid_json_class?(jc)
|
55
|
+
Class.whitelist_json_classes ||= false
|
56
|
+
Class.whitelist_json_classes ?
|
57
|
+
!Class.permitted_json_classes.include?(jc) : jc.to_s.to_class.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.validate_json_hash(jh)
|
61
|
+
jh.each { |k,v|
|
62
|
+
if k == ::JSON.create_id && invalid_json_class?(v)
|
63
|
+
raise ArgumentError, "can't create json class #{v}"
|
64
|
+
elsif v.is_a?(Array)
|
65
|
+
validate_json_array(v)
|
66
|
+
elsif v.is_a?(Hash)
|
67
|
+
validate_json_hash(v)
|
68
|
+
end
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.validate_json_array(ja)
|
73
|
+
ja.each { |jai|
|
74
|
+
if jai.is_a?(Array)
|
75
|
+
validate_json_array(jai)
|
76
|
+
elsif jai.is_a?(Hash)
|
77
|
+
validate_json_hash(jai)
|
78
|
+
end
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
# Two stage json parser.
|
83
|
+
# For more details why this is required see json issue:
|
84
|
+
# https://github.com/flori/json/issues/179
|
85
|
+
#
|
86
|
+
# FIXME this will only work for json >= 1.7.6 where
|
87
|
+
# create_additions is defined
|
88
|
+
def self.parse(js)
|
89
|
+
jp = ::JSON.parse js, :create_additions => false
|
90
|
+
if jp.is_a?(Array)
|
91
|
+
validate_json_array(jp)
|
92
|
+
elsif jp.is_a?(Hash)
|
93
|
+
validate_json_hash(jp)
|
94
|
+
else
|
95
|
+
return jp
|
96
|
+
end
|
97
|
+
::JSON.parse js, :create_additions => true
|
98
|
+
end
|
99
|
+
|
100
|
+
end # class JSONParser
|
101
|
+
end # module RJR
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# RJR Logger Class
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011-2014 Mohammed Morsi <mo@morsi.org>
|
4
|
+
# Licensed under the Apache License, Version 2.0
|
5
|
+
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
module RJR
|
9
|
+
|
10
|
+
# Logger helper class.
|
11
|
+
#
|
12
|
+
# Encapsulates the standard ruby logger in a thread safe manner. Dispatches
|
13
|
+
# class methods to an internally tracked logger to provide global access.
|
14
|
+
#
|
15
|
+
# TODO handle logging errors (log size too big, logrotate, etc)
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# RJR::Logger.info 'my message'
|
19
|
+
# RJR::Logger.warn 'my warning'
|
20
|
+
class Logger
|
21
|
+
private
|
22
|
+
def self._instantiate_logger
|
23
|
+
if @logger.nil?
|
24
|
+
#STDOUT.sync = true
|
25
|
+
output = @log_to || ENV['RJR_LOG'] || STDOUT
|
26
|
+
@logger = ::Logger.new(output)
|
27
|
+
@logger.level = @log_level || ::Logger::FATAL
|
28
|
+
@logger_mutex = Mutex.new
|
29
|
+
@filters = []
|
30
|
+
@highlights = []
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
public
|
35
|
+
|
36
|
+
# Add method which to call on every log message to determine
|
37
|
+
# if messages should be included/excluded
|
38
|
+
def self.add_filter(filter)
|
39
|
+
@logger_mutex.synchronize{
|
40
|
+
@filters << filter
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add a method which to call on every log message to determine
|
45
|
+
# if message should be highlighted
|
46
|
+
def self.highlight(hlight)
|
47
|
+
@logger_mutex.synchronize{
|
48
|
+
@highlights << hlight
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.method_missing(method_id, *args)
|
53
|
+
_instantiate_logger
|
54
|
+
@logger_mutex.synchronize {
|
55
|
+
args = args.first if args.first.is_a?(Array)
|
56
|
+
args.each { |a|
|
57
|
+
# run highlights / filters against output before
|
58
|
+
# sending formatted output to logger
|
59
|
+
# TODO allow user to customize highlight mechanism/text
|
60
|
+
na = @highlights.any? { |h| h.call a } ?
|
61
|
+
"\e[1m\e[31m#{a}\e[0m\e[0m" : a
|
62
|
+
@logger.send(method_id, na) if @filters.all? { |f| f.call a }
|
63
|
+
}
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.safe_exec(*args, &bl)
|
68
|
+
_instantiate_logger
|
69
|
+
@logger_mutex.synchronize {
|
70
|
+
bl.call *args
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.logger
|
75
|
+
_instantiate_logger
|
76
|
+
@logger
|
77
|
+
end
|
78
|
+
|
79
|
+
# Set log destination
|
80
|
+
# @param dst destination which to log to (file name, STDOUT, etc)
|
81
|
+
def self.log_to(dst)
|
82
|
+
@log_to = dst
|
83
|
+
@logger = nil
|
84
|
+
_instantiate_logger
|
85
|
+
end
|
86
|
+
|
87
|
+
# Set log level.
|
88
|
+
# @param level one of the standard rails log levels (default fatal)
|
89
|
+
def self.log_level=(level)
|
90
|
+
_instantiate_logger
|
91
|
+
if level.is_a?(String)
|
92
|
+
level = case level
|
93
|
+
when 'debug' then
|
94
|
+
::Logger::DEBUG
|
95
|
+
when 'info' then
|
96
|
+
::Logger::INFO
|
97
|
+
when 'warn' then
|
98
|
+
::Logger::WARN
|
99
|
+
when 'error' then
|
100
|
+
::Logger::ERROR
|
101
|
+
when 'fatal' then
|
102
|
+
::Logger::FATAL
|
103
|
+
end
|
104
|
+
end
|
105
|
+
@log_level = level
|
106
|
+
@logger.level = level
|
107
|
+
end
|
108
|
+
|
109
|
+
# Return true if log level is set to debug, else false
|
110
|
+
def self.debug?
|
111
|
+
@log_level == ::Logger::DEBUG
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end # module RJR
|
116
|
+
|
117
|
+
# Serialized puts, uses logger lock to serialize puts output.
|
118
|
+
# Definiting it in Kernel as 'puts' is defined there
|
119
|
+
#
|
120
|
+
# Though this could go in core_ext, since it's pretty specific
|
121
|
+
# to RJR logger, adding here
|
122
|
+
module Kernel
|
123
|
+
def sputs(*args)
|
124
|
+
::RJR::Logger.safe_exec {
|
125
|
+
puts *args
|
126
|
+
}
|
127
|
+
end
|
128
|
+
end
|
data/lib/rjr/version.rb
CHANGED
data/site/jrw.js
CHANGED
data/specs/args_spec.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'rjr/util/args'
|
2
|
+
|
3
|
+
module RJR
|
4
|
+
describe Arguments do
|
5
|
+
describe "#intialize" do
|
6
|
+
it "initializes arguments" do
|
7
|
+
a = Arguments.new :args => [42]
|
8
|
+
a.args.should == [42]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "#validate!" do
|
13
|
+
context "acceptable is a hash" do
|
14
|
+
context "at least one argument not in acceptable hash keys" do
|
15
|
+
it "raises an ArgumentError" do
|
16
|
+
a = Arguments.new :args => ['invalid', 42]
|
17
|
+
expect {
|
18
|
+
a.validate! 'valid' => 1
|
19
|
+
}.to raise_error(ArgumentError)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "all arguments are in acceptable hash keys" do
|
24
|
+
it "does not raise an error" do
|
25
|
+
a = Arguments.new :args => ['valid', 42]
|
26
|
+
expect {
|
27
|
+
a.validate! 'valid' => 1
|
28
|
+
}.not_to raise_error
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "no arguments" do
|
33
|
+
it "does not raise an error" do
|
34
|
+
a = Arguments.new
|
35
|
+
expect {
|
36
|
+
a.validate! 'valid' => 1
|
37
|
+
}.not_to raise_error
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "converts acceptable keys to strings before comparison" do
|
42
|
+
a = Arguments.new :args => ['valid', 42]
|
43
|
+
expect {
|
44
|
+
a.validate! :valid => 1
|
45
|
+
}.not_to raise_error
|
46
|
+
end
|
47
|
+
|
48
|
+
it "skips over # of arguments specified by acceptable hash values" do
|
49
|
+
a = Arguments.new :args => ['valid', 42]
|
50
|
+
expect {
|
51
|
+
a.validate! :valid => 1
|
52
|
+
}.not_to raise_error
|
53
|
+
|
54
|
+
expect {
|
55
|
+
a.validate! :valid => 0
|
56
|
+
}.to raise_error(ArgumentError)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "acceptable is an array" do
|
61
|
+
context "at least one argument not on acceptable list" do
|
62
|
+
it "raises an ArgumentError" do
|
63
|
+
a = Arguments.new :args => ['invalid']
|
64
|
+
expect {
|
65
|
+
a.validate! 'valid'
|
66
|
+
}.to raise_error(ArgumentError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "all arguments on acceptable list" do
|
71
|
+
it "does not raise an error" do
|
72
|
+
a = Arguments.new :args => ['valid']
|
73
|
+
expect {
|
74
|
+
a.validate! 'valid'
|
75
|
+
}.not_to raise_error
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "no arguments" do
|
80
|
+
it "does not raise an error" do
|
81
|
+
a = Arguments.new
|
82
|
+
expect {
|
83
|
+
a.validate! 'valid'
|
84
|
+
}.not_to raise_error
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "converts acceptable list to strings before comparison" do
|
89
|
+
a = Arguments.new :args => ['valid']
|
90
|
+
expect {
|
91
|
+
a.validate! :valid
|
92
|
+
}.not_to raise_error
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#extract" do
|
98
|
+
before(:each) do
|
99
|
+
@a = Arguments.new :args => ['match1', 'val1', 'val2',
|
100
|
+
'match2', 'val2', 'match3']
|
101
|
+
end
|
102
|
+
|
103
|
+
it "returns extracted map key & following values from arguments" do
|
104
|
+
result = @a.extract 'match1' => 2, 'match3' => 0
|
105
|
+
result.should == [['match1', 'val1', 'val2'], ['match3']]
|
106
|
+
end
|
107
|
+
|
108
|
+
it "matches symbollic map keys" do
|
109
|
+
result = @a.extract :match2 => 1
|
110
|
+
result.should == [['match2', 'val2']]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe "#specifies?" do
|
115
|
+
context "arguments includes tag" do
|
116
|
+
it "returns true" do
|
117
|
+
a = Arguments.new :args => ['with_id', 42]
|
118
|
+
a.specifies?('with_id').should be_true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
context "arguments does not include tag" do
|
123
|
+
it "returns false" do
|
124
|
+
a = Arguments.new :args => ['with_id', 42]
|
125
|
+
a.specifies?('with_name').should be_false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "#specifier_for" do
|
131
|
+
it "returns argument at index of tag + 1" do
|
132
|
+
a = Arguments.new :args => ['with_id', 42]
|
133
|
+
a.specifier_for('with_id').should == 42
|
134
|
+
end
|
135
|
+
|
136
|
+
context "arguments do not specify tag" do
|
137
|
+
it "returns nil" do
|
138
|
+
a = Arguments.new :args => ['with_id', 42]
|
139
|
+
a.specifier_for('with_name').should be_nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end # describe Arguments
|
144
|
+
end # module RJR
|