saper 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
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