action_tree 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.
@@ -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