pincers 0.5.2 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 51cc155a36f380781aa91b00a53f5996aae5b9b1
4
- data.tar.gz: b7ef59a62aa573b6be45914692fc603ef91195c0
3
+ metadata.gz: b243194fe1a23f2a16a34c0e7007de2d9f4eecfc
4
+ data.tar.gz: b442f0a02e79c6fc600d1b50cff6187ffc0bc3d4
5
5
  SHA512:
6
- metadata.gz: 78e3f46f2d2a4bd2bcb82d6ca2c5eda70f2683f5bc769eb086204eb82e7168b7b02a6687550b47870187f2172319184d6f47d1b98a33984f7fc50860f40a0868
7
- data.tar.gz: 95d1bbf4b2cc3ccea044ecc0c8bfea63477b56d489487edf2d9f110943e5b231d955f25c47e25a0e8c661a593d696c9003642cacdceaace35ad26f1ae7943c5b
6
+ metadata.gz: 9814b3a3c6139e62e53fea8b0448173196e2fd657656b9cfb49aa7856fd12e0d312842de844284e481821d223af838abf25f547107f7c90db921e10ee5969554
7
+ data.tar.gz: d91861c7c3ab3b717acfc703f758de7ea09dd1a89d6394b5c80932c069b4710a3d17b0b16dbc8004378ed4ce1d6985a77b4d631f9b5f1055515de171738bb95c
data/lib/pincers.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require "nokogiri"
1
2
  require "forwardable"
2
3
  require "pincers/version"
3
4
  require "pincers/errors"
@@ -1,4 +1,3 @@
1
- require 'nokogiri'
2
1
  require 'pincers/backend/base'
3
2
 
4
3
  module Pincers::Backend
@@ -1,4 +1,5 @@
1
1
  module Pincers::Core
2
+
2
3
  class Download < Struct.new(:mime, :data)
3
4
 
4
5
  def self.from_http_response(_response)
@@ -0,0 +1,72 @@
1
+ require 'pincers/css/parser'
2
+ require 'pincers/support/xpath_builder'
3
+
4
+ module Pincers::Core
5
+ class Query
6
+
7
+ def self.build_from_options(_backend, _selector, _options={}, &_block)
8
+ limit = _options.delete(:limit)
9
+ lang = :xpath
10
+ exp = nil
11
+
12
+ if _selector
13
+ parser = Pincers::CSS::Parser.new _selector
14
+ if parser.is_extended?
15
+ exp = parser.to_xpath('.//') # Should we use // for root?
16
+ exp = exp.first if exp.length == 1
17
+ else
18
+ lang = :css
19
+ exp = _selector
20
+ end
21
+ elsif _options[:xpath]
22
+ exp = _options[:xpath]
23
+ else
24
+ builder = Pincers::Support::XPathBuilder.new _options
25
+ _block.call builder unless _block.nil?
26
+ exp = builder.expression
27
+ end
28
+
29
+ self.new _backend, lang, exp, limit
30
+ end
31
+
32
+ attr_reader :lang, :query, :limit
33
+
34
+ def initialize(_backend, _lang, _query, _limit)
35
+ @backend = _backend
36
+ @lang = _lang
37
+ @query = _query
38
+ @limit = _limit
39
+ end
40
+
41
+ def execute(_elements, _force_limit=nil)
42
+ fun = case @lang
43
+ when :xpath then :search_by_xpath
44
+ else :search_by_css end
45
+
46
+ query_elements _elements, fun, _force_limit || @limit
47
+ end
48
+
49
+ private
50
+
51
+ def query_elements(_elements, _fun, _limit)
52
+ elements = []
53
+ explode_elements(_elements) do |element, query|
54
+ limit = _limit.nil? ? nil : _limit - elements.count
55
+ elements += @backend.send(_fun, element, query, limit)
56
+ break if !_limit.nil? && elements.length >= _limit
57
+ end
58
+ elements
59
+ end
60
+
61
+ def explode_elements(_elements)
62
+ _elements.each do |element|
63
+ if @query.is_a? Array
64
+ @query.each { |q| yield element, q }
65
+ else
66
+ yield element, @query
67
+ end
68
+ end
69
+ end
70
+
71
+ end
72
+ end
@@ -116,7 +116,7 @@ module Pincers::Core
116
116
  when :parent
117
117
  backend.switch_to_parent_frame
118
118
  when String
119
- backend.switch_to_frame css(_frame).element!
119
+ backend.switch_to_frame search(_frame).element!
120
120
  when SearchContext
121
121
  backend.switch_to_frame _frame.element!
122
122
  else
@@ -1,7 +1,7 @@
1
1
  require 'pincers/extension/queries'
2
2
  require 'pincers/extension/actions'
3
3
  require 'pincers/extension/labs'
4
- require 'pincers/support/query'
4
+ require 'pincers/core/query'
5
5
 
6
6
  module Pincers::Core
7
7
  class SearchContext
@@ -91,18 +91,15 @@ module Pincers::Core
91
91
  if elements.last.nil? then nil else wrap_siblings [elements.last] end
92
92
  end
93
93
 
94
- def css(_selector, _options={})
95
- wrap_errors do
96
- query = Pincers::Support::Query.new backend, :css, _selector, _options[:limit]
97
- wrap_childs query
94
+ def search(_selector=nil, _options={}, &_block)
95
+ if _selector.is_a? Hash
96
+ _options = _selector
97
+ _selector = nil
98
98
  end
99
- end
100
99
 
101
- def xpath(_selector, _options={})
102
- wrap_errors do
103
- query = Pincers::Support::Query.new backend, :xpath, _selector, _options[:limit]
104
- wrap_childs query
105
- end
100
+ query = Query.build_from_options(backend, _selector, _options, &_block)
101
+
102
+ wrap_errors { wrap_childs query }
106
103
  end
107
104
 
108
105
  def attribute(_name, _value=nil)
@@ -0,0 +1,29 @@
1
+ require 'pincers/css/xpath_visitor'
2
+
3
+ module Pincers::CSS
4
+
5
+ class Parser
6
+
7
+ IS_EXTENDED_RGX = /:(contains|has|first|last|even|odd|eq|gt|lt|button|checkbox|file|image|password|radio|reset|submit|text|selected|checked)([^\w\-]|$)/
8
+
9
+ attr_reader :selector
10
+
11
+ def is_extended?
12
+ IS_EXTENDED_RGX === selector
13
+ end
14
+
15
+ def initialize(_selector)
16
+ @selector = _selector
17
+ end
18
+
19
+ def to_xpath(_prefix='//')
20
+ # use nokogiri parser and our custom visitor
21
+ ::Nokogiri::CSS.xpath_for @selector, {
22
+ prefix: _prefix,
23
+ visitor: XPathVisitor.new
24
+ }
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,81 @@
1
+ module Pincers::CSS
2
+
3
+ class XPathVisitor < ::Nokogiri::CSS::XPathVisitor
4
+
5
+ # jQuery extended functions and classes
6
+
7
+ def visit_function_contains(_node) # override nokofiri impl to search in value attribute too
8
+ "(contains(., #{_node.value[1]}) or contains(@value, #{_node.value[1]}))"
9
+ end
10
+
11
+ def visit_function_has(_node)
12
+ _node.value[1].accept(self)
13
+ end
14
+
15
+ def visit_function_eq(_node) # override nokogiri impl to make it zero-based
16
+ "(position()-1)=#{_node.value[1]}"
17
+ end
18
+
19
+ def visit_function_gt(_node) # override nokogiri impl to make it zero-based
20
+ # "((#{_node.value[1]} >= 0 and position() > #{_node.value[1]}) or (#{_node.value[1]} < 0 and position() < #{_node.value[1]}))"
21
+ "(position()-1)>#{_node.value[1]}"
22
+ end
23
+
24
+ def visit_function_lt(_node)
25
+ "(position()-1)<#{_node.value[1]}"
26
+ end
27
+
28
+ def visit_pseudo_class_input(_node)
29
+ "((name()='input' and not(@type='hidden')) or name()='textarea' or name()='select' or name()='button')"
30
+ end
31
+
32
+ def visit_pseudo_class_button(_node)
33
+ "(name()='button' or (name()='input' and @type='button'))"
34
+ end
35
+
36
+ def visit_pseudo_class_checkbox(_node)
37
+ "@type='checkbox'"
38
+ end
39
+
40
+ def visit_pseudo_class_file(_node)
41
+ "@type='file'"
42
+ end
43
+
44
+ def visit_pseudo_class_image(_node)
45
+ "@type='image'"
46
+ end
47
+
48
+ def visit_pseudo_class_password(_node)
49
+ "@type='password'"
50
+ end
51
+
52
+ def visit_pseudo_class_radio(_node)
53
+ "@type='radio'"
54
+ end
55
+
56
+ def visit_pseudo_class_reset(_node)
57
+ "@type='reset'"
58
+ end
59
+
60
+ def visit_pseudo_class_text(_node)
61
+ "@type='text'"
62
+ end
63
+
64
+ def visit_pseudo_class_selected(_node)
65
+ "@selected"
66
+ end
67
+
68
+ def visit_pseudo_class_checked(_node)
69
+ "@checked"
70
+ end
71
+
72
+ def visit_pseudo_class_odd(_node)
73
+ "position() mod 2 = 1"
74
+ end
75
+
76
+ def visit_pseudo_class_even(_node)
77
+ "position() mod 2 = 0"
78
+ end
79
+
80
+ end
81
+ end
@@ -49,11 +49,11 @@ module Pincers::Extension
49
49
  end
50
50
 
51
51
  def find_option_by_label(_label, _options)
52
- css("option", _options).find { |o| o.text == _label }
52
+ search(_options.merge(xpath: ".//option[contains(.,'#{_label}')]")).first
53
53
  end
54
54
 
55
55
  def find_option_by_value(_value, _options)
56
- css("option", _options).find { |o| o[:value] == _value.to_s }
56
+ search(_options.merge(xpath: ".//option[@value='#{_value}']")).first
57
57
  end
58
58
 
59
59
  end
@@ -5,7 +5,7 @@ module Pincers::Extension
5
5
  nk_root = Pincers.for_nokogiri to_html
6
6
 
7
7
  unless root?
8
- nk_root = nk_root.css('body > *') # nokogiri will inject valid html structure around contents
8
+ nk_root = nk_root.search('body > *') # nokogiri will inject valid html structure around contents
9
9
  end
10
10
 
11
11
  if _block.nil?
@@ -1,21 +1,8 @@
1
- require 'pincers/support/xpath_builder'
2
-
3
1
  module Pincers::Extension
4
2
  module Queries
5
3
 
6
4
  TEXT_INPUTS = ['text', 'email', 'number', 'email', 'color', 'password', 'search', 'tel', 'url']
7
5
 
8
- def search(_options={}, &_block)
9
- query_options = {
10
- limit: _options.delete(:limit)
11
- }
12
-
13
- builder = Pincers::Support::XPathBuilder.new _options
14
- _block.call builder unless _block.nil?
15
-
16
- xpath builder.expression, query_options
17
- end
18
-
19
6
  def id
20
7
  self[:id]
21
8
  end
@@ -40,11 +27,11 @@ module Pincers::Extension
40
27
  end
41
28
 
42
29
  def selected(_options={})
43
- first!.css('option', _options).select { |opt| opt.selected? }
30
+ first!.search('option', _options).select(&:selected?)
44
31
  end
45
32
 
46
33
  def checked(_options={})
47
- first!.css('input', _options).select { |opt| opt.checked? }
34
+ first!.search('input', _options).select(&:checked?)
48
35
  end
49
36
 
50
37
  def input_mode
@@ -1,3 +1,3 @@
1
1
  module Pincers
2
- VERSION = "0.5.2"
2
+ VERSION = "0.6.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pincers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ignacio Baixas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-09-04 00:00:00.000000000 Z
11
+ date: 2015-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -219,8 +219,11 @@ files:
219
219
  - lib/pincers/backend/webdriver.rb
220
220
  - lib/pincers/core/cookies.rb
221
221
  - lib/pincers/core/download.rb
222
+ - lib/pincers/core/query.rb
222
223
  - lib/pincers/core/root_context.rb
223
224
  - lib/pincers/core/search_context.rb
225
+ - lib/pincers/css/parser.rb
226
+ - lib/pincers/css/xpath_visitor.rb
224
227
  - lib/pincers/errors.rb
225
228
  - lib/pincers/extension/actions.rb
226
229
  - lib/pincers/extension/labs.rb
@@ -233,7 +236,6 @@ files:
233
236
  - lib/pincers/support/cookie.rb
234
237
  - lib/pincers/support/cookie_jar.rb
235
238
  - lib/pincers/support/http_client.rb
236
- - lib/pincers/support/query.rb
237
239
  - lib/pincers/support/xpath_builder.rb
238
240
  - lib/pincers/version.rb
239
241
  homepage: https://github.com/platanus/pincers
@@ -1,31 +0,0 @@
1
- module Pincers::Support
2
- class Query
3
-
4
- attr_reader :lang, :query, :limit
5
-
6
- def initialize(_backend, _lang, _query, _limit)
7
- @backend = _backend
8
- @lang = _lang
9
- @query = _query
10
- @limit = _limit
11
- end
12
-
13
- def execute(_elements, _force_limit=nil)
14
- fun = case @lang
15
- when :xpath then :search_by_xpath
16
- else :search_by_css end
17
-
18
- explode_elements _elements, fun, _force_limit || @limit
19
- end
20
-
21
- def explode_elements(_elements, _fun, _limit)
22
- _elements.inject([]) do |r, element|
23
- limit = _limit.nil? ? nil : _limit - r.count
24
- r = r + @backend.send(_fun, element, @query, limit)
25
- break r if !_limit.nil? && r.length >= _limit
26
- next r
27
- end
28
- end
29
-
30
- end
31
- end