hexp 0.4.2 → 0.4.3

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: 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