action_tree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,47 @@
1
+
2
+ require 'set'
3
+ require 'backports'
4
+
5
+ module ActionTree
6
+
7
+ require 'action_tree/eval_scope'
8
+ require 'action_tree/capture_hash'
9
+ require 'action_tree/dialect_helper'
10
+
11
+ module Plugins
12
+ require 'action_tree/plugins/tilt'
13
+ end
14
+
15
+
16
+ module Dialect
17
+ def new(*prms, &blk)
18
+ self::Node.new(*prms, &blk)
19
+ end
20
+ def apply(mod)
21
+ self::Node.send(:include, mod::NodeMixin)
22
+ self::Match.send(:include, mod::MatchMixin)
23
+ self::Match::DEFAULT_HELPERS << mod::Helpers
24
+ end
25
+ end
26
+
27
+ # load basic dialect
28
+ module Basic
29
+ extend Dialect
30
+ require "action_tree/basic/node"
31
+ require "action_tree/basic/match"
32
+ end
33
+
34
+ # shorthand
35
+ def self.new(&blk)
36
+ Basic.new(&blk)
37
+ end
38
+
39
+ # layer proc wrapper
40
+ class Layer < Proc; end
41
+ def layer(*prms, &blk)
42
+ Layer.new(*prms, &blk)
43
+ end
44
+
45
+ end
46
+
47
+
@@ -0,0 +1,41 @@
1
+
2
+ require 'action_tree'
3
+
4
+
5
+
6
+ describe ActionTree::EvalScope do
7
+ it 'has access to local variables' do
8
+ @say = 'tra la la a ring a ding ding'
9
+ ActionTree::EvalScope.new(self).instance_eval do
10
+ @say
11
+ end.should == 'tra la la a ring a ding ding'
12
+ end
13
+
14
+ it 'extends modules' do
15
+ module Hooga
16
+ def words_of_wisdom
17
+ 'touch my tra la la my ding ding dong'
18
+ end
19
+ end
20
+ ActionTree::EvalScope.new(Hooga).words_of_wisdom.should ==
21
+ 'touch my tra la la my ding ding dong'
22
+ end
23
+
24
+ it 'overwrites sooner with later extensions' do
25
+ prepare = Proc.new { @greeting = 'Hello' }
26
+ greet = Proc.new { "#{@greeting} #{get_name}" }
27
+ module Booga
28
+ def get_name
29
+ 'Melchior'
30
+ end
31
+ end
32
+ module Shooga
33
+ def get_name
34
+ 'Baltazar'
35
+ end
36
+ end
37
+ scope = ActionTree::EvalScope.new(Booga, Shooga, self)
38
+ scope.instance_eval(&prepare)
39
+ scope.instance_eval(&greet).should == 'Hello Baltazar'
40
+ end
41
+ end
@@ -0,0 +1,149 @@
1
+
2
+ require 'action_tree'
3
+
4
+ describe ActionTree::Basic::Node do
5
+
6
+
7
+ # PATH PARSING AND DESCENDING
8
+
9
+ describe 'descend' do
10
+ it 'returns self when descending to nil, "", "/" or []' do
11
+ [nil, '', '/', []].each do |location|
12
+ subject.descend(location).should == subject
13
+ end
14
+ end
15
+
16
+ it 'returns a new node when descending one level' do
17
+ n = subject.descend('/the')
18
+ n.should be_a(ActionTree::Basic::Node)
19
+ n.should_not == subject
20
+ end
21
+
22
+ it 'returns a new node when descending two levels' do
23
+ n = subject.descend('/the/world')
24
+ n.should be_a(ActionTree::Basic::Node)
25
+ n.should_not == subject
26
+ n.should_not == subject.descend('/the')
27
+ end
28
+
29
+ it 'gives same node when descending two times, one level' do
30
+ subject.descend('/the').should == subject.descend('/the')
31
+ end
32
+
33
+ it 'gives same node when descending two times, three levels' do
34
+ subject.descend('/the/world/is').should == subject.descend('/the/world/is')
35
+ end
36
+
37
+ it 'gives birth recursively and selectively when descending' do
38
+ n = subject.descend('/the/world/is/good')
39
+ n.token.should == 'good'
40
+ n.should == subject.descend('the').descend('world/is').descend('good')
41
+ end
42
+ end
43
+
44
+
45
+
46
+ # TOKEN DIFFERENTIATION
47
+
48
+ describe 'with string token' do
49
+ subject { ActionTree::Basic::Node.new('noodles') }
50
+
51
+ it 'matches the same token' do
52
+ subject.match?('noodles').should be_true
53
+ end
54
+
55
+ it 'does not match another token' do
56
+ subject.match?('naddles').should be_false
57
+ end
58
+
59
+ it 'names no captures' do
60
+ subject.capture_names.should == []
61
+ end
62
+
63
+ end
64
+
65
+ describe 'with string token with four/five captures' do
66
+ subject do
67
+ ActionTree::Basic::Node.new(':year-:month-:day-:word-and-:word')
68
+ end
69
+
70
+ it 'matches a valid match' do
71
+ subject.match?('2007-08-20-widgets-and-gadgets').should be_true
72
+ end
73
+
74
+ it 'does not match invalid match' do
75
+ subject.match?('2007-08-20-widgets-gadgets').should be_false
76
+ end
77
+
78
+ it 'names the four captures' do
79
+ subject.capture_names.should == %w{year month day word word}
80
+ end
81
+
82
+ end
83
+
84
+ describe 'with regexp capture' do
85
+ subject { ActionTree::Basic::Node.new(/[0-9]+\.[0-9]+/) }
86
+
87
+ it 'matches the same token' do
88
+ subject.match?('65.2').should be_true
89
+ end
90
+
91
+ it 'does not match another token' do
92
+ subject.match?('beef').should be_false
93
+ subject.match?('abba.beatles').should be_false
94
+ end
95
+
96
+ it 'captures to "match"' do
97
+ subject.capture_names.should == ['match']
98
+ end
99
+
100
+ end
101
+
102
+ describe 'with grouped regexp capture' do
103
+ subject { ActionTree::Basic::Node.new(/([0-9]+)\.([0-9]+)/) }
104
+
105
+ it 'matches the same token' do
106
+ subject.match?('65.2').should be_true
107
+ end
108
+
109
+ it 'does not match another token' do
110
+ subject.match?('beef').should be_false
111
+ subject.match?('abba.beatles').should be_false
112
+ end
113
+
114
+ it 'captures to "match"' do
115
+ subject.capture_names.should == ['match']
116
+ end
117
+
118
+ end
119
+
120
+
121
+ describe 'with symbol capture' do
122
+ subject { ActionTree::Basic::Node.new(:hometown) }
123
+
124
+ it 'matches any word' do
125
+ %w{Osaka Oslo Baden-Baden}.each do |town|
126
+ subject.match?(town).should be_true
127
+ end
128
+ end
129
+
130
+ it 'names the capture' do
131
+ subject.capture_names.should == ['hometown']
132
+ end
133
+
134
+ end
135
+
136
+ describe 'dsl' do
137
+ it 'mounts' do
138
+ pending
139
+ end
140
+
141
+ it 'defines helpers' do
142
+ subject.helpers { def guru; 'Ramana'; end }
143
+ o = Object.new
144
+ o.extend(subject.helper_scope)
145
+ o.guru.should == 'Ramana'
146
+ end
147
+ end
148
+
149
+ end
@@ -0,0 +1,120 @@
1
+
2
+
3
+ require 'action_tree'
4
+
5
+
6
+
7
+
8
+ describe ActionTree::Basic::Match do
9
+
10
+ describe 'captures' do
11
+
12
+ subject do
13
+ ActionTree.new do
14
+ r :genus do
15
+ a { @genus }
16
+ r ':species-:subspecies' do
17
+ a { "#{@genus} #{@species} #{@subspecies}" }
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ it 'no values when no match' do
24
+ subject.match('/one/two/three/bobsleigh').run.should be_nil
25
+ end
26
+
27
+ it 'one value' do
28
+ subject.match('/Homo').run.should == 'Homo'
29
+ end
30
+
31
+ it 'values and GET variables' do
32
+ subject.match('/Homo/Sapiens-Sapiens').run.should ==
33
+ 'Homo Sapiens Sapiens'
34
+ end
35
+ end
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+ describe 'universe integration test' do
45
+
46
+ subject do
47
+ ActionTree::Basic::Node.new do
48
+ before { @altitude = :astronomical }
49
+ after { @boring = false }
50
+ action { "no sound at #{@altitude} altitudes" }
51
+ helpers { def infinite?; true; end }
52
+ not_found { 'nix nada zip' }
53
+ route :world do
54
+ before { @status = :threatened }
55
+ helpers { def infinite?; false; end }
56
+ route :continent do
57
+ before { @order = 'political' }
58
+ route :country do
59
+ before { @context = 'national' }
60
+ after { @context = 'life' }
61
+ helpers { def violent?; false; end }
62
+ action('show') { 'not much to see' }
63
+ route :municipality do
64
+ before { @boring = true }
65
+ helpers { def boring?; true; end }
66
+ action { [@world, @continent, @country, @municipality] if boring? }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ it 'runs root action witin same scope as root before filter' do
75
+ subject.match('/').run.should == 'no sound at astronomical altitudes'
76
+ end
77
+
78
+ it 'allows overwriting root actions' do
79
+ subject.action { 'Zarathustra' }
80
+ subject.match('').run.should == 'Zarathustra'
81
+ end
82
+
83
+ it 'root has access to root helper' do
84
+ subject.action { "Infinite!" if infinite? }
85
+ subject.match('/').run.should == 'Infinite!'
86
+ end
87
+
88
+ it 'root invokes root not found' do
89
+ subject.match('/earth/europe/norway/akershus/super-cheese').run.should == 'nix nada zip'
90
+ end
91
+
92
+ it 'sets instance variables from captures' do
93
+ subject.match('/earth/europe/norway/akershus').run.should ==
94
+ %w{earth europe norway akershus}
95
+ end
96
+
97
+ # self
98
+
99
+
100
+
101
+
102
+
103
+ # inherited
104
+
105
+ it 'runs ancestors before filters' do
106
+ end
107
+
108
+ it 'runs ancestors after filters' do
109
+ end
110
+
111
+ it 'has access to ancestors helpers' do
112
+ end
113
+
114
+ it 'invokes ancestors not_found handler' do
115
+ end
116
+ end
117
+
118
+ end
119
+
120
+
@@ -0,0 +1,140 @@
1
+
2
+ require 'action_tree'
3
+ require 'fileutils'
4
+
5
+
6
+
7
+ describe ActionTree do
8
+
9
+ describe 'integration' do
10
+
11
+ subject do
12
+ ActionTree.new do
13
+ helpers { def h1; 'bob'; end }
14
+ helpers { def h1; '1'; end }
15
+ helpers { def h2; h1; end }
16
+ helpers { def h4; '6'; end }
17
+ before { @v = h2 + '2' }
18
+ before { @v = @v + '3' }
19
+ before { @v2 = 'sleigh' }
20
+ a { @v }
21
+ helpers('/a') {def h3; @v2; end}
22
+ before('/a') { @v2 = '4' }
23
+ before('/a') { @v3 = @v + h3 }
24
+ a('/a') { @v3 }
25
+ before('/a/b') { @v4 = @v3 + '5' }
26
+ before('/a/b') { @v5 = @v4 }
27
+ a('/a/b') { @v5 }
28
+ not_found('a/b') { @v5 + h4 }
29
+ end
30
+ end
31
+
32
+ it 'works, if only just a bit' do
33
+ subject.match('/').run.should == '123'
34
+ end
35
+
36
+ it 'works' do
37
+ subject.match('a/b').run.should == '12345'
38
+ end
39
+
40
+ it 'works with not_found' do
41
+ subject.match('a/b/y').run.should ==
42
+ '123456'
43
+ end
44
+ end
45
+
46
+
47
+
48
+
49
+ describe 'integration scenario' do
50
+
51
+ subject do
52
+
53
+ ActionTree.new do
54
+ before { @msg = 'root hook ok' }
55
+ with('houses') do
56
+ action { 'house index' }
57
+ action('create') {'newly built house'}
58
+ with(:id) do
59
+ action { "showing house #{@id}" }
60
+ action('edit') { "editing house #{@id}" }
61
+ action('destroy') { "destroying house #{@id}" }
62
+ action(/[0-9]+/) {'number match'}
63
+ end
64
+ end
65
+ before('houses/:id') { @msg = 'house hook ok' }
66
+ action('long/action/weird/url') { 'long' }
67
+ action(/[0-9]+/) { 'root number match' }
68
+ end
69
+
70
+
71
+ end
72
+
73
+
74
+ it 'matches "" => self' do
75
+ subject.match('').node.should == subject
76
+ end
77
+
78
+ it 'matches "/" => self' do
79
+ subject.match('/').node.should == subject
80
+ end
81
+
82
+ it 'returns nil for nonexistant level one node' do
83
+ subject.match('/nothing').found?.should be_false
84
+ end
85
+
86
+ it 'returns nil for nonexistant level two node' do
87
+ subject.match('/long/nothing').found?.should be_false
88
+ end
89
+
90
+ it 'matches a first level request' do
91
+ subject.match('/houses').node.should == subject.descend('houses')
92
+ end
93
+
94
+ it 'matches a second level request' do
95
+ subject.match('/houses/create').node.should == subject.descend('houses/create')
96
+ end
97
+
98
+ it 'matches a second level capture' do
99
+ subject.match('/houses/4').node.should == subject.descend('houses/:id')
100
+ end
101
+
102
+ it 'matches beneath second level capture' do
103
+ subject.match('/houses/4/edit').node.should == subject.descend('houses/:id/edit')
104
+ end
105
+
106
+ it 'returns nil for nonexistant node beneath capture' do
107
+ subject.match('/houses/4/nothing_here').found?.should be_false
108
+ end
109
+
110
+ it 'gets the path right' do
111
+ subject.match('/long/action/weird/url').path.should == '/long/action/weird/url'
112
+ end
113
+
114
+ it 'gets the path right with captures' do
115
+ subject.match('/houses/53/edit').path.should == '/houses/53/edit'
116
+ end
117
+
118
+ it 'ignores trailing slashes' do
119
+ subject.match('/long/action').node.should ==
120
+ subject.match('long/action/').node
121
+ end
122
+
123
+ it 'matches regexp in root scope' do
124
+ subject.match('/346').node.should == subject.descend([/[0-9]+/])
125
+ end
126
+
127
+ it 'fills in path for regexp in root scope' do
128
+ subject.match('/346').path.should == '/346'
129
+ end
130
+
131
+ it 'matches regexp in capture scope' do
132
+ subject.match('/houses/5/4').node.should == subject.descend(['houses', :id, /[0-9]+/])
133
+ end
134
+
135
+ it 'fills in path for regexp in capture scope' do
136
+ subject.match('/houses/5/4').path.should == '/houses/5/4'
137
+ end
138
+ end
139
+
140
+ end