hexp 0.2.0 → 0.3.0

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