saxxy 0.1.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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +13 -0
  5. data/LICENSE +22 -0
  6. data/README.md +117 -0
  7. data/Rakefile +12 -0
  8. data/lib/saxxy.rb +2 -0
  9. data/lib/saxxy/activatable.rb +160 -0
  10. data/lib/saxxy/callbacks/libxml.rb +26 -0
  11. data/lib/saxxy/callbacks/nokogiri.rb +30 -0
  12. data/lib/saxxy/callbacks/ox.rb +66 -0
  13. data/lib/saxxy/callbacks/sax.rb +86 -0
  14. data/lib/saxxy/context.rb +88 -0
  15. data/lib/saxxy/context_tree.rb +85 -0
  16. data/lib/saxxy/event.rb +83 -0
  17. data/lib/saxxy/event_registry.rb +122 -0
  18. data/lib/saxxy/node_action.rb +59 -0
  19. data/lib/saxxy/node_rule.rb +90 -0
  20. data/lib/saxxy/parsers/base.rb +28 -0
  21. data/lib/saxxy/parsers/libxml.rb +52 -0
  22. data/lib/saxxy/parsers/nokogiri.rb +28 -0
  23. data/lib/saxxy/parsers/ox.rb +30 -0
  24. data/lib/saxxy/service.rb +47 -0
  25. data/lib/saxxy/utils/agent.rb +66 -0
  26. data/lib/saxxy/utils/callback_array.rb +27 -0
  27. data/lib/saxxy/utils/helpers.rb +13 -0
  28. data/lib/saxxy/version.rb +3 -0
  29. data/saxxy.gemspec +21 -0
  30. data/spec/saxxy/activatable_spec.rb +344 -0
  31. data/spec/saxxy/callbacks/sax_spec.rb +456 -0
  32. data/spec/saxxy/context_spec.rb +51 -0
  33. data/spec/saxxy/context_tree_spec.rb +68 -0
  34. data/spec/saxxy/event_registry_spec.rb +137 -0
  35. data/spec/saxxy/event_spec.rb +49 -0
  36. data/spec/saxxy/node_action_spec.rb +46 -0
  37. data/spec/saxxy/node_rule_spec.rb +99 -0
  38. data/spec/saxxy/parsers/libxml_spec.rb +104 -0
  39. data/spec/saxxy/parsers/nokogiri_spec.rb +200 -0
  40. data/spec/saxxy/parsers/ox_spec.rb +175 -0
  41. data/spec/saxxy/utils/agent_spec.rb +63 -0
  42. data/spec/spec_helper.rb +28 -0
  43. data/spec/support/agent_macros.rb +24 -0
  44. metadata +155 -0
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+ require "saxxy/node_rule"
3
+ require "saxxy/node_action"
4
+ require "saxxy/context"
5
+
6
+
7
+ describe Saxxy::Context do
8
+ let(:rule) { Saxxy::NodeRule.new("div") }
9
+
10
+ it "should include activatable" do
11
+ Saxxy::Context.ancestors.should include(Saxxy::Activatable)
12
+ end
13
+
14
+ describe "#initialize" do
15
+ it "should start with empty child_contexts and actions" do
16
+ subject.actions.should be_empty
17
+ subject.child_contexts.should be_empty
18
+ end
19
+
20
+ it "should start with an activation rule if one is given" do
21
+ subject.activation_rule.should be_nil
22
+ Saxxy::Context.new(rule).activation_rule.should == rule
23
+ end
24
+ end
25
+
26
+ # #register action is used to register either an action or a child context
27
+ describe "#register" do
28
+ it "should add an action in the actions array if argument is a NodeAction" do
29
+ action = Saxxy::NodeAction.new(rule)
30
+ expect { subject.register("foo") }.to_not change { subject.actions }
31
+ expect { subject.register(action) }.to change { subject.actions }.by([action])
32
+ end
33
+
34
+ it "should add a child context and change its parent to self if argument is a Context" do
35
+ ctx = Saxxy::Context.new(rule)
36
+ ctx.parent_context.should be_nil
37
+ subject.register(ctx).child_contexts.should == [ctx]
38
+ ctx.parent_context.should == subject
39
+ end
40
+ end
41
+
42
+ describe "#has_parent?" do
43
+ it "returns false if the context has no parent" do
44
+ ctx = Saxxy::Context.new(rule)
45
+ ctx.has_parent?.should be_false
46
+ subject.register(ctx)
47
+ ctx.has_parent?.should be_true
48
+ end
49
+ end
50
+
51
+ end
@@ -0,0 +1,68 @@
1
+ require "spec_helper"
2
+ require "saxxy/node_rule"
3
+ require "saxxy/node_action"
4
+ require "saxxy/context_tree"
5
+
6
+
7
+ describe Saxxy::ContextTree do
8
+ let(:subject) { Saxxy::ContextTree.new {} }
9
+
10
+ describe "#initialize" do
11
+ it "sets root to a new context without a rule" do
12
+ subject.root.should be_a(Saxxy::Context)
13
+ subject.root.activation_rule.should be_nil
14
+ end
15
+
16
+ it "should call the eval_subtree! with the provided block" do
17
+ block = ->(){}
18
+ Saxxy::ContextTree.any_instance.should_receive(:eval_subtree!).with(&block)
19
+ Saxxy::ContextTree.new(&block)
20
+ end
21
+ end
22
+
23
+ # Maybe we should make the #on action idempotent on the sense that it will merge
24
+ # the actions (the blocks) if it is called with the same arguments more than once.
25
+ describe "#on" do
26
+ it "should not change the root context" do
27
+ expect { subject.on("div", { class: /foo/ }) }.to_not change { subject.root }
28
+ end
29
+
30
+ it "creates an action and registers it on the root context" do
31
+ expect { subject.on("div", { class: /foo/ }) }.to change { subject.root.actions.size }.by(1)
32
+ end
33
+
34
+ it "creates a rule for the action with the specified arguments" do
35
+ subject.on("div", { class: /foo/ })
36
+ subject.root.actions[0].activation_rule.element.should == "div"
37
+ subject.root.actions[0].activation_rule.attributes.should == { "class" => /foo/ }
38
+ end
39
+ end
40
+
41
+ describe "#under" do
42
+ it "should add a child context to the root" do
43
+ expect { subject.under("div", { class: /foo/ }) }.to change { subject.root.child_contexts.length }.by(1)
44
+ end
45
+
46
+ it "should create a tree of contexts if it has nested #under calls" do
47
+ subject.under("div") do
48
+ under("span") { under("ul") }
49
+ under("div", class: "bar") { under("ul") { under("li", class: /foo/); under("span") } }
50
+ end
51
+ subject.root.should have(1).child_contexts
52
+ subject.root.child_contexts[0].should have(2).child_contexts
53
+ subject.root.child_contexts[0].child_contexts[0].should have(1).child_contexts
54
+ subject.root.child_contexts[0].child_contexts[0].child_contexts[0].should have(0).child_contexts
55
+ subject.root.child_contexts[0].child_contexts[1].should have(1).child_contexts
56
+ subject.root.child_contexts[0].child_contexts[1].child_contexts[0].should have(2).child_contexts
57
+ end
58
+
59
+ it "should build an activation rule for the child context from arguments" do
60
+ args = ["div", { class: /foo/ }]
61
+ subject.under(*args)
62
+ ctx = subject.root.child_contexts[0]
63
+ ctx.activation_rule.element.should == "div"
64
+ ctx.activation_rule.attributes.should == { "class" => /foo/ }
65
+ end
66
+ end
67
+
68
+ end
@@ -0,0 +1,137 @@
1
+ require "spec_helper"
2
+ require "saxxy/node_rule"
3
+ require "saxxy/node_action"
4
+ require "saxxy/event_registry"
5
+
6
+
7
+ describe Saxxy::EventRegistry do
8
+
9
+ def actions(reg = subject)
10
+ reg.instance_variable_get("@actions")
11
+ end
12
+
13
+
14
+ describe "#initialize" do
15
+ it "sets the actions to an empty hash" do
16
+ actions(Saxxy::EventRegistry.new).should == {}
17
+ end
18
+ end
19
+
20
+
21
+ describe "#register_event_from_action" do
22
+ let(:rule) { Saxxy::NodeRule.new("div", { title: "foo" }) }
23
+ let(:action) { Saxxy::NodeAction.new(rule) }
24
+
25
+ it "should create the action entry with events as the value if it does not exist yet" do
26
+ actions().should == {}
27
+ event = subject.register_event_from_action(action)
28
+ actions().should == { action => [event] }
29
+ end
30
+
31
+ it "should append the event to the action entry" do
32
+ ev1 = subject.register_event_from_action(action)
33
+ ev2 = subject.register_event_from_action(action)
34
+ actions()[action].should == [ev1, ev2]
35
+ end
36
+ end
37
+
38
+
39
+ describe "#events" do
40
+ let(:rule1) { Saxxy::NodeRule.new("div", { class: /foo/ }) }
41
+ let(:rule2) { Saxxy::NodeRule.new("div", { title: "foo" }) }
42
+ let(:action1) { Saxxy::NodeAction.new(rule1) }
43
+ let(:action2) { Saxxy::NodeAction.new(rule2) }
44
+
45
+ it "should return an array consisting of the last event of each action" do
46
+ 5.times { subject.register_event_from_action(action1) }
47
+ last_ev1 = subject.register_event_from_action(action1)
48
+ 5.times { subject.register_event_from_action(action2) }
49
+ last_ev2 = subject.register_event_from_action(action2)
50
+ subject.events.should == [last_ev1, last_ev2]
51
+ end
52
+ end
53
+
54
+
55
+ describe "#push_text" do
56
+ let(:rule1) { Saxxy::NodeRule.new("div", { class: /foo/ }) }
57
+ let(:rule2) { Saxxy::NodeRule.new("div", { title: "foo" }) }
58
+ let(:action1) { Saxxy::NodeAction.new(rule1) }
59
+ let(:action2) { Saxxy::NodeAction.new(rule2) }
60
+
61
+ it "should add the text to the events (last events)" do
62
+ 5.times { subject.register_event_from_action(action1) }
63
+ last_ev1 = subject.register_event_from_action(action1)
64
+ 5.times { subject.register_event_from_action(action2) }
65
+ last_ev2 = subject.register_event_from_action(action2)
66
+ expect { subject.push_text("foo") }.to change {
67
+ [last_ev1, last_ev2].map(&:text)
68
+ }.from(["", ""]).to(["foo", "foo"])
69
+ end
70
+ end
71
+
72
+
73
+ describe "#deactivate_events_on" do
74
+ let(:rule1) { Saxxy::NodeRule.new("div", { class: /foo/ }) }
75
+ let(:rule2) { Saxxy::NodeRule.new("span", { title: "foo" }) }
76
+
77
+ it "should call deactivate_events_on on every event with the element name as argument" do
78
+ last_ev1 = subject.register_event_from_action(a1 = Saxxy::NodeAction.new(rule1))
79
+ last_ev2 = subject.register_event_from_action(a2 = Saxxy::NodeAction.new(rule2))
80
+ last_ev1.should_receive(:deactivate_on).with("div")
81
+ last_ev2.should_receive(:deactivate_on).with("div")
82
+ subject.deactivate_events_on("div")
83
+ end
84
+
85
+ it "should not remove inactive events" do
86
+ last_ev1 = subject.register_event_from_action(a1 = Saxxy::NodeAction.new(rule1))
87
+ last_ev2 = subject.register_event_from_action(a2 = Saxxy::NodeAction.new(rule2))
88
+ expect { subject.deactivate_events_on("div") }.to_not change { actions()[a1] }
89
+ expect { subject.deactivate_events_on("div") }.to_not change { actions()[a2] }
90
+ end
91
+
92
+ it "should remove active events that match the element name" do
93
+ last_ev11 = subject.register_event_from_action(a1 = Saxxy::NodeAction.new(rule1)).tap do |e|
94
+ e.activate_on("div", "class" => "foo")
95
+ end
96
+ last_ev12 = subject.register_event_from_action(a1).tap { |e| e.activate_on("div", "class" => "foo") }
97
+ last_ev21 = subject.register_event_from_action(a2 = Saxxy::NodeAction.new(rule2))
98
+ expect {
99
+ subject.deactivate_events_on("div")
100
+ }.to change { actions()[a1] }.from([last_ev11, last_ev12]).to([last_ev11])
101
+ end
102
+
103
+ it "should delete the action if the last event gets removed" do
104
+ last_ev1 = subject.register_event_from_action(a1 = Saxxy::NodeAction.new(rule1)).tap do |e|
105
+ e.activate_on("div", "class" => "foo")
106
+ end
107
+ last_ev2 = subject.register_event_from_action(a2 = Saxxy::NodeAction.new(rule2))
108
+ expect {
109
+ subject.deactivate_events_on("div")
110
+ }.to change { actions()[a1] }.from([last_ev1]).to(nil)
111
+ end
112
+
113
+ it "should not remove already activated events that don't match the element name" do
114
+ last_ev1 = subject.register_event_from_action(a1 = Saxxy::NodeAction.new(rule1)).tap do |e|
115
+ e.activate_on("div", "class" => "foo")
116
+ end
117
+ last_ev2 = subject.register_event_from_action(a2 = Saxxy::NodeAction.new(rule2))
118
+ expect { subject.deactivate_events_on("span") }.to_not change { actions()[a1] }
119
+ expect { subject.deactivate_events_on("span") }.to_not change { actions()[a2] }
120
+ end
121
+
122
+ it "should append the text of the removed event to the next one in the queue" do
123
+ last_ev11 = subject.register_event_from_action(a1 = Saxxy::NodeAction.new(rule1)).tap do |e|
124
+ e.append_text("foo")
125
+ e.activate_on("div", "class" => "foo")
126
+ end
127
+ last_ev12 = subject.register_event_from_action(a1).tap do |e|
128
+ e.append_text(" bar")
129
+ e.activate_on("div", "class" => "foo")
130
+ end
131
+ last_ev21 = subject.register_event_from_action(a2 = Saxxy::NodeAction.new(rule2))
132
+ expect {
133
+ subject.deactivate_events_on("div")
134
+ }.to change { last_ev11.text }.from("foo").to("foo bar")
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+ require "saxxy/node_action"
3
+ require "saxxy/node_rule"
4
+ require "saxxy/event"
5
+
6
+
7
+ describe Saxxy::Event do
8
+
9
+ def event(action = Saxxy::NodeAction.new(Saxxy::NodeRule.new("div")))
10
+ Saxxy::Event.new(action)
11
+ end
12
+
13
+ it "should include activatable" do
14
+ Saxxy::Event.ancestors.should include(Saxxy::Activatable)
15
+ end
16
+
17
+ describe "#initialize" do
18
+ it "should set the action to the argument" do
19
+ action = Saxxy::NodeAction.new(Saxxy::NodeRule.new("div"))
20
+ event(action).action.should == action
21
+ end
22
+
23
+ it "should call initialize_activatable with the activation rule that action has" do
24
+ rule = Saxxy::NodeRule.new("div")
25
+ Saxxy::Event.any_instance.should_receive(:initialize_activatable).with(rule)
26
+ event(Saxxy::NodeAction.new(rule))
27
+ end
28
+
29
+ it "should set the text instance variable to an empty string" do
30
+ event().text.should == ""
31
+ end
32
+ end
33
+
34
+ describe "#append_text" do
35
+ it "appends the text provided as an argument to the @text instance variable" do
36
+ e = event()
37
+ expect { e.append_text("foo") }.to change { e.text }.from("").to("foo")
38
+ end
39
+ end
40
+
41
+ describe "#fire" do
42
+ it "should delegate the call to the action with the text as the argument" do
43
+ e = event()
44
+ e.action.should_receive(:call).with(e.text, nil, {})
45
+ e.fire
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,46 @@
1
+ require "spec_helper"
2
+ require "saxxy/node_rule"
3
+ require "saxxy/node_action"
4
+
5
+
6
+ describe Saxxy::NodeAction do
7
+ let(:rule) { Saxxy::NodeRule.new("div") }
8
+
9
+ describe "#initialize" do
10
+ it "initializes with an activation rule and with a block" do
11
+ block = ->() { "this is a test block" }
12
+ action = Saxxy::NodeAction.new(rule, &block)
13
+ action.action.should == block
14
+ action.activation_rule.should == rule
15
+ end
16
+
17
+ it "initializes with the id block if none is given" do
18
+ Saxxy::NodeAction.new(rule).action.call("foo").should == "foo"
19
+ end
20
+ end
21
+
22
+ describe "#matches" do
23
+ it "should delegate the call to activation_rule" do
24
+ action = Saxxy::NodeAction.new(rule)
25
+ args = ["div", { class: /foo/ }]
26
+ action.activation_rule.should_receive(:matches).with(*args)
27
+ action.matches(*args)
28
+ end
29
+ end
30
+
31
+ describe "#call" do
32
+ it "should delegate the call to an instance exec on the binding i.e. context if a context is given" do
33
+ a_context = Object.new
34
+ action = Saxxy::NodeAction.new(rule, a_context)
35
+ a_context.should_receive(:instance_exec).with(["foo"], &action.action)
36
+ action.call("foo")
37
+ end
38
+
39
+ it "should delegate the call to an instance exec on itself if no context is given" do
40
+ action = Saxxy::NodeAction.new(rule)
41
+ action.should_receive(:instance_exec).with(["foo"], &action.action)
42
+ action.call("foo")
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,99 @@
1
+ require "spec_helper"
2
+ require "saxxy/node_rule"
3
+
4
+
5
+ describe Saxxy::NodeRule do
6
+
7
+ describe "#matches" do
8
+ it "should return false if @element does not match first argument as string" do
9
+ nr = Saxxy::NodeRule.new("div")
10
+ nr.element.should eql("div")
11
+ nr.matches("span").should be_false
12
+ end
13
+
14
+ it "should return false if @element does not match first argument as regexp" do
15
+ nr = Saxxy::NodeRule.new(/div/)
16
+ nr.element.should be_a(Regexp)
17
+ nr.matches("span").should be_false
18
+ end
19
+
20
+ it "should return true if @element matches first argument as string and empty @attributes" do
21
+ nr = Saxxy::NodeRule.new("div")
22
+ nr.attributes.should be_empty
23
+ nr.matches("div").should be_true
24
+ end
25
+
26
+ it "should return true if @element matches first argument as regexp and empty @attributes" do
27
+ nr = Saxxy::NodeRule.new(/^d[iv]+?/)
28
+ nr.attributes.should be_empty
29
+ nr.matches("div").should be_true
30
+ end
31
+
32
+ it "should return false if @element matches first argument as string
33
+ and non-empty @attributes with keys not in second argument's keys" do
34
+ nr = Saxxy::NodeRule.new("div", { class: /fooo?/ })
35
+ nr.attributes.should_not be_empty
36
+ nr.matches("div", { foo: "bar" }).should be_false
37
+ end
38
+
39
+ it "should return false if @element matches first argument as regexp
40
+ and non-empty @attributes with keys not in second argument's keys" do
41
+ nr = Saxxy::NodeRule.new(/div/, { class: /fooo?/ })
42
+ nr.attributes.should_not be_empty
43
+ nr.matches("div", { foo: "bar" }).should be_false
44
+ end
45
+
46
+
47
+ it "should return true if @element matches first argument as string
48
+ and non-empty @attributes with keys in second argument's keys and
49
+ at the same time same values should match" do
50
+ nr = Saxxy::NodeRule.new("div", { class: /fooo?/ })
51
+ nr.attributes.should_not be_empty
52
+ nr.matches("div", { foo: "bar", class: "foo" }).should be_true
53
+ end
54
+
55
+ it "should return true if @element matches first argument as regexp
56
+ and non-empty @attributes with keys in second argument's keys and
57
+ at the same time same values should match" do
58
+ nr = Saxxy::NodeRule.new(/div/, { class: "foo" })
59
+ nr.attributes.should_not be_empty
60
+ nr.matches("div", { foo: "bar", class: "foo" }).should be_true
61
+ end
62
+ end
63
+
64
+
65
+ describe "#match_attributes" do
66
+ it "should return true on any attrs hash if the attributes are empty" do
67
+ nr = Saxxy::NodeRule.new(/div/)
68
+ nr.match_attributes({class: "some class", title: "bar"}).should be_true
69
+ end
70
+
71
+ it "should return true if an attribute exists and is nil
72
+ and the matching does not contain this attribute" do
73
+ nr = Saxxy::NodeRule.new(/div/, class: nil)
74
+ nr.match_attributes({}).should be_true
75
+ end
76
+
77
+ it "should return true if an attribute exists and is nil
78
+ and the matching does contain this attribute and is nil" do
79
+ nr = Saxxy::NodeRule.new(/div/, class: nil)
80
+ nr.match_attributes({ class: nil }).should be_true
81
+ end
82
+
83
+ it "should return false if an attribute exists and is nil
84
+ and the matching does contain this attribute and is not nil" do
85
+ nr = Saxxy::NodeRule.new(/div/, class: nil)
86
+ nr.match_attributes({ class: "foo" }).should be_false
87
+ end
88
+
89
+ it "should return true if it has a subset of the matching attributes" do
90
+ nr = Saxxy::NodeRule.new(/div/, title: "foo", class: "bar")
91
+ nr.match_attributes({ title: "foo", class: "bar", rel: "baz" }).should be_true
92
+ end
93
+
94
+ it "should return false if it has a superset of the matching attributes" do
95
+ nr = Saxxy::NodeRule.new(/div/, title: "foo", class: "bar", rel: "baz")
96
+ nr.match_attributes({ title: "foo", class: "bar" }).should be_false
97
+ end
98
+ end
99
+ end