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,204 @@
1
+ #
2
+ # DO NOT MODIFY!!!!
3
+ # This file is automatically generated by Racc 1.4.6
4
+ # from Racc grammer file "".
5
+ #
6
+
7
+ require 'racc/parser.rb'
8
+
9
+
10
+ require 'journey/parser_extras'
11
+ module Journey
12
+ class Parser < Racc::Parser
13
+ ##### State transition tables begin ###
14
+
15
+ racc_action_table = [
16
+ 17, 22, 13, 15, 14, 7, 15, 16, 8, 19,
17
+ 13, 15, 14, 7, 24, 16, 8, 19, 13, 15,
18
+ 14, 7, nil, 16, 8, 13, 15, 14, 7, nil,
19
+ 16, 8, 13, 15, 14, 7, nil, 16, 8 ]
20
+
21
+ racc_action_check = [
22
+ 1, 17, 1, 1, 1, 1, 8, 1, 1, 1,
23
+ 20, 20, 20, 20, 20, 20, 20, 20, 7, 7,
24
+ 7, 7, nil, 7, 7, 19, 19, 19, 19, nil,
25
+ 19, 19, 0, 0, 0, 0, nil, 0, 0 ]
26
+
27
+ racc_action_pointer = [
28
+ 30, 0, nil, nil, nil, nil, nil, 16, 3, nil,
29
+ nil, nil, nil, nil, nil, nil, nil, 1, nil, 23,
30
+ 8, nil, nil, nil, nil ]
31
+
32
+ racc_action_default = [
33
+ -18, -18, -2, -3, -4, -5, -6, -18, -18, -10,
34
+ -11, -12, -13, -14, -15, -16, -17, -18, -1, -18,
35
+ -18, -9, 25, -8, -7 ]
36
+
37
+ racc_goto_table = [
38
+ 18, 1, 21, nil, nil, nil, nil, nil, 20, nil,
39
+ nil, nil, nil, nil, nil, nil, nil, nil, 23, 18 ]
40
+
41
+ racc_goto_check = [
42
+ 2, 1, 7, nil, nil, nil, nil, nil, 1, nil,
43
+ nil, nil, nil, nil, nil, nil, nil, nil, 2, 2 ]
44
+
45
+ racc_goto_pointer = [
46
+ nil, 1, -1, nil, nil, nil, nil, -6, nil, nil,
47
+ nil ]
48
+
49
+ racc_goto_default = [
50
+ nil, nil, 2, 3, 4, 5, 6, 10, 9, 11,
51
+ 12 ]
52
+
53
+ racc_reduce_table = [
54
+ 0, 0, :racc_error,
55
+ 2, 11, :_reduce_1,
56
+ 1, 11, :_reduce_2,
57
+ 1, 11, :_reduce_none,
58
+ 1, 12, :_reduce_none,
59
+ 1, 12, :_reduce_none,
60
+ 1, 12, :_reduce_none,
61
+ 3, 15, :_reduce_7,
62
+ 3, 13, :_reduce_8,
63
+ 2, 16, :_reduce_9,
64
+ 1, 14, :_reduce_none,
65
+ 1, 14, :_reduce_none,
66
+ 1, 14, :_reduce_none,
67
+ 1, 14, :_reduce_none,
68
+ 1, 19, :_reduce_14,
69
+ 1, 18, :_reduce_15,
70
+ 1, 17, :_reduce_16,
71
+ 1, 20, :_reduce_17 ]
72
+
73
+ racc_reduce_n = 18
74
+
75
+ racc_shift_n = 25
76
+
77
+ racc_token_table = {
78
+ false => 0,
79
+ :error => 1,
80
+ :SLASH => 2,
81
+ :LITERAL => 3,
82
+ :SYMBOL => 4,
83
+ :LPAREN => 5,
84
+ :RPAREN => 6,
85
+ :DOT => 7,
86
+ :STAR => 8,
87
+ :OR => 9 }
88
+
89
+ racc_nt_base = 10
90
+
91
+ racc_use_result_var = true
92
+
93
+ Racc_arg = [
94
+ racc_action_table,
95
+ racc_action_check,
96
+ racc_action_default,
97
+ racc_action_pointer,
98
+ racc_goto_table,
99
+ racc_goto_check,
100
+ racc_goto_default,
101
+ racc_goto_pointer,
102
+ racc_nt_base,
103
+ racc_reduce_table,
104
+ racc_token_table,
105
+ racc_shift_n,
106
+ racc_reduce_n,
107
+ racc_use_result_var ]
108
+
109
+ Racc_token_to_s_table = [
110
+ "$end",
111
+ "error",
112
+ "SLASH",
113
+ "LITERAL",
114
+ "SYMBOL",
115
+ "LPAREN",
116
+ "RPAREN",
117
+ "DOT",
118
+ "STAR",
119
+ "OR",
120
+ "$start",
121
+ "expressions",
122
+ "expression",
123
+ "or",
124
+ "terminal",
125
+ "group",
126
+ "star",
127
+ "literal",
128
+ "symbol",
129
+ "slash",
130
+ "dot" ]
131
+
132
+ Racc_debug_parser = false
133
+
134
+ ##### State transition tables end #####
135
+
136
+ # reduce 0 omitted
137
+
138
+ def _reduce_1(val, _values, result)
139
+ result = Cat.new(val.first, val.last)
140
+ result
141
+ end
142
+
143
+ def _reduce_2(val, _values, result)
144
+ result = val.first
145
+ result
146
+ end
147
+
148
+ # reduce 3 omitted
149
+
150
+ # reduce 4 omitted
151
+
152
+ # reduce 5 omitted
153
+
154
+ # reduce 6 omitted
155
+
156
+ def _reduce_7(val, _values, result)
157
+ result = Group.new(val[1])
158
+ result
159
+ end
160
+
161
+ def _reduce_8(val, _values, result)
162
+ result = Or.new([val.first, val.last])
163
+ result
164
+ end
165
+
166
+ def _reduce_9(val, _values, result)
167
+ result = Star.new(Symbol.new(val.last.left))
168
+ result
169
+ end
170
+
171
+ # reduce 10 omitted
172
+
173
+ # reduce 11 omitted
174
+
175
+ # reduce 12 omitted
176
+
177
+ # reduce 13 omitted
178
+
179
+ def _reduce_14(val, _values, result)
180
+ result = Slash.new('/')
181
+ result
182
+ end
183
+
184
+ def _reduce_15(val, _values, result)
185
+ result = Symbol.new(val.first)
186
+ result
187
+ end
188
+
189
+ def _reduce_16(val, _values, result)
190
+ result = Literal.new(val.first)
191
+ result
192
+ end
193
+
194
+ def _reduce_17(val, _values, result)
195
+ result = Dot.new(val.first)
196
+ result
197
+ end
198
+
199
+ def _reduce_none(val, _values, result)
200
+ val[0]
201
+ end
202
+
203
+ end # class Parser
204
+ end # module Journey
@@ -0,0 +1,47 @@
1
+ class Journey::Parser
2
+
3
+ token SLASH LITERAL SYMBOL LPAREN RPAREN DOT STAR OR
4
+
5
+ rule
6
+ expressions
7
+ : expressions expression { result = Cat.new(val.first, val.last) }
8
+ | expression { result = val.first }
9
+ | or
10
+ ;
11
+ expression
12
+ : terminal
13
+ | group
14
+ | star
15
+ ;
16
+ group
17
+ : LPAREN expressions RPAREN { result = Group.new(val[1]) }
18
+ ;
19
+ or
20
+ : expressions OR expression { result = Or.new([val.first, val.last]) }
21
+ ;
22
+ star
23
+ : STAR literal { result = Star.new(Symbol.new(val.last.left)) }
24
+ ;
25
+ terminal
26
+ : symbol
27
+ | literal
28
+ | slash
29
+ | dot
30
+ ;
31
+ slash
32
+ : SLASH { result = Slash.new('/') }
33
+ ;
34
+ symbol
35
+ : SYMBOL { result = Symbol.new(val.first) }
36
+ ;
37
+ literal
38
+ : LITERAL { result = Literal.new(val.first) }
39
+ dot
40
+ : DOT { result = Dot.new(val.first) }
41
+ ;
42
+
43
+ end
44
+
45
+ ---- header
46
+
47
+ require 'journey/parser_extras'
@@ -0,0 +1,21 @@
1
+ require 'journey/scanner'
2
+ require 'journey/nodes/node'
3
+
4
+ module Journey
5
+ class Parser < Racc::Parser
6
+ include Journey::Nodes
7
+
8
+ def initialize
9
+ @scanner = Scanner.new
10
+ end
11
+
12
+ def parse string
13
+ @scanner.scan_setup string
14
+ do_parse
15
+ end
16
+
17
+ def next_token
18
+ @scanner.next_token
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,190 @@
1
+ module Journey
2
+ module Path
3
+ class Pattern
4
+ attr_reader :spec, :requirements, :anchored
5
+
6
+ def initialize strexp
7
+ parser = Journey::Parser.new
8
+
9
+ @anchored = true
10
+
11
+ case strexp
12
+ when String
13
+ @spec = parser.parse strexp
14
+ @requirements = {}
15
+ @separators = "/.?"
16
+ when Router::Strexp
17
+ @spec = parser.parse strexp.path
18
+ @requirements = strexp.requirements
19
+ @separators = strexp.separators.join
20
+ @anchored = strexp.anchor
21
+ else
22
+ raise "wtf bro: #{strexp}"
23
+ end
24
+
25
+ @names = nil
26
+ @optional_names = nil
27
+ @required_names = nil
28
+ @re = nil
29
+ end
30
+
31
+ def ast
32
+ @spec.grep(Nodes::Symbol).each do |node|
33
+ re = @requirements[node.to_sym]
34
+ node.regexp = re if re
35
+ end
36
+
37
+ @spec.grep(Nodes::Star).each do |node|
38
+ node = node.left
39
+ node.regexp = @requirements[node.to_sym] || /(.+)/
40
+ end
41
+
42
+ @spec
43
+ end
44
+
45
+ def names
46
+ @names ||= spec.grep(Nodes::Symbol).map { |n| n.name }
47
+ end
48
+
49
+ def required_names
50
+ @required_names ||= names - optional_names
51
+ end
52
+
53
+ def optional_names
54
+ @optional_names ||= spec.grep(Nodes::Group).map { |group|
55
+ group.grep(Nodes::Symbol)
56
+ }.flatten.map { |n| n.name }.uniq
57
+ end
58
+
59
+ class RegexpOffsets < Journey::Visitors::Visitor # :nodoc:
60
+ attr_reader :offsets
61
+
62
+ def initialize matchers
63
+ @matchers = matchers
64
+ @capture_count = [0]
65
+ end
66
+
67
+ def visit node
68
+ super
69
+ @capture_count
70
+ end
71
+
72
+ def visit_SYMBOL node
73
+ node = node.to_sym
74
+
75
+ if @matchers.key? node
76
+ re = /#{@matchers[node]}|/
77
+ @capture_count.push((re.match('').length - 1) + (@capture_count.last || 0))
78
+ else
79
+ @capture_count << (@capture_count.last || 0)
80
+ end
81
+ end
82
+ end
83
+
84
+ class AnchoredRegexp < Journey::Visitors::Visitor # :nodoc:
85
+ def initialize separator, matchers
86
+ @separator = separator
87
+ @matchers = matchers
88
+ @separator_re = "([^#{separator}]+)"
89
+ super()
90
+ end
91
+
92
+ def accept node
93
+ %r{\A#{visit node}\Z}
94
+ end
95
+
96
+ def visit_CAT node
97
+ [visit(node.left), visit(node.right)].join
98
+ end
99
+
100
+ def visit_SYMBOL node
101
+ node = node.to_sym
102
+
103
+ return @separator_re unless @matchers.key? node
104
+
105
+ re = @matchers[node]
106
+ # FIXME: is the question mark needed?
107
+ "(#{re}?)"
108
+ end
109
+
110
+ def visit_GROUP node
111
+ "(?:#{visit node.left})?"
112
+ end
113
+
114
+ def visit_LITERAL node
115
+ Regexp.escape node.left
116
+ end
117
+ alias :visit_DOT :visit_LITERAL
118
+
119
+ def visit_SLASH node
120
+ node.left
121
+ end
122
+
123
+ def visit_STAR node
124
+ "(.+)"
125
+ end
126
+ end
127
+
128
+ class UnanchoredRegexp < AnchoredRegexp # :nodoc:
129
+ def accept node
130
+ %r{\A#{visit node}}
131
+ end
132
+ end
133
+
134
+ class MatchData
135
+ attr_reader :names
136
+
137
+ def initialize names, offsets, match
138
+ @names = names
139
+ @offsets = offsets
140
+ @match = match
141
+ end
142
+
143
+ def captures
144
+ (length - 1).times.map { |i| self[i + 1] }
145
+ end
146
+
147
+ def [] x
148
+ idx = @offsets[x - 1] + x
149
+ @match[idx]
150
+ end
151
+
152
+ def length
153
+ @offsets.length
154
+ end
155
+
156
+ def post_match
157
+ @match.post_match
158
+ end
159
+
160
+ def to_s
161
+ @match.to_s
162
+ end
163
+ end
164
+
165
+ def match other
166
+ return unless match = to_regexp.match(other)
167
+ MatchData.new names, offsets, match
168
+ end
169
+ alias :=~ :match
170
+
171
+ def source
172
+ to_regexp.source
173
+ end
174
+
175
+ private
176
+ def to_regexp
177
+ @re ||= regexp_visitor.new(@separators, @requirements).accept spec
178
+ end
179
+
180
+ def regexp_visitor
181
+ @anchored ? AnchoredRegexp : UnanchoredRegexp
182
+ end
183
+
184
+ def offsets
185
+ viz = RegexpOffsets.new @requirements
186
+ viz.accept spec
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,92 @@
1
+ module Journey
2
+ class Route
3
+ attr_reader :app, :path, :verb, :defaults, :ip, :name
4
+
5
+ attr_reader :constraints
6
+ alias :conditions :constraints
7
+
8
+ ##
9
+ # +path+ is a path constraint.
10
+ # +constraints+ is a hash of constraints to be applied to this route.
11
+ def initialize name, app, path, constraints, defaults = {}
12
+ constraints = constraints.dup
13
+ @name = name
14
+ @app = app
15
+ @path = path
16
+ @verb = constraints[:request_method] || //
17
+ @ip = constraints.delete(:ip) || //
18
+
19
+ @constraints = constraints
20
+ @constraints.keep_if { |_,v| Regexp === v || String === v }
21
+ @defaults = defaults
22
+ @required_defaults = nil
23
+ @required_parts = nil
24
+ @parts = nil
25
+ @decorated_ast = nil
26
+ end
27
+
28
+ def ast
29
+ return @decorated_ast if @decorated_ast
30
+
31
+ @decorated_ast = path.ast
32
+ @decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self }
33
+ @decorated_ast
34
+ end
35
+
36
+ def requirements # :nodoc:
37
+ # needed for rails `rake routes`
38
+ path.requirements.merge(@defaults).delete_if { |_,v|
39
+ /.+?/ == v
40
+ }
41
+ end
42
+
43
+ def segments
44
+ @path.names
45
+ end
46
+
47
+ def required_keys
48
+ path.required_names.map { |x| x.to_sym } + required_defaults.keys
49
+ end
50
+
51
+ def score constraints
52
+ required_keys = path.required_names
53
+ supplied_keys = constraints.map { |k,v| v && k.to_s }.compact
54
+
55
+ return -1 unless (required_keys - supplied_keys).empty?
56
+
57
+ score = (supplied_keys & path.names).length
58
+ score + (required_defaults.length * 2)
59
+ end
60
+
61
+ def parts
62
+ @parts ||= segments.map { |n| n.to_sym }
63
+ end
64
+ alias :segment_keys :parts
65
+
66
+ def format path_options
67
+ (defaults.keys - required_parts).each do |key|
68
+ path_options.delete key if defaults[key].to_s == path_options[key].to_s
69
+ end
70
+
71
+ formatter = Visitors::Formatter.new(path_options)
72
+
73
+ formatted_path = formatter.accept(path.spec)
74
+ formatted_path.gsub(/\/\x00/, '')
75
+ end
76
+
77
+ def optional_parts
78
+ path.optional_names.map { |n| n.to_sym }
79
+ end
80
+
81
+ def required_parts
82
+ @required_parts ||= path.required_names.map { |n| n.to_sym }
83
+ end
84
+
85
+ def required_defaults
86
+ @required_defaults ||= begin
87
+ matches = parts
88
+ @defaults.dup.delete_if { |k,_| matches.include? k }
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,22 @@
1
+ module Journey
2
+ class Router
3
+ class Strexp
4
+ class << self
5
+ alias :compile :new
6
+ end
7
+
8
+ attr_reader :path, :requirements, :separators, :anchor
9
+
10
+ def initialize path, requirements, separators, anchor = true
11
+ @path = path
12
+ @requirements = requirements
13
+ @separators = separators
14
+ @anchor = anchor
15
+ end
16
+
17
+ def names
18
+ @path.scan(/:\w+/).map { |s| s.tr(':', '') }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,57 @@
1
+ require 'uri'
2
+
3
+ module Journey
4
+ class Router
5
+ class Utils
6
+ # Normalizes URI path.
7
+ #
8
+ # Strips off trailing slash and ensures there is a leading slash.
9
+ #
10
+ # normalize_path("/foo") # => "/foo"
11
+ # normalize_path("/foo/") # => "/foo"
12
+ # normalize_path("foo") # => "/foo"
13
+ # normalize_path("") # => "/"
14
+ def self.normalize_path(path)
15
+ path = "/#{path}"
16
+ path.squeeze!('/')
17
+ path.sub!(%r{/+\Z}, '')
18
+ path = '/' if path == ''
19
+ path
20
+ end
21
+
22
+ # URI path and fragment escaping
23
+ # http://tools.ietf.org/html/rfc3986
24
+ module UriEscape
25
+ # Symbol captures can generate multiple path segments, so include /.
26
+ reserved_segment = '/'
27
+ reserved_fragment = '/?'
28
+ reserved_pchar = ':@&=+$,;%'
29
+
30
+ safe_pchar = "#{URI::REGEXP::PATTERN::UNRESERVED}#{reserved_pchar}"
31
+ safe_segment = "#{safe_pchar}#{reserved_segment}"
32
+ safe_fragment = "#{safe_pchar}#{reserved_fragment}"
33
+ if RUBY_VERSION >= '1.9'
34
+ UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false).freeze
35
+ UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false).freeze
36
+ else
37
+ UNSAFE_SEGMENT = Regexp.new("[^#{safe_segment}]", false, 'N').freeze
38
+ UNSAFE_FRAGMENT = Regexp.new("[^#{safe_fragment}]", false, 'N').freeze
39
+ end
40
+ end
41
+
42
+ Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI
43
+
44
+ def self.escape_path(path)
45
+ Parser.escape(path.to_s, UriEscape::UNSAFE_SEGMENT)
46
+ end
47
+
48
+ def self.escape_fragment(fragment)
49
+ Parser.escape(fragment.to_s, UriEscape::UNSAFE_FRAGMENT)
50
+ end
51
+
52
+ def self.unescape_uri(uri)
53
+ Parser.unescape(uri)
54
+ end
55
+ end
56
+ end
57
+ end