hexp 0.0.1 → 0.2.0

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