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
@@ -1,7 +1,7 @@
1
1
  module Saper
2
2
 
3
3
  # Generic exception acting as a parent class for all exceptions in Saper library.
4
- class Error < Exception; end
4
+ class Error < StandardError; end
5
5
 
6
6
  # FileUnreadable is raised whenever a recipe file cannot be read.
7
7
  class FileUnreadable < Error; end
@@ -33,6 +33,9 @@ module Saper
33
33
  # InvalidInput is raised whenever Saper::Action does not support this type of input.
34
34
  class InvalidInput < Error; end
35
35
 
36
+ # InvalidOutput is raised when execution of an action yields unexpected results.
37
+ class InvalidOutput < Error; end
38
+
36
39
  # RecipeNotFound is raised whenever action calls a recipe that cannot be found.
37
40
  class RecipeNotFound < Error; end
38
41
 
@@ -2,7 +2,7 @@ module Saper
2
2
  class Keychain
3
3
 
4
4
  # @todo
5
- def self.load(path)
5
+ def self.load(path, options = {})
6
6
  case File.extname(path)
7
7
  when '.yaml'
8
8
  load_yaml(path)
@@ -31,7 +31,7 @@ module Saper
31
31
 
32
32
  # Returns a new keychain instance
33
33
  # @return [Saper::Keychain]
34
- def initialize(services = {})
34
+ def initialize(services = {}, options = {})
35
35
  @services = Hash[services.map { |k,v| [k.to_s, v] }]
36
36
  end
37
37
 
@@ -9,66 +9,21 @@ module Saper
9
9
  @io = io || StringIO.new
10
10
  end
11
11
 
12
- def download(url)
13
- io.write "%s %s\r\n" % [bold("Downloading"), url]
12
+ def executing_recipe(recipe)
13
+ io.write "Executing recipe: %s\n" % recipe.id
14
14
  end
15
15
 
16
- def runtime(instance, indent = 0)
17
- io.write bold("Recipe tree\r\n") if indent == 0
18
- io.write "%s%s\r\n" % [" " * indent, action(instance)]
19
- instance.children.each do |child|
20
- runtime(child, indent + 1)
21
- end
16
+ def new_browser(browser)
17
+ io.write "New browser: %s\n" % browser.agent
22
18
  end
23
19
 
24
- def bold(string)
25
- "\033[1m%s\033[0m" % string
20
+ def new_post_request(url)
21
+ io.write "Posting to %s\n" % url
26
22
  end
27
23
 
28
- def red(string)
29
- "\033[31m%s\033[0m" % string
24
+ def new_get_request(url)
25
+ io.write "Getting %s\n" % url
30
26
  end
31
-
32
- def green(string)
33
- "\033[32m%s\033[0m" % string
34
- end
35
-
36
- def action(instance)
37
- "%s %s > %s" % [action_name(instance), action_input(instance), action_output(instance)]
38
- end
39
-
40
- def action_input(instance)
41
- item(instance.input)
42
- end
43
-
44
- def action_output(instance)
45
- item(instance.action.multiple? ? instance.output : instance.output.first)
46
- end
47
-
48
- def action_name(instance)
49
- instance.error? ? red(instance.action.name) : green(instance.action.name)
50
- end
51
-
52
- def item(instance)
53
- case instance
54
- when Array
55
- "[%s]" % instance.map { |i| item(i) }.join(",")
56
- when nil
57
- 'nil'
58
- when Items::Document
59
- 'Document'
60
- when Items::HTML
61
- 'HTML'
62
- when String
63
- '%s' % instance
64
- when Items::Text
65
- '%s' % instance.to_s
66
- when Items::Atom
67
- '%s' % instance.to_hash
68
- else
69
- instance.class
70
- end
71
- end
72
-
27
+
73
28
  end
74
29
  end
@@ -52,16 +52,36 @@ module Saper
52
52
  @options[:name].nil? ? nil : @options[:name].to_s
53
53
  end
54
54
 
55
+ # Returns `true` if recipe accepts specified type as input.
56
+ # @param type [Symbol]
57
+ # @return [Boolean]
58
+ def accepts?(type)
59
+ actions.each do |action|
60
+ unless action.accepts?(type)
61
+ return false
62
+ end
63
+ type = action.output_for(type)
64
+ end
65
+ true
66
+ end
67
+
68
+ # Sets (one) or returns (all) acceptable input types.
69
+ # @param input [Symbol]
70
+ # @return [Array<Symbol>]
71
+ def accepts
72
+ Action::INPUT_TYPES.select { |type| accepts?(type) }
73
+ end
74
+
55
75
  # Returns a list of data types required as input.
56
76
  # @return [Array<Symbol>]
57
- def input_required
58
- empty? ? [] : actions.first.requires
77
+ def requires
78
+ accepts.include?(:nothing) ? [] : accepts
59
79
  end
60
80
 
61
81
  # Returns `true` if recipe requires some input.
62
82
  # @return [Boolean]
63
- def input_required?
64
- !input_required.empty?
83
+ def requires?
84
+ !requires.empty?
65
85
  end
66
86
 
67
87
  # Adds a new action to the end of the recipe and returns self.
@@ -94,9 +114,10 @@ module Saper
94
114
  unless runtime.is_a?(Runtime)
95
115
  runtime = Runtime.new({}, runtime || {})
96
116
  end
117
+ runtime.logger.executing_recipe(self)
97
118
  stack = Stack.new(runtime, input)
98
119
  enum = enum_for(:each_stack, stack)
99
- Result.new(enum.to_a)
120
+ Result.new(enum)
100
121
  end
101
122
 
102
123
  # Returns `true` if recipe contains no actions.
@@ -141,6 +162,10 @@ module Saper
141
162
  yield stack
142
163
  return nil
143
164
  end
165
+ if stack.filtered?
166
+ yield stack
167
+ return nil
168
+ end
144
169
  if index >= actions.size
145
170
  yield stack
146
171
  return nil
@@ -162,9 +187,15 @@ module Saper
162
187
  rescue Saper::Error => error
163
188
  results = error
164
189
  end
190
+ if results.is_a?(Items::Nothing) && results.filtered?
191
+ yield stack.add(results); return nil
192
+ end
165
193
  unless results.is_a?(Array)
166
194
  yield stack.add(results); return nil
167
195
  end
196
+ if results.empty?
197
+ yield stack.add(Items::Nothing.new)
198
+ end
168
199
  results.each do |result|
169
200
  yield stack.copy.add(result)
170
201
  end
@@ -4,14 +4,34 @@ module Saper
4
4
  # @todo
5
5
  attr_reader :stacks
6
6
 
7
+ # @todo
8
+ attr_reader :duration
9
+
7
10
  # @todo
8
11
  def initialize(stacks = [])
9
- @stacks = stacks.to_a
12
+ started = Time.now
13
+ @stacks = stacks.to_a
14
+ @duration = Time.now - started
10
15
  if block_given?
11
16
  yield self
12
17
  end
13
18
  end
14
19
 
20
+ # @todo
21
+ def http_requests
22
+ browser.requests
23
+ end
24
+
25
+ # @todo
26
+ def bytes_sent
27
+ browser.sent
28
+ end
29
+
30
+ # @todo
31
+ def bytes_received
32
+ browser.received
33
+ end
34
+
15
35
  # @todo
16
36
  def success?
17
37
  not failure?
@@ -19,7 +39,7 @@ module Saper
19
39
 
20
40
  # @todo
21
41
  def failure?
22
- stacks.all?(&:error?)
42
+ filtered_stacks.all?(&:error?)
23
43
  end
24
44
 
25
45
  # @todo
@@ -34,17 +54,17 @@ module Saper
34
54
 
35
55
  # @todo
36
56
  def to_saper_array
37
- stacks.map(&:result)
57
+ filtered_stacks.map(&:result)
38
58
  end
39
59
 
40
60
  # @todo
41
61
  def to_native_array
42
- to_saper_array.map(&:to_native)
62
+ to_saper_array.map { |i| i.is_a?(Saper::Item) ? i.to_native : i }
43
63
  end
44
64
 
45
65
  # @todo
46
66
  def to_serialized_array
47
- to_saper_array.map(&:serialize)
67
+ to_saper_array.map { |i| i.is_a?(Saper::Item) ? i.serialize : i }
48
68
  end
49
69
 
50
70
  # @todo
@@ -68,5 +88,16 @@ module Saper
68
88
  JSON.dump(serialize)
69
89
  end
70
90
 
91
+ private
92
+
93
+ def filtered_stacks
94
+ stacks.reject(&:filtered?)
95
+ end
96
+
97
+ # @todo
98
+ def browser
99
+ stacks.first.runtime.browser
100
+ end
101
+
71
102
  end
72
103
  end
@@ -15,6 +15,9 @@ module Saper
15
15
  # @return [Hash]
16
16
  attr_reader :variables
17
17
 
18
+ # @todo
19
+ attr_reader :logger
20
+
18
21
  # Returns a new Saper::Runtime instance.
19
22
  # @param variables [Hash]
20
23
  # @param options [Hash]
@@ -23,24 +26,27 @@ module Saper
23
26
  # @option options [String, Symbol] :agent User-Agent type or string
24
27
  # @return [Saper::Runtime]
25
28
  def initialize(variables = {}, options = {})
26
- if options.nil?
27
- puts self.inspect
28
- raise 'stop'
29
+ unless options.is_a?(Hash)
30
+ options = {}
31
+ end
32
+ if options[:logger].is_a?(Logger)
33
+ @logger = options[:logger]
34
+ else
35
+ @logger = Logger.new
36
+ end
37
+ if options[:browser].is_a?(Browser)
38
+ @browser = options[:browser]
39
+ else
40
+ @browser = Browser.new(:agent => options[:agent], :logger => logger)
29
41
  end
30
42
  if options[:keychain].is_a?(Keychain)
31
43
  @keychain = options[:keychain]
32
44
  end
33
45
  if options[:keychain].is_a?(String)
34
- @keychain = Keychain.load(options[:keychain])
46
+ @keychain = Keychain.load(options[:keychain], :logger => logger)
35
47
  end
36
48
  unless @keychain.is_a?(Keychain)
37
- @keychain = Keychain.new
38
- end
39
- if options[:browser].is_a?(Browser)
40
- @browser = options[:browser]
41
- end
42
- unless @browser.is_a?(Browser)
43
- @browser = Browser.new(:agent => options[:agent])
49
+ @keychain = Keychain.new({}, :logger => logger)
44
50
  end
45
51
  @variables = variables
46
52
  if block_given?
@@ -14,7 +14,13 @@ module Saper
14
14
  # @param items [Saper::Item]
15
15
  # @return [Saper::Stack]
16
16
  def initialize(runtime, *items)
17
- @runtime, @items = runtime, items.flatten
17
+ @runtime = runtime
18
+ @items = items.flatten
19
+ end
20
+
21
+ # @todo
22
+ def size
23
+ @items.size
18
24
  end
19
25
 
20
26
  # Returns a duplicate instance of self.
@@ -36,12 +42,22 @@ module Saper
36
42
  error? ? Items::Nothing.new : last
37
43
  end
38
44
 
45
+ # @todo
46
+ def error
47
+ error? ? last : nil
48
+ end
49
+
39
50
  # Returns the result of the last action.
40
51
  # @return [Saper::Item]
41
52
  def last
42
53
  @items.last || Items::Nothing.new
43
54
  end
44
55
 
56
+ # @todo
57
+ def filtered?
58
+ last.is_a?(Items::Nothing) && last.filtered?
59
+ end
60
+
45
61
  # @todo
46
62
  def error?
47
63
  last.is_a?(Saper::Error)
@@ -118,7 +118,7 @@ module Saper
118
118
 
119
119
  # @todo
120
120
  def to_s
121
- @noko.is_a?(Nokogiri::HTML::Document) ? to_native : @noko.to_s
121
+ @noko.is_a?(Nokogiri::HTML::Document) ? to_native : @noko.to_s.strip
122
122
  end
123
123
 
124
124
  end
@@ -2,11 +2,20 @@ module Saper
2
2
  module Items
3
3
  class Nothing < Item
4
4
 
5
+ # @todo
6
+ def initialize(opts = {})
7
+ @opts = opts
8
+ end
9
+
5
10
  # @todo
6
11
  def nil?
7
12
  true
8
13
  end
9
14
 
15
+ def filtered?
16
+ @opts[:filtered] == true
17
+ end
18
+
10
19
  # @todo
11
20
  def to_native
12
21
  nil
@@ -26,10 +26,14 @@ module Saper
26
26
  raise(InvalidItem, string)
27
27
  end
28
28
  begin
29
- Nokogiri::XML.parse(string, uri, charset)
29
+ noko = Nokogiri::XML.parse(string, uri, charset)
30
30
  rescue
31
31
  raise(InvalidItem, string)
32
32
  end
33
+ if noko.root.nil?
34
+ raise(InvalidItem, string)
35
+ end
36
+ noko
33
37
  end
34
38
 
35
39
  # @todo
@@ -1,3 +1,3 @@
1
1
  module Saper
2
- VERSION = "0.5.2"
2
+ VERSION = "0.5.3"
3
3
  end
@@ -0,0 +1,5 @@
1
+ # https://github.com/rubysl/rubysl-net-http/blob/2.0/lib/rubysl/net/http/http.rb
2
+ # https://github.com/rubysl/rubysl-net-http/blob/2.0/lib/net/http/generic_request.rb
3
+ # https://github.com/rubysl/rubysl-net-http/blob/2.0/lib/net/http/header.rb
4
+ # https://github.com/sparklemotion/http-cookie
5
+
@@ -0,0 +1,18 @@
1
+ require './tungsten'
2
+
3
+ #a = Tungsten::Connection.new("www.yandex.ru", 80)
4
+ #b = Net::HTTP::Get.new('/')
5
+ c = Tungsten.get("https://www.yandex.ru")
6
+
7
+ puts c.code.class
8
+ puts c.headers
9
+ puts c.redirect?
10
+ puts c.headers["Location"]
11
+ #d = a.request(c)
12
+ #puts d.code
13
+ #puts d.message
14
+
15
+ #d.each_capitalized do |k, v|
16
+ # puts "%s: %s" % [k, v]
17
+ #end
18
+
@@ -0,0 +1,33 @@
1
+ require "net/http"
2
+ require "uri"
3
+
4
+ require_relative "tungsten/headers"
5
+ require_relative "tungsten/jar"
6
+ require_relative "tungsten/request"
7
+ require_relative "tungsten/response"
8
+
9
+ module Tungsten
10
+
11
+ AGENTS = {
12
+ :ie6 => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
13
+ :ie7 => 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)',
14
+ :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)',
15
+ :ie9 => 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)',
16
+ :mozilla => 'Mozilla/5.0 (Windows; U; Windows NT 5.0; en-US; rv:1.4b) Gecko/20030516 Mozilla Firebird/0.6',
17
+ :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',
18
+ :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',
19
+ :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',
20
+ :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',
21
+ :tungsten => 'Mozilla/5.0 (compatible; Tungsten Ruby client)'
22
+ }
23
+
24
+ def self.get(*args, &block)
25
+ Request.new(:get, *args, &block).response
26
+ end
27
+
28
+ def self.post(*args, &block)
29
+ Request.new(:get, *args, &block).response
30
+ end
31
+
32
+ end
33
+