andyjeffries-journey 1.0.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 (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