hexp 0.2.0 → 0.3.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.
data/Rakefile CHANGED
@@ -13,8 +13,8 @@ namespace :ci do
13
13
  metrics:yardstick:verify
14
14
  metrics:flog
15
15
  metrics:flay
16
- metrics:reek
17
16
  ]
17
+ # metrics:reek
18
18
  # metrics:rubocop
19
19
  end
20
20
 
@@ -29,3 +29,19 @@ task :push => :gem do
29
29
  sh "git push --tags"
30
30
  sh "gem push pkg/hexp-#{Hexp::VERSION}.gem"
31
31
  end
32
+
33
+ desc "update gh-pages"
34
+ task :doc2gh do
35
+ sh "git diff-files --quiet || exit 1"
36
+ sh "git diff-index --quiet --cached HEAD || exit 1"
37
+ sh "yardoc"
38
+ sh "[ -d /tmp/doc ] && rm -rf /tmp/doc"
39
+ sh "mv doc /tmp"
40
+ sh "git co gh-pages"
41
+ sh "rm -rf *"
42
+ sh "cp -r /tmp/doc/* ."
43
+ sh "git add ."
44
+ sh "git commit -m 'Update gh-pages with YARD docs'"
45
+ sh "git push origin gh-pages"
46
+ sh "git co master"
47
+ end
@@ -0,0 +1,23 @@
1
+ require 'benchmark/ips'
2
+
3
+ def ten_times_ten(depth = 10)
4
+ if depth == 0
5
+ H[:foo]
6
+ else
7
+ H[:foo, [ten_times_ten(depth - 1)] * 10]
8
+ end
9
+ end
10
+
11
+
12
+ Benchmark.ips do |x|
13
+ big_tree = ten_times_ten
14
+
15
+ x.report('rewrite') do
16
+ big_tree.rewrite {|x| x}
17
+ end
18
+
19
+ x.report('add_class') do
20
+ big_tree.rewrite {|x| x.add_class('hello')}
21
+ end
22
+
23
+ end
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 11
3
- total_score: 293.0
3
+ total_score: 302.0
data/config/flog.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 45.7
2
+ threshold: 47.1
data/config/reek.yml CHANGED
@@ -91,6 +91,7 @@ TooManyStatements:
91
91
  - Hexp::Builder#tag!
92
92
  - Hexp::CssSelector::Attribute#matches?
93
93
  - Hexp::CssSelector::Parser#rewrite_simple
94
+ - Hexp::Node::Normalize#normalized_children
94
95
  max_statements: 6
95
96
  UncommunicativeMethodName:
96
97
  enabled: true
@@ -137,3 +138,8 @@ UtilityFunction:
137
138
  - Hexp::TextNode#attributes
138
139
  - Hexp::TextNode#children
139
140
  max_helper_calls: 0
141
+ PrimaDonnaMethod:
142
+ exclude:
143
+ - Hexp::Builder#_raise_if_missing!
144
+ - Hexp::Builder#tag!
145
+ - Hexp::Builder#text!
data/config/yardstick.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  ---
2
- threshold: 93.7
2
+ threshold: 99.1
3
+ require_exact_threshold: false
3
4
  rules:
4
5
  ApiTag::Presence:
5
6
  enabled: true
@@ -25,7 +26,11 @@ rules:
25
26
  - Hexp::Nokogiri::Equality#equal_text?
26
27
  ReturnTag:
27
28
  enabled: true
28
- exclude: []
29
+ exclude:
30
+ - Hexp::CssSelector::Members#initialize
31
+ - Hexp::CssSelector::Members.included
32
+ - Hexp::CssSelector::Named#initialize
33
+ - Hexp::Builder#_raise_if_empty!
29
34
  Summary::Presence:
30
35
  enabled: true
31
36
  exclude: []
data/hexp.gemspec CHANGED
@@ -17,11 +17,12 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = `git ls-files -- spec`.split($/)
18
18
  gem.extra_rdoc_files = %w[README.md]
19
19
 
20
- gem.add_runtime_dependency 'sass' , '~> 3.2'
21
- gem.add_runtime_dependency 'nokogiri' , '~> 1.6'
22
- gem.add_runtime_dependency 'ice_nine' , '~> 0.8'
23
- gem.add_runtime_dependency 'equalizer' , '~> 0.0'
20
+ gem.add_runtime_dependency 'sass', '~> 3.2'
21
+ gem.add_runtime_dependency 'nokogiri', '~> 1.6'
22
+ gem.add_runtime_dependency 'ice_nine', '~> 0.9'
23
+ gem.add_runtime_dependency 'equalizer', '~> 0.0'
24
24
 
25
- gem.add_development_dependency 'rake', '~> 10.1'
25
+ gem.add_development_dependency 'rake', '~> 10.1'
26
26
  gem.add_development_dependency 'rspec', '~> 2.14'
27
+ gem.add_development_dependency 'benchmark_suite', '~> 1.0'
27
28
  end
data/lib/hexp.rb CHANGED
@@ -9,11 +9,12 @@ require 'equalizer'
9
9
  module Hexp
10
10
  # Inject the Hexp::DSL module into classes that include Hexp
11
11
  #
12
- # @param klazz [Class] The class that included Hexp
12
+ # @param [Class] klazz
13
+ # The class that included Hexp
13
14
  #
14
15
  # @return [Class]
15
- # @api private
16
16
  #
17
+ # @api private
17
18
  def self.included(klazz)
18
19
  klazz.send(:include, Hexp::DSL)
19
20
  end
@@ -22,28 +23,30 @@ module Hexp
22
23
  #
23
24
  # Delegates to IceNine
24
25
  #
25
- # @param args [Array] arguments to pass on
26
- # @return Object
27
- # @api private
26
+ # @param [Array] args
27
+ # arguments to pass on
28
+ # @return [Object]
28
29
  #
30
+ # @api private
29
31
  def self.deep_freeze(*args)
30
32
  IceNine.deep_freeze(*args)
31
33
  end
32
34
 
33
35
  # Variant of ::Array with slightly modified semantics
34
36
  #
35
- # Array() is often used to wrap a value in an Array, unless it's already
36
- # an array. However if your object implements #to_a, then Array() will use
37
+ # `Array()` is often used to wrap a value in an Array, unless it's already
38
+ # an array. However if your object implements `#to_a`, then `Array()` will use
37
39
  # that value. Because of this objects that aren't Array-like will get
38
40
  # converted as well, such as Struct objects.
39
41
  #
40
42
  # This implementation relies on #to_ary, which signals that the Object is
41
43
  # a drop-in replacement for an actual Array.
42
44
  #
43
- # @param arg [Object]
45
+ # @param [Object] arg
46
+ #
44
47
  # @return [Array]
45
- # @api private
46
48
  #
49
+ # @api private
47
50
  def self.Array(arg)
48
51
  if arg.respond_to? :to_ary
49
52
  arg.to_ary
@@ -61,10 +64,12 @@ module Hexp
61
64
  # @example
62
65
  # Hexp.parse('<div>hello</div>') #=> H[:div, "hello"]
63
66
  #
64
- # @param html [String] A HTML document
67
+ # @param [String] html
68
+ # A HTML document
69
+ #
65
70
  # @return [Hexp::Node]
66
- # @api public
67
71
  #
72
+ # @api public
68
73
  def self.parse(html)
69
74
  root = Nokogiri(html).root
70
75
  raise Hexp::ParseError, "Failed to parse HTML : no document root" if root.nil?
@@ -73,7 +78,7 @@ module Hexp
73
78
 
74
79
  # Use builder syntax to create a Hexp
75
80
  #
76
- # (see Hexp::Builder)
81
+ # @see Hexp::Builder
77
82
  #
78
83
  # @example
79
84
  # list = Hexp.build do
@@ -84,10 +89,13 @@ module Hexp
84
89
  # end
85
90
  # end
86
91
  #
87
- # @param args [Array]
92
+ # @param [Array] args
93
+ #
94
+ # @yieldparam [Hexp::Builder]
95
+ #
88
96
  # @return [Hexp::Builder]
89
- # @api public
90
97
  #
98
+ # @api public
91
99
  def self.build(*args, &block)
92
100
  Hexp::Builder.new(*args, &block)
93
101
  end
@@ -95,18 +103,18 @@ module Hexp
95
103
  end
96
104
 
97
105
  require 'hexp/version'
98
- require 'hexp/dsl'
99
-
100
106
 
101
107
  require 'hexp/node/attributes'
102
108
  require 'hexp/node/children'
103
-
104
109
  require 'hexp/node'
110
+
111
+ require 'hexp/dsl'
112
+
105
113
  require 'hexp/node/normalize'
106
114
  require 'hexp/node/domize'
107
115
  require 'hexp/node/pp'
108
116
  require 'hexp/node/rewriter'
109
- require 'hexp/node/selector'
117
+ require 'hexp/node/selection'
110
118
  require 'hexp/node/css_selection'
111
119
 
112
120
  require 'hexp/text_node'
data/lib/hexp/builder.rb CHANGED
@@ -4,19 +4,24 @@ module Hexp
4
4
  class Builder < BasicObject
5
5
  include ::Hexp
6
6
 
7
- # def inspect
8
- # ::Kernel.puts ::Kernel.caller ; ::Kernel.exit
9
- # end
10
-
11
7
  # Construct a new builder, and start building
12
8
  #
13
- # The recommended way to call this is through `Hexp.build`.
9
+ # The recommended way to call this is through `Hexp.build`. If the block
10
+ # takes an argument, then builder methods need to be called on that variable.
11
+ #
12
+ # @example With an explicit builder
13
+ # hi = Hexp.build {|html| html.span "Hello" ; html.span " World"}
14
+ #
15
+ # @example Without a builder object
16
+ # hi = Hexp.build { span "Hello" ; span " World"}
14
17
  #
15
- # @param tag [Symbol] The tag of the outermost element (optional)
16
- # @param args [Array<Hash|String>] Extra arguments, a String for a text
17
- # node, a Hash for attributes
18
- # @param block [Proc] The block containing builder directives, can be with
19
- # or without an argument.
18
+ # @param [Symbol] tag
19
+ # The tag of the outermost element (optional)
20
+ # @param [Array<Hash,String>] args
21
+ # Extra arguments, a String for a text node, a Hash for attributes
22
+ #
23
+ # @yieldparam [Hexp::Builder]
24
+ # If the block takes an argument it will receive the builder object
20
25
  #
21
26
  # @api private
22
27
  #
@@ -40,15 +45,17 @@ module Hexp
40
45
  # end
41
46
  # hexp.to_html #=> "<div><p>Oh the code, such sweet joy it brings</p></div>"
42
47
  #
43
- # @param tag [Symbol] The tag name, like 'div' or 'head'
44
- # @param args [Array<Hash|String>] A hash of attributes, or a string to use
45
- # inside the tag, or both. Multiple occurences of each can be
46
- # specified
47
- # @param block [Proc] Builder directives for the contents of the tag
48
- # @return [NilClass]
48
+ # @param [Symbol] tag
49
+ # The tag name, like 'div' or 'head'
50
+ # @param [Array<Hash|String>] args
51
+ # A hash of attributes, or a string to use inside the tag, or both. Multiple
52
+ # occurences of each can be specified
53
+ # @param [Proc] block
54
+ # Builder directives for the contents of the tag
49
55
  #
50
- # @api public
56
+ # @return [nil]
51
57
  #
58
+ # @api public
52
59
  def tag!(tag, *args, &block)
53
60
  text, attributes = nil, {}
54
61
  args.each do |arg|
@@ -84,10 +91,12 @@ module Hexp
84
91
  # end
85
92
  # end
86
93
  #
87
- # @param text [String] the text to add
94
+ # @param [String] text
95
+ # the text to add
96
+ #
88
97
  # @return [Hexp::Builder] self
89
- # @api public
90
98
  #
99
+ # @api public
91
100
  def text!(text)
92
101
  _raise_if_empty! "Hexp::Builder needs a root element to add text elements to"
93
102
  @stack.last[2] << text.to_s
@@ -113,7 +122,9 @@ module Hexp
113
122
  # end
114
123
  # node.to_html #=> <div><button>click me!</button></div>
115
124
  #
116
- # @params args [Array<:to_hexp>] Hexpable objects to add to the current tag
125
+ # @param [Array<#to_hexp>] args
126
+ # Hexpable objects to add to the current tag
127
+ #
117
128
  # @return [Hexp::Builder]
118
129
  #
119
130
  # @api public
@@ -139,8 +150,8 @@ module Hexp
139
150
  # Hexp.build { div { text! 'hello' } }.to_hexp # => H[:div, ["hello"]]
140
151
  #
141
152
  # @return [Hexp::Node]
142
- # @api public
143
153
  #
154
+ # @api public
144
155
  def to_hexp
145
156
  _raise_if_empty!
146
157
  ::Hexp::Node[*@stack.last]
@@ -157,9 +168,10 @@ module Hexp
157
168
  # builder calls.
158
169
  #
159
170
  # @param block [Proc]
160
- # @return [NilClass]
161
- # @api private
162
171
  #
172
+ # @return [nil]
173
+ #
174
+ # @api private
163
175
  def _process(&block)
164
176
  if block.arity == 1
165
177
  block.call(self)
@@ -178,14 +190,15 @@ module Hexp
178
190
  # end
179
191
  #
180
192
  # @api private
181
- #
182
193
  class NodeBuilder
183
194
  # Create new NodeBuilder
184
195
  #
185
- # @param node [Array] (tag, attrs, children) triplet
186
- # @param builder [Hexp::Builder] The parent builder to delegate back
187
- # @api private
196
+ # @param [Array] node
197
+ # (tag, attrs, children) triplet
198
+ # @param [Hexp::Builder] builder
199
+ # The parent builder to delegate back
188
200
  #
201
+ # @api private
189
202
  def initialize(node, builder)
190
203
  @node, @builder = node, builder
191
204
  end
@@ -196,14 +209,16 @@ module Hexp
196
209
  # Hexp.build { div.strong.warn }.to_hexp
197
210
  # # => H[:div, class: 'strong warn']
198
211
  #
199
- # @param sym [Symbol] the class to add
212
+ # @param [Symbol] sym
213
+ # the class to add
214
+ #
200
215
  # @return [Hexp::Builder::NodeBuilder] self
201
- # @api public
202
216
  #
217
+ # @api public
203
218
  def method_missing(sym, &block)
204
219
  attrs = @node[1]
205
220
  @node[1] = attrs.merge class: [attrs[:class], sym.to_s].compact.join(' ')
206
- @builder._process &block if block
221
+ @builder._process(&block) if block
207
222
  self
208
223
  end
209
224
  end
@@ -218,10 +233,10 @@ module Hexp
218
233
  # p Hexp.build { div }
219
234
  #
220
235
  # @return [String]
221
- # @api public
222
236
  #
237
+ # @api public
223
238
  def inspect
224
- "#<Hexp::Builder #{@stack.empty? ? '[]' :to_hexp.inspect}>"
239
+ "#<Hexp::Builder #{@stack.empty? ? '[]' : to_hexp.inspect}>"
225
240
  end
226
241
 
227
242
  # Gratefully borrowed from Builder.
@@ -245,10 +260,14 @@ module Hexp
245
260
 
246
261
  # Raise an exception if nothing has been built yet
247
262
  #
248
- # @param text [String] The error message
249
- # @raises {Hexp::FormatError}
250
- # @api private
263
+ # @param [String] text
264
+ # The error message
265
+ #
266
+ # @raise [Hexp::FormatError]
267
+ # if the builder is converted to a {Hexp::Node} before a root element is
268
+ # defined.
251
269
  #
270
+ # @api private
252
271
  def _raise_if_empty!(text = 'Hexp::Builder is lacking a root element.')
253
272
  ::Kernel.raise ::Hexp::FormatError, text if @stack.empty?
254
273
  end
@@ -10,10 +10,14 @@ module Hexp
10
10
 
11
11
  # Member nodes
12
12
  #
13
+ # @return [Array]
14
+ #
15
+ # @api private
13
16
  attr_reader :members
14
17
 
15
18
  # Shared initializer for parse tree nodes with children (members)
16
19
  #
20
+ # @api private
17
21
  def initialize(members)
18
22
  @members = Hexp.deep_freeze(members)
19
23
  end
@@ -23,9 +27,9 @@ module Hexp
23
27
  # @example
24
28
  # CommaSequence[member1, member2]
25
29
  #
26
- # @param klass [Class]
27
- # @api private
30
+ # @param [Class] klass
28
31
  #
32
+ # @api private
29
33
  def self.included(klass)
30
34
  def klass.[](*members)
31
35
  new(members)
@@ -35,8 +39,8 @@ module Hexp
35
39
  # Return a debugging representation
36
40
  #
37
41
  # @return [String]
38
- # @api private
39
42
  #
43
+ # @api private
40
44
  def inspect
41
45
  "#{self.class.name.split('::').last}[#{self.members.map(&:inspect).join(', ')}]"
42
46
  end
@@ -46,12 +50,29 @@ module Hexp
46
50
  #
47
51
  module Named
48
52
  include Equalizer.new(:name)
53
+
54
+ # The name of this element
55
+ #
56
+ # @return [String]
57
+ #
58
+ # @api private
49
59
  attr_reader :name
50
60
 
61
+ # Shared constructor that sets a name
62
+ #
63
+ # @param [String] name
64
+ # the name of the element
65
+ #
66
+ # @api private
51
67
  def initialize(name)
52
68
  @name = name.freeze
53
69
  end
54
70
 
71
+ # Return a representation convenient for debugging
72
+ #
73
+ # @return [String]
74
+ #
75
+ # @api private
55
76
  def inspect
56
77
  "<#{self.class.name.split('::').last} name=#{name}>"
57
78
  end
@@ -61,24 +82,22 @@ module Hexp
61
82
  #
62
83
  # Contains a number of {Sequence} objects
63
84
  #
64
- # For example : `span .big, a'
85
+ # For example : 'span .big, a'
65
86
  #
66
87
  class CommaSequence
67
88
  include Members
68
89
 
69
- # def inspect
70
- # members.map(&:inspect).join(', ')
71
- # end
72
-
73
90
  # Does any sequence in this comma sequence fully match the given element
74
91
  #
75
92
  # This method does not recurse, it only checks if any of the sequences in
76
93
  # this CommaSequence with a length of one can fully match the given
77
94
  # element.
78
95
  #
79
- # @param element [Hexp::Node]
96
+ # @param [Hexp::Node] element
97
+ #
80
98
  # @return [Boolean]
81
99
  #
100
+ # @api private
82
101
  def matches?(element)
83
102
  members.any? do |sequence|
84
103
  sequence.members.count == 1 &&
@@ -90,20 +109,29 @@ module Hexp
90
109
  # A single CSS sequence like 'div span .foo'
91
110
  #
92
111
  class Sequence
93
-
94
112
  include Members
95
113
 
114
+ # Does the first element of this sequence match the element
115
+ #
116
+ # @param [Hexp::Node] element
117
+ #
118
+ # @return [true, false]
119
+ #
120
+ # @api private
96
121
  def head_matches?(element)
97
122
  members.first.matches?(element)
98
123
  end
99
124
 
125
+ # Drop the first element of this Sequence
126
+ #
127
+ # This returns a new Sequence, with one member less.
128
+ #
129
+ # @return [Hexp::CssSelector::Sequence]
130
+ #
131
+ # @api private
100
132
  def drop_head
101
133
  self.class.new(members.drop(1))
102
134
  end
103
-
104
- # def inspect
105
- # members.map(&:inspect).join(' ')
106
- # end
107
135
  end
108
136
 
109
137
  # A CSS sequence that relates to a single element, like 'div.caption:first'
@@ -111,28 +139,41 @@ module Hexp
111
139
  class SimpleSequence
112
140
  include Members
113
141
 
142
+ # Does the element match all parts of this SimpleSequence
143
+ #
144
+ # @params [Hexp::Node] element
145
+ #
146
+ # @return [true, false]
147
+ #
148
+ # @api private
114
149
  def matches?(element)
115
150
  members.all? do |simple|
116
151
  simple.matches?(element)
117
152
  end
118
153
  end
119
-
120
- # def inspect
121
- # members.map(&:inspect).join
122
- # end
123
154
  end
124
155
 
125
156
  # A CSS element declaration, like 'div'
126
157
  class Element
127
158
  include Named
128
159
 
160
+ # Does the node match this element selector
161
+ #
162
+ # @param [Hexp::Node] element
163
+ #
164
+ # @return [true, false]
165
+ #
166
+ # @api private
129
167
  def matches?(element)
130
168
  element.tag.to_s == name
131
169
  end
170
+ end
132
171
 
133
- # def inspect
134
- # name
135
- # end
172
+ # Match any element, '*'
173
+ class Universal
174
+ def matches?(element)
175
+ true
176
+ end
136
177
  end
137
178
 
138
179
  # A CSS class declaration, like '.foo'
@@ -140,6 +181,13 @@ module Hexp
140
181
  class Class
141
182
  include Named
142
183
 
184
+ # Does the node match this class selector
185
+ #
186
+ # @param [Hexp::Node] element
187
+ #
188
+ # @return [true, false]
189
+ #
190
+ # @api private
143
191
  def matches?(element)
144
192
  element.class?(name)
145
193
  end
@@ -150,6 +198,13 @@ module Hexp
150
198
  class Id
151
199
  include Named
152
200
 
201
+ # Does the node match this id selector
202
+ #
203
+ # @param [Hexp::Node] element
204
+ #
205
+ # @return [true, false]
206
+ #
207
+ # @api private
153
208
  def matches?(element)
154
209
  element.attr('id') == name
155
210
  end
@@ -157,22 +212,66 @@ module Hexp
157
212
 
158
213
  # An attribute selector, like [href^="http://"]
159
214
  #
215
+ # @!attribute [r] name
216
+ # @return [String] The attribute name
217
+ # @!attribute [r] namespace
218
+ # @return [String]
219
+ # @!attribute [r] operator
220
+ # @return [String] The operator that works on an attribute value
221
+ # @!attribute [r] value
222
+ # @return [String] The value to match against
223
+ # @!attribute [r] flags
224
+ # @return [String]
225
+ #
226
+ # @api private
160
227
  class Attribute
161
228
  include Equalizer.new(:name, :namespace, :operator, :value, :flags)
229
+
162
230
  attr_reader :name, :namespace, :operator, :value, :flags
163
231
 
232
+ # Construct a new Attribute selector
233
+ #
234
+ # The attributes directly mimic those returned from the SASS parser, even
235
+ # though we don't use all of them.
236
+ #
237
+ # @param [String] name
238
+ # Name of the attribute, like 'href'
239
+ # @param [String] namespace
240
+ # unused
241
+ # @param [nil, String] operator
242
+ # Operator like '~=', '^=', ... Use blank to simply test attribute
243
+ # presence.
244
+ # @param [String] value
245
+ # Value to test for, operator dependent
246
+ # @param [Object] flags
247
+ # unused
248
+ #
249
+ # @api private
164
250
  def initialize(name, namespace, operator, value, flags)
165
- @name = name.freeze
251
+ @name = name.freeze
166
252
  @namespace = namespace.freeze
167
- @operator = operator.freeze
168
- @value = value.freeze
169
- @flag = flags.freeze
253
+ @operator = operator.freeze
254
+ @value = value.freeze
255
+ @flag = flags.freeze
170
256
  end
171
257
 
258
+ # Debugging representation
259
+ #
260
+ # @return [String]
261
+ #
262
+ # @api private
172
263
  def inspect
173
264
  "<#{self.class.name.split('::').last} name=#{name} namespace=#{namespace.inspect} operator=#{operator.inspect} value=#{value.inspect} flags=#{flags.inspect}>"
174
265
  end
175
266
 
267
+ # Does the node match this attribute selector
268
+ #
269
+ # @param [Hexp::Node] element
270
+ # node to test against
271
+ #
272
+ # @return [true, false]
273
+ #
274
+ # @api private
176
275
  def matches?(element)
177
276
  return false unless element[name]
178
277
  attribute = element[name]