saper 0.5.1 → 0.5.2
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/README.md +3 -0
- data/Rakefile +6 -6
- data/bin/saper +17 -36
- data/lib/saper.rb +63 -20
- data/lib/saper/actions/append_with.rb +1 -1
- data/lib/saper/actions/convert_to_html.rb +1 -1
- data/lib/saper/actions/convert_to_json.rb +6 -2
- data/lib/saper/actions/convert_to_markdown.rb +1 -1
- data/lib/saper/actions/convert_to_time.rb +38 -1
- data/lib/saper/actions/convert_to_xml.rb +1 -1
- data/lib/saper/actions/create_atom.rb +6 -2
- data/lib/saper/actions/fetch.rb +3 -2
- data/lib/saper/actions/fetch_with_token.rb +18 -0
- data/lib/saper/actions/find.rb +1 -1
- data/lib/saper/actions/find_first.rb +1 -1
- data/lib/saper/actions/get_attribute.rb +7 -2
- data/lib/saper/actions/get_contents.rb +1 -1
- data/lib/saper/actions/get_text.rb +1 -1
- data/lib/saper/actions/nothing.rb +11 -0
- data/lib/saper/actions/prepend_with.rb +2 -2
- data/lib/saper/actions/remove_after.rb +2 -2
- data/lib/saper/actions/remove_before.rb +2 -2
- data/lib/saper/actions/remove_matching.rb +2 -2
- data/lib/saper/actions/remove_tags.rb +1 -1
- data/lib/saper/actions/replace.rb +1 -1
- data/lib/saper/actions/run_recipe.rb +2 -2
- data/lib/saper/actions/run_recipe_and_save.rb +3 -3
- data/lib/saper/actions/save.rb +2 -2
- data/lib/saper/actions/select_matching.rb +2 -2
- data/lib/saper/actions/set_input.rb +1 -1
- data/lib/saper/actions/skip_tags.rb +1 -1
- data/lib/saper/actions/split.rb +1 -1
- data/lib/saper/arguments/attribute.rb +6 -0
- data/lib/saper/arguments/recipe.rb +12 -6
- data/lib/saper/arguments/service.rb +12 -0
- data/lib/saper/arguments/text.rb +1 -0
- data/lib/saper/arguments/timezone.rb +2 -1
- data/lib/saper/arguments/url.rb +12 -0
- data/lib/saper/arguments/variable.rb +2 -1
- data/lib/saper/arguments/xpath.rb +2 -1
- data/lib/saper/core/action.rb +43 -13
- data/lib/saper/core/argument.rb +30 -14
- data/lib/saper/core/browser.rb +29 -28
- data/lib/saper/core/dsl.rb +14 -16
- data/lib/saper/core/error.rb +1 -1
- data/lib/saper/core/item.rb +2 -2
- data/lib/saper/core/keychain.rb +58 -5
- data/lib/saper/core/namespace.rb +15 -18
- data/lib/saper/core/recipe.rb +53 -12
- data/lib/saper/core/result.rb +72 -0
- data/lib/saper/core/runtime.rb +44 -177
- data/lib/saper/core/stack.rb +57 -0
- data/lib/saper/items/atom.rb +18 -1
- data/lib/saper/items/document.rb +39 -19
- data/lib/saper/items/html.rb +52 -7
- data/lib/saper/items/json.rb +19 -1
- data/lib/saper/items/markdown.rb +14 -4
- data/lib/saper/items/nothing.rb +6 -0
- data/lib/saper/items/text.rb +28 -3
- data/lib/saper/items/time.rb +25 -2
- data/lib/saper/items/url.rb +8 -2
- data/lib/saper/items/xml.rb +51 -11
- data/lib/{lib → saper/patches}/mechanize.rb +0 -0
- data/lib/{lib → saper/patches}/nokogiri.rb +0 -0
- data/lib/saper/version.rb +2 -2
- data/spec/actions/append_with_spec.rb +29 -52
- data/spec/actions/convert_to_html_spec.rb +13 -30
- data/spec/actions/convert_to_json_spec.rb +13 -30
- data/spec/actions/convert_to_markdown_spec.rb +17 -19
- data/spec/actions/convert_to_time_spec.rb +25 -43
- data/spec/actions/convert_to_xml_spec.rb +15 -11
- data/spec/actions/create_atom_spec.rb +11 -19
- data/spec/actions/fetch_spec.rb +3 -8
- data/spec/actions/find_first_spec.rb +38 -40
- data/spec/actions/find_spec.rb +23 -39
- data/spec/actions/get_attribute_spec.rb +30 -3
- data/spec/actions/{get_contents.rb → get_contents_spec.rb} +0 -0
- data/spec/actions/{get_text.rb → get_text_spec.rb} +0 -0
- data/spec/actions/nothing_spec.rb +7 -0
- data/spec/actions/prepend_with_spec.rb +31 -18
- data/spec/actions/{remove_after.rb → remove_after_spec.rb} +0 -0
- data/spec/actions/{remove_before.rb → remove_before_spec.rb} +0 -0
- data/spec/actions/remove_matching_spec.rb +7 -0
- data/spec/actions/remove_tags_spec.rb +7 -0
- data/spec/actions/run_recipe_and_save_spec.rb +7 -0
- data/spec/actions/run_recipe_spec.rb +7 -0
- data/spec/arguments/attribute_spec.rb +7 -0
- data/spec/arguments/recipe_spec.rb +7 -0
- data/spec/arguments/text_spec.rb +7 -0
- data/spec/arguments/timezone_spec.rb +7 -0
- data/spec/arguments/variable_spec.rb +7 -0
- data/spec/arguments/xpath_spec.rb +7 -0
- data/spec/core/action_spec.rb +13 -142
- data/spec/core/argument_spec.rb +38 -71
- data/spec/core/browser_spec.rb +18 -3
- data/spec/core/dsl_spec.rb +1 -1
- data/spec/core/item_spec.rb +1 -1
- data/spec/core/namespace_spec.rb +0 -11
- data/spec/core/recipe_spec.rb +54 -77
- data/spec/core/result_spec.rb +7 -0
- data/spec/core/runtime_spec.rb +20 -151
- data/spec/core/stack_spec.rb +7 -0
- data/spec/integration/simple_invalid_spec.rb +39 -0
- data/spec/integration/simple_valid_spec.rb +39 -0
- data/spec/items/atom_spec.rb +6 -1
- data/spec/items/document_spec.rb +93 -15
- data/spec/items/html_spec.rb +45 -28
- data/spec/items/json_spec.rb +10 -10
- data/spec/items/markdown_spec.rb +24 -3
- data/spec/items/nothing_spec.rb +1 -1
- data/spec/items/text_spec.rb +13 -41
- data/spec/items/time_spec.rb +25 -4
- data/spec/items/url_spec.rb +1 -7
- data/spec/items/xml_spec.rb +46 -39
- data/spec/spec_helper.rb +2 -21
- metadata +63 -60
- data/lib/lib/json_search.rb +0 -54
- data/spec/actions/run_recipe_and_save_spec.tmp.rb +0 -52
- data/spec/actions/run_recipe_spec.tmp.rb +0 -53
data/lib/saper/core/browser.rb
CHANGED
@@ -10,13 +10,26 @@ module Saper
|
|
10
10
|
# Approximate number of bytes sent.
|
11
11
|
attr_reader :sent
|
12
12
|
|
13
|
+
AGENTS = {
|
14
|
+
:ie6 => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
|
15
|
+
:ie7 => 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
|
16
|
+
:ie8 => 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
|
17
|
+
:ie9 => 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)',
|
18
|
+
:mozilla => 'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4b) Gecko/20030516 Mozilla Firebird/0.6',
|
19
|
+
:safari => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/534.51.22',
|
20
|
+
:iphone => 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1C28 Safari/419.3',
|
21
|
+
:ipad => 'Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10',
|
22
|
+
:android => 'Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13',
|
23
|
+
:saper => 'Mozilla/5.0 (compatible; Saper Ruby client %s)' % Saper::VERSION
|
24
|
+
}
|
25
|
+
|
13
26
|
# Returns a new Browser instance.
|
14
27
|
# @option options [Symbol] :agent User agent
|
15
28
|
# @option options [Hash] :headers Additional request headers
|
16
29
|
# @option options [Logger] :logger Logger instance
|
17
30
|
# @return [Saper::Browser]
|
18
31
|
def initialize(options = {})
|
19
|
-
@agent = options.delete(:agent)
|
32
|
+
@agent = options.delete(:agent) || :saper
|
20
33
|
@headers = options.delete(:headers)
|
21
34
|
@logger = options.delete(:logger) || Saper::Logger.new
|
22
35
|
@history = []
|
@@ -61,46 +74,34 @@ module Saper
|
|
61
74
|
@logger.download(url)
|
62
75
|
@history.push url
|
63
76
|
data = @mech.get(url, query)
|
64
|
-
Saper::Items::Document.new data
|
77
|
+
Saper::Items::Document.new data.body, data.uri, data.header
|
78
|
+
rescue Mechanize::ResponseCodeError
|
79
|
+
Saper::Items::Nothing.new # TODO: change to custom exception
|
65
80
|
end
|
66
81
|
|
67
82
|
# Performs a POST request and returns Saper::Document.
|
68
83
|
# @param url [String] URL to request
|
69
84
|
# @param query [Hash] payload
|
70
85
|
# @return [Saper::Document]
|
71
|
-
def post(url, query = {})
|
86
|
+
def post(url, query = {}, headers = {})
|
72
87
|
@logger.download(url)
|
73
88
|
@history.push url
|
74
|
-
data = @mech.post(url, query)
|
75
|
-
Saper::Items::Document.new data
|
89
|
+
data = @mech.post(url, query, headers)
|
90
|
+
Saper::Items::Document.new data.body, data.uri, data.header
|
91
|
+
rescue Mechanize::ResponseCodeError
|
92
|
+
Saper::Items::Nothing.new # TODO: change to custom exception
|
93
|
+
end
|
94
|
+
|
95
|
+
# @todo
|
96
|
+
def post_with_bearer_token(url, query, token)
|
97
|
+
post(url, query, { "Authorization" => "Bearer %s" % token })
|
76
98
|
end
|
77
99
|
|
78
100
|
# Returns User-Agent string used with requests.
|
79
101
|
# @return [String]
|
80
102
|
def agent
|
81
|
-
|
82
|
-
when :ie6
|
83
|
-
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
|
84
|
-
when :ie7
|
85
|
-
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)'
|
86
|
-
when :ie8
|
87
|
-
'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727)'
|
88
|
-
when :ie9
|
89
|
-
'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)'
|
90
|
-
when :mozilla
|
91
|
-
'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4b) Gecko/20030516 Mozilla Firebird/0.6'
|
92
|
-
when :safari
|
93
|
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/534.51.22'
|
94
|
-
when :iphone
|
95
|
-
'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420+ (KHTML, like Gecko) Version/3.0 Mobile/1C28 Safari/419.3'
|
96
|
-
when :ipad
|
97
|
-
'Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10'
|
98
|
-
when :android
|
99
|
-
'Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13'
|
100
|
-
else
|
101
|
-
'Mozilla/5.0 (compatible; Saper Ruby client %s)' % Saper::VERSION
|
102
|
-
end
|
103
|
+
AGENTS[@agent.to_sym] || @agent.to_s
|
103
104
|
end
|
104
|
-
|
105
|
+
|
105
106
|
end
|
106
107
|
end
|
data/lib/saper/core/dsl.rb
CHANGED
@@ -16,26 +16,21 @@ module Saper
|
|
16
16
|
# A mixin module with a set of parsing methods
|
17
17
|
module Methods
|
18
18
|
|
19
|
+
# @todo
|
19
20
|
def namespace
|
20
21
|
@namespace ||= Saper::Namespace.new
|
21
22
|
end
|
22
23
|
|
24
|
+
# @todo
|
23
25
|
def recipe(id, name = nil, &block)
|
24
|
-
namespace[id] = Recipe.parse(id, name,
|
26
|
+
namespace[id] = Recipe.parse(id, name, namespace, &block)
|
25
27
|
end
|
26
28
|
|
29
|
+
# @todo
|
27
30
|
def [](name)
|
28
31
|
namespace[name]
|
29
32
|
end
|
30
33
|
|
31
|
-
def run_by_default(*args)
|
32
|
-
namespace.run_by_default(*args)
|
33
|
-
end
|
34
|
-
|
35
|
-
def run(*args)
|
36
|
-
namespace.run(*args)
|
37
|
-
end
|
38
|
-
|
39
34
|
end
|
40
35
|
|
41
36
|
class Recipe
|
@@ -45,9 +40,9 @@ module Saper
|
|
45
40
|
# `#to_recipe`).
|
46
41
|
# @param id [Symbol] recipe ID
|
47
42
|
# @return [Saper::Recipe, Saper::DSL::Recipe]
|
48
|
-
def self.parse(id, name = nil,
|
49
|
-
instance = self.new(id,
|
50
|
-
if
|
43
|
+
def self.parse(id = nil, name = nil, ns = nil, &block)
|
44
|
+
instance = self.new(id, name, ns, &block)
|
45
|
+
if ns.is_a?(Namespace)
|
51
46
|
instance
|
52
47
|
else
|
53
48
|
instance.to_recipe
|
@@ -56,16 +51,19 @@ module Saper
|
|
56
51
|
|
57
52
|
attr_reader :recipe
|
58
53
|
|
59
|
-
|
60
|
-
|
54
|
+
# @todo
|
55
|
+
def initialize(id, name = nil, ns = nil, &block)
|
56
|
+
@recipe, @ns, @block = Saper::Recipe.new(id, :name => name), ns, block
|
61
57
|
end
|
62
58
|
|
63
|
-
|
59
|
+
# @todo
|
60
|
+
def to_recipe(*args)
|
64
61
|
self.instance_eval(&@block) if recipe.empty?; recipe
|
65
62
|
end
|
66
63
|
|
64
|
+
# @todo
|
67
65
|
def method_missing(name, *args, &block)
|
68
|
-
@recipe << Saper::Action.new(name, *args, :namespace => @
|
66
|
+
@recipe << Saper::Action.new(name, *args, :namespace => @ns, &block)
|
69
67
|
end
|
70
68
|
|
71
69
|
end
|
data/lib/saper/core/error.rb
CHANGED
@@ -9,7 +9,7 @@ module Saper
|
|
9
9
|
# NoAction is raised whenever an implementation of Saper::Action is not found.
|
10
10
|
class ActionNotFound < Error; end
|
11
11
|
|
12
|
-
# NamespaceMissing is raised whenever Saper::
|
12
|
+
# NamespaceMissing is raised whenever Saper::Action requires a missing namespace.
|
13
13
|
class NamespaceMissing < Error; end
|
14
14
|
|
15
15
|
# NoAction is raised whenever an unsupported object is added to Saper::Recipe.
|
data/lib/saper/core/item.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Saper
|
2
2
|
class Item
|
3
3
|
|
4
|
-
# Tracks subclasses of Saper::
|
4
|
+
# Tracks subclasses of Saper::Item.
|
5
5
|
# @return [Class]
|
6
6
|
def self.inherited(base)
|
7
7
|
subclasses[base.type] = base
|
@@ -66,7 +66,7 @@ module Saper
|
|
66
66
|
self.class.type.to_sym
|
67
67
|
end
|
68
68
|
|
69
|
-
#
|
69
|
+
# @todo
|
70
70
|
def serialize
|
71
71
|
to_native
|
72
72
|
end
|
data/lib/saper/core/keychain.rb
CHANGED
@@ -1,18 +1,71 @@
|
|
1
1
|
module Saper
|
2
2
|
class Keychain
|
3
|
-
|
3
|
+
|
4
|
+
# @todo
|
5
|
+
def self.load(path)
|
6
|
+
case File.extname(path)
|
7
|
+
when '.yaml'
|
8
|
+
load_yaml(path)
|
9
|
+
when '.yml'
|
10
|
+
load_yaml(path)
|
11
|
+
when '.json'
|
12
|
+
load_json(path)
|
13
|
+
else
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# @todo
|
19
|
+
def self.load_yaml(path)
|
20
|
+
new YAML.load_file(path)
|
21
|
+
rescue
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# @todo
|
26
|
+
def self.load_json(path)
|
27
|
+
new JSON.parse(IO.read(path), :symbolize_names => true)
|
28
|
+
rescue
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
|
4
32
|
# Returns a new keychain instance
|
5
33
|
# @return [Saper::Keychain]
|
6
|
-
def initialize
|
7
|
-
|
34
|
+
def initialize(services = {})
|
35
|
+
@services = Hash[services.map { |k,v| [k.to_s, v] }]
|
8
36
|
end
|
9
37
|
|
10
38
|
# Returns access credentials for the specified service.
|
11
39
|
# @param service [String, Symbol] service name
|
12
40
|
# @return [Hash, nil]
|
13
41
|
def [](service)
|
14
|
-
|
42
|
+
@services[service.to_s] || {}
|
43
|
+
end
|
44
|
+
|
45
|
+
# @todo
|
46
|
+
def login(service)
|
47
|
+
self[service][:login]
|
48
|
+
end
|
49
|
+
|
50
|
+
# @todo
|
51
|
+
def password(service)
|
52
|
+
self[service][:password]
|
53
|
+
end
|
54
|
+
|
55
|
+
# @todo
|
56
|
+
def token(service)
|
57
|
+
self[service][:token]
|
58
|
+
end
|
59
|
+
|
60
|
+
# @todo
|
61
|
+
def has?(service)
|
62
|
+
!self[service].empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
# @todo
|
66
|
+
def services
|
67
|
+
@services.keys.select { |service| has?(service) }
|
15
68
|
end
|
16
69
|
|
17
70
|
end
|
18
|
-
end
|
71
|
+
end
|
data/lib/saper/core/namespace.rb
CHANGED
@@ -5,14 +5,14 @@ module Saper
|
|
5
5
|
# @param recipe [Symbol] recipe name
|
6
6
|
# @param input [Object] input for recipe
|
7
7
|
# @return [Saper::Runtime]
|
8
|
-
def self.
|
9
|
-
load(file).run(recipe, input)
|
8
|
+
def self.run_from_file(file, recipe, input, opts = {})
|
9
|
+
load(file).run(recipe, input, opts)
|
10
10
|
end
|
11
11
|
|
12
12
|
# Parses recipes saved in a file.
|
13
13
|
# @param file [String] file name
|
14
14
|
# @return [Saper::Namespace]
|
15
|
-
def self.load(
|
15
|
+
def self.load(path)
|
16
16
|
Namespace.parse File.read(path)
|
17
17
|
end
|
18
18
|
|
@@ -64,18 +64,13 @@ module Saper
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
-
# @todo
|
68
|
-
def run_by_default(symbol = nil)
|
69
|
-
@default ||= symbol
|
70
|
-
end
|
71
|
-
|
72
67
|
# Runs an recipe and returns resulting Saper:Runtime.
|
73
68
|
# @param input [Object]
|
74
69
|
# @param name [String, Symbol] action name
|
75
70
|
# @param options [Hash] options for Runtime instance
|
76
71
|
# @return [Saper::Runtime]
|
77
72
|
def run(name = nil, input = nil, options ={})
|
78
|
-
self[name].run(input, options
|
73
|
+
self[name].run(input, options)
|
79
74
|
end
|
80
75
|
|
81
76
|
# Returns recipe with specified ID or fails silently if not found.
|
@@ -93,17 +88,19 @@ module Saper
|
|
93
88
|
# @param id [String, Symbol] recipe ID
|
94
89
|
# @return [Saper::Recipe]
|
95
90
|
def [](id)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
return value
|
100
|
-
end
|
101
|
-
if value.respond_to?(:to_recipe)
|
102
|
-
return @recipes[id.to_s] = value.to_recipe
|
103
|
-
end
|
91
|
+
id = id.to_s
|
92
|
+
if @recipes.key?(id)
|
93
|
+
value = @recipes[id]
|
104
94
|
else
|
105
|
-
find(id)
|
95
|
+
value = find(id)
|
106
96
|
end
|
97
|
+
if value.is_a?(Recipe)
|
98
|
+
return value
|
99
|
+
end
|
100
|
+
if value.respond_to?(:to_recipe)
|
101
|
+
return @recipes[id] = value.to_recipe
|
102
|
+
end
|
103
|
+
raise RecipeNotFound, id
|
107
104
|
end
|
108
105
|
|
109
106
|
# Caches recipe instance.
|
data/lib/saper/core/recipe.rb
CHANGED
@@ -11,7 +11,7 @@ module Saper
|
|
11
11
|
unless data.is_a?(Hash)
|
12
12
|
raise(InvalidRecipe, data)
|
13
13
|
end
|
14
|
-
new(data[:id], :name => data[:name]
|
14
|
+
new(data[:id], :name => data[:name]) do |recipe|
|
15
15
|
unless data[:actions].nil?
|
16
16
|
recipe.push *Action.unserialize(data[:actions], namespace)
|
17
17
|
end
|
@@ -52,12 +52,6 @@ module Saper
|
|
52
52
|
@options[:name].nil? ? nil : @options[:name].to_s
|
53
53
|
end
|
54
54
|
|
55
|
-
# Returns Saper::Namespace instance.
|
56
|
-
# @return [Namespace]
|
57
|
-
def namespace
|
58
|
-
@options[:namespace].is_a?(Namespace) ? @options[:namespace] : nil
|
59
|
-
end
|
60
|
-
|
61
55
|
# Returns a list of data types required as input.
|
62
56
|
# @return [Array<Symbol>]
|
63
57
|
def input_required
|
@@ -92,12 +86,17 @@ module Saper
|
|
92
86
|
self
|
93
87
|
end
|
94
88
|
|
95
|
-
# Runs recipe and returns
|
89
|
+
# Runs recipe and returns Result instance.
|
96
90
|
# @param input [object] input for the first action
|
97
|
-
# @param
|
98
|
-
# @return [Saper::
|
99
|
-
def run(input = nil,
|
100
|
-
|
91
|
+
# @param runtime [Runtime, Hash] Runtime instance or options for a new one
|
92
|
+
# @return [Saper::Result]
|
93
|
+
def run(input = nil, runtime = nil)
|
94
|
+
unless runtime.is_a?(Runtime)
|
95
|
+
runtime = Runtime.new({}, runtime || {})
|
96
|
+
end
|
97
|
+
stack = Stack.new(runtime, input)
|
98
|
+
enum = enum_for(:each_stack, stack)
|
99
|
+
Result.new(enum.to_a)
|
101
100
|
end
|
102
101
|
|
103
102
|
# Returns `true` if recipe contains no actions.
|
@@ -130,5 +129,47 @@ module Saper
|
|
130
129
|
serialize.to_json(*args)
|
131
130
|
end
|
132
131
|
|
132
|
+
private
|
133
|
+
|
134
|
+
# Yields resulting Stack instances.
|
135
|
+
# @param stack [Saper::Stack]
|
136
|
+
# @param index [Integer] action undex
|
137
|
+
# @yield [Saper::Stack]
|
138
|
+
# @return [nil]
|
139
|
+
def each_stack(stack, index = 0, &block)
|
140
|
+
if stack.error?
|
141
|
+
yield stack
|
142
|
+
return nil
|
143
|
+
end
|
144
|
+
if index >= actions.size
|
145
|
+
yield stack
|
146
|
+
return nil
|
147
|
+
end
|
148
|
+
each_action_result(stack, actions[index]) do |stack|
|
149
|
+
each_stack(stack, index + 1, &block)
|
150
|
+
end
|
151
|
+
nil
|
152
|
+
end
|
153
|
+
|
154
|
+
# Yields action results one-by-one.
|
155
|
+
# @param stack [Saper::Stack]
|
156
|
+
# @param action [Saper::Action]
|
157
|
+
# @yield [Saper::Item]
|
158
|
+
# @return [nil]
|
159
|
+
def each_action_result(stack, action)
|
160
|
+
begin
|
161
|
+
results = action.run(stack.last, stack.runtime)
|
162
|
+
rescue Saper::Error => error
|
163
|
+
results = error
|
164
|
+
end
|
165
|
+
unless results.is_a?(Array)
|
166
|
+
yield stack.add(results); return nil
|
167
|
+
end
|
168
|
+
results.each do |result|
|
169
|
+
yield stack.copy.add(result)
|
170
|
+
end
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
|
133
174
|
end
|
134
175
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Saper
|
2
|
+
class Result
|
3
|
+
|
4
|
+
# @todo
|
5
|
+
attr_reader :stacks
|
6
|
+
|
7
|
+
# @todo
|
8
|
+
def initialize(stacks = [])
|
9
|
+
@stacks = stacks.to_a
|
10
|
+
if block_given?
|
11
|
+
yield self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# @todo
|
16
|
+
def success?
|
17
|
+
not failure?
|
18
|
+
end
|
19
|
+
|
20
|
+
# @todo
|
21
|
+
def failure?
|
22
|
+
stacks.all?(&:error?)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @todo
|
26
|
+
def size
|
27
|
+
stacks.size
|
28
|
+
end
|
29
|
+
|
30
|
+
# @todo
|
31
|
+
def multiple?
|
32
|
+
size > 1
|
33
|
+
end
|
34
|
+
|
35
|
+
# @todo
|
36
|
+
def to_saper_array
|
37
|
+
stacks.map(&:result)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @todo
|
41
|
+
def to_native_array
|
42
|
+
to_saper_array.map(&:to_native)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @todo
|
46
|
+
def to_serialized_array
|
47
|
+
to_saper_array.map(&:serialize)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @todo
|
51
|
+
def to_saper
|
52
|
+
multiple? ? to_saper_array : to_saper_array.first
|
53
|
+
end
|
54
|
+
|
55
|
+
# @todo
|
56
|
+
def to_native
|
57
|
+
multiple? ? to_native_array : to_native_array.first
|
58
|
+
end
|
59
|
+
|
60
|
+
# @todo
|
61
|
+
def serialize
|
62
|
+
multiple? ? to_serialized_array : to_serialized_array.first
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns runtime results as JSON.
|
66
|
+
# @return [Array]
|
67
|
+
def to_json(*args)
|
68
|
+
JSON.dump(serialize)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|