andyjeffries-journey 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/.autotest +8 -0
  2. data/.gemtest +0 -0
  3. data/CHANGELOG.rdoc +6 -0
  4. data/Gemfile +11 -0
  5. data/Manifest.txt +49 -0
  6. data/README.rdoc +48 -0
  7. data/Rakefile +31 -0
  8. data/journey.gemspec +42 -0
  9. data/lib/journey/backwards.rb +5 -0
  10. data/lib/journey/core-ext/hash.rb +11 -0
  11. data/lib/journey/formatter.rb +129 -0
  12. data/lib/journey/gtg/builder.rb +159 -0
  13. data/lib/journey/gtg/simulator.rb +44 -0
  14. data/lib/journey/gtg/transition_table.rb +152 -0
  15. data/lib/journey/nfa/builder.rb +74 -0
  16. data/lib/journey/nfa/dot.rb +34 -0
  17. data/lib/journey/nfa/simulator.rb +45 -0
  18. data/lib/journey/nfa/transition_table.rb +164 -0
  19. data/lib/journey/nodes/node.rb +104 -0
  20. data/lib/journey/parser.rb +204 -0
  21. data/lib/journey/parser.y +47 -0
  22. data/lib/journey/parser_extras.rb +21 -0
  23. data/lib/journey/path/pattern.rb +190 -0
  24. data/lib/journey/route.rb +92 -0
  25. data/lib/journey/router/strexp.rb +22 -0
  26. data/lib/journey/router/utils.rb +57 -0
  27. data/lib/journey/router.rb +138 -0
  28. data/lib/journey/routes.rb +74 -0
  29. data/lib/journey/scanner.rb +58 -0
  30. data/lib/journey/visitors.rb +186 -0
  31. data/lib/journey/visualizer/d3.min.js +2 -0
  32. data/lib/journey/visualizer/fsm.css +34 -0
  33. data/lib/journey/visualizer/fsm.js +134 -0
  34. data/lib/journey/visualizer/index.html.erb +50 -0
  35. data/lib/journey/visualizer/reset.css +48 -0
  36. data/lib/journey.rb +5 -0
  37. data/test/gtg/test_builder.rb +77 -0
  38. data/test/gtg/test_transition_table.rb +113 -0
  39. data/test/helper.rb +4 -0
  40. data/test/nfa/test_simulator.rb +96 -0
  41. data/test/nfa/test_transition_table.rb +70 -0
  42. data/test/nodes/test_symbol.rb +15 -0
  43. data/test/path/test_pattern.rb +260 -0
  44. data/test/route/definition/test_parser.rb +108 -0
  45. data/test/route/definition/test_scanner.rb +52 -0
  46. data/test/router/test_strexp.rb +30 -0
  47. data/test/router/test_utils.rb +19 -0
  48. data/test/test_route.rb +95 -0
  49. data/test/test_router.rb +464 -0
  50. data/test/test_routes.rb +38 -0
  51. metadata +171 -0
@@ -0,0 +1,260 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ module Path
5
+ class TestPattern < MiniTest::Unit::TestCase
6
+ x = /.+/
7
+ {
8
+ '/:controller(/:action)' => %r{\A/(#{x}?)(?:/([^/.?]+))?\Z},
9
+ '/:controller/foo' => %r{\A/(#{x}?)/foo\Z},
10
+ '/:controller/:action' => %r{\A/(#{x}?)/([^/.?]+)\Z},
11
+ '/:controller' => %r{\A/(#{x}?)\Z},
12
+ '/:controller(/:action(/:id))' => %r{\A/(#{x}?)(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z},
13
+ '/:controller/:action.xml' => %r{\A/(#{x}?)/([^/.?]+)\.xml\Z},
14
+ '/:controller.:format' => %r{\A/(#{x}?)\.([^/.?]+)\Z},
15
+ '/:controller(.:format)' => %r{\A/(#{x}?)(?:\.([^/.?]+))?\Z},
16
+ '/:controller/*foo' => %r{\A/(#{x}?)/(.+)\Z},
17
+ '/:controller/*foo/bar' => %r{\A/(#{x}?)/(.+)/bar\Z},
18
+ }.each do |path, expected|
19
+ define_method(:"test_to_regexp_#{path}") do
20
+ strexp = Router::Strexp.new(
21
+ path,
22
+ { :controller => /.+/ },
23
+ ["/", ".", "?"]
24
+ )
25
+ path = Pattern.new strexp
26
+ assert_equal(expected, path.send(:to_regexp))
27
+ end
28
+ end
29
+
30
+ {
31
+ '/:controller(/:action)' => %r{\A/(#{x}?)(?:/([^/.?]+))?},
32
+ '/:controller/foo' => %r{\A/(#{x}?)/foo},
33
+ '/:controller/:action' => %r{\A/(#{x}?)/([^/.?]+)},
34
+ '/:controller' => %r{\A/(#{x}?)},
35
+ '/:controller(/:action(/:id))' => %r{\A/(#{x}?)(?:/([^/.?]+)(?:/([^/.?]+))?)?},
36
+ '/:controller/:action.xml' => %r{\A/(#{x}?)/([^/.?]+)\.xml},
37
+ '/:controller.:format' => %r{\A/(#{x}?)\.([^/.?]+)},
38
+ '/:controller(.:format)' => %r{\A/(#{x}?)(?:\.([^/.?]+))?},
39
+ '/:controller/*foo' => %r{\A/(#{x}?)/(.+)},
40
+ '/:controller/*foo/bar' => %r{\A/(#{x}?)/(.+)/bar},
41
+ }.each do |path, expected|
42
+ define_method(:"test_to_non_anchored_regexp_#{path}") do
43
+ strexp = Router::Strexp.new(
44
+ path,
45
+ { :controller => /.+/ },
46
+ ["/", ".", "?"],
47
+ false
48
+ )
49
+ path = Pattern.new strexp
50
+ assert_equal(expected, path.send(:to_regexp))
51
+ end
52
+ end
53
+
54
+ {
55
+ '/:controller(/:action)' => %w{ controller action },
56
+ '/:controller/foo' => %w{ controller },
57
+ '/:controller/:action' => %w{ controller action },
58
+ '/:controller' => %w{ controller },
59
+ '/:controller(/:action(/:id))' => %w{ controller action id },
60
+ '/:controller/:action.xml' => %w{ controller action },
61
+ '/:controller.:format' => %w{ controller format },
62
+ '/:controller(.:format)' => %w{ controller format },
63
+ '/:controller/*foo' => %w{ controller foo },
64
+ '/:controller/*foo/bar' => %w{ controller foo },
65
+ }.each do |path, expected|
66
+ define_method(:"test_names_#{path}") do
67
+ strexp = Router::Strexp.new(
68
+ path,
69
+ { :controller => /.+/ },
70
+ ["/", ".", "?"]
71
+ )
72
+ path = Pattern.new strexp
73
+ assert_equal(expected, path.names)
74
+ end
75
+ end
76
+
77
+ def test_to_regexp_with_extended_group
78
+ strexp = Router::Strexp.new(
79
+ '/page/:name',
80
+ { :name => /
81
+ #ROFL
82
+ (tender|love
83
+ #MAO
84
+ )/x },
85
+ ["/", ".", "?"]
86
+ )
87
+ path = Pattern.new strexp
88
+ assert_match('/page/tender', path)
89
+ assert_match('/page/love', path)
90
+ refute_match('/page/loving', path)
91
+ end
92
+
93
+ def test_optional_names
94
+ [
95
+ ['/:foo(/:bar(/:baz))', %w{ bar baz }],
96
+ ['/:foo(/:bar)', %w{ bar }],
97
+ ['/:foo(/:bar)/:lol(/:baz)', %w{ bar baz }],
98
+ ].each do |pattern, list|
99
+ path = Pattern.new pattern
100
+ assert_equal list.sort, path.optional_names.sort
101
+ end
102
+ end
103
+
104
+ def test_to_regexp_with_group
105
+ strexp = Router::Strexp.new(
106
+ '/page/:name',
107
+ { :name => /(tender|love)/ },
108
+ ["/", ".", "?"]
109
+ )
110
+ path = Pattern.new strexp
111
+ assert_match('/page/tender', path)
112
+ assert_match('/page/love', path)
113
+ refute_match('/page/loving', path)
114
+ end
115
+
116
+ def test_ast_sets_regular_expressions
117
+ requirements = { :name => /(tender|love)/, :value => /./ }
118
+ strexp = Router::Strexp.new(
119
+ '/page/:name/:value',
120
+ requirements,
121
+ ["/", ".", "?"]
122
+ )
123
+
124
+ assert_equal requirements, strexp.requirements
125
+
126
+ path = Pattern.new strexp
127
+ nodes = path.ast.grep(Nodes::Symbol)
128
+ assert_equal 2, nodes.length
129
+ nodes.each do |node|
130
+ assert_equal requirements[node.to_sym], node.regexp
131
+ end
132
+ end
133
+
134
+ def test_match_data_with_group
135
+ strexp = Router::Strexp.new(
136
+ '/page/:name',
137
+ { :name => /(tender|love)/ },
138
+ ["/", ".", "?"]
139
+ )
140
+ path = Pattern.new strexp
141
+ match = path.match '/page/tender'
142
+ assert_equal 'tender', match[1]
143
+ assert_equal 2, match.length
144
+ end
145
+
146
+ def test_match_data_with_multi_group
147
+ strexp = Router::Strexp.new(
148
+ '/page/:name/:id',
149
+ { :name => /t(((ender|love)))()/ },
150
+ ["/", ".", "?"]
151
+ )
152
+ path = Pattern.new strexp
153
+ match = path.match '/page/tender/10'
154
+ assert_equal 'tender', match[1]
155
+ assert_equal '10', match[2]
156
+ assert_equal 3, match.length
157
+ assert_equal %w{ tender 10 }, match.captures
158
+ end
159
+
160
+ def test_insensitive_regexp_with_group
161
+ strexp = Router::Strexp.new(
162
+ '/page/:name/aaron',
163
+ { :name => /(tender|love)/i },
164
+ ["/", ".", "?"]
165
+ )
166
+ path = Pattern.new strexp
167
+ assert_match('/page/TENDER/aaron', path)
168
+ assert_match('/page/loVE/aaron', path)
169
+ refute_match('/page/loVE/AAron', path)
170
+ end
171
+
172
+ def test_to_regexp_with_strexp
173
+ strexp = Router::Strexp.new('/:controller', { }, ["/", ".", "?"])
174
+ path = Pattern.new strexp
175
+ x = %r{\A/([^/.?]+)\Z}
176
+
177
+ assert_equal(x.source, path.source)
178
+ end
179
+
180
+ def test_to_regexp_defaults
181
+ path = Pattern.new '/:controller(/:action(/:id))'
182
+ expected = %r{\A/([^/.?]+)(?:/([^/.?]+)(?:/([^/.?]+))?)?\Z}
183
+ assert_equal expected, path.send(:to_regexp)
184
+ end
185
+
186
+ def test_failed_match
187
+ path = Pattern.new '/:controller(/:action(/:id(.:format)))'
188
+ uri = 'content'
189
+
190
+ refute path =~ uri
191
+ end
192
+
193
+ def test_match_controller
194
+ path = Pattern.new '/:controller(/:action(/:id(.:format)))'
195
+ uri = '/content'
196
+
197
+ match = path =~ uri
198
+ assert_equal %w{ controller action id format }, match.names
199
+ assert_equal 'content', match[1]
200
+ assert_nil match[2]
201
+ assert_nil match[3]
202
+ assert_nil match[4]
203
+ end
204
+
205
+ def test_match_controller_action
206
+ path = Pattern.new '/:controller(/:action(/:id(.:format)))'
207
+ uri = '/content/list'
208
+
209
+ match = path =~ uri
210
+ assert_equal %w{ controller action id format }, match.names
211
+ assert_equal 'content', match[1]
212
+ assert_equal 'list', match[2]
213
+ assert_nil match[3]
214
+ assert_nil match[4]
215
+ end
216
+
217
+ def test_match_controller_action_id
218
+ path = Pattern.new '/:controller(/:action(/:id(.:format)))'
219
+ uri = '/content/list/10'
220
+
221
+ match = path =~ uri
222
+ assert_equal %w{ controller action id format }, match.names
223
+ assert_equal 'content', match[1]
224
+ assert_equal 'list', match[2]
225
+ assert_equal '10', match[3]
226
+ assert_nil match[4]
227
+ end
228
+
229
+ def test_match_literal
230
+ path = Path::Pattern.new "/books(/:action(.:format))"
231
+
232
+ uri = '/books'
233
+ match = path =~ uri
234
+ assert_equal %w{ action format }, match.names
235
+ assert_nil match[1]
236
+ assert_nil match[2]
237
+ end
238
+
239
+ def test_match_literal_with_action
240
+ path = Path::Pattern.new "/books(/:action(.:format))"
241
+
242
+ uri = '/books/list'
243
+ match = path =~ uri
244
+ assert_equal %w{ action format }, match.names
245
+ assert_equal 'list', match[1]
246
+ assert_nil match[2]
247
+ end
248
+
249
+ def test_match_literal_with_action_and_format
250
+ path = Path::Pattern.new "/books(/:action(.:format))"
251
+
252
+ uri = '/books/list.rss'
253
+ match = path =~ uri
254
+ assert_equal %w{ action format }, match.names
255
+ assert_equal 'list', match[1]
256
+ assert_equal 'rss', match[2]
257
+ end
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,108 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ module Definition
5
+ class TestParser < MiniTest::Unit::TestCase
6
+ def setup
7
+ @parser = Parser.new
8
+ end
9
+
10
+ def test_slash
11
+ assert_equal :SLASH, @parser.parse('/').type
12
+ assert_round_trip '/'
13
+ end
14
+
15
+ def test_segment
16
+ assert_round_trip '/foo'
17
+ end
18
+
19
+ def test_segments
20
+ assert_round_trip '/foo/bar'
21
+ end
22
+
23
+ def test_segment_symbol
24
+ assert_round_trip '/foo/:id'
25
+ end
26
+
27
+ def test_symbol
28
+ assert_round_trip '/:foo'
29
+ end
30
+
31
+ def test_group
32
+ assert_round_trip '(/:foo)'
33
+ end
34
+
35
+ def test_groups
36
+ assert_round_trip '(/:foo)(/:bar)'
37
+ end
38
+
39
+ def test_nested_groups
40
+ assert_round_trip '(/:foo(/:bar))'
41
+ end
42
+
43
+ def test_dot_symbol
44
+ assert_round_trip('.:format')
45
+ end
46
+
47
+ def test_dot_literal
48
+ assert_round_trip('.xml')
49
+ end
50
+
51
+ def test_segment_dot
52
+ assert_round_trip('/foo.:bar')
53
+ end
54
+
55
+ def test_segment_group_dot
56
+ assert_round_trip('/foo(.:bar)')
57
+ end
58
+
59
+ def test_segment_group
60
+ assert_round_trip('/foo(/:action)')
61
+ end
62
+
63
+ def test_segment_groups
64
+ assert_round_trip('/foo(/:action)(/:bar)')
65
+ end
66
+
67
+ def test_segment_nested_groups
68
+ assert_round_trip('/foo(/:action(/:bar))')
69
+ end
70
+
71
+ def test_group_followed_by_path
72
+ assert_round_trip('/foo(/:action)/:bar')
73
+ end
74
+
75
+ def test_star
76
+ assert_round_trip('*foo')
77
+ assert_round_trip('/*foo')
78
+ assert_round_trip('/bar/*foo')
79
+ assert_round_trip('/bar/(*foo)')
80
+ end
81
+
82
+ def test_or
83
+ assert_round_trip('a|b')
84
+ assert_round_trip('a|b|c')
85
+ assert_round_trip('(a|b)|c')
86
+ assert_round_trip('a|(b|c)')
87
+ assert_round_trip('*a|(b|c)')
88
+ assert_round_trip('*a|:b|c')
89
+ end
90
+
91
+ def test_arbitrary
92
+ assert_round_trip('/bar/*foo#')
93
+ end
94
+
95
+ def test_literal_dot_paren
96
+ assert_round_trip "/sprockets.js(.:format)"
97
+ end
98
+
99
+ def test_groups_with_dot
100
+ assert_round_trip "/(:locale)(.:format)"
101
+ end
102
+
103
+ def assert_round_trip str
104
+ assert_equal str, @parser.parse(str).to_s
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,52 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ module Definition
5
+ class TestScanner < MiniTest::Unit::TestCase
6
+ def setup
7
+ @scanner = Scanner.new
8
+ end
9
+
10
+ # /page/:id(/:action)(.:format)
11
+ def test_tokens
12
+ [
13
+ ['/', [[:SLASH, '/']]],
14
+ ['*omg', [[:STAR, '*'], [:LITERAL, 'omg']]],
15
+ ['/page', [[:SLASH, '/'], [:LITERAL, 'page']]],
16
+ ['/:page', [[:SLASH, '/'], [:SYMBOL, ':page']]],
17
+ ['/(:page)', [
18
+ [:SLASH, '/'],
19
+ [:LPAREN, '('],
20
+ [:SYMBOL, ':page'],
21
+ [:RPAREN, ')'],
22
+ ]],
23
+ ['(/:action)', [
24
+ [:LPAREN, '('],
25
+ [:SLASH, '/'],
26
+ [:SYMBOL, ':action'],
27
+ [:RPAREN, ')'],
28
+ ]],
29
+ ['(())', [[:LPAREN, '('],
30
+ [:LPAREN, '('], [:RPAREN, ')'], [:RPAREN, ')']]],
31
+ ['(.:format)', [
32
+ [:LPAREN, '('],
33
+ [:DOT, '.'],
34
+ [:SYMBOL, ':format'],
35
+ [:RPAREN, ')'],
36
+ ]],
37
+ ].each do |str, expected|
38
+ @scanner.scan_setup str
39
+ assert_tokens expected, @scanner
40
+ end
41
+ end
42
+
43
+ def assert_tokens tokens, scanner
44
+ toks = []
45
+ while tok = scanner.next_token
46
+ toks << tok
47
+ end
48
+ assert_equal tokens, toks
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,30 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ class Router
5
+ class TestStrexp < MiniTest::Unit::TestCase
6
+ def test_many_names
7
+ exp = Strexp.new(
8
+ "/:controller(/:action(/:id(.:format)))",
9
+ {:controller=>/.+?/},
10
+ ["/", ".", "?"],
11
+ true)
12
+
13
+ assert_equal ["controller", "action", "id", "format"], exp.names
14
+ end
15
+
16
+ def test_names
17
+ {
18
+ "/bar(.:format)" => %w{ format },
19
+ ":format" => %w{ format },
20
+ ":format-" => %w{ format },
21
+ ":format0" => %w{ format0 },
22
+ ":format1,:format2" => %w{ format1 format2 },
23
+ }.each do |string, expected|
24
+ exp = Strexp.new(string, {}, ["/", ".", "?"])
25
+ assert_equal expected, exp.names
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ class Router
5
+ class TestUtils < MiniTest::Unit::TestCase
6
+ def test_path_escape
7
+ assert_equal "a/b%20c+d", Utils.escape_path("a/b c+d")
8
+ end
9
+
10
+ def test_fragment_escape
11
+ assert_equal "a/b%20c+d?e", Utils.escape_fragment("a/b c+d?e")
12
+ end
13
+
14
+ def test_uri_unescape
15
+ assert_equal "a/b c+d", Utils.unescape_uri("a%2Fb%20c+d")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,95 @@
1
+ require 'helper'
2
+
3
+ module Journey
4
+ class TestRoute < MiniTest::Unit::TestCase
5
+ def test_initialize
6
+ app = Object.new
7
+ path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
8
+ defaults = Object.new
9
+ route = Route.new("name", app, path, {}, defaults)
10
+
11
+ assert_equal app, route.app
12
+ assert_equal path, route.path
13
+ assert_equal defaults, route.defaults
14
+ end
15
+
16
+ def test_route_adds_itself_as_memo
17
+ app = Object.new
18
+ path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
19
+ defaults = Object.new
20
+ route = Route.new("name", app, path, {}, defaults)
21
+
22
+ route.ast.grep(Nodes::Terminal).each do |node|
23
+ assert_equal route, node.memo
24
+ end
25
+ end
26
+
27
+ def test_ip_address
28
+ path = Path::Pattern.new '/messages/:id(.:format)'
29
+ route = Route.new("name", nil, path, {:ip => '192.168.1.1'},
30
+ { :controller => 'foo', :action => 'bar' })
31
+ assert_equal '192.168.1.1', route.ip
32
+ end
33
+
34
+ def test_default_ip
35
+ path = Path::Pattern.new '/messages/:id(.:format)'
36
+ route = Route.new("name", nil, path, {},
37
+ { :controller => 'foo', :action => 'bar' })
38
+ assert_equal(//, route.ip)
39
+ end
40
+
41
+ def test_format_empty
42
+ path = Path::Pattern.new '/messages/:id(.:format)'
43
+ route = Route.new("name", nil, path, {},
44
+ { :controller => 'foo', :action => 'bar' })
45
+
46
+ assert_equal '/messages', route.format({})
47
+ end
48
+
49
+ def test_format_with_star
50
+ path = Path::Pattern.new '/:controller/*extra'
51
+ route = Route.new("name", nil, path, {},
52
+ { :controller => 'foo', :action => 'bar' })
53
+ assert_equal '/foo/himom', route.format({
54
+ :controller => 'foo',
55
+ :extra => 'himom',
56
+ })
57
+ end
58
+
59
+ def test_connects_all_match
60
+ path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))'
61
+ route = Route.new("name", nil, path, {:action => 'bar'}, { :controller => 'foo' })
62
+
63
+ assert_equal '/foo/bar/10', route.format({
64
+ :controller => 'foo',
65
+ :action => 'bar',
66
+ :id => 10
67
+ })
68
+ end
69
+
70
+ def test_extras_are_not_included_if_optional
71
+ path = Path::Pattern.new '/page/:id(/:action)'
72
+ route = Route.new("name", nil, path, { }, { :action => 'show' })
73
+
74
+ assert_equal '/page/10', route.format({ :id => 10 })
75
+ end
76
+
77
+ def test_score
78
+ path = Path::Pattern.new "/page/:id(/:action)(.:format)"
79
+ specific = Route.new "name", nil, path, {}, {:controller=>"pages", :action=>"show"}
80
+
81
+ path = Path::Pattern.new "/:controller(/:action(/:id))(.:format)"
82
+ generic = Route.new "name", nil, path, {}
83
+
84
+ knowledge = {:id=>20, :controller=>"pages", :action=>"show"}
85
+
86
+ routes = [specific, generic]
87
+
88
+ refute_equal specific.score(knowledge), generic.score(knowledge)
89
+
90
+ found = routes.sort_by { |r| r.score(knowledge) }.last
91
+
92
+ assert_equal specific, found
93
+ end
94
+ end
95
+ end