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
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
|