saper 0.5.2 → 0.5.3

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  3. data/Rakefile +7 -5
  4. data/bin/saper +51 -1
  5. data/lib/saper/actions/create_atom.rb +0 -5
  6. data/lib/saper/actions/nothing.rb +2 -0
  7. data/lib/saper/actions/remove_matching.rb +1 -1
  8. data/lib/saper/actions/run_recipe.rb +5 -5
  9. data/lib/saper/actions/run_recipe_and_save.rb +5 -5
  10. data/lib/saper/actions/select_matching.rb +1 -1
  11. data/lib/saper/actions/set_input.rb +0 -5
  12. data/lib/saper/core/action.rb +69 -53
  13. data/lib/saper/core/argument.rb +3 -0
  14. data/lib/saper/core/browser.rb +3 -2
  15. data/lib/saper/core/error.rb +4 -1
  16. data/lib/saper/core/keychain.rb +2 -2
  17. data/lib/saper/core/logger.rb +9 -54
  18. data/lib/saper/core/recipe.rb +36 -5
  19. data/lib/saper/core/result.rb +36 -5
  20. data/lib/saper/core/runtime.rb +17 -11
  21. data/lib/saper/core/stack.rb +17 -1
  22. data/lib/saper/items/html.rb +1 -1
  23. data/lib/saper/items/nothing.rb +9 -0
  24. data/lib/saper/items/xml.rb +5 -1
  25. data/lib/saper/version.rb +1 -1
  26. data/lib/tungsten/lib/README +5 -0
  27. data/lib/tungsten/lib/test.rb +18 -0
  28. data/lib/tungsten/lib/tungsten.rb +33 -0
  29. data/lib/tungsten/lib/tungsten/headers.rb +37 -0
  30. data/lib/tungsten/lib/tungsten/jar.rb +17 -0
  31. data/lib/tungsten/lib/tungsten/request.rb +102 -0
  32. data/lib/tungsten/lib/tungsten/response.rb +101 -0
  33. data/spec/actions/append_with_spec.rb +144 -21
  34. data/spec/actions/convert_to_html_spec.rb +126 -11
  35. data/spec/actions/convert_to_json_spec.rb +126 -11
  36. data/spec/actions/convert_to_markdown_spec.rb +126 -11
  37. data/spec/actions/convert_to_time_spec.rb +150 -15
  38. data/spec/actions/convert_to_xml_spec.rb +136 -11
  39. data/spec/actions/create_atom_spec.rb +116 -7
  40. data/spec/actions/fetch_spec.rb +110 -3
  41. data/spec/actions/find_first_spec.rb +149 -24
  42. data/spec/actions/find_spec.rb +167 -19
  43. data/spec/actions/get_attribute_spec.rb +145 -15
  44. data/spec/actions/get_contents_spec.rb +125 -4
  45. data/spec/actions/get_text_spec.rb +125 -4
  46. data/spec/actions/nothing_spec.rb +119 -4
  47. data/spec/actions/prepend_with_spec.rb +148 -22
  48. data/spec/actions/remove_after_spec.rb +160 -4
  49. data/spec/actions/remove_before_spec.rb +160 -4
  50. data/spec/actions/remove_matching_spec.rb +164 -4
  51. data/spec/actions/remove_tags_spec.rb +1 -1
  52. data/spec/actions/replace_spec.rb +1 -1
  53. data/spec/actions/run_recipe_and_save_spec.rb +1 -1
  54. data/spec/actions/run_recipe_spec.rb +1 -1
  55. data/spec/actions/save_spec.rb +1 -1
  56. data/spec/actions/select_matching_spec.rb +164 -4
  57. data/spec/actions/set_input_spec.rb +1 -1
  58. data/spec/actions/skip_tags_spec.rb +1 -1
  59. data/spec/actions/split_spec.rb +1 -1
  60. data/spec/arguments/attribute_spec.rb +1 -1
  61. data/spec/arguments/recipe_spec.rb +1 -1
  62. data/spec/arguments/text_spec.rb +1 -1
  63. data/spec/arguments/timezone_spec.rb +1 -1
  64. data/spec/arguments/variable_spec.rb +1 -1
  65. data/spec/arguments/xpath_spec.rb +1 -1
  66. data/spec/core/action_spec.rb +25 -13
  67. data/spec/core/argument_spec.rb +55 -31
  68. data/spec/core/browser_spec.rb +21 -11
  69. data/spec/core/dsl_spec.rb +1 -1
  70. data/spec/core/item_spec.rb +1 -1
  71. data/spec/core/keychain_spec.rb +1 -1
  72. data/spec/core/logger_spec.rb +1 -1
  73. data/spec/core/namespace_spec.rb +1 -1
  74. data/spec/core/recipe_spec.rb +85 -51
  75. data/spec/core/result_spec.rb +1 -1
  76. data/spec/core/runtime_spec.rb +45 -27
  77. data/spec/core/stack_spec.rb +1 -1
  78. data/spec/core/type_spec.rb +1 -1
  79. data/spec/integration/simple_invalid_spec.rb +46 -9
  80. data/spec/integration/simple_valid_spec.rb +9 -9
  81. data/spec/items/atom_spec.rb +7 -7
  82. data/spec/items/document_spec.rb +29 -29
  83. data/spec/items/html_spec.rb +80 -48
  84. data/spec/items/json_spec.rb +17 -11
  85. data/spec/items/markdown_spec.rb +30 -18
  86. data/spec/items/nothing_spec.rb +1 -1
  87. data/spec/items/text_spec.rb +12 -10
  88. data/spec/items/time_spec.rb +35 -21
  89. data/spec/items/url_spec.rb +1 -1
  90. data/spec/items/xml_spec.rb +75 -42
  91. data/spec/spec_helper.rb +90 -2
  92. metadata +51 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7aba132c13d3bc80c0034121a49cb2c45841a5c2
4
- data.tar.gz: 12e4ca23e2d339a1951e2495f7791444160f3daa
3
+ metadata.gz: fd213bbb0ccdf59a2743bea247a3466463d71e14
4
+ data.tar.gz: 682c8ef1d14f62253819905414c964837f8818d9
5
5
  SHA512:
6
- metadata.gz: 0ff6858a582e44d4e61682a014e351d688d9b303870b516a897dd48c50ab66efddd42d3077d2cc87470da4cd4173faeefbc13f3375df214353ce1d17869d13f7
7
- data.tar.gz: f8cc29550eaa47a9040fbebbf659dddd627b470dfbcd4d2a42998741d5d4aab187b2b49bcfe813279b2e3eff3989be4fdc0ccb36d47e5e3f9b0a63777cdd0414
6
+ metadata.gz: b31225725e71ed95b1fe7af32a912755ac5d0198acd8e5a0e968c31c4ef97dc517ed0ce786ec6e9992975809c7493632d42a1de03dcd37d413e6318a9598d6d0
7
+ data.tar.gz: 9c656ccb71b79b7b708db2b7f9116af6c3675a9df3cf2a7fdc1dda7b26fda64c5572481ac8474434e2b0f2c483786f3c38ea6ac195f98c63ee240786c33a3de8
data/README.md CHANGED
@@ -11,6 +11,7 @@ Common use scenarios:
11
11
 
12
12
  [![Gem Version](https://badge.fury.io/rb/saper.svg)](http://badge.fury.io/rb/saper)
13
13
  [![Build Status](https://travis-ci.org/merimond/saper.svg?branch=master)](https://travis-ci.org/merimond/saper)
14
+ [![Code Climate](https://codeclimate.com/github/merimond/saper/badges/gpa.svg)](https://codeclimate.com/github/merimond/saper)
14
15
 
15
16
  ## Installing
16
17
 
@@ -43,7 +44,7 @@ Given that file is saved as _myrecipe.txt_, you can now use the command line:
43
44
  Alternatively, you can use Ruby:
44
45
 
45
46
  #!/usr/bin/env ruby
46
- Saper.run("myrecipe.txt", :bloomberg).results
47
+ Saper.run_from_file("myrecipe.txt", :bloomberg).serialize
47
48
 
48
49
  ## Data flow
49
50
 
@@ -109,7 +110,7 @@ Below is a list of all available actions:
109
110
 
110
111
  - **set_input** (string) - sets input for the following action.
111
112
  - **create_atom** - create an Atom from saved variables.
112
- - **run_recipe_and_save** (variable, recipe) - run another recipe and save its result as a variable.
113
+ - **run_recipe_and_save** (recipe, variable) - run another recipe and save its result as a variable.
113
114
  - **run_recipe** (recipe) - run another recipe and use it's output as input for the next action.
114
115
  - **save** (variable) - save input as a variable.
115
116
 
@@ -126,4 +127,4 @@ Below is a list of all available actions:
126
127
 
127
128
  Copyright (c) 2013 Merimond Corporation. MIT license, see [LICENSE] for details.
128
129
 
129
- [LICENSE]: http://github.com/merimond/saper/blob/master/LICENSE
130
+ [LICENSE]: http://github.com/merimond/saper/blob/master/LICENSE
data/Rakefile CHANGED
@@ -1,14 +1,16 @@
1
1
  require 'rubygems'
2
2
  require 'bundler'
3
- require 'rake/testtask'
3
+ #require 'rake/testtask'
4
4
  require 'yard'
5
5
 
6
6
  Bundler.require
7
7
  Bundler::GemHelper.install_tasks
8
8
 
9
- desc "Run tests"
10
- Rake::TestTask.new do |t|
11
- t.test_files = FileList['spec/**/*_spec.rb']
9
+ begin
10
+ require 'rspec/core/rake_task'
11
+ RSpec::Core::RakeTask.new(:spec)
12
+ rescue LoadError
13
+ # no rspec available
12
14
  end
13
15
 
14
16
  desc "Generate docs"
@@ -16,4 +18,4 @@ YARD::Rake::YardocTask.new do |t|
16
18
  t.files = ['lib/saper/**/*.rb']
17
19
  end
18
20
 
19
- task :default => :test
21
+ task :default => :spec
data/bin/saper CHANGED
@@ -14,6 +14,7 @@ Options are ...
14
14
  opt :input, "Recipe input", :type => :string
15
15
  opt :agent, "User-Agent", :type => :string
16
16
  opt :keychain, "Keychain file", :type => :string
17
+ opt :verbose, "Verbose output"
17
18
  end
18
19
 
19
20
  if opts[:recipe].nil?
@@ -24,8 +25,57 @@ unless ARGV.size == 1
24
25
  Trollop::die "Please specify filename"
25
26
  end
26
27
 
28
+ def bold(string)
29
+ puts "\n\033[1m%s\033[0m" % string
30
+ end
31
+
32
+ def red(string)
33
+ puts "\033[31m%s\033[0m" % string
34
+ end
35
+
36
+ def green(string)
37
+ puts "\033[32m%s\033[0m" % string
38
+ end
39
+
40
+ def print_value(value)
41
+ puts "> %s" % value.class
42
+ end
43
+
44
+ def print_action_name(action, error = false)
45
+ error ? red(action.name) : green(action.name)
46
+ end
47
+
48
+ def print_each_action(stack, actions)
49
+ actions.each_with_index do |action, i|
50
+ print_value stack.to_a[i]
51
+ print_action_name action, stack.to_a[i+1].is_a?(Saper::Error)
52
+ end
53
+ end
54
+
55
+ def print_each_stack(result, actions)
56
+ result.stacks.each_with_index do |stack, i|
57
+ bold "STACK %s / %s" % [i + 1, result.size]
58
+ print_each_action(stack, actions)
59
+ end
60
+ end
61
+
62
+ if opts[:verbose]
63
+ bold "ACTION LOG"
64
+ opts[:logger] = Saper::Logger.new($stdout)
65
+ end
66
+
27
67
  begin
28
- puts Saper.run_from_file(ARGV.first, opts.delete(:recipe), opts.delete(:input), opts).serialize
68
+ recipe = Saper.load(ARGV.first)[opts[:recipe]]
69
+ result = recipe.run(opts[:input], opts)
70
+ if opts[:verbose]
71
+ bold "RESULTS: %s" % result.size
72
+ end
73
+ result.to_serialized_array.each do |result|
74
+ result.nil? ? red("null") : green(result)
75
+ end
76
+ if opts[:verbose] && result.failure?
77
+ print_each_stack(result, recipe.actions)
78
+ end
29
79
  rescue Errno::ENOENT => e
30
80
  Trollop::die("File not found: %s" % ARGV.first)
31
81
  rescue NoMethodError => e
@@ -12,11 +12,6 @@ module Saper
12
12
  end
13
13
  end
14
14
 
15
- # Overrides default logic
16
- def requires
17
- []
18
- end
19
-
20
15
  end
21
16
  end
22
17
  end
@@ -2,6 +2,8 @@ module Saper
2
2
  module Actions
3
3
  class Nothing < Action
4
4
 
5
+ accepts :anything
6
+
5
7
  run do |runtime, input|
6
8
  input
7
9
  end
@@ -6,7 +6,7 @@ module Saper
6
6
  accepts :text, :returns => :text
7
7
 
8
8
  run do |runtime, input, pattern|
9
- input.to_s =~ Regexp.new(pattern) ? Items::Nothing.new : input
9
+ input.to_s =~ Regexp.new(pattern) ? Items::Nothing.new(:filtered => true) : input
10
10
  end
11
11
 
12
12
  end
@@ -3,20 +3,20 @@ module Saper
3
3
  class RunRecipe < Action
4
4
 
5
5
  argument :recipe
6
- accepts :anything, :returns => Proc.new { |instance| nil } # TODO
6
+ accepts :anything, :returns => Proc.new { |instance| instance.args.first.requires }
7
7
 
8
8
  run do |runtime, input, subrecipe|
9
9
  subrecipe.run(input).to_saper
10
10
  end
11
11
 
12
12
  # Overrides default logic
13
- def multiple?
14
- args.first.multiple?
13
+ def accepts?(type)
14
+ args.first.accepts?(type)
15
15
  end
16
16
 
17
17
  # Overrides default logic
18
- def requires
19
- args.first.input_required
18
+ def multiple?
19
+ args.first.multiple?
20
20
  end
21
21
 
22
22
  end
@@ -2,19 +2,19 @@ module Saper
2
2
  module Actions
3
3
  class RunRecipeAndSave < Action
4
4
 
5
- argument :variable
6
5
  argument :recipe
7
- accepts :anything
6
+ argument :variable
7
+ accepts :anything, :returns => Proc.new { |instance| instance.args.first.requires }
8
8
 
9
- run do |runtime, input, variable, subrecipe|
9
+ run do |runtime, input, subrecipe, variable|
10
10
  result = subrecipe.run(input).to_saper
11
11
  runtime[variable] = result unless result.nil?
12
12
  input
13
13
  end
14
14
 
15
15
  # Overrides default logic
16
- def requires
17
- args.last.input_required
16
+ def accepts?(type)
17
+ args.first.accepts?(type)
18
18
  end
19
19
 
20
20
  end
@@ -6,7 +6,7 @@ module Saper
6
6
  accepts :text, :returns => :text
7
7
 
8
8
  run do |runtime, input, pattern|
9
- input.to_s =~ Regexp.new(pattern) ? input : Items::Nothing.new
9
+ input.to_s =~ Regexp.new(pattern) ? input : Items::Nothing.new(:filtered => true)
10
10
  end
11
11
 
12
12
  end
@@ -9,11 +9,6 @@ module Saper
9
9
  string
10
10
  end
11
11
 
12
- # Overrides default logic
13
- def requires
14
- []
15
- end
16
-
17
12
  end
18
13
  end
19
14
  end
@@ -1,6 +1,8 @@
1
1
  module Saper
2
2
  class Action
3
3
 
4
+ INPUT_TYPES = %i(text atom document html json markdown nothing time url xml)
5
+
4
6
  # Tracks subclasses of Saper::Action.
5
7
  # @return [Class]
6
8
  def self.inherited(base)
@@ -52,47 +54,17 @@ module Saper
52
54
  return @types.nil? ? [] : @types.keys
53
55
  end
54
56
  if input == :anything
55
- return @types = {}
57
+ INPUT_TYPES.each { |type| accepts(type, options) }
56
58
  end
57
59
  output = options[:returns] || input
58
- unless Item.exists?(input)
59
- raise(InvalidInput, "Invalid input type: %s" % input)
60
- end
61
- if output.is_a?(Symbol) && !Item.exists?(output)
62
- raise(InvalidInput, "Invalid output type: %s" % output)
63
- end
64
60
  @types ||= {}
65
61
  @types[input] = output
66
62
  end
67
63
 
68
- # Returns `true` if action accepts no input.
69
- # @return [Boolean]
70
- def self.accepts_nothing?
71
- @types.nil?
72
- end
73
-
74
- # Returns `true` if action accepts at least some kind of input.
75
- # @return [Boolean]
76
- def self.accepts_something?
77
- !accepts_nothing? && !accepts_anything?
78
- end
79
-
80
- # Returns `true` if action accepts any kind of input.
81
- # @return [Boolean]
82
- def self.accepts_anything?
83
- @types.empty?
84
- end
85
-
86
64
  # Returns `true` if action accepts specified type as input.
87
65
  # @param type [Symbol]
88
66
  # @return [Boolean]
89
67
  def self.accepts?(type)
90
- if accepts_nothing?
91
- return false
92
- end
93
- if accepts_anything?
94
- return true
95
- end
96
68
  accepts.include?(type)
97
69
  end
98
70
 
@@ -102,6 +74,13 @@ module Saper
102
74
  @block ||= block
103
75
  end
104
76
 
77
+ # Returns the type of output for specfied input type.
78
+ # @param type [Symbol] argument type (e.g. :text, :xpath)
79
+ # @return [Array<Symbol>]
80
+ def self.output_for(type)
81
+ accepts?(type) ? @types[type] : :nothing
82
+ end
83
+
105
84
  # Returns `true` if action returns multiple items. Note that this method will report incorrect data for some actions. Use #multiple? instead.
106
85
  def self.returns_multiple_items?
107
86
  @multiple == true
@@ -168,25 +147,9 @@ module Saper
168
147
  # @return [void] depends on action type
169
148
  def run(input = nil, runtime = nil)
170
149
  runtime ||= Runtime.new
171
- input = validate_input(input)
172
- block.call(runtime, input, *args)
173
- end
174
-
175
- # @todo
176
- def validate_input(input)
177
- unless self.class.accepts_something?
178
- return input
179
- end
180
- unless input.is_a?(Item)
181
- input = self.class.accepts.map { |type| Item.try(type, input) }.compact.first
182
- end
183
- if input.nil?
184
- raise(InvalidInput, input)
185
- end
186
- unless self.class.accepts?(input.type)
187
- raise(InvalidInput, input)
188
- end
189
- input
150
+ input = validate_input(input)
151
+ output = block.call(runtime, input, *args)
152
+ validate_output output, output_for(input.type)
190
153
  end
191
154
 
192
155
  # Returns human readable action name.
@@ -201,10 +164,18 @@ module Saper
201
164
  @options[:namespace].is_a?(Namespace) ? @options[:namespace] : nil
202
165
  end
203
166
 
204
- # Returns a list of data types that are accepted as input.
167
+ # Returns `true` if action accepts specified type as input.
168
+ # @param type [Symbol]
169
+ # @return [Boolean]
170
+ def accepts?(type)
171
+ self.class.accepts.include?(type)
172
+ end
173
+
174
+ # Returns the type of output for specfied input type.
175
+ # @param type [Symbol] argument type (e.g. :text, :xpath)
205
176
  # @return [Array<Symbol>]
206
- def requires
207
- self.class.accepts
177
+ def output_for(type)
178
+ out = self.class.output_for(type); out.is_a?(Proc) ? out.call(self) : out
208
179
  end
209
180
 
210
181
  # Returns values of action arguments.
@@ -219,6 +190,12 @@ module Saper
219
190
  { :type => self.class.type, :args => @arguments.map(&:serialize) }
220
191
  end
221
192
 
193
+ # Returns a JSON representation of this action.
194
+ # @return [String]
195
+ def to_json(*args)
196
+ serialize.to_json(*args)
197
+ end
198
+
222
199
  # Returns `true` if action returns multiple items.
223
200
  # @return [Boolean]
224
201
  def multiple?
@@ -237,5 +214,44 @@ module Saper
237
214
  "\t%s %s" % [self.class.type, @arguments.map(&:to_string).join(", ")]
238
215
  end
239
216
 
217
+ private
218
+
219
+ # @todo
220
+ def validate_output(output, type)
221
+ if output.is_a?(Array) && output.empty?
222
+ raise InvalidOutput
223
+ end
224
+ if output.is_a?(Array)
225
+ return output.map { |o| validate_output(o, type) }
226
+ end
227
+ if output.is_a?(Items::Nothing) && output.filtered?
228
+ return output
229
+ end
230
+ unless output.is_a?(Item)
231
+ output = Item.try(type, output)
232
+ end
233
+ if output.is_a?(Item) && output.type == type
234
+ return output
235
+ end
236
+ raise InvalidOutput
237
+ end
238
+
239
+ # @todo
240
+ def validate_input(input)
241
+ unless input.is_a?(Item)
242
+ input = self.class.accepts.map { |type| Item.try(type, input) }.compact.first
243
+ end
244
+ if input.nil? && accepts?(:nothing)
245
+ return Items::Nothing.new
246
+ end
247
+ if input.nil?
248
+ raise(InvalidInput, input)
249
+ end
250
+ unless accepts?(input.type)
251
+ raise(InvalidInput, input)
252
+ end
253
+ input
254
+ end
255
+
240
256
  end
241
257
  end
@@ -60,6 +60,9 @@ module Saper
60
60
  # @param value [Object]
61
61
  # @return [self]
62
62
  def set(value)
63
+ if value.is_a?(Saper::Argument)
64
+ value = value.value
65
+ end
63
66
  unless valid?(value)
64
67
  raise InvalidArgument, value
65
68
  else
@@ -52,6 +52,7 @@ module Saper
52
52
  @received += resp.to_hash.to_s.size
53
53
  @received += body.size
54
54
  end
55
+ @logger.new_browser(self)
55
56
  end
56
57
 
57
58
  # Returns the number of HTTP requests.
@@ -71,7 +72,7 @@ module Saper
71
72
  # @param query [Hash] query options
72
73
  # @return [Saper::Document]
73
74
  def get(url, query = {})
74
- @logger.download(url)
75
+ @logger.new_get_request(url)
75
76
  @history.push url
76
77
  data = @mech.get(url, query)
77
78
  Saper::Items::Document.new data.body, data.uri, data.header
@@ -84,7 +85,7 @@ module Saper
84
85
  # @param query [Hash] payload
85
86
  # @return [Saper::Document]
86
87
  def post(url, query = {}, headers = {})
87
- @logger.download(url)
88
+ @logger.new_post_request(url)
88
89
  @history.push url
89
90
  data = @mech.post(url, query, headers)
90
91
  Saper::Items::Document.new data.body, data.uri, data.header