saper 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -0
  3. data/Rakefile +6 -6
  4. data/bin/saper +17 -36
  5. data/lib/saper.rb +63 -20
  6. data/lib/saper/actions/append_with.rb +1 -1
  7. data/lib/saper/actions/convert_to_html.rb +1 -1
  8. data/lib/saper/actions/convert_to_json.rb +6 -2
  9. data/lib/saper/actions/convert_to_markdown.rb +1 -1
  10. data/lib/saper/actions/convert_to_time.rb +38 -1
  11. data/lib/saper/actions/convert_to_xml.rb +1 -1
  12. data/lib/saper/actions/create_atom.rb +6 -2
  13. data/lib/saper/actions/fetch.rb +3 -2
  14. data/lib/saper/actions/fetch_with_token.rb +18 -0
  15. data/lib/saper/actions/find.rb +1 -1
  16. data/lib/saper/actions/find_first.rb +1 -1
  17. data/lib/saper/actions/get_attribute.rb +7 -2
  18. data/lib/saper/actions/get_contents.rb +1 -1
  19. data/lib/saper/actions/get_text.rb +1 -1
  20. data/lib/saper/actions/nothing.rb +11 -0
  21. data/lib/saper/actions/prepend_with.rb +2 -2
  22. data/lib/saper/actions/remove_after.rb +2 -2
  23. data/lib/saper/actions/remove_before.rb +2 -2
  24. data/lib/saper/actions/remove_matching.rb +2 -2
  25. data/lib/saper/actions/remove_tags.rb +1 -1
  26. data/lib/saper/actions/replace.rb +1 -1
  27. data/lib/saper/actions/run_recipe.rb +2 -2
  28. data/lib/saper/actions/run_recipe_and_save.rb +3 -3
  29. data/lib/saper/actions/save.rb +2 -2
  30. data/lib/saper/actions/select_matching.rb +2 -2
  31. data/lib/saper/actions/set_input.rb +1 -1
  32. data/lib/saper/actions/skip_tags.rb +1 -1
  33. data/lib/saper/actions/split.rb +1 -1
  34. data/lib/saper/arguments/attribute.rb +6 -0
  35. data/lib/saper/arguments/recipe.rb +12 -6
  36. data/lib/saper/arguments/service.rb +12 -0
  37. data/lib/saper/arguments/text.rb +1 -0
  38. data/lib/saper/arguments/timezone.rb +2 -1
  39. data/lib/saper/arguments/url.rb +12 -0
  40. data/lib/saper/arguments/variable.rb +2 -1
  41. data/lib/saper/arguments/xpath.rb +2 -1
  42. data/lib/saper/core/action.rb +43 -13
  43. data/lib/saper/core/argument.rb +30 -14
  44. data/lib/saper/core/browser.rb +29 -28
  45. data/lib/saper/core/dsl.rb +14 -16
  46. data/lib/saper/core/error.rb +1 -1
  47. data/lib/saper/core/item.rb +2 -2
  48. data/lib/saper/core/keychain.rb +58 -5
  49. data/lib/saper/core/namespace.rb +15 -18
  50. data/lib/saper/core/recipe.rb +53 -12
  51. data/lib/saper/core/result.rb +72 -0
  52. data/lib/saper/core/runtime.rb +44 -177
  53. data/lib/saper/core/stack.rb +57 -0
  54. data/lib/saper/items/atom.rb +18 -1
  55. data/lib/saper/items/document.rb +39 -19
  56. data/lib/saper/items/html.rb +52 -7
  57. data/lib/saper/items/json.rb +19 -1
  58. data/lib/saper/items/markdown.rb +14 -4
  59. data/lib/saper/items/nothing.rb +6 -0
  60. data/lib/saper/items/text.rb +28 -3
  61. data/lib/saper/items/time.rb +25 -2
  62. data/lib/saper/items/url.rb +8 -2
  63. data/lib/saper/items/xml.rb +51 -11
  64. data/lib/{lib → saper/patches}/mechanize.rb +0 -0
  65. data/lib/{lib → saper/patches}/nokogiri.rb +0 -0
  66. data/lib/saper/version.rb +2 -2
  67. data/spec/actions/append_with_spec.rb +29 -52
  68. data/spec/actions/convert_to_html_spec.rb +13 -30
  69. data/spec/actions/convert_to_json_spec.rb +13 -30
  70. data/spec/actions/convert_to_markdown_spec.rb +17 -19
  71. data/spec/actions/convert_to_time_spec.rb +25 -43
  72. data/spec/actions/convert_to_xml_spec.rb +15 -11
  73. data/spec/actions/create_atom_spec.rb +11 -19
  74. data/spec/actions/fetch_spec.rb +3 -8
  75. data/spec/actions/find_first_spec.rb +38 -40
  76. data/spec/actions/find_spec.rb +23 -39
  77. data/spec/actions/get_attribute_spec.rb +30 -3
  78. data/spec/actions/{get_contents.rb → get_contents_spec.rb} +0 -0
  79. data/spec/actions/{get_text.rb → get_text_spec.rb} +0 -0
  80. data/spec/actions/nothing_spec.rb +7 -0
  81. data/spec/actions/prepend_with_spec.rb +31 -18
  82. data/spec/actions/{remove_after.rb → remove_after_spec.rb} +0 -0
  83. data/spec/actions/{remove_before.rb → remove_before_spec.rb} +0 -0
  84. data/spec/actions/remove_matching_spec.rb +7 -0
  85. data/spec/actions/remove_tags_spec.rb +7 -0
  86. data/spec/actions/run_recipe_and_save_spec.rb +7 -0
  87. data/spec/actions/run_recipe_spec.rb +7 -0
  88. data/spec/arguments/attribute_spec.rb +7 -0
  89. data/spec/arguments/recipe_spec.rb +7 -0
  90. data/spec/arguments/text_spec.rb +7 -0
  91. data/spec/arguments/timezone_spec.rb +7 -0
  92. data/spec/arguments/variable_spec.rb +7 -0
  93. data/spec/arguments/xpath_spec.rb +7 -0
  94. data/spec/core/action_spec.rb +13 -142
  95. data/spec/core/argument_spec.rb +38 -71
  96. data/spec/core/browser_spec.rb +18 -3
  97. data/spec/core/dsl_spec.rb +1 -1
  98. data/spec/core/item_spec.rb +1 -1
  99. data/spec/core/namespace_spec.rb +0 -11
  100. data/spec/core/recipe_spec.rb +54 -77
  101. data/spec/core/result_spec.rb +7 -0
  102. data/spec/core/runtime_spec.rb +20 -151
  103. data/spec/core/stack_spec.rb +7 -0
  104. data/spec/integration/simple_invalid_spec.rb +39 -0
  105. data/spec/integration/simple_valid_spec.rb +39 -0
  106. data/spec/items/atom_spec.rb +6 -1
  107. data/spec/items/document_spec.rb +93 -15
  108. data/spec/items/html_spec.rb +45 -28
  109. data/spec/items/json_spec.rb +10 -10
  110. data/spec/items/markdown_spec.rb +24 -3
  111. data/spec/items/nothing_spec.rb +1 -1
  112. data/spec/items/text_spec.rb +13 -41
  113. data/spec/items/time_spec.rb +25 -4
  114. data/spec/items/url_spec.rb +1 -7
  115. data/spec/items/xml_spec.rb +46 -39
  116. data/spec/spec_helper.rb +2 -21
  117. metadata +63 -60
  118. data/lib/lib/json_search.rb +0 -54
  119. data/spec/actions/run_recipe_and_save_spec.tmp.rb +0 -52
  120. data/spec/actions/run_recipe_spec.tmp.rb +0 -53
@@ -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
- case @agent
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
@@ -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, :namespace => namespace, &block)
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, options = {}, &block)
49
- instance = self.new(id, options.merge(:name => name), &block)
50
- if options[:namespace].is_a?(Namespace)
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
- def initialize(id = nil, options = {}, &block)
60
- @recipe, @block = Saper::Recipe.new(id, options), block
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
- def to_recipe
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 => @recipe.namespace, &block)
66
+ @recipe << Saper::Action.new(name, *args, :namespace => @ns, &block)
69
67
  end
70
68
 
71
69
  end
@@ -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::Runtime requires a missing namespace.
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.
@@ -1,7 +1,7 @@
1
1
  module Saper
2
2
  class Item
3
3
 
4
- # Tracks subclasses of Saper::Argument.
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
@@ -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
- # TODO: not yet implemented
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
- # TODO: not yet implemented
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
@@ -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.run(file, recipe, input = nil)
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(file)
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.merge(:namespace => self))
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
- if @recipes.key?(id.to_s)
97
- value = @recipes[id.to_s]
98
- if value.is_a?(Recipe)
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.
@@ -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], :namespace => namespace) do |recipe|
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 Runtime instance.
89
+ # Runs recipe and returns Result instance.
96
90
  # @param input [object] input for the first action
97
- # @param options [Hash] options for Runtime object
98
- # @return [Saper::Runtime]
99
- def run(input = nil, options = {})
100
- Runtime.new(actions, input, { :namespace => namespace }.merge(options))
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