hexp 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe165e0149eb663683b10c6bbea75d2e594171c2
4
- data.tar.gz: d0678b856d53b37e98652d7a987ced5be71ae220
3
+ metadata.gz: 45fe82cfc464b888c41d8b632a929f6074d9b1cb
4
+ data.tar.gz: 9a015de7db34bd56cadf8304a940d51fea9f6d9e
5
5
  SHA512:
6
- metadata.gz: 19bf3b836ac3a73e7b033d9e75c3323f85813660d56624b9f704b3d60c0ae74563c95ad6a64d77f6419330ce2592e4cecae4981e713e740ccb5e70545d37dc40
7
- data.tar.gz: 7188623fe858bfe9c84f1c6c4c83061e6535cfae4bcd1558b7296d489116e4190f4a93ab5745b12fc24e18e45e48a98bbfb19749ef60a7978b3bd0ffb168899e
6
+ metadata.gz: ef380c391229dcb9716d9472882714ded77f5c3e0169f8ff98312a5e23bd1b61e3caed513f0e2c561e86b9e399b94ee88d0fbd7606f1ee83b353884199cff596
7
+ data.tar.gz: 29570e22c84b840bd04429ca27f5dc9535e3e4b080007ffda9e80f9472b61ae476ca1a4a036600c8db6f6cd9921a2be0cd44dc8c394505b7a5642874b6148454
@@ -19,3 +19,4 @@ matrix:
19
19
  - rvm: jruby
20
20
  - rvm: ruby-head
21
21
  - rvm: jruby-head
22
+ - rvm: rbx
@@ -2,6 +2,16 @@
2
2
 
3
3
  [full diff](http://github.com/plexus/hexp/compare/v0.4.2...master)
4
4
 
5
+ ### v0.4.3
6
+
7
+ Performance improvements
8
+
9
+ * Introduce MutableTreeWalk to speed up css selection
10
+ * Drop Adamantium. This means we have less of a guarantee of deep
11
+ immutability, but it does speed things up
12
+ * Prevent type coercion from happening if inputs are already valid
13
+ * Raise an exception when a node's tag is not a Symbol
14
+
5
15
  ### v0.4.2
6
16
 
7
17
  * Added Hexp::List#append
data/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  # Hexp
14
14
 
15
- **Hexp** (pronounced [ˈɦækspi:]) is a DOM API for Ruby. It lets you treat HTML in your applications as objects, instead of strings. It is a standalone, framework independent library. You can use it to build full web pages, or to clean up your helpers and presenters.
15
+ **Hexp** (pronounced [ˈɦækspi:]) is a DOM API for Ruby. It lets you treat HTML in your applications as objects, instead of strings. It is a standalone, framework independent library. You can use it to build full web pages, or just to clean up your helpers and presenters.
16
16
 
17
17
  ## Fundamentals
18
18
 
data/Rakefile CHANGED
@@ -48,6 +48,7 @@ task :default => :mutant
48
48
 
49
49
  task :mutant do
50
50
  pattern = ENV.fetch('PATTERN', 'Hexp*')
51
- result = Mutant::CLI.run(%w[-Ilib -rhexp --use rspec --score 100] + [pattern])
51
+ opts = ENV.fetch('MUTANT_OPTS', '').split(' ')
52
+ result = Mutant::CLI.run(%w[-Ilib -rhexp --use rspec --score 100] + opts + [pattern])
52
53
  fail unless result == Mutant::CLI::EXIT_SUCCESS
53
54
  end
@@ -19,7 +19,6 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_runtime_dependency 'sass', '~> 3.2.19'
21
21
  gem.add_runtime_dependency 'nokogiri', '~> 1.6'
22
- gem.add_runtime_dependency 'adamantium', '~> 0.2'
23
22
  gem.add_runtime_dependency 'equalizer', '~> 0.0'
24
23
  gem.add_runtime_dependency 'concord', '~> 0.0'
25
24
 
@@ -4,7 +4,6 @@ require 'pathname'
4
4
 
5
5
  require 'nokogiri'
6
6
  require 'sass'
7
- require 'adamantium'
8
7
  require 'equalizer'
9
8
  require 'concord'
10
9
 
@@ -126,3 +125,4 @@ require 'hexp/h'
126
125
 
127
126
  require 'hexp/builder'
128
127
  require 'hexp/unparser'
128
+ require 'hexp/mutable_tree_walk'
@@ -4,7 +4,6 @@ module Hexp
4
4
  #
5
5
  module Members
6
6
  include Equalizer.new(:members)
7
- include Adamantium
8
7
 
9
8
  extend Forwardable
10
9
  def_delegator :@members, :empty?
@@ -106,6 +105,12 @@ module Hexp
106
105
  sequence.head_matches?(element)
107
106
  end
108
107
  end
108
+
109
+ def matches_path?(path)
110
+ members.any? do |sequence|
111
+ sequence.matches_path?(path)
112
+ end
113
+ end
109
114
  end
110
115
 
111
116
  # A single CSS sequence like 'div span .foo'
@@ -124,6 +129,24 @@ module Hexp
124
129
  members.first.matches?(element)
125
130
  end
126
131
 
132
+ # Warning: Highly optimized cryptic code
133
+ def matches_path?(path)
134
+ return false if path.length < members.length
135
+ return false unless members.last.matches?(path.last)
136
+
137
+ path_idx = path.length - 2
138
+ mem_idx = members.length - 2
139
+
140
+ until path_idx < mem_idx || mem_idx == -1
141
+ if members[mem_idx].matches?(path[path_idx])
142
+ mem_idx -= 1
143
+ end
144
+ path_idx -= 1
145
+ end
146
+
147
+ mem_idx == -1
148
+ end
149
+
127
150
  # Drop the first element of this Sequence
128
151
  #
129
152
  # This returns a new Sequence, with one member less.
@@ -2,8 +2,6 @@ module Hexp
2
2
  # A list of nodes
3
3
  #
4
4
  class List < DelegateClass(Array)
5
- include Adamantium
6
-
7
5
  # Create new Hexp::List
8
6
  #
9
7
  # @example
@@ -14,7 +12,11 @@ module Hexp
14
12
  #
15
13
  # @api public
16
14
  def initialize(nodes)
17
- super nodes.to_ary.map(&Node::Normalize.method(:coerce_node)).freeze
15
+ if nodes.instance_of?(List)
16
+ super(nodes.__getobj__).freeze
17
+ else
18
+ super nodes.to_ary.map(&Node::Normalize.method(:coerce_node)).freeze
19
+ end
18
20
  end
19
21
 
20
22
  # Convenience constructor
@@ -0,0 +1,79 @@
1
+ module Hexp
2
+ class MutableTreeWalk
3
+ attr_reader :root, :path, :result
4
+
5
+ def initialize(root)
6
+ @root = root
7
+ @path = [root]
8
+ @replacements = [{}]
9
+ @result = nil
10
+ end
11
+
12
+ def next!
13
+ return if end?
14
+ if current.children.any?
15
+ @path << current.children.first
16
+ @replacements << {}
17
+ elsif @path.length == 1
18
+ @result = @path.pop
19
+ else
20
+ backtrack_and_right!
21
+ end
22
+ end
23
+
24
+ def backtrack_and_right!
25
+ while at_rightmost_child?
26
+ @path.pop
27
+ handle_replacements!
28
+ if @path.length == 1 #back at start, we're done
29
+ @result = @path.pop
30
+ return
31
+ end
32
+ end
33
+ go_right!
34
+ end
35
+
36
+ def replace!(val)
37
+ @replacements.last[current_idx] = val
38
+ end
39
+
40
+ def handle_replacements!
41
+ replacements = @replacements.pop
42
+ return if replacements.empty?
43
+ new_children = [*current.children]
44
+ replacements.each do |idx, val|
45
+ new_children[idx..idx] = val
46
+ end
47
+ new_node = current.set_children(new_children)
48
+ if @path.length == 1
49
+ @path = [new_node]
50
+ else
51
+ @replacements.last[current_idx] = new_node
52
+ end
53
+ end
54
+
55
+ def at_rightmost_child?
56
+ current.equal? parent.children.last
57
+ end
58
+
59
+ def go_right!
60
+ @path[-1] = parent.children[current_idx + 1]
61
+ end
62
+
63
+ def current_idx
64
+ parent.children.find_index { |ch| current.equal?(ch) }
65
+ end
66
+
67
+ def parent
68
+ @path[-2]
69
+ end
70
+
71
+ def current
72
+ @path.last
73
+ end
74
+
75
+ def end?
76
+ @path.empty?
77
+ end
78
+ end
79
+ end
@@ -53,14 +53,12 @@ module Hexp
53
53
  #
54
54
  class Node
55
55
  include Concord::Public.new(:tag, :attributes, :children)
56
- include Adamantium
57
56
  extend Forwardable
58
57
 
59
58
  include Hexp::Node::Attributes
60
59
  include Hexp::Node::Children
61
60
 
62
61
  alias attrs attributes
63
- memoize :class_list
64
62
 
65
63
  # The HTML tag of this node
66
64
  #
@@ -125,7 +123,19 @@ module Hexp
125
123
  # @api public
126
124
  #
127
125
  def initialize(*args)
128
- super(*Normalize.new(args).call)
126
+ tag_ok = args[0].instance_of?(Symbol)
127
+ raise "The tag of node should be a Symbol" unless tag_ok
128
+
129
+ attrs_ok = args[1].instance_of?(Hash) &&
130
+ args[1].all? {|k,v| k.instance_of?(String) && v.instance_of?(String) }
131
+
132
+ if attrs_ok && args[2].instance_of?(List)
133
+ super(args[0], args[1], args[2])
134
+ elsif attrs_ok && args[2].instance_of?(Array)
135
+ super(args[0], args[1], List.new(args[2]))
136
+ else
137
+ super(*Normalize.new(args).call)
138
+ end.freeze
129
139
  end
130
140
 
131
141
  # Standard hexp coercion protocol, return self
@@ -88,7 +88,7 @@ module Hexp
88
88
  #
89
89
  # @api public
90
90
  def class_list
91
- (attr('class') || '').split(' ')
91
+ @class_list ||= (attr('class') || '').split(' ')
92
92
  end
93
93
 
94
94
  # Remove a CSS class from this element
@@ -73,10 +73,15 @@ module Hexp
73
73
  def each(&block)
74
74
  return to_enum(:each) unless block_given?
75
75
 
76
- @node.children.each do |child|
77
- next_selection_for(child).each(&block)
76
+ walk = MutableTreeWalk.new(@node)
77
+
78
+ until walk.end?
79
+ if comma_sequence.matches_path?(walk.path)
80
+ yield walk.current
81
+ end
82
+ walk.next!
78
83
  end
79
- yield @node if node_matches?
84
+
80
85
  self
81
86
  end
82
87
 
@@ -91,14 +96,16 @@ module Hexp
91
96
  def rewrite(&block)
92
97
  return @node if @node.text?
93
98
 
94
- new_node = H[
95
- @node.tag,
96
- @node.attributes,
97
- @node.children.flat_map do |child|
98
- next_selection_for(child).rewrite(&block)
99
+ walk = MutableTreeWalk.new(@node)
100
+
101
+ until walk.end?
102
+ if comma_sequence.matches_path?(walk.path)
103
+ walk.replace!(block.call(walk.current))
99
104
  end
100
- ]
101
- node_matches? ? block.call(new_node) : new_node
105
+ walk.next!
106
+ end
107
+
108
+ walk.result
102
109
  end
103
110
 
104
111
  private
@@ -131,49 +138,6 @@ module Hexp
131
138
  comma_sequence.matches?(@node)
132
139
  end
133
140
 
134
- # Consume the matching part of the comma sequence, return the rest
135
- #
136
- # Returns a new comma sequence with the parts removed that have been
137
- # consumed by matching against this node. If no part matches, returns nil.
138
- #
139
- # @return [Hexp::CssSelector::CommaSequence]
140
- #
141
- # @api private
142
- def next_comma_sequence
143
- @next_comma_sequence ||= CssSelector::CommaSequence.new(consume_matching_heads)
144
- end
145
-
146
- # Recurse down a child down, passing in the remaining part of the selector
147
- #
148
- # @param [Hexp::Node] child
149
- # One of the children of the node in this selection object
150
- #
151
- # @return [Hexp::Node::CssSelection]
152
- #
153
- # @api private
154
- def next_selection_for(child)
155
- self.class.new(child, next_comma_sequence)
156
- end
157
-
158
- # For each sequence in the comma sequence, remove the head if it matches
159
- #
160
- # For example, if this node is a `H[:div]`, and the selector is
161
- # `span.foo, div a[:href]`, then the result of this method will be
162
- # `span.foo, a[:href]`. This can then be used to match any child nodes.
163
- #
164
- # @return [Hexp::CssSelector::CommaSequence]
165
- #
166
- # @api private
167
- def consume_matching_heads
168
- comma_sequence.members.flat_map do |sequence|
169
- if sequence.head_matches? @node
170
- [sequence, sequence.drop_head]
171
- else
172
- [sequence]
173
- end
174
- end.reject(&:empty?)
175
- end
176
-
177
141
  end
178
142
  end
179
143
  end
@@ -43,6 +43,7 @@ module Hexp
43
43
  #
44
44
  # @api private
45
45
  def normalized_attributes
46
+ return attributes if attributes.all? {|k,v| k.instance_of?(String) && v.instance_of?(String) }
46
47
  Hash[*
47
48
  attributes.flat_map do |key, value|
48
49
  [key, value].map(&:to_s)
@@ -56,10 +57,12 @@ module Hexp
56
57
  #
57
58
  # @api private
58
59
  def children
59
- children = @raw.drop(1)
60
- children = children.drop(1) if children.first.instance_of?(Hash)
61
- children = children.first.to_ary if children.first.respond_to?(:to_ary)
62
- children
60
+ start = @raw[1].instance_of?(Hash) ? 2 : 1
61
+ if @raw[start].respond_to?(:to_ary)
62
+ @raw[start].to_ary
63
+ else
64
+ @raw.drop(start)
65
+ end
63
66
  end
64
67
 
65
68
  # Normalize the third element of a hexp node, the list of children
@@ -68,7 +71,11 @@ module Hexp
68
71
  #
69
72
  # @api private
70
73
  def normalized_children
71
- Hexp::List[* children ]
74
+ if children.instance_of?(Hexp::List)
75
+ children
76
+ else
77
+ Hexp::List.new( children )
78
+ end
72
79
  end
73
80
 
74
81
  def self.coerce_node(node)
@@ -7,8 +7,6 @@ module Hexp
7
7
  # converted to `TextNode` instances, so there is usually no reason to instantiate
8
8
  # these yourself.
9
9
  class TextNode < DelegateClass(String)
10
- include Adamantium
11
-
12
10
  # Inspect the TextNode
13
11
  #
14
12
  # This delegates to the underlying String, making it
@@ -1,3 +1,3 @@
1
1
  module Hexp
2
- VERSION = '0.4.2'
2
+ VERSION = '0.4.3'
3
3
  end
@@ -0,0 +1,72 @@
1
+ RSpec.describe Hexp::MutableTreeWalk do
2
+
3
+ let(:node) { H[:ul, H[:p, [H[:li, 'foo', 'boo'], H[:li, 'bar']]]] }
4
+ let(:walk) { Hexp::MutableTreeWalk.new(node) }
5
+
6
+ it 'should start at the root' do
7
+ expect(walk.current).to eql node
8
+ end
9
+
10
+ it 'should not have a parent at the root' do
11
+ expect(walk.parent).to be_nil
12
+ end
13
+
14
+ it 'should descend to the children' do
15
+ walk.next!
16
+ expect(walk.current).to eql H[:p, [H[:li, ["foo", "boo"]], H[:li, ["bar"]]]]
17
+ end
18
+
19
+ it 'should go depth first' do
20
+ 2.times { walk.next! }
21
+ expect(walk.current).to eql H[:li, 'foo', 'boo']
22
+ end
23
+
24
+ it 'should also do text nodes' do
25
+ 3.times { walk.next! }
26
+ expect(walk.current).to eq 'foo'
27
+ end
28
+
29
+ it 'should go left to right' do
30
+ 4.times { walk.next! }
31
+ expect(walk.current).to eq 'boo'
32
+ end
33
+
34
+ it 'should go back up and right' do
35
+ 5.times { walk.next! }
36
+ expect(walk.current).to eql H[:li, 'bar']
37
+ end
38
+
39
+ it 'should finish on nil' do
40
+ 7.times { walk.next! }
41
+ expect(walk.current).to be_nil
42
+ expect(walk.end?).to be true
43
+ end
44
+
45
+ it 'stays at the end' do
46
+ 8.times { walk.next! }
47
+ expect(walk.end?).to be true
48
+ end
49
+
50
+ it 'should allow replacements' do
51
+ 2.times { walk.next! }
52
+ walk.replace! H[:foo]
53
+ 6.times { walk.next! }
54
+ expect(walk.result).to eql H[:ul, H[:p, H[:foo], H[:li, 'bar']]]
55
+ end
56
+
57
+ it 'should allow replacements' do
58
+ 7.times do
59
+ walk.next!
60
+ if !walk.end? && !walk.current.text? && walk.current.tag?(:li)
61
+ walk.replace! H[:span, walk.current]
62
+ end
63
+ end
64
+
65
+ expect(walk.result).to eql H[:ul,
66
+ H[:p,
67
+ H[:span, H[:li, 'foo', 'boo']],
68
+ H[:span, H[:li, 'bar']]]]
69
+ end
70
+
71
+
72
+ end
@@ -18,7 +18,7 @@ describe Hexp::Node::CssSelection do
18
18
  let(:hexp) { H[:span, {id: 'span-1'}, H[:span, id: 'span-2']] }
19
19
 
20
20
  it 'should match all nodes of that tag' do
21
- expect(selection.to_a).to eq [ H[:span, id: 'span-2'], hexp ]
21
+ expect(selection.to_a).to eq [ hexp, H[:span, id: 'span-2'] ]
22
22
  end
23
23
  end
24
24
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hexp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.4.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arne Brasseur
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-04 00:00:00.000000000 Z
11
+ date: 2014-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sass
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.6'
41
- - !ruby/object:Gem::Dependency
42
- name: adamantium
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '0.2'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '0.2'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: equalizer
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -193,6 +179,7 @@ files:
193
179
  - lib/hexp/errors.rb
194
180
  - lib/hexp/h.rb
195
181
  - lib/hexp/list.rb
182
+ - lib/hexp/mutable_tree_walk.rb
196
183
  - lib/hexp/node.rb
197
184
  - lib/hexp/node/attributes.rb
198
185
  - lib/hexp/node/children.rb
@@ -222,6 +209,7 @@ files:
222
209
  - spec/unit/hexp/dsl_spec.rb
223
210
  - spec/unit/hexp/h_spec.rb
224
211
  - spec/unit/hexp/list_spec.rb
212
+ - spec/unit/hexp/mutable_tree_walk_spec.rb
225
213
  - spec/unit/hexp/node/attr_spec.rb
226
214
  - spec/unit/hexp/node/attributes_spec.rb
227
215
  - spec/unit/hexp/node/children_spec.rb
@@ -280,6 +268,7 @@ test_files:
280
268
  - spec/unit/hexp/dsl_spec.rb
281
269
  - spec/unit/hexp/h_spec.rb
282
270
  - spec/unit/hexp/list_spec.rb
271
+ - spec/unit/hexp/mutable_tree_walk_spec.rb
283
272
  - spec/unit/hexp/node/attr_spec.rb
284
273
  - spec/unit/hexp/node/attributes_spec.rb
285
274
  - spec/unit/hexp/node/children_spec.rb