hexp 0.3.3 → 0.4.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|