saxxy 0.1.0

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