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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +8 -3
  4. data/Changelog.md +32 -1
  5. data/Gemfile +0 -5
  6. data/Rakefile +21 -15
  7. data/hexp.gemspec +6 -4
  8. data/lib/hexp.rb +5 -14
  9. data/lib/hexp/core_ext/nil.rb +9 -0
  10. data/lib/hexp/css_selector.rb +2 -1
  11. data/lib/hexp/h.rb +9 -1
  12. data/lib/hexp/list.rb +6 -1
  13. data/lib/hexp/node.rb +9 -2
  14. data/lib/hexp/node/attributes.rb +1 -1
  15. data/lib/hexp/node/children.rb +4 -2
  16. data/lib/hexp/node/normalize.rb +24 -28
  17. data/lib/hexp/nokogiri/reader.rb +1 -1
  18. data/lib/hexp/text_node.rb +6 -0
  19. data/lib/hexp/unparser.rb +73 -0
  20. data/lib/hexp/version.rb +1 -1
  21. data/spec/integration/literal_syntax_spec.rb +2 -2
  22. data/spec/shared_helper.rb +1 -1
  23. data/spec/unit/hexp/builder_spec.rb +2 -2
  24. data/spec/unit/hexp/css_selector/attribute_spec.rb +24 -24
  25. data/spec/unit/hexp/css_selector/class_spec.rb +3 -3
  26. data/spec/unit/hexp/css_selector/comma_sequence_spec.rb +1 -1
  27. data/spec/unit/hexp/css_selector/element_spec.rb +2 -2
  28. data/spec/unit/hexp/css_selector/simple_sequence_spec.rb +8 -8
  29. data/spec/unit/hexp/css_selector/universal_spec.rb +1 -1
  30. data/spec/unit/hexp/dsl_spec.rb +3 -3
  31. data/spec/unit/hexp/h_spec.rb +2 -2
  32. data/spec/unit/hexp/node/attributes_spec.rb +4 -4
  33. data/spec/unit/hexp/node/class_spec.rb +7 -7
  34. data/spec/unit/hexp/node/normalize_spec.rb +14 -6
  35. data/spec/unit/hexp/node/rewrite_spec.rb +1 -1
  36. data/spec/unit/hexp/node/text_spec.rb +1 -1
  37. data/spec/unit/hexp/node/to_dom_spec.rb +1 -1
  38. data/spec/unit/hexp/node/to_html_spec.rb +17 -1
  39. data/spec/unit/hexp/nokogiri/equality_spec.rb +6 -6
  40. data/spec/unit/hexp/text_node_spec.rb +2 -2
  41. metadata +62 -34
  42. data/Gemfile.devtools +0 -55
  43. data/Gemfile.lock +0 -186
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cd5812a1b6d435382838232f4cec4d2fdb2162d2
4
- data.tar.gz: eeff9a4c48a878863a8bc020d4ae37d0dd01d8ee
3
+ metadata.gz: 7da79589fadbf74a0c0fa19ad9f409ca9f0dc855
4
+ data.tar.gz: 65e4754146881f1d5a23df35ae4b4f7a964ad886
5
5
  SHA512:
6
- metadata.gz: dbfde6d4249a6726cc2c492a26b14e2ad79b253844fb813eae65a1fa8eeedac4106a8dcc16155fcbe87312124448e6f6fbfb6078f151cb26eeb01dfbcbe659f1
7
- data.tar.gz: 8ad112e34c87099bf467b5fa1e5a6ff5795a8d4494c24f6c2cd8074b8edb87b57dfc3bccc176fc85d20ab0bcac1b4366d0b942acf922f98e348f5c35631e8826
6
+ metadata.gz: 82271f2b2f56f71a71b2897b3668c7dbb2b8283a73df551d4ad6ae790e2cec806fb6901e6253c300f5bf1d2b7a4244bdbb0fde09cf3faf414df00d3fd6d1cc84
7
+ data.tar.gz: fd56366470868b9c0df687b945e9da1ce17e2d94fbf81b4be38a454810ca684ba1c1d5bcfcee1f28d8fe80b45320b9072bea9b8ed1269b9e93bf1b3dce0272e0
data/.gitignore CHANGED
@@ -23,3 +23,4 @@ measurements/report.txt
23
23
 
24
24
  # Automatic Ruby switching
25
25
  .ruby-version
26
+ Gemfile.lock
@@ -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.0
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-19mode
16
+ - rvm: jruby
12
17
  - rvm: ruby-head
13
18
  - rvm: jruby-head
@@ -1,5 +1,36 @@
1
1
  ### Development
2
- [full changelog](http://github.com/plexus/hexp/compare/v0.2.0...master)
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
@@ -1,8 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- group :development do
4
- gem 'devtools', github: 'rom-rb/devtools'
5
- eval File.read('Gemfile.devtools')
6
- end
7
-
8
3
  gemspec
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
- desc 'Run metrics (except mutant, rubocop) and spec'
10
- task travis: %w[
11
- metrics:coverage
12
- spec:integration
13
- metrics:yardstick:verify
14
- metrics:flog
15
- metrics:flay
16
- ]
17
- # metrics:reek
18
- # metrics:rubocop
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
@@ -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 'ice_nine', '~> 0.9'
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', '~> 10.1'
26
- gem.add_development_dependency 'rspec', '~> 2.14'
27
- gem.add_development_dependency 'benchmark_suite', '~> 1.0'
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
@@ -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 'ice_nine'
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'
@@ -0,0 +1,9 @@
1
+ # This file is *not* loaded by default. You have to explicitly require 'hexp/core_ext/nil'
2
+
3
+ class NilClass
4
+ HEXP_NIL = Hexp::TextNode.new('')
5
+
6
+ def to_hexp
7
+ HEXP_NIL
8
+ end
9
+ end
@@ -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 = Hexp.deep_freeze(members)
23
+ @members = members
23
24
  end
24
25
 
25
26
  # Create a class level collection constructor
@@ -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=Hexp::Node
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
@@ -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
@@ -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
- to_dom(options).to_html
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) && H == self
328
+ if defined?(H)
322
329
  'H'
323
330
  else
324
331
  self.name
@@ -88,7 +88,7 @@ module Hexp
88
88
  #
89
89
  # @api public
90
90
  def class_list
91
- @class_list ||= (attr('class') || '').split(' ').freeze
91
+ (attr('class') || '').split(' ')
92
92
  end
93
93
 
94
94
  # Remove a CSS class from this element
@@ -72,9 +72,10 @@ module Hexp
72
72
  # @return [Hexp::Node]
73
73
  #
74
74
  # @api public
75
- def set_children(new_children)
76
- H[tag, attributes, new_children]
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
@@ -11,8 +11,8 @@ module Hexp
11
11
  # Hexp::Node::Normalize.new([:p, {class:'foo'}])
12
12
  #
13
13
  # @api public
14
- def initialize(node)
15
- @raw = node
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
- last = @raw.last
60
- if last.respond_to? :to_ary
61
- last.to_ary
62
- elsif @raw.count < 2 || last.instance_of?(Hash)
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
- children.map do |child|
77
- case child
78
- when Hexp::Node, Hexp::TextNode
79
- child
80
- when String
81
- Hexp::TextNode.new(child)
82
- when ->(ch) { ch.respond_to? :to_hexp }
83
- response = child.to_hexp
84
- raise FormatError, "to_hexp must return a Hexp::Node, got #{response.inspect}" unless response.instance_of?(Hexp::Node) || response.instance_of?(Hexp::TextNode)
85
- response
86
- when Array
87
- Hexp::Node[*child.map(&:freeze)]
88
- else
89
- raise FormatError, "Invalid value in Hexp literal : #{child.inspect} (#{child.class}) does not implement #to_hexp ; #{children.inspect}"
90
- end
91
- end
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
 
@@ -20,7 +20,7 @@ module Hexp
20
20
  end
21
21
 
22
22
  recurse = ->(next_node) { call(next_node) }
23
- H[node.name.to_sym, attrs, node.children.map(&recurse)]
23
+ H[node.name.to_sym, attrs || {}, node.children.map(&recurse)]
24
24
  end
25
25
  end
26
26
  end
@@ -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 = '&amp;'.freeze
13
+ E_APOS = '&#x27;'.freeze
14
+ E_QUOT = '&quot;'.freeze
15
+ E_LT = '&lt;'.freeze
16
+ E_GT = '&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