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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/pincers/chenso/backend.rb +162 -0
  3. data/lib/pincers/chenso/browsing_context.rb +76 -0
  4. data/lib/pincers/chenso/browsing_manager.rb +67 -0
  5. data/lib/pincers/chenso/factory.rb +27 -0
  6. data/lib/pincers/chenso/html_form_request.rb +7 -0
  7. data/lib/pincers/chenso/html_page_request.rb +27 -0
  8. data/lib/pincers/{backend/base.rb → core/base_backend.rb} +18 -8
  9. data/lib/pincers/{factories/base.rb → core/base_factory.rb} +2 -2
  10. data/lib/pincers/core/helpers/form.rb +106 -0
  11. data/lib/pincers/core/{query.rb → helpers/query.rb} +3 -4
  12. data/lib/pincers/core/replicas/form.rb +43 -0
  13. data/lib/pincers/core/replicas/link.rb +18 -0
  14. data/lib/pincers/core/root_context.rb +25 -6
  15. data/lib/pincers/core/search_context.rb +24 -3
  16. data/lib/pincers/errors.rb +4 -1
  17. data/lib/pincers/extension/labs.rb +3 -0
  18. data/lib/pincers/factory.rb +9 -4
  19. data/lib/pincers/http/base_document.rb +20 -0
  20. data/lib/pincers/http/client.rb +134 -0
  21. data/lib/pincers/{support → http}/cookie.rb +1 -1
  22. data/lib/pincers/{support → http}/cookie_jar.rb +4 -3
  23. data/lib/pincers/http/errors.rb +26 -0
  24. data/lib/pincers/http/request.rb +62 -0
  25. data/lib/pincers/http/response_document.rb +24 -0
  26. data/lib/pincers/http/session.rb +99 -0
  27. data/lib/pincers/http/utils.rb +43 -0
  28. data/lib/pincers/nokogiri/backend.rb +151 -0
  29. data/lib/pincers/{factories/nokogiri.rb → nokogiri/factory.rb} +5 -5
  30. data/lib/pincers/version.rb +1 -1
  31. data/lib/pincers/{backend/webdriver.rb → webdriver/backend.rb} +22 -31
  32. data/lib/pincers/{factories/webdriver.rb → webdriver/factory.rb} +5 -5
  33. data/lib/pincers/webdriver/http_document.rb +23 -0
  34. metadata +42 -13
  35. data/lib/pincers/backend/nokogiri.rb +0 -66
  36. data/lib/pincers/core/download.rb +0 -14
  37. 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 _backend.document_root, nil, nil
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 _steps }
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
@@ -69,7 +69,10 @@ module Pincers
69
69
  @original = _exc
70
70
  end
71
71
 
72
- # IDEA: join backtraces?
72
+ def backtrace
73
+ # IDEA: join backtraces?
74
+ @original.backtrace
75
+ end
73
76
 
74
77
  end
75
78
 
@@ -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
@@ -2,7 +2,7 @@ module Pincers
2
2
  module Factory
3
3
 
4
4
  def for_webdriver(_driver=nil, _options={}, &_block)
5
- require 'pincers/factories/webdriver'
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 = Factories::Webdriver.new_context _options
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/factories/nokogiri'
26
+ require 'pincers/nokogiri/factory'
27
27
 
28
28
  _options[:document] = _document
29
29
 
30
- Factories::Nokogiri.new_context _options
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