pincers 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/pincers/chenso/backend.rb +162 -0
- data/lib/pincers/chenso/browsing_context.rb +76 -0
- data/lib/pincers/chenso/browsing_manager.rb +67 -0
- data/lib/pincers/chenso/factory.rb +27 -0
- data/lib/pincers/chenso/html_form_request.rb +7 -0
- data/lib/pincers/chenso/html_page_request.rb +27 -0
- data/lib/pincers/{backend/base.rb → core/base_backend.rb} +18 -8
- data/lib/pincers/{factories/base.rb → core/base_factory.rb} +2 -2
- data/lib/pincers/core/helpers/form.rb +106 -0
- data/lib/pincers/core/{query.rb → helpers/query.rb} +3 -4
- data/lib/pincers/core/replicas/form.rb +43 -0
- data/lib/pincers/core/replicas/link.rb +18 -0
- data/lib/pincers/core/root_context.rb +25 -6
- data/lib/pincers/core/search_context.rb +24 -3
- data/lib/pincers/errors.rb +4 -1
- data/lib/pincers/extension/labs.rb +3 -0
- data/lib/pincers/factory.rb +9 -4
- data/lib/pincers/http/base_document.rb +20 -0
- data/lib/pincers/http/client.rb +134 -0
- data/lib/pincers/{support → http}/cookie.rb +1 -1
- data/lib/pincers/{support → http}/cookie_jar.rb +4 -3
- data/lib/pincers/http/errors.rb +26 -0
- data/lib/pincers/http/request.rb +62 -0
- data/lib/pincers/http/response_document.rb +24 -0
- data/lib/pincers/http/session.rb +99 -0
- data/lib/pincers/http/utils.rb +43 -0
- data/lib/pincers/nokogiri/backend.rb +151 -0
- data/lib/pincers/{factories/nokogiri.rb → nokogiri/factory.rb} +5 -5
- data/lib/pincers/version.rb +1 -1
- data/lib/pincers/{backend/webdriver.rb → webdriver/backend.rb} +22 -31
- data/lib/pincers/{factories/webdriver.rb → webdriver/factory.rb} +5 -5
- data/lib/pincers/webdriver/http_document.rb +23 -0
- metadata +42 -13
- data/lib/pincers/backend/nokogiri.rb +0 -66
- data/lib/pincers/core/download.rb +0 -14
- data/lib/pincers/support/http_client.rb +0 -123
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'pincers/http/utils'
|
2
|
+
|
3
|
+
module Pincers::Core::Helpers
|
4
|
+
|
5
|
+
class Form
|
6
|
+
|
7
|
+
attr_reader :backend
|
8
|
+
|
9
|
+
def initialize(_backend, _form_element, _trigger_element = nil)
|
10
|
+
@backend = _backend
|
11
|
+
@form = _form_element
|
12
|
+
@trigger = _trigger_element
|
13
|
+
@force_multipart = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def action
|
17
|
+
@action ||= begin
|
18
|
+
(trigger_attr(:formaction) || form_attr(:action) || '')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def method
|
23
|
+
@method ||= begin
|
24
|
+
(trigger_attr(:formmethod) || form_attr(:method) || 'get').downcase.to_sym
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def target
|
29
|
+
@target ||= begin
|
30
|
+
trigger_attr(:formtarget) || form_attr(:target)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def encoding
|
35
|
+
process_inputs
|
36
|
+
@encoding ||= begin
|
37
|
+
if @force_multipart
|
38
|
+
Pincers::Http::Utils::FORM_MULTIPART
|
39
|
+
else
|
40
|
+
trigger_attr(:formenctype) || form_attr(:enctype) || Pincers::Http::Utils::FORM_URLENCODED
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def inputs
|
46
|
+
process_inputs
|
47
|
+
@inputs
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def trigger_attr(_name)
|
53
|
+
return nil if @trigger.nil?
|
54
|
+
backend.extract_element_attribute(@trigger, _name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def form_attr(_name)
|
58
|
+
backend.extract_element_attribute(@form, _name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def process_inputs
|
62
|
+
return unless @inputs.nil?
|
63
|
+
elements = backend.search_by_xpath(@form, './/*[@name]', nil)
|
64
|
+
@inputs = elements.map do |input|
|
65
|
+
category = categorize_input input
|
66
|
+
next nil if category.nil?
|
67
|
+
|
68
|
+
@force_multipart = true if category == :multipart
|
69
|
+
|
70
|
+
value = backend.extract_element_attribute(input, :value)
|
71
|
+
next nil if value.nil?
|
72
|
+
|
73
|
+
name = backend.extract_element_attribute(input, :name)
|
74
|
+
[name, value]
|
75
|
+
end.reject(&:nil?)
|
76
|
+
end
|
77
|
+
|
78
|
+
def categorize_input(_input)
|
79
|
+
case backend.extract_element_tag _input
|
80
|
+
when 'input'
|
81
|
+
input_type = backend.extract_element_attribute(_input, :type)
|
82
|
+
return nil if input_type == 'submit' && !is_trigger?(_input)
|
83
|
+
return nil if input_type == 'checkbox' && !is_checked?(_input)
|
84
|
+
return nil if input_type == 'radio' && !is_checked?(_input)
|
85
|
+
input_type == 'file' ? :multipart : :urlencoded
|
86
|
+
when 'button'
|
87
|
+
input_type = backend.extract_element_attribute(_input, :type) || 'submit'
|
88
|
+
return nil if input_type != 'submit' || !is_trigger?(_input)
|
89
|
+
:urlencoded
|
90
|
+
when 'textarea', 'select'
|
91
|
+
:urlencoded
|
92
|
+
else
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def is_trigger?(_input)
|
98
|
+
return false if @trigger.nil?
|
99
|
+
backend.elements_equal _input, @trigger
|
100
|
+
end
|
101
|
+
|
102
|
+
def is_checked?(_input)
|
103
|
+
backend.extract_element_attribute _input, :checked
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'pincers/css/parser'
|
2
2
|
require 'pincers/support/xpath_builder'
|
3
3
|
|
4
|
-
module Pincers::Core
|
4
|
+
module Pincers::Core::Helpers
|
5
5
|
class Query
|
6
6
|
|
7
|
-
def self.build_from_options(_backend, _selector, _options={}, &_block)
|
7
|
+
def self.build_from_options(_backend, _selector, _options = {}, &_block)
|
8
8
|
limit = _options.delete(:limit)
|
9
9
|
lang = :xpath
|
10
10
|
exp = nil
|
@@ -38,7 +38,7 @@ module Pincers::Core
|
|
38
38
|
@limit = _limit
|
39
39
|
end
|
40
40
|
|
41
|
-
def execute(_elements, _force_limit=nil)
|
41
|
+
def execute(_elements, _force_limit = nil)
|
42
42
|
fun = case @lang
|
43
43
|
when :xpath then :search_by_xpath
|
44
44
|
else :search_by_css end
|
@@ -67,6 +67,5 @@ module Pincers::Core
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
end
|
70
|
-
|
71
70
|
end
|
72
71
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'pincers/core/helpers/form'
|
2
|
+
|
3
|
+
module Pincers::Core::Replicas
|
4
|
+
class Form
|
5
|
+
|
6
|
+
def initialize(_backend, _element)
|
7
|
+
@backend = _backend
|
8
|
+
@form = Pincers::Core::Helpers::Form.new(_backend, _element)
|
9
|
+
@pairs = @form.inputs
|
10
|
+
end
|
11
|
+
|
12
|
+
def set(_name, _value, _replace = true)
|
13
|
+
_name = _name.to_s
|
14
|
+
unset(_name) if _replace
|
15
|
+
@pairs << [_name, _value]
|
16
|
+
_value
|
17
|
+
end
|
18
|
+
|
19
|
+
def unset(_name)
|
20
|
+
_name = _name.to_s
|
21
|
+
@pairs.delete_if { |p| p[0] == _name }
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def get(_name, _as_array = false)
|
26
|
+
_name = _name.to_s
|
27
|
+
values = @pairs.select { |p| p[0] == _name }.map { |p| p[1] }
|
28
|
+
_as_array = true if _na me.include? '['
|
29
|
+
return values.first if values.length <= 1 && !_as_array
|
30
|
+
values
|
31
|
+
end
|
32
|
+
|
33
|
+
alias :[] :get
|
34
|
+
alias :[]= :set
|
35
|
+
|
36
|
+
def submit(_http_client=nil)
|
37
|
+
client = _http_client || @backend.as_http_client
|
38
|
+
client.send(@form.method, @form.action) do |request|
|
39
|
+
request.set_form_data(@pairs, @form.encoding)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'pincers/http/utils'
|
2
|
+
|
3
|
+
module Pincers::Core::Replicas
|
4
|
+
class Link
|
5
|
+
|
6
|
+
attr_reader :ref
|
7
|
+
|
8
|
+
def initialize(_backend, _element)
|
9
|
+
@backend = _backend
|
10
|
+
@ref = Pincers::Http::Utils.parse_uri _backend.extract_element_attribute(_element, :href)
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch(_http_client=nil)
|
14
|
+
client = _http_client || @backend.as_http_client
|
15
|
+
client.get(@ref)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -7,7 +7,7 @@ module Pincers::Core
|
|
7
7
|
attr_reader :config
|
8
8
|
|
9
9
|
def initialize(_backend, _config={})
|
10
|
-
super
|
10
|
+
super nil, nil, nil
|
11
11
|
@backend = _backend
|
12
12
|
@config = Pincers.config.values.merge _config
|
13
13
|
end
|
@@ -20,6 +20,14 @@ module Pincers::Core
|
|
20
20
|
true
|
21
21
|
end
|
22
22
|
|
23
|
+
def elements
|
24
|
+
@backend.document_root
|
25
|
+
end
|
26
|
+
|
27
|
+
def element
|
28
|
+
@backend.document_root.first
|
29
|
+
end
|
30
|
+
|
23
31
|
def document
|
24
32
|
@backend.document
|
25
33
|
end
|
@@ -61,10 +69,6 @@ module Pincers::Core
|
|
61
69
|
self
|
62
70
|
end
|
63
71
|
|
64
|
-
def download(_url)
|
65
|
-
wrap_errors { backend.fetch_resource _url }
|
66
|
-
end
|
67
|
-
|
68
72
|
def forward(_steps=1)
|
69
73
|
wrap_errors { backend.navigate_forward _steps }
|
70
74
|
self
|
@@ -76,7 +80,7 @@ module Pincers::Core
|
|
76
80
|
end
|
77
81
|
|
78
82
|
def refresh
|
79
|
-
wrap_errors { backend.refresh_document
|
83
|
+
wrap_errors { backend.refresh_document }
|
80
84
|
self
|
81
85
|
end
|
82
86
|
|
@@ -97,6 +101,21 @@ module Pincers::Core
|
|
97
101
|
@config[:advanced_mode]
|
98
102
|
end
|
99
103
|
|
104
|
+
def download(_url)
|
105
|
+
as_http_client.get(_url).document
|
106
|
+
end
|
107
|
+
|
108
|
+
def as_http_client(&_block)
|
109
|
+
http_client = backend.as_http_client
|
110
|
+
unless _block.nil?
|
111
|
+
r = _block.call http_client
|
112
|
+
# sync_with http_client # TODO :copy cookies and maybe url?
|
113
|
+
r
|
114
|
+
else
|
115
|
+
http_client
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
100
119
|
private
|
101
120
|
|
102
121
|
def wrap_siblings(_elements)
|
@@ -1,7 +1,9 @@
|
|
1
|
+
require 'pincers/core/helpers/query'
|
2
|
+
require 'pincers/core/replicas/link'
|
3
|
+
require 'pincers/core/replicas/form'
|
1
4
|
require 'pincers/extension/queries'
|
2
5
|
require 'pincers/extension/actions'
|
3
6
|
require 'pincers/extension/labs'
|
4
|
-
require 'pincers/core/query'
|
5
7
|
|
6
8
|
module Pincers::Core
|
7
9
|
class SearchContext
|
@@ -97,7 +99,7 @@ module Pincers::Core
|
|
97
99
|
_selector = nil
|
98
100
|
end
|
99
101
|
|
100
|
-
query = Query.build_from_options(backend, _selector, _options, &_block)
|
102
|
+
query = Helpers::Query.build_from_options(backend, _selector, _options, &_block)
|
101
103
|
|
102
104
|
wrap_errors { wrap_childs query }
|
103
105
|
end
|
@@ -164,6 +166,26 @@ module Pincers::Core
|
|
164
166
|
self
|
165
167
|
end
|
166
168
|
|
169
|
+
def submit(&_block)
|
170
|
+
wrap_errors do
|
171
|
+
# _block.call(FormSetter.new _element) if _block
|
172
|
+
backend.submit_form element!
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def replicate
|
177
|
+
wrap_errors do
|
178
|
+
case tag
|
179
|
+
when 'form'
|
180
|
+
Replicas::Form.new backend, element!
|
181
|
+
when 'a'
|
182
|
+
Replicas::Link.new backend, element!
|
183
|
+
else
|
184
|
+
raise Pincers::MissingFeatureError, "No replica avaliable for #{tag}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
167
189
|
# context related
|
168
190
|
|
169
191
|
def goto
|
@@ -297,6 +319,5 @@ module Pincers::Core
|
|
297
319
|
|
298
320
|
return false
|
299
321
|
end
|
300
|
-
|
301
322
|
end
|
302
323
|
end
|
data/lib/pincers/errors.rb
CHANGED
@@ -4,6 +4,9 @@ module Pincers::Extension
|
|
4
4
|
def readonly(&_block)
|
5
5
|
nk_root = Pincers.for_nokogiri to_html
|
6
6
|
|
7
|
+
# nk_root = Pincers.for_chenso http_client
|
8
|
+
# nk_root.backend.push_document current_url, to_html
|
9
|
+
|
7
10
|
unless root?
|
8
11
|
nk_root = nk_root.search('body > *') # nokogiri will inject valid html structure around contents
|
9
12
|
end
|
data/lib/pincers/factory.rb
CHANGED
@@ -2,7 +2,7 @@ module Pincers
|
|
2
2
|
module Factory
|
3
3
|
|
4
4
|
def for_webdriver(_driver=nil, _options={}, &_block)
|
5
|
-
require 'pincers/
|
5
|
+
require 'pincers/webdriver/factory'
|
6
6
|
|
7
7
|
if _driver.is_a? Hash
|
8
8
|
_options = _driver
|
@@ -11,7 +11,7 @@ module Pincers
|
|
11
11
|
|
12
12
|
_options[:driver] = _driver || config.webdriver_bridge
|
13
13
|
|
14
|
-
context =
|
14
|
+
context = Webdriver::Factory.new_context _options
|
15
15
|
|
16
16
|
if _block
|
17
17
|
begin
|
@@ -23,11 +23,16 @@ module Pincers
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def for_nokogiri(_document, _options={})
|
26
|
-
require 'pincers/
|
26
|
+
require 'pincers/nokogiri/factory'
|
27
27
|
|
28
28
|
_options[:document] = _document
|
29
29
|
|
30
|
-
|
30
|
+
Nokogiri::Factory.new_context _options
|
31
|
+
end
|
32
|
+
|
33
|
+
def for_chenso(_options={})
|
34
|
+
require 'pincers/chenso/factory'
|
35
|
+
Chenso::Factory.new_context _options
|
31
36
|
end
|
32
37
|
|
33
38
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Pincers::Http
|
2
|
+
class BaseDocument
|
3
|
+
|
4
|
+
def uri
|
5
|
+
raise NotImplementedError
|
6
|
+
end
|
7
|
+
|
8
|
+
def content_type
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
def content
|
13
|
+
raise NotImplementedError
|
14
|
+
end
|
15
|
+
|
16
|
+
def save(_path)
|
17
|
+
File.write _path, content
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'pincers/http/utils'
|
3
|
+
require 'pincers/http/session'
|
4
|
+
require 'pincers/http/request'
|
5
|
+
require 'pincers/http/response_document'
|
6
|
+
require 'pincers/http/errors'
|
7
|
+
|
8
|
+
module Pincers::Http
|
9
|
+
class Client
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def self.build_from_options(_options = {})
|
13
|
+
session = Session.new
|
14
|
+
session.proxy = _options[:proxy] if _options.key? :proxy
|
15
|
+
session.headers.merge! _options[:headers] if _options.key? :headers
|
16
|
+
session.redirect_limit = _options[:redirect_limit] if _options.key? :redirect_limit
|
17
|
+
|
18
|
+
client = self.new session, _options[:document]
|
19
|
+
client.freeze if _options[:freeze]
|
20
|
+
client
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :session, :document, :frozen
|
24
|
+
def_delegators :@document, :content_type, :content, :uri
|
25
|
+
|
26
|
+
def initialize(_session, _document={})
|
27
|
+
@session = _session
|
28
|
+
@document = _document
|
29
|
+
@frozen = false
|
30
|
+
end
|
31
|
+
|
32
|
+
def freeze
|
33
|
+
@frozen = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def unfreeze
|
37
|
+
@frozen = false
|
38
|
+
end
|
39
|
+
|
40
|
+
def cookies
|
41
|
+
@session.cookie_jar.cookies
|
42
|
+
end
|
43
|
+
|
44
|
+
def set_cookie(_cookie)
|
45
|
+
@session.cookie_jar.set _cookie
|
46
|
+
end
|
47
|
+
|
48
|
+
def get(_url, _query = nil, &_block)
|
49
|
+
request = build_request :get, _url
|
50
|
+
request.set_query _query unless _query.nil?
|
51
|
+
_block.call request unless _block.nil?
|
52
|
+
perform_in_session request
|
53
|
+
end
|
54
|
+
|
55
|
+
def post(_url, _data = nil, &_block)
|
56
|
+
request = build_request :post, _url
|
57
|
+
load_data_in_request request, _data unless _data.nil?
|
58
|
+
_block.call request unless _block.nil?
|
59
|
+
perform_in_session request
|
60
|
+
end
|
61
|
+
|
62
|
+
def put(_url, _data = nil, &_block)
|
63
|
+
request = build_request :put, _url
|
64
|
+
load_data_in_request request, _data unless _data.nil?
|
65
|
+
_block.call request unless _block.nil?
|
66
|
+
perform_in_session request
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete(_url, &_block)
|
70
|
+
request = build_request :delete, _url
|
71
|
+
_block.call request unless _block.nil?
|
72
|
+
perform_in_session request
|
73
|
+
end
|
74
|
+
|
75
|
+
def fork(_keep_session = true)
|
76
|
+
fork_session = _keep_session ? @session : @session.clone
|
77
|
+
self.class.new fork_session, @document
|
78
|
+
end
|
79
|
+
|
80
|
+
def absolute_uri_for(_url)
|
81
|
+
uri = _url.is_a?(URI) ? _url : Utils.parse_uri(_url)
|
82
|
+
if uri.relative?
|
83
|
+
raise ArgumentError, 'Absolute url was required' if @document.nil?
|
84
|
+
uri = URI.join(@document.uri, uri)
|
85
|
+
end
|
86
|
+
uri
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
SUPPORTED_MODES = [:form, :urlencoded, :multipart]
|
92
|
+
|
93
|
+
def build_request(_type, _url)
|
94
|
+
Request.new _type, absolute_uri_for(_url)
|
95
|
+
end
|
96
|
+
|
97
|
+
def load_data_in_request(_request, _data)
|
98
|
+
if _data.is_a? Hash
|
99
|
+
mode = :form
|
100
|
+
if _data.keys.length == 1 and SUPPORTED_MODES.include? _data.keys.first
|
101
|
+
mode = _data.keys.first
|
102
|
+
_data = _data[mode]
|
103
|
+
end
|
104
|
+
|
105
|
+
case mode
|
106
|
+
when :form
|
107
|
+
_request.set_form_data _data
|
108
|
+
when :urlencoded
|
109
|
+
_request.set_form_data _data, Utils::FORM_URLENCODED
|
110
|
+
when :multipart
|
111
|
+
_request.set_form_data _data, Utils::FORM_MULTIPART
|
112
|
+
end
|
113
|
+
else
|
114
|
+
_request.data = _data.to_s
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def perform_in_session(_request)
|
119
|
+
begin
|
120
|
+
new_document = ResponseDocument.new @session.perform _request
|
121
|
+
rescue
|
122
|
+
@document = nil unless frozen
|
123
|
+
raise
|
124
|
+
end
|
125
|
+
|
126
|
+
if frozen
|
127
|
+
self.class.new @session, @document
|
128
|
+
else
|
129
|
+
@document = new_document
|
130
|
+
self
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|