hexp 0.3.3 → 0.4.0.beta1
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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +8 -3
- data/Changelog.md +32 -1
- data/Gemfile +0 -5
- data/Rakefile +21 -15
- data/hexp.gemspec +6 -4
- data/lib/hexp.rb +5 -14
- data/lib/hexp/core_ext/nil.rb +9 -0
- data/lib/hexp/css_selector.rb +2 -1
- data/lib/hexp/h.rb +9 -1
- data/lib/hexp/list.rb +6 -1
- data/lib/hexp/node.rb +9 -2
- data/lib/hexp/node/attributes.rb +1 -1
- data/lib/hexp/node/children.rb +4 -2
- data/lib/hexp/node/normalize.rb +24 -28
- data/lib/hexp/nokogiri/reader.rb +1 -1
- data/lib/hexp/text_node.rb +6 -0
- data/lib/hexp/unparser.rb +73 -0
- data/lib/hexp/version.rb +1 -1
- data/spec/integration/literal_syntax_spec.rb +2 -2
- data/spec/shared_helper.rb +1 -1
- data/spec/unit/hexp/builder_spec.rb +2 -2
- data/spec/unit/hexp/css_selector/attribute_spec.rb +24 -24
- data/spec/unit/hexp/css_selector/class_spec.rb +3 -3
- data/spec/unit/hexp/css_selector/comma_sequence_spec.rb +1 -1
- data/spec/unit/hexp/css_selector/element_spec.rb +2 -2
- data/spec/unit/hexp/css_selector/simple_sequence_spec.rb +8 -8
- data/spec/unit/hexp/css_selector/universal_spec.rb +1 -1
- data/spec/unit/hexp/dsl_spec.rb +3 -3
- data/spec/unit/hexp/h_spec.rb +2 -2
- data/spec/unit/hexp/node/attributes_spec.rb +4 -4
- data/spec/unit/hexp/node/class_spec.rb +7 -7
- data/spec/unit/hexp/node/normalize_spec.rb +14 -6
- data/spec/unit/hexp/node/rewrite_spec.rb +1 -1
- data/spec/unit/hexp/node/text_spec.rb +1 -1
- data/spec/unit/hexp/node/to_dom_spec.rb +1 -1
- data/spec/unit/hexp/node/to_html_spec.rb +17 -1
- data/spec/unit/hexp/nokogiri/equality_spec.rb +6 -6
- data/spec/unit/hexp/text_node_spec.rb +2 -2
- metadata +62 -34
- data/Gemfile.devtools +0 -55
- data/Gemfile.lock +0 -186
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7da79589fadbf74a0c0fa19ad9f409ca9f0dc855
|
4
|
+
data.tar.gz: 65e4754146881f1d5a23df35ae4b4f7a964ad886
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82271f2b2f56f71a71b2897b3668c7dbb2b8283a73df551d4ad6ae790e2cec806fb6901e6253c300f5bf1d2b7a4244bdbb0fde09cf3faf414df00d3fd6d1cc84
|
7
|
+
data.tar.gz: fd56366470868b9c0df687b945e9da1ce17e2d94fbf81b4be38a454810ca684ba1c1d5bcfcee1f28d8fe80b45320b9072bea9b8ed1269b9e93bf1b3dce0272e0
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,13 +1,18 @@
|
|
1
|
+
# Currently fails on JRuby, partly due to Nokogiri behaving differently,
|
2
|
+
# and partly for some other reason I'm not sure about regarding
|
3
|
+
# Hexp::Node::Normalize
|
1
4
|
language: ruby
|
2
5
|
script: "bundle exec rspec"
|
3
6
|
rvm:
|
4
|
-
- 1.9.2
|
5
7
|
- 1.9.3
|
6
8
|
- 2.0.0
|
7
|
-
- 2.1.
|
9
|
+
- 2.1.1
|
10
|
+
- 2.1.2
|
11
|
+
- jruby
|
12
|
+
- jruby-head
|
8
13
|
- ruby-head
|
9
14
|
matrix:
|
10
15
|
allow_failures:
|
11
|
-
- rvm: jruby
|
16
|
+
- rvm: jruby
|
12
17
|
- rvm: ruby-head
|
13
18
|
- rvm: jruby-head
|
data/Changelog.md
CHANGED
@@ -1,5 +1,36 @@
|
|
1
1
|
### Development
|
2
|
-
|
2
|
+
|
3
|
+
[full diff](http://github.com/plexus/hexp/compare/v0.3.3...master)
|
4
|
+
|
5
|
+
### v0.4.0.beta1
|
6
|
+
|
7
|
+
[full diff](http://github.com/plexus/hexp/compare/v0.3.3...v0.4.0.beta1)
|
8
|
+
|
9
|
+
* Do our own HTML unparsing, instead of going through Nokogiri,
|
10
|
+
causing a big speed improvement.
|
11
|
+
* Make H[] notation more lenient
|
12
|
+
* Make array around list of children optional
|
13
|
+
`H[:p, [H[:span, 'foo'], ' ', H[:span, 'bar']]]` =>
|
14
|
+
`H[:p, H[:span, 'foo'], ' ', H[:span, 'bar']]`
|
15
|
+
* Allow creating node lists without a wrapping node, e.g.
|
16
|
+
`H[H[:span, 'foo'], ' ', H[:span, 'bar']]`
|
17
|
+
* Make Hexp::List and Hexp::TextNode respond to to_html
|
18
|
+
* Add Hexp::Node#tag? as a complement to Hexp::Node#text?
|
19
|
+
|
20
|
+
### v0.3.3
|
21
|
+
|
22
|
+
[full diff](http://github.com/plexus/hexp/compare/v0.3.0...v0.3.3)
|
23
|
+
|
24
|
+
* Bugfix regarding string values in attribute CSS selectors
|
25
|
+
* Update dependencies
|
26
|
+
|
27
|
+
### v0.3.0
|
28
|
+
|
29
|
+
[full diff](http://github.com/plexus/hexp/compare/v0.2.0...v0.3.0)
|
30
|
+
|
31
|
+
* Improved CSS selector support
|
32
|
+
* Handle CDATA sections when parsing through Nokogiri
|
33
|
+
* Improved documentation
|
3
34
|
|
4
35
|
### v0.2.0
|
5
36
|
|
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1,22 +1,19 @@
|
|
1
|
-
require 'devtools'
|
2
1
|
require 'rubygems/package_task'
|
3
2
|
|
4
|
-
Devtools.init_rake_tasks
|
5
|
-
|
6
3
|
# Redefine rake:ci:metrics to disable rubocop, will tackle that laundry list
|
7
4
|
# some other time
|
8
|
-
namespace :ci do
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
5
|
+
# namespace :ci do
|
6
|
+
# desc 'Run metrics (except mutant, rubocop) and spec'
|
7
|
+
# task travis: %w[
|
8
|
+
# metrics:coverage
|
9
|
+
# spec:integration
|
10
|
+
# metrics:yardstick:verify
|
11
|
+
# metrics:flog
|
12
|
+
# metrics:flay
|
13
|
+
# ]
|
14
|
+
# # metrics:reek
|
15
|
+
# # metrics:rubocop
|
16
|
+
# end
|
20
17
|
|
21
18
|
|
22
19
|
spec = Gem::Specification.load(File.expand_path('../hexp.gemspec', __FILE__))
|
@@ -45,3 +42,12 @@ task :doc2gh do
|
|
45
42
|
sh "git push origin gh-pages"
|
46
43
|
sh "git co master"
|
47
44
|
end
|
45
|
+
|
46
|
+
require 'mutant'
|
47
|
+
task :default => :mutant
|
48
|
+
|
49
|
+
task :mutant do
|
50
|
+
pattern = ENV.fetch('PATTERN', 'Hexp*')
|
51
|
+
result = Mutant::CLI.run(%w[-Ilib -rhexp --use rspec --score 100] + [pattern])
|
52
|
+
fail unless result == Mutant::CLI::EXIT_SUCCESS
|
53
|
+
end
|
data/hexp.gemspec
CHANGED
@@ -19,10 +19,12 @@ 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 '
|
22
|
+
gem.add_runtime_dependency 'adamantium', '~> 0.2'
|
23
23
|
gem.add_runtime_dependency 'equalizer', '~> 0.0'
|
24
24
|
|
25
|
-
gem.add_development_dependency 'rake'
|
26
|
-
gem.add_development_dependency 'rspec'
|
27
|
-
gem.add_development_dependency 'benchmark_suite'
|
25
|
+
gem.add_development_dependency 'rake'
|
26
|
+
gem.add_development_dependency 'rspec'
|
27
|
+
gem.add_development_dependency 'benchmark_suite'
|
28
|
+
gem.add_development_dependency 'mutant-rspec'
|
29
|
+
gem.add_development_dependency 'rspec-its'
|
28
30
|
end
|
data/lib/hexp.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
require 'delegate'
|
2
2
|
require 'forwardable'
|
3
|
+
require 'pathname'
|
3
4
|
|
4
5
|
require 'nokogiri' # TODO => replace with Builder
|
5
6
|
require 'sass'
|
6
|
-
require '
|
7
|
+
require 'adamantium'
|
7
8
|
require 'equalizer'
|
8
9
|
|
9
10
|
module Hexp
|
11
|
+
ROOT = Pathname(__FILE__).dirname.parent
|
12
|
+
|
10
13
|
# Inject the Hexp::DSL module into classes that include Hexp
|
11
14
|
#
|
12
15
|
# @param [Class] klazz
|
@@ -19,19 +22,6 @@ module Hexp
|
|
19
22
|
klazz.send(:include, Hexp::DSL)
|
20
23
|
end
|
21
24
|
|
22
|
-
# Deep freeze an object
|
23
|
-
#
|
24
|
-
# Delegates to IceNine
|
25
|
-
#
|
26
|
-
# @param [Array] args
|
27
|
-
# arguments to pass on
|
28
|
-
# @return [Object]
|
29
|
-
#
|
30
|
-
# @api private
|
31
|
-
def self.deep_freeze(*args)
|
32
|
-
IceNine.deep_freeze(*args)
|
33
|
-
end
|
34
|
-
|
35
25
|
# Variant of ::Array with slightly modified semantics
|
36
26
|
#
|
37
27
|
# `Array()` is often used to wrap a value in an Array, unless it's already
|
@@ -134,3 +124,4 @@ require 'hexp/sass/selector_parser'
|
|
134
124
|
require 'hexp/h'
|
135
125
|
|
136
126
|
require 'hexp/builder'
|
127
|
+
require 'hexp/unparser'
|
data/lib/hexp/css_selector.rb
CHANGED
@@ -4,6 +4,7 @@ module Hexp
|
|
4
4
|
#
|
5
5
|
module Members
|
6
6
|
include Equalizer.new(:members)
|
7
|
+
include Adamantium
|
7
8
|
|
8
9
|
extend Forwardable
|
9
10
|
def_delegator :@members, :empty?
|
@@ -19,7 +20,7 @@ module Hexp
|
|
19
20
|
#
|
20
21
|
# @api private
|
21
22
|
def initialize(members)
|
22
|
-
@members =
|
23
|
+
@members = members
|
23
24
|
end
|
24
25
|
|
25
26
|
# Create a class level collection constructor
|
data/lib/hexp/h.rb
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
if defined?(::H) && ::H != Hexp::Node
|
2
2
|
$stderr.puts "WARN: H is already defined, Hexp H[] shorthand not available"
|
3
3
|
else
|
4
|
-
H
|
4
|
+
module H
|
5
|
+
def self.[](*args)
|
6
|
+
if args.first.is_a? Symbol
|
7
|
+
Hexp::Node[*args]
|
8
|
+
else
|
9
|
+
Hexp::List[*args]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
5
13
|
end
|
data/lib/hexp/list.rb
CHANGED
@@ -2,6 +2,7 @@ module Hexp
|
|
2
2
|
# A list of nodes
|
3
3
|
#
|
4
4
|
class List < DelegateClass(Array)
|
5
|
+
include Adamantium
|
5
6
|
|
6
7
|
# Create new Hexp::List
|
7
8
|
#
|
@@ -13,7 +14,7 @@ module Hexp
|
|
13
14
|
#
|
14
15
|
# @api public
|
15
16
|
def initialize(nodes)
|
16
|
-
super nodes.to_ary.freeze
|
17
|
+
super nodes.to_ary.map(&Node::Normalize.method(:coerce_node)).freeze
|
17
18
|
end
|
18
19
|
|
19
20
|
# Convenience constructor
|
@@ -82,5 +83,9 @@ module Hexp
|
|
82
83
|
def eql?(other)
|
83
84
|
self == other && self.class == other.class
|
84
85
|
end
|
86
|
+
|
87
|
+
def to_html
|
88
|
+
each_with_object('') {|n,s| s << n.to_html}
|
89
|
+
end
|
85
90
|
end
|
86
91
|
end
|
data/lib/hexp/node.rb
CHANGED
@@ -53,11 +53,14 @@ module Hexp
|
|
53
53
|
#
|
54
54
|
class Node
|
55
55
|
include Equalizer.new(:tag, :attributes, :children)
|
56
|
+
include Adamantium
|
56
57
|
extend Forwardable
|
57
58
|
|
58
59
|
include Hexp::Node::Attributes
|
59
60
|
include Hexp::Node::Children
|
60
61
|
|
62
|
+
memoize :class_list
|
63
|
+
|
61
64
|
# The HTML tag of this node
|
62
65
|
#
|
63
66
|
# @example
|
@@ -146,7 +149,7 @@ module Hexp
|
|
146
149
|
# @api public
|
147
150
|
#
|
148
151
|
def to_html(options = {})
|
149
|
-
|
152
|
+
Unparser.new(options).call(self)
|
150
153
|
end
|
151
154
|
|
152
155
|
# Convert this node into a Nokogiri Document
|
@@ -200,6 +203,10 @@ module Hexp
|
|
200
203
|
false
|
201
204
|
end
|
202
205
|
|
206
|
+
def tag?(tag)
|
207
|
+
self.tag == tag
|
208
|
+
end
|
209
|
+
|
203
210
|
# Return a new node, with a different tag
|
204
211
|
#
|
205
212
|
# @example
|
@@ -318,7 +325,7 @@ module Hexp
|
|
318
325
|
# @api private
|
319
326
|
#
|
320
327
|
def inspect_name
|
321
|
-
if defined?(H)
|
328
|
+
if defined?(H)
|
322
329
|
'H'
|
323
330
|
else
|
324
331
|
self.name
|
data/lib/hexp/node/attributes.rb
CHANGED
data/lib/hexp/node/children.rb
CHANGED
@@ -72,9 +72,10 @@ module Hexp
|
|
72
72
|
# @return [Hexp::Node]
|
73
73
|
#
|
74
74
|
# @api public
|
75
|
-
def
|
76
|
-
H[tag, attributes,
|
75
|
+
def content(*args)
|
76
|
+
H[tag, attributes, *args]
|
77
77
|
end
|
78
|
+
alias set_children content
|
78
79
|
|
79
80
|
# Perform an action on each child node, and replace the node with the result
|
80
81
|
#
|
@@ -94,6 +95,7 @@ module Hexp
|
|
94
95
|
return to_enum(:map_children) unless block_given?
|
95
96
|
H[tag, attributes, children.map(&blk)]
|
96
97
|
end
|
98
|
+
|
97
99
|
end
|
98
100
|
end
|
99
101
|
end
|
data/lib/hexp/node/normalize.rb
CHANGED
@@ -11,8 +11,8 @@ module Hexp
|
|
11
11
|
# Hexp::Node::Normalize.new([:p, {class:'foo'}])
|
12
12
|
#
|
13
13
|
# @api public
|
14
|
-
def initialize(
|
15
|
-
@raw =
|
14
|
+
def initialize(args)
|
15
|
+
@raw = args
|
16
16
|
end
|
17
17
|
|
18
18
|
# Normalize to strict hexp nodes, cfr SPEC.md for details
|
@@ -56,14 +56,10 @@ module Hexp
|
|
56
56
|
#
|
57
57
|
# @api private
|
58
58
|
def children
|
59
|
-
|
60
|
-
if
|
61
|
-
|
62
|
-
|
63
|
-
[]
|
64
|
-
else
|
65
|
-
[last]
|
66
|
-
end
|
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
|
67
63
|
end
|
68
64
|
|
69
65
|
# Normalize the third element of a hexp node, the list of children
|
@@ -72,24 +68,24 @@ module Hexp
|
|
72
68
|
#
|
73
69
|
# @api private
|
74
70
|
def normalized_children
|
75
|
-
Hexp::List[*
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
71
|
+
Hexp::List[* children ]
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.coerce_node(node)
|
75
|
+
case node
|
76
|
+
when Hexp::Node, Hexp::TextNode
|
77
|
+
node
|
78
|
+
when String
|
79
|
+
Hexp::TextNode.new(node)
|
80
|
+
when ->(ch) { ch.respond_to? :to_hexp }
|
81
|
+
response = node.to_hexp
|
82
|
+
raise FormatError, "to_hexp must return a Hexp::Node, got #{response.inspect}" unless response.instance_of?(Hexp::Node) || response.instance_of?(Hexp::TextNode)
|
83
|
+
response
|
84
|
+
when Array
|
85
|
+
Hexp::Node[*node]
|
86
|
+
else
|
87
|
+
raise FormatError, "Invalid value in Hexp literal : #{node.inspect} (#{node.class}) does not implement #to_hexp"
|
88
|
+
end
|
93
89
|
end
|
94
90
|
end
|
95
91
|
|
data/lib/hexp/nokogiri/reader.rb
CHANGED
data/lib/hexp/text_node.rb
CHANGED
@@ -7,6 +7,8 @@ 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
|
+
|
10
12
|
# Inspect the TextNode
|
11
13
|
#
|
12
14
|
# This delegates to the underlying String, making it
|
@@ -142,5 +144,9 @@ module Hexp
|
|
142
144
|
def select(&block)
|
143
145
|
Node::Selection.new(self, block)
|
144
146
|
end
|
147
|
+
|
148
|
+
def to_html(opts = {})
|
149
|
+
Unparser.new(opts).call(self)
|
150
|
+
end
|
145
151
|
end
|
146
152
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Hexp
|
2
|
+
class Unparser
|
3
|
+
APOS = ?'.freeze
|
4
|
+
QUOT = ?".freeze
|
5
|
+
LT = '<'.freeze
|
6
|
+
GT = '>'.freeze
|
7
|
+
SPACE = ' '.freeze
|
8
|
+
EQ = '='.freeze
|
9
|
+
AMP = '&'.freeze
|
10
|
+
FSLASH = '/'.freeze
|
11
|
+
|
12
|
+
E_AMP = '&'.freeze
|
13
|
+
E_APOS = '''.freeze
|
14
|
+
E_QUOT = '"'.freeze
|
15
|
+
E_LT = '<'.freeze
|
16
|
+
E_GT = '>'.freeze
|
17
|
+
|
18
|
+
ESCAPE_ATTR_APOS = {AMP => E_AMP, APOS => E_APOS}
|
19
|
+
ESCAPE_ATTR_QUOT = {AMP => E_AMP, QUOT => E_QUOT}
|
20
|
+
ESCAPE_TEXT = {AMP => E_AMP, APOS => E_APOS, QUOT => E_QUOT, LT => E_LT, GT => E_GT}
|
21
|
+
|
22
|
+
ESCAPE_ATTR_APOS_REGEX = Regexp.new("[#{ESCAPE_ATTR_APOS.keys.join}]")
|
23
|
+
ESCAPE_ATTR_QUOT_REGEX = Regexp.new("[#{ESCAPE_ATTR_QUOT.keys.join}]")
|
24
|
+
ESCAPE_TEXT_REGEX = Regexp.new("[#{ESCAPE_TEXT.keys.join}]")
|
25
|
+
|
26
|
+
DEFAULT_OPTIONS = {
|
27
|
+
encoding: Encoding.default_external
|
28
|
+
}
|
29
|
+
|
30
|
+
def initialize(options)
|
31
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def call(node)
|
35
|
+
@buffer = String.new.force_encoding(@options[:encoding])
|
36
|
+
add_node(node)
|
37
|
+
@buffer.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def add_node(node)
|
43
|
+
if node.text?
|
44
|
+
@buffer << escape_text(node)
|
45
|
+
else
|
46
|
+
add_tag(node.tag, node.attributes, node.children)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_tag(tag, attrs, children)
|
51
|
+
@buffer << LT << tag.to_s
|
52
|
+
unless attrs.empty?
|
53
|
+
attrs.each {|k,v| add_attr(k,v)}
|
54
|
+
end
|
55
|
+
@buffer << GT
|
56
|
+
children.each(&method(:add_node))
|
57
|
+
@buffer << LT << FSLASH << tag.to_s << GT
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_attr(key, value)
|
61
|
+
@buffer << SPACE << key << EQ
|
62
|
+
add_attr_value(value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_attr_value(value)
|
66
|
+
@buffer << APOS << value.gsub(ESCAPE_ATTR_APOS_REGEX, ESCAPE_ATTR_APOS) << APOS
|
67
|
+
end
|
68
|
+
|
69
|
+
def escape_text(text)
|
70
|
+
text.gsub(ESCAPE_TEXT_REGEX, ESCAPE_TEXT)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|