pincers 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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