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