hexp 0.0.1 → 0.2.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.
Files changed (69) hide show
  1. data/.travis.yml +12 -3
  2. data/Changelog.md +9 -0
  3. data/Gemfile +3 -5
  4. data/Gemfile.devtools +20 -18
  5. data/Gemfile.lock +97 -84
  6. data/Rakefile +16 -0
  7. data/config/flay.yml +2 -2
  8. data/config/flog.yml +1 -1
  9. data/config/reek.yml +42 -18
  10. data/config/rubocop.yml +31 -0
  11. data/config/yardstick.yml +39 -1
  12. data/examples/from_nokogiri.rb +77 -0
  13. data/examples/selector_rewriter_chaining.rb +14 -0
  14. data/examples/todo.rb +138 -0
  15. data/examples/widget.rb +64 -0
  16. data/hexp.gemspec +8 -3
  17. data/lib/hexp.rb +103 -2
  18. data/lib/hexp/builder.rb +256 -0
  19. data/lib/hexp/css_selector.rb +205 -0
  20. data/lib/hexp/css_selector/parser.rb +74 -0
  21. data/lib/hexp/css_selector/sass_parser.rb +22 -0
  22. data/lib/hexp/dom.rb +0 -2
  23. data/lib/hexp/dsl.rb +27 -0
  24. data/lib/hexp/errors.rb +21 -0
  25. data/lib/hexp/h.rb +5 -2
  26. data/lib/hexp/list.rb +67 -9
  27. data/lib/hexp/node.rb +197 -41
  28. data/lib/hexp/node/attributes.rb +176 -0
  29. data/lib/hexp/node/children.rb +44 -0
  30. data/lib/hexp/node/css_selection.rb +73 -0
  31. data/lib/hexp/node/domize.rb +52 -6
  32. data/lib/hexp/node/normalize.rb +19 -9
  33. data/lib/hexp/node/pp.rb +32 -0
  34. data/lib/hexp/node/rewriter.rb +52 -0
  35. data/lib/hexp/node/selector.rb +59 -0
  36. data/lib/hexp/nokogiri/equality.rb +61 -0
  37. data/lib/hexp/nokogiri/reader.rb +27 -0
  38. data/lib/hexp/sass/selector_parser.rb +4 -0
  39. data/lib/hexp/text_node.rb +129 -9
  40. data/lib/hexp/version.rb +1 -1
  41. data/notes +34 -0
  42. data/spec/shared_helper.rb +6 -0
  43. data/spec/spec_helper.rb +2 -6
  44. data/spec/unit/hexp/builder_spec.rb +101 -0
  45. data/spec/unit/hexp/css_selector/attribute_spec.rb +137 -0
  46. data/spec/unit/hexp/css_selector/class_spec.rb +15 -0
  47. data/spec/unit/hexp/css_selector/comma_sequence_spec.rb +20 -0
  48. data/spec/unit/hexp/css_selector/element_spec.rb +11 -0
  49. data/spec/unit/hexp/css_selector/parser_spec.rb +51 -0
  50. data/spec/unit/hexp/css_selector/simple_sequence_spec.rb +48 -0
  51. data/spec/unit/hexp/dsl_spec.rb +55 -0
  52. data/spec/unit/hexp/h_spec.rb +38 -0
  53. data/spec/unit/hexp/list_spec.rb +19 -0
  54. data/spec/unit/hexp/node/attr_spec.rb +55 -0
  55. data/spec/unit/hexp/node/attributes_spec.rb +125 -0
  56. data/spec/unit/hexp/node/children_spec.rb +33 -0
  57. data/spec/unit/hexp/node/class_spec.rb +37 -0
  58. data/spec/unit/hexp/node/css_selection_spec.rb +86 -0
  59. data/spec/unit/hexp/node/normalize_spec.rb +12 -6
  60. data/spec/unit/hexp/node/rewrite_spec.rb +67 -30
  61. data/spec/unit/hexp/node/selector_spec.rb +78 -0
  62. data/spec/unit/hexp/node/text_spec.rb +7 -0
  63. data/spec/unit/hexp/node/to_dom_spec.rb +1 -1
  64. data/spec/unit/hexp/nokogiri/reader_spec.rb +8 -0
  65. data/spec/unit/hexp/parse_spec.rb +23 -0
  66. data/spec/unit/hexp/text_node_spec.rb +25 -0
  67. data/spec/unit/hexp_spec.rb +33 -0
  68. metadata +129 -16
  69. data/lib/hexp/format_error.rb +0 -8
@@ -6,3 +6,9 @@ RSpec::Matchers.define :dom_eq do |other_dom|
6
6
  Hexp::Nokogiri::Equality.new(dom, other_dom).call
7
7
  end
8
8
  end
9
+
10
+ RSpec.configure do |configuration|
11
+ configuration.mock_with :rspec do |configuration|
12
+ configuration.syntax = :expect
13
+ end
14
+ end
@@ -13,14 +13,10 @@ if ENV['COVERAGE'] == 'true'
13
13
 
14
14
  SimpleCov.start do
15
15
  command_name 'spec:unit'
16
+ add_filter 'hexp/h.rb'
16
17
 
17
- # add_filter 'config'
18
-
19
- # add_group 'Finalizer', 'lib/data_mapper/finalizer'
20
-
21
- minimum_coverage 98.10 # 0.10 lower under JRuby
18
+ minimum_coverage 98.5
22
19
  end
23
-
24
20
  end
25
21
 
26
22
  require 'shared_helper'
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::Builder do
4
+ context 'with an empty block' do
5
+ it 'should raise an exception' do
6
+ expect { Hexp::Builder.new {}.to_hexp }.to raise_exception
7
+ end
8
+ end
9
+
10
+ context 'with a tag and attributes passed to the constructor' do
11
+ let(:builder) do
12
+ Hexp::Builder.new :div, class: 'acatalectic'
13
+ end
14
+ it 'should use them as the root element' do
15
+ expect(builder.to_hexp).to eq H[:div, class: 'acatalectic']
16
+ end
17
+ end
18
+
19
+ context 'with a block parameter' do
20
+ it 'should pass the builder to the block' do
21
+ Hexp::Builder.new do |builder|
22
+ expect(Hexp::Builder === builder).to be_true
23
+ end
24
+ end
25
+
26
+ it 'should evaluate the block in the caller context' do
27
+ this = self
28
+ Hexp::Builder.new do |builder|
29
+ expect(this).to eq self
30
+ end
31
+ end
32
+
33
+ it 'should turn calls to the build object into elements' do
34
+ hexp = Hexp::Builder.new do |builder|
35
+ builder.div class: 'jintishi' do
36
+ builder.br
37
+ end
38
+ end.to_hexp
39
+ expect(hexp).to eq(H[:div, {class: 'jintishi'}, H[:br]])
40
+ end
41
+ end
42
+
43
+ context 'without a block parameter' do
44
+ it 'should evaluate in the context of the builder' do
45
+ this = self
46
+ Hexp::Builder.new do
47
+ this.expect(::Hexp::Builder === self).to this.be_true
48
+ end
49
+ end
50
+
51
+ it 'should turn bare method calls into elements' do
52
+ hexp = Hexp::Builder.new do
53
+ span do
54
+ p({class: 'lyrical'}, "I'm with you in Rockland")
55
+ end
56
+ end.to_hexp
57
+ expect(hexp).to eq(H[:span, H[:p, {class: 'lyrical'}, "I'm with you in Rockland"]])
58
+ end
59
+ end
60
+
61
+ describe 'composing' do
62
+ it 'should allow inserting Hexpable values with <<' do
63
+ hexp = Hexp::Builder.new do
64
+ div do |builder|
65
+ builder << ::H[:span]
66
+ end
67
+ end.to_hexp
68
+ expect(hexp).to eq(H[:div, H[:span]])
69
+ end
70
+
71
+ it 'should raise exception when inserting a non-hexp' do
72
+ expect {
73
+ Hexp::Builder.new {|b| b << Object.new }
74
+ }.to raise_exception(Hexp::FormatError)
75
+ end
76
+ end
77
+
78
+ describe Hexp::Builder::NodeBuilder do
79
+ it 'lets you add CSS classes through method calls' do
80
+ hexp = Hexp::Builder.new do
81
+ div.milky.ponderous do
82
+ blink 'My gracious, how wondrous'
83
+ end
84
+ end.to_hexp
85
+ expect(hexp).to eq(H[:div, {class: 'milky ponderous'}, H[:blink, 'My gracious, how wondrous']])
86
+ end
87
+ end
88
+
89
+ it 'should add text nodes with text!' do
90
+ hexp = Hexp::Builder.new do
91
+ div do
92
+ text! 'Babyface, bijou, scharmninkel'
93
+ end
94
+ end.to_hexp
95
+ expect(hexp).to eq(H[:div, 'Babyface, bijou, scharmninkel'])
96
+ end
97
+
98
+ it 'should return an inspection string' do
99
+ expect(Hexp::Builder.new { div }.inspect).to eq "#<Hexp::Builder H[:div]>"
100
+ end
101
+ end
@@ -0,0 +1,137 @@
1
+ describe Hexp::CssSelector::Attribute do
2
+ subject(:selector) { described_class.new(name, namespace, operator, value, flags) }
3
+ let(:name) { nil }
4
+ let(:namespace) { nil }
5
+ let(:operator) { nil }
6
+ let(:value) { nil }
7
+ let(:flags) { nil }
8
+
9
+ describe 'without an operator' do
10
+ let(:name) { 'href' }
11
+
12
+ it 'should match elements with the attribute present' do
13
+ expect(selector.matches? H[:a, href: 'http://foo']).to be_true
14
+ end
15
+
16
+ it 'should match elements with an empty attribute present' do
17
+ expect(selector.matches? H[:a, href: '']).to be_true
18
+ end
19
+
20
+ it 'should not match elements without the attribute present' do
21
+ expect(selector.matches? H[:a]).to be_false
22
+ end
23
+ end
24
+
25
+ describe 'with the "=" operator' do
26
+ let(:name) { 'class' }
27
+ let(:operator) { '=' }
28
+ let(:value) { 'foo' }
29
+
30
+ it "should match if the attribute's value is exactly equal to the given value" do
31
+ expect(selector.matches? H[:a, class: 'foo']).to be_true
32
+ end
33
+
34
+ it "should not match if the attribute's value contains more than the given value" do
35
+ expect(selector.matches? H[:a, class: 'foofoo']).to be_false
36
+ end
37
+
38
+ it "should not match if the attribute's value does not contain the given value" do
39
+ expect(selector.matches? H[:a, class: 'fo']).to be_false
40
+ end
41
+ end
42
+
43
+ describe 'the "~=" operator' do
44
+ let(:name) { 'class' }
45
+ let(:operator) { '~=' }
46
+ let(:value) { 'foo' }
47
+
48
+ it 'should match an entry in a space separated list' do
49
+ expect(selector.matches? H[:a, class: 'foo bla baz']).to be_true
50
+ end
51
+
52
+ it 'should return false if there is no entry that matches' do
53
+ expect(selector.matches? H[:a, class: 'bla baz']).to be_false
54
+ end
55
+
56
+ it 'should return false if there is no such attribute' do
57
+ expect(selector.matches? H[:a]).to be_false
58
+ end
59
+ end
60
+
61
+ describe 'the "|=" operator' do
62
+ let(:name) { 'id' }
63
+ let(:operator) { '|=' }
64
+ let(:value) { 'foo' }
65
+
66
+ it 'should match if the attribute starts with the value, followed by a dash' do
67
+ expect(selector.matches? H[:a, id: 'foo-1']).to be_true
68
+ end
69
+
70
+ it 'should not match if the value is not at the start' do
71
+ expect(selector.matches? H[:a, id: 'myfoo-1']).to be_false
72
+ end
73
+
74
+ it 'should not match if the value is not followed by a dash' do
75
+ expect(selector.matches? H[:a, id: 'foo1']).to be_false
76
+ end
77
+ end
78
+
79
+ describe 'the "^=" operator' do
80
+ let(:name) { 'id' }
81
+ let(:operator) { '^=' }
82
+ let(:value) { 'foo' }
83
+
84
+ it 'should match if the attribute is just the value' do
85
+ expect(selector.matches? H[:a, id: 'foo']).to be_true
86
+ end
87
+
88
+ it 'should match if the attribute starts with the value' do
89
+ expect(selector.matches? H[:a, id: 'foohi']).to be_true
90
+ end
91
+
92
+ it 'should not match if the value is not at the start' do
93
+ expect(selector.matches? H[:a, id: 'myfoo-1']).to be_false
94
+ end
95
+ end
96
+
97
+ describe 'the "$=" operator' do
98
+ let(:name) { 'id' }
99
+ let(:operator) { '$=' }
100
+ let(:value) { 'foo' }
101
+
102
+ it 'should match if the attribute is just the value' do
103
+ expect(selector.matches? H[:a, id: 'foo']).to be_true
104
+ end
105
+
106
+ it 'should match if the attribute ends starts with the value' do
107
+ expect(selector.matches? H[:a, id: 'hifoo']).to be_true
108
+ end
109
+
110
+ it 'should not match if the value is not at the end' do
111
+ expect(selector.matches? H[:a, id: 'foo-1']).to be_false
112
+ end
113
+ end
114
+
115
+ describe 'the "*=" operator' do
116
+ let(:name) { 'id' }
117
+ let(:operator) { '*=' }
118
+ let(:value) { 'foo' }
119
+
120
+ it 'should match if the attribute is just the value' do
121
+ expect(selector.matches? H[:a, id: 'foo']).to be_true
122
+ end
123
+
124
+ it 'should match if the attribute starts starts with the value' do
125
+ expect(selector.matches? H[:a, id: 'foohi']).to be_true
126
+ end
127
+
128
+ it 'should match if the attribute ends starts with the value' do
129
+ expect(selector.matches? H[:a, id: 'hifoo']).to be_true
130
+ end
131
+
132
+ it 'should not match if the value is not in the attribute' do
133
+ expect(selector.matches? H[:a, id: 'yomofohoho']).to be_false
134
+ end
135
+ end
136
+
137
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::CssSelector::Class do
4
+ it 'should match elements having the giving class' do
5
+ expect(described_class.new('big').matches?(H[:div, class: 'big'])).to be_true
6
+ end
7
+
8
+ it 'should not match elements not having the given class' do
9
+ expect(described_class.new('big').matches?(H[:div, class: 'small'])).to be_false
10
+ end
11
+
12
+ it 'should work with elements with multiple classes' do
13
+ expect(described_class.new('foo').matches?(H[:div, class: 'foo bar'])).to be_true
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::CssSelector::CommaSequence do
4
+ let(:comma_sequence) { Hexp::CssSelector::Parser.call(selector) }
5
+
6
+ it 'has members' do
7
+ described_class.new([:foo]).members == [:foo]
8
+ end
9
+
10
+ describe '#matches?' do
11
+ context do
12
+ let(:selector) { 'ul li, li' }
13
+ let(:element) { H[:li, class: 'baz'] }
14
+
15
+ it 'should match' do
16
+ expect(comma_sequence.matches?(element)).to be_true
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::CssSelector::Element do
4
+ it 'should match elements with the same name' do
5
+ expect(described_class.new('tag').matches?(H[:tag])).to be_true
6
+ end
7
+
8
+ it 'should not match elements with a different name' do
9
+ expect(described_class.new('spane').matches?(H[:div])).to be_false
10
+ end
11
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::CssSelector::Parser do
4
+ HC = Hexp::CssSelector # Is there really no way to include constant lookup in this namespace ?!
5
+
6
+ subject(:parse_tree) { described_class.call(selector) }
7
+
8
+
9
+ context 'with a single tag' do
10
+ let(:selector) { 'body' }
11
+ it {
12
+ should eq HC::CommaSequence[
13
+ HC::Sequence[
14
+ HC::SimpleSequence[
15
+ HC::Element.new('body')]]]
16
+ }
17
+ end
18
+
19
+ context 'with SASS specific syntax' do
20
+ let(:selector) { '&.foo' }
21
+ it 'should raise an exception' do
22
+ expect{parse_tree}.to raise_exception
23
+ end
24
+ end
25
+
26
+ context 'with an element, class and id specifier' do
27
+ let(:selector) { '#main a.strong' }
28
+ it {
29
+ should eq HC::CommaSequence[
30
+ HC::Sequence[
31
+ HC::SimpleSequence[
32
+ HC::Id.new('main')],
33
+ HC::SimpleSequence[
34
+ HC::Element.new('a'),
35
+ HC::Class.new('strong')]]]
36
+ }
37
+ end
38
+
39
+ context 'with an attribute selector' do
40
+ let(:selector) { 'div[link=href]' }
41
+ it {
42
+ should eq HC::CommaSequence[
43
+ HC::Sequence[
44
+ HC::SimpleSequence[
45
+ HC::Element.new('div'),
46
+ HC::Attribute.new('link', nil, '=', 'href', nil),
47
+ ]]]
48
+ }
49
+ end
50
+
51
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::CssSelector::SimpleSequence do
4
+ context 'with a single element member' do
5
+ let(:sequence) { described_class[Hexp::CssSelector::Element.new('div')] }
6
+
7
+ it 'should match when the element has the same tag name' do
8
+ expect(sequence.matches?(H[:div])).to be_true
9
+ end
10
+
11
+ it 'should not match when the tag name differs' do
12
+ expect(sequence.matches?(H[:span])).to be_false
13
+ end
14
+ end
15
+
16
+ context 'with a single class member' do
17
+ let(:sequence) { described_class[Hexp::CssSelector::Class.new('mega')] }
18
+
19
+ it 'should match when the element has a class by that name' do
20
+ expect(sequence.matches?(H[:div, class: 'mega'])).to be_true
21
+ end
22
+
23
+ it 'should not match when the element has no classes' do
24
+ expect(sequence.matches?(H[:span])).to be_false
25
+ end
26
+
27
+ it 'should not match when the element has no classes by that name' do
28
+ expect(sequence.matches?(H[:span, class: 'megalopolis'])).to be_false
29
+ end
30
+ end
31
+
32
+ context 'with an element and class' do
33
+ let(:sequence) do described_class[
34
+ Hexp::CssSelector::Element.new('div'),
35
+ Hexp::CssSelector::Class.new('mega')
36
+ ]
37
+ end
38
+
39
+ it 'should match if all parts are satisfied' do
40
+ expect(sequence.matches?(H[:div, class: 'mega'])).to be_true
41
+ end
42
+
43
+ it 'should not match if one parts is not satisfied' do
44
+ expect(sequence.matches?(H[:div, class: 'foo'])).to be_false
45
+ expect(sequence.matches?(H[:span, class: 'mega'])).to be_false
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hexp::DSL do
4
+ let(:jotie) { 'Liefste, Hart en woorden houden voor jou stil' }
5
+ let(:hexpable) do
6
+ Class.new do
7
+ include Hexp::DSL
8
+
9
+ def initialize(klz, words)
10
+ @class, @words = klz, words
11
+ end
12
+
13
+ def to_hexp
14
+ H[:div, {class: @class}, [@words]]
15
+ end
16
+ end.new('prinses', jotie)
17
+ end
18
+
19
+ {
20
+ tag: :div,
21
+ attributes: {'class' => 'prinses'},
22
+ children: ['Liefste, Hart en woorden houden voor jou stil'],
23
+ }.each do |method, result|
24
+ it "should delegate `#{method}' to to_hexp" do
25
+ expect(hexpable.public_send(method)).to eq(result)
26
+ end
27
+ end
28
+
29
+ it "should delegate `to_html' to to_hexp" do
30
+ expect(hexpable.to_html).to match \
31
+ %r{<div class="prinses">Liefste, Hart en woorden houden voor jou stil</div>}
32
+ end
33
+
34
+ it "should delegate `attr' to to_hexp" do
35
+ expect(hexpable.attr('class')).to eq('prinses')
36
+ expect(hexpable.attr('class', 'scharminkel')).to eq(
37
+ H[:div, {class: 'scharminkel'}, [jotie]]
38
+ )
39
+ end
40
+
41
+ it "should delegate `select' to to_hexp" do
42
+ expect(hexpable.select{|el| el.text?}.to_a).to eq([jotie])
43
+ end
44
+
45
+ it "should delegate `class?' to to_hexp" do
46
+ expect(hexpable.class?(:prinses)).to be_true
47
+ expect(hexpable.class?(:prins)).to be_false
48
+ end
49
+
50
+ it "should delegate `rewrite' to to_hexp" do
51
+ expect(hexpable.rewrite {|el| 'De liefde speelt me parten' if el.text?}.to_hexp)
52
+ .to eq H[:div, {class: 'prinses'}, ['De liefde speelt me parten']]
53
+ end
54
+
55
+ end