saper 0.5.2 → 0.5.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +4 -3
- data/Rakefile +7 -5
- data/bin/saper +51 -1
- data/lib/saper/actions/create_atom.rb +0 -5
- data/lib/saper/actions/nothing.rb +2 -0
- data/lib/saper/actions/remove_matching.rb +1 -1
- data/lib/saper/actions/run_recipe.rb +5 -5
- data/lib/saper/actions/run_recipe_and_save.rb +5 -5
- data/lib/saper/actions/select_matching.rb +1 -1
- data/lib/saper/actions/set_input.rb +0 -5
- data/lib/saper/core/action.rb +69 -53
- data/lib/saper/core/argument.rb +3 -0
- data/lib/saper/core/browser.rb +3 -2
- data/lib/saper/core/error.rb +4 -1
- data/lib/saper/core/keychain.rb +2 -2
- data/lib/saper/core/logger.rb +9 -54
- data/lib/saper/core/recipe.rb +36 -5
- data/lib/saper/core/result.rb +36 -5
- data/lib/saper/core/runtime.rb +17 -11
- data/lib/saper/core/stack.rb +17 -1
- data/lib/saper/items/html.rb +1 -1
- data/lib/saper/items/nothing.rb +9 -0
- data/lib/saper/items/xml.rb +5 -1
- data/lib/saper/version.rb +1 -1
- data/lib/tungsten/lib/README +5 -0
- data/lib/tungsten/lib/test.rb +18 -0
- data/lib/tungsten/lib/tungsten.rb +33 -0
- data/lib/tungsten/lib/tungsten/headers.rb +37 -0
- data/lib/tungsten/lib/tungsten/jar.rb +17 -0
- data/lib/tungsten/lib/tungsten/request.rb +102 -0
- data/lib/tungsten/lib/tungsten/response.rb +101 -0
- data/spec/actions/append_with_spec.rb +144 -21
- data/spec/actions/convert_to_html_spec.rb +126 -11
- data/spec/actions/convert_to_json_spec.rb +126 -11
- data/spec/actions/convert_to_markdown_spec.rb +126 -11
- data/spec/actions/convert_to_time_spec.rb +150 -15
- data/spec/actions/convert_to_xml_spec.rb +136 -11
- data/spec/actions/create_atom_spec.rb +116 -7
- data/spec/actions/fetch_spec.rb +110 -3
- data/spec/actions/find_first_spec.rb +149 -24
- data/spec/actions/find_spec.rb +167 -19
- data/spec/actions/get_attribute_spec.rb +145 -15
- data/spec/actions/get_contents_spec.rb +125 -4
- data/spec/actions/get_text_spec.rb +125 -4
- data/spec/actions/nothing_spec.rb +119 -4
- data/spec/actions/prepend_with_spec.rb +148 -22
- data/spec/actions/remove_after_spec.rb +160 -4
- data/spec/actions/remove_before_spec.rb +160 -4
- data/spec/actions/remove_matching_spec.rb +164 -4
- data/spec/actions/remove_tags_spec.rb +1 -1
- data/spec/actions/replace_spec.rb +1 -1
- data/spec/actions/run_recipe_and_save_spec.rb +1 -1
- data/spec/actions/run_recipe_spec.rb +1 -1
- data/spec/actions/save_spec.rb +1 -1
- data/spec/actions/select_matching_spec.rb +164 -4
- data/spec/actions/set_input_spec.rb +1 -1
- data/spec/actions/skip_tags_spec.rb +1 -1
- data/spec/actions/split_spec.rb +1 -1
- data/spec/arguments/attribute_spec.rb +1 -1
- data/spec/arguments/recipe_spec.rb +1 -1
- data/spec/arguments/text_spec.rb +1 -1
- data/spec/arguments/timezone_spec.rb +1 -1
- data/spec/arguments/variable_spec.rb +1 -1
- data/spec/arguments/xpath_spec.rb +1 -1
- data/spec/core/action_spec.rb +25 -13
- data/spec/core/argument_spec.rb +55 -31
- data/spec/core/browser_spec.rb +21 -11
- data/spec/core/dsl_spec.rb +1 -1
- data/spec/core/item_spec.rb +1 -1
- data/spec/core/keychain_spec.rb +1 -1
- data/spec/core/logger_spec.rb +1 -1
- data/spec/core/namespace_spec.rb +1 -1
- data/spec/core/recipe_spec.rb +85 -51
- data/spec/core/result_spec.rb +1 -1
- data/spec/core/runtime_spec.rb +45 -27
- data/spec/core/stack_spec.rb +1 -1
- data/spec/core/type_spec.rb +1 -1
- data/spec/integration/simple_invalid_spec.rb +46 -9
- data/spec/integration/simple_valid_spec.rb +9 -9
- data/spec/items/atom_spec.rb +7 -7
- data/spec/items/document_spec.rb +29 -29
- data/spec/items/html_spec.rb +80 -48
- data/spec/items/json_spec.rb +17 -11
- data/spec/items/markdown_spec.rb +30 -18
- data/spec/items/nothing_spec.rb +1 -1
- data/spec/items/text_spec.rb +12 -10
- data/spec/items/time_spec.rb +35 -21
- data/spec/items/url_spec.rb +1 -1
- data/spec/items/xml_spec.rb +75 -42
- data/spec/spec_helper.rb +90 -2
- metadata +51 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fd213bbb0ccdf59a2743bea247a3466463d71e14
|
4
|
+
data.tar.gz: 682c8ef1d14f62253819905414c964837f8818d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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** (
|
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
|
-
|
10
|
-
|
11
|
-
|
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 => :
|
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
|
-
|
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
|
@@ -3,20 +3,20 @@ module Saper
|
|
3
3
|
class RunRecipe < Action
|
4
4
|
|
5
5
|
argument :recipe
|
6
|
-
accepts :anything, :returns => Proc.new { |instance|
|
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
|
14
|
-
args.first.
|
13
|
+
def accepts?(type)
|
14
|
+
args.first.accepts?(type)
|
15
15
|
end
|
16
16
|
|
17
17
|
# Overrides default logic
|
18
|
-
def
|
19
|
-
args.first.
|
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
|
-
|
6
|
+
argument :variable
|
7
|
+
accepts :anything, :returns => Proc.new { |instance| instance.args.first.requires }
|
8
8
|
|
9
|
-
run do |runtime, input,
|
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
|
17
|
-
args.
|
16
|
+
def accepts?(type)
|
17
|
+
args.first.accepts?(type)
|
18
18
|
end
|
19
19
|
|
20
20
|
end
|
data/lib/saper/core/action.rb
CHANGED
@@ -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
|
-
|
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
|
172
|
-
block.call(runtime, input, *args)
|
173
|
-
|
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
|
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
|
207
|
-
self.class.
|
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
|
data/lib/saper/core/argument.rb
CHANGED
data/lib/saper/core/browser.rb
CHANGED
@@ -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.
|
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.
|
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
|