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.
- data/.autotest +8 -0
- data/.gemtest +0 -0
- data/CHANGELOG.rdoc +6 -0
- data/Gemfile +11 -0
- data/Manifest.txt +49 -0
- data/README.rdoc +48 -0
- data/Rakefile +31 -0
- data/journey.gemspec +42 -0
- data/lib/journey/backwards.rb +5 -0
- data/lib/journey/core-ext/hash.rb +11 -0
- data/lib/journey/formatter.rb +129 -0
- data/lib/journey/gtg/builder.rb +159 -0
- data/lib/journey/gtg/simulator.rb +44 -0
- data/lib/journey/gtg/transition_table.rb +152 -0
- data/lib/journey/nfa/builder.rb +74 -0
- data/lib/journey/nfa/dot.rb +34 -0
- data/lib/journey/nfa/simulator.rb +45 -0
- data/lib/journey/nfa/transition_table.rb +164 -0
- data/lib/journey/nodes/node.rb +104 -0
- data/lib/journey/parser.rb +204 -0
- data/lib/journey/parser.y +47 -0
- data/lib/journey/parser_extras.rb +21 -0
- data/lib/journey/path/pattern.rb +190 -0
- data/lib/journey/route.rb +92 -0
- data/lib/journey/router/strexp.rb +22 -0
- data/lib/journey/router/utils.rb +57 -0
- data/lib/journey/router.rb +138 -0
- data/lib/journey/routes.rb +74 -0
- data/lib/journey/scanner.rb +58 -0
- data/lib/journey/visitors.rb +186 -0
- data/lib/journey/visualizer/d3.min.js +2 -0
- data/lib/journey/visualizer/fsm.css +34 -0
- data/lib/journey/visualizer/fsm.js +134 -0
- data/lib/journey/visualizer/index.html.erb +50 -0
- data/lib/journey/visualizer/reset.css +48 -0
- data/lib/journey.rb +5 -0
- data/test/gtg/test_builder.rb +77 -0
- data/test/gtg/test_transition_table.rb +113 -0
- data/test/helper.rb +4 -0
- data/test/nfa/test_simulator.rb +96 -0
- data/test/nfa/test_transition_table.rb +70 -0
- data/test/nodes/test_symbol.rb +15 -0
- data/test/path/test_pattern.rb +260 -0
- data/test/route/definition/test_parser.rb +108 -0
- data/test/route/definition/test_scanner.rb +52 -0
- data/test/router/test_strexp.rb +30 -0
- data/test/router/test_utils.rb +19 -0
- data/test/test_route.rb +95 -0
- data/test/test_router.rb +464 -0
- data/test/test_routes.rb +38 -0
- 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
|