pincers 0.6.0 → 0.7.0
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.
- 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
|