usher 0.4.8 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,23 +2,62 @@ class Usher
2
2
  class Route
3
3
  class Path
4
4
 
5
- attr_reader :dynamic_parts, :dynamic_map, :dynamic_indicies, :route, :parts, :dynamic_required_keys, :dynamic_keys
5
+ attr_accessor :route
6
+ attr_reader :parts
6
7
 
7
8
  def initialize(route, parts)
8
- @route = route
9
- @parts = parts
10
- @dynamic_indicies = []
11
- @parts.each_index{|i| @dynamic_indicies << i if @parts[i].is_a?(Variable)}
12
- @dynamic_parts = @parts.values_at(*@dynamic_indicies)
13
- @dynamic_map = {}
14
- @dynamic_parts.each{|p| @dynamic_map[p.name] = p }
15
- @dynamic_keys = @dynamic_map.keys
16
- @dynamic_required_keys = @dynamic_parts.select{|dp| !dp.default_value}.map{|dp| dp.name}
9
+ self.route = route
10
+ self.parts = parts
11
+ end
12
+
13
+ def dynamic_indicies
14
+ unless dynamic? && @dynamic_indicies
15
+ @dynamic_indicies = []
16
+ parts.each_index{|i| @dynamic_indicies << i if parts[i].is_a?(Variable)}
17
+ end
18
+ @dynamic_indicies
19
+ end
20
+
21
+ def dynamic_parts
22
+ @dynamic_parts ||= parts.values_at(*dynamic_indicies) if dynamic?
23
+ end
24
+
25
+ def dynamic_map
26
+ unless dynamic? && @dynamic_map
27
+ @dynamic_map = {}
28
+ dynamic_parts.each{|p| @dynamic_map[p.name] = p }
29
+ end
30
+ @dynamic_map
31
+ end
32
+
33
+ def dynamic_keys
34
+ @dynamic_keys ||= dynamic_map.keys if dynamic?
35
+ end
36
+
37
+ def dynamic_required_keys
38
+ @dynamic_required_keys ||= dynamic_parts.select{|dp| !dp.default_value}.map{|dp| dp.name} if dynamic?
39
+ end
40
+
41
+ def dynamic?
42
+ @dynamic
17
43
  end
18
44
 
19
45
  def can_generate_from?(keys)
20
- (@dynamic_required_keys - keys).size.zero?
46
+ (dynamic_required_keys - keys).size.zero?
47
+ end
48
+
49
+ # Merges paths for use in generation
50
+ def merge(other_path)
51
+ new_parts = parts + other_path.parts
52
+ Path.new(route, new_parts)
21
53
  end
54
+
55
+ private
56
+ def parts=(parts)
57
+ @parts = parts
58
+ @dynamic = @parts.any?{|p| p.is_a?(Variable)}
59
+ end
60
+
22
61
  end
23
62
  end
24
63
  end
@@ -0,0 +1,65 @@
1
+ class Usher
2
+ class Route
3
+
4
+ module Util
5
+
6
+ class Group < Array
7
+ attr_accessor :group_type
8
+ attr_accessor :parent
9
+
10
+ def inspect
11
+ "#{group_type}->#{super}"
12
+ end
13
+
14
+ def initialize(group_type, parent)
15
+ @group_type = group_type
16
+ @parent = parent
17
+ end
18
+ end
19
+
20
+ def self.cartesian_product!(lval, rval)
21
+ product = []
22
+ (lval.size * rval.size).times do |index|
23
+ val = []
24
+ val.push(*lval[index % lval.size])
25
+ val.push(*rval[index % rval.size])
26
+ product << val
27
+ end
28
+ lval.replace(product)
29
+ end
30
+
31
+ def self.expand_path(parts)
32
+ if parts.is_a?(Array)
33
+ paths = [[]]
34
+
35
+ unless parts.respond_to?(:group_type)
36
+ new_parts = Group.new(:any, nil)
37
+ parts.each{|p| new_parts << p}
38
+ parts = new_parts
39
+ end
40
+
41
+ case parts.group_type
42
+ when :all
43
+ parts.each do |p|
44
+ cartesian_product!(paths, expand_path(p))
45
+ end
46
+ when :any
47
+ parts.each do |p|
48
+ cartesian_product!(paths, expand_path(p))
49
+ end
50
+ paths.unshift([])
51
+ when :one
52
+ cartesian_product!(paths, parts.collect do |p|
53
+ expand_path(p)
54
+ end)
55
+ end
56
+ paths.each{|p| p.compact!; p.flatten! }
57
+ paths
58
+ else
59
+ [[parts]]
60
+ end
61
+
62
+ end
63
+ end
64
+ end
65
+ end
@@ -2,19 +2,14 @@ class Usher
2
2
  class Route
3
3
  class Variable
4
4
  attr_reader :type, :name, :validator, :regex_matcher
5
- attr_accessor :look_ahead, :globs_capture_separators, :default_value
5
+ attr_accessor :look_ahead, :default_value
6
6
 
7
- def initialize(type, name, validator = nil, regex_matcher = nil, globs_capture_separators = false)
8
- @type = type
9
- @name = :"#{name}"
7
+ def initialize(name, regex_matcher = nil, validator = nil)
8
+ @name = name.to_s.to_sym
10
9
  @validator = validator
11
10
  @regex_matcher = regex_matcher
12
- @globs_capture_separators = globs_capture_separators
13
- end
14
-
15
- def to_s
16
- "#{type}#{name}"
17
11
  end
12
+ private :initialize
18
13
 
19
14
  def valid!(val)
20
15
  case @validator
@@ -22,7 +17,7 @@ class Usher
22
17
  begin
23
18
  @validator.call(val)
24
19
  rescue Exception => e
25
- raise ValidationException.new("#{val} does not conform to #{@validator}, root cause #{e.inspect}")
20
+ raise ValidationException.new("#{val} does not conform to #{@g}, root cause #{e.inspect}")
26
21
  end
27
22
  else
28
23
  @validator === val or raise(ValidationException.new("#{val} does not conform to #{@validator}, root cause #{e.inspect}"))
@@ -30,8 +25,24 @@ class Usher
30
25
  end
31
26
 
32
27
  def ==(o)
33
- o && (o.type == @type && o.name == @name && o.validator == @validator)
28
+ o && (o.class == self.class && o.name == @name && o.validator == @validator)
29
+ end
30
+
31
+ class Single < Variable
32
+ def to_s
33
+ ":#{name}"
34
+ end
34
35
  end
36
+
37
+ class Glob < Variable
38
+ def to_s
39
+ "*#{name}"
40
+ end
41
+ end
42
+
43
+ Greedy = Class.new(Variable)
44
+
35
45
  end
46
+
36
47
  end
37
48
  end
@@ -3,156 +3,19 @@ require 'strscan'
3
3
  class Usher
4
4
  class Splitter
5
5
 
6
- def self.for_delimiters(delimiters, valid_regex)
7
- delimiters_regex = delimiters.collect{|d| Regexp.quote(d)} * '|'
8
- SplitterInstance.new(
9
- delimiters,
10
- Regexp.new('((:|\*)?' + valid_regex + '|' + delimiters_regex + '|\(|\)|\||\{)'),
11
- Regexp.new("[#{delimiters.collect{|d| Regexp.quote(d)}}]|[^#{delimiters.collect{|d| Regexp.quote(d)}}]+")
12
- )
6
+ def self.for_delimiters(router, valid_regex)
7
+ SplitterInstance.new(Regexp.new("[#{router.delimiters.collect{|d| Regexp.quote(d)}.join}]|[^#{router.delimiters.collect{|d| Regexp.quote(d)}.join}]+"))
13
8
  end
14
-
15
- attr_reader :paths
16
9
 
17
10
  class SplitterInstance
18
-
19
- attr_reader :delimiter_chars
20
-
21
- def initialize(delimiters, split_regex, url_split_regex)
22
- @delimiters = delimiters
23
- @delimiter_chars = delimiters.collect{|d| d[0]}
24
- @split_regex = split_regex
11
+
12
+ def initialize(url_split_regex)
25
13
  @url_split_regex = url_split_regex
26
14
  end
27
15
 
28
16
  def url_split(path)
29
17
  path.scan(@url_split_regex)
30
18
  end
31
-
32
- def split(path, requirements = nil, default_values = nil)
33
- parts = Group.new(:all, nil)
34
- ss = StringScanner.new(path)
35
- current_group = parts
36
- while !ss.eos?
37
- part = ss.scan(@split_regex)
38
- case part[0]
39
- when ?*, ?:
40
- type = part.slice!(0).chr.to_sym
41
- current_group << Usher::Route::Variable.new(type, part, requirements && requirements[part.to_sym])
42
- when ?{
43
- pattern = ''
44
- count = 1
45
- variable = ss.scan(/[:\*]([^,]+),/)
46
- until count.zero?
47
- regex_part = ss.scan(/\{|\}|[^\{\}]+/)
48
- case regex_part[0]
49
- when ?{
50
- count += 1
51
- when ?}
52
- count -= 1
53
- end
54
- pattern << regex_part
55
- end
56
- pattern.slice!(pattern.length - 1)
57
- regex = Regexp.new(pattern)
58
- if variable
59
- variable_type = variable.slice!(0).chr.to_sym
60
- variable_name = variable[0, variable.size - 1].to_sym
61
- current_group << Usher::Route::Variable.new(variable_type, variable_name, requirements && requirements[variable_name], regex)
62
- else
63
- current_group << regex
64
- end
65
- when ?(
66
- new_group = Group.new(:any, current_group)
67
- current_group << new_group
68
- current_group = new_group
69
- when ?)
70
- current_group = current_group.parent.type == :one ? current_group.parent.parent : current_group.parent
71
- when ?|
72
- unless current_group.parent.type == :one
73
- detached_group = current_group.parent.pop
74
- new_group = Group.new(:one, detached_group.parent)
75
- detached_group.parent = new_group
76
- detached_group.type = :all
77
- new_group << detached_group
78
- new_group.parent << new_group
79
- end
80
- current_group.parent << Group.new(:all, current_group.parent)
81
- current_group = current_group.parent.last
82
- else
83
- current_group << part
84
- end
85
- end unless !path || path.empty?
86
- paths = calc_paths(parts)
87
- paths.each do |path|
88
- path.each_with_index do |part, index|
89
- if part.is_a?(Usher::Route::Variable)
90
- part.default_value = default_values[part.name] if default_values
91
-
92
- case part.type
93
- when :*
94
- part.look_ahead = path[index + 1, path.size].find{|p| !p.is_a?(Usher::Route::Variable) && !@delimiter_chars.include?(p[0])} || nil
95
- when :':'
96
- part.look_ahead = path[index + 1, path.size].find{|p| @delimiter_chars.include?(p[0])} || @delimiters.first
97
- end
98
- end
99
- end
100
- end
101
- paths
102
- end
103
-
104
- private
105
-
106
- def cartesian_product!(lval, rval)
107
- product = []
108
- (lval.size * rval.size).times do |index|
109
- val = []
110
- val.push(*lval[index % lval.size])
111
- val.push(*rval[index % rval.size])
112
- product << val
113
- end
114
- lval.replace(product)
115
- end
116
-
117
- def calc_paths(parts)
118
- if parts.is_a?(Group)
119
- paths = [[]]
120
- case parts.type
121
- when :all
122
- parts.each do |p|
123
- cartesian_product!(paths, calc_paths(p))
124
- end
125
- when :any
126
- parts.each do |p|
127
- cartesian_product!(paths, calc_paths(p))
128
- end
129
- paths.unshift([])
130
- when :one
131
- cartesian_product!(paths, parts.collect do |p|
132
- calc_paths(p)
133
- end)
134
- end
135
- paths.each{|p| p.compact!; p.flatten! }
136
- paths
137
- else
138
- [[parts]]
139
- end
140
-
141
- end
142
- end
143
-
144
- class Group < Array
145
- attr_accessor :type
146
- attr_accessor :parent
147
-
148
- def inspect
149
- "#{type}->#{super}"
150
- end
151
-
152
- def initialize(type, parent)
153
- @type = type
154
- @parent = parent
155
- end
156
19
  end
157
20
 
158
21
  end
data/lib/usher/util.rb ADDED
@@ -0,0 +1,6 @@
1
+ class Usher
2
+ module Util
3
+ autoload :Generators, File.join(File.dirname(__FILE__), 'util', 'generate')
4
+ autoload :Parser, File.join(File.dirname(__FILE__), 'util', 'parser')
5
+ end
6
+ end
@@ -0,0 +1,129 @@
1
+ require 'rack'
2
+
3
+ unless Rack::Utils.respond_to?(:uri_escape)
4
+ module Rack
5
+
6
+ module Utils
7
+
8
+ def uri_escape(s)
9
+ s.to_s.gsub(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) {
10
+ '%'<<$1.unpack('H2'*$1.size).join('%').upcase
11
+ }.tr(' ', '+')
12
+ end
13
+ module_function :uri_escape
14
+
15
+ def uri_unescape(s)
16
+ gsub(/((?:%[0-9a-fA-F]{2})+)/n){
17
+ [$1.delete('%')].pack('H*')
18
+ }
19
+ end
20
+ module_function :uri_unescape
21
+
22
+ end
23
+ end
24
+ end
25
+
26
+ class Usher
27
+ module Util
28
+ class Generators
29
+
30
+ class URL
31
+
32
+ attr_accessor :usher
33
+
34
+ def generate_full(routing_lookup, request, params = nil)
35
+ path = path_for_routing_lookup(routing_lookup, params)
36
+ result = generate_start(path, request)
37
+ result << generate_path(path, params)
38
+ end
39
+
40
+ def generate(routing_lookup, params = nil)
41
+ generate_path(path_for_routing_lookup(routing_lookup, params), params)
42
+ end
43
+
44
+ def generate_start(path, request)
45
+ result = (path.route.generate_with && path.route.generate_with.scheme || request.scheme).dup
46
+ result << '://'
47
+ result << (path.route.generate_with && path.route.generate_with.host) ? path.route.generate_with.host : request.host
48
+ port = path.route.generate_with && path.route.generate_with.port || request.port
49
+ if result[4] == ?s
50
+ result << ':' << port.to_s unless port == 443
51
+ else
52
+ result << ':' << port.to_s unless port == 80
53
+ end
54
+ result
55
+ end
56
+
57
+ def path_for_routing_lookup(routing_lookup, params = {})
58
+ path = case routing_lookup
59
+ when Symbol
60
+ route = @usher.named_routes[routing_lookup]
61
+ route.find_matching_path(params || {})
62
+ when Route
63
+ routing_lookup.find_matching_path(params || {})
64
+ when nil
65
+ params.is_a?(Hash) ? @usher.path_for_options(params) : raise
66
+ when Route::Path
67
+ routing_lookup
68
+ end
69
+ end
70
+
71
+ # Generates a completed URL based on a +route+ or set of optional +params+
72
+ #
73
+ # set = Usher.new
74
+ # route = set.add_named_route(:test_route, '/:controller/:action')
75
+ # set.generate_url(nil, {:controller => 'c', :action => 'a'}) == '/c/a' => true
76
+ # set.generate_url(:test_route, {:controller => 'c', :action => 'a'}) == '/c/a' => true
77
+ # set.generate_url(route.primary_path, {:controller => 'c', :action => 'a'}) == '/c/a' => true
78
+ def generate_path(path, params = nil)
79
+ raise UnrecognizedException.new unless path
80
+
81
+ params = Array(params) if params.is_a?(String)
82
+ if params.is_a?(Array)
83
+ given_size = params.size
84
+ extra_params = params.last.is_a?(Hash) ? params.pop : nil
85
+ params = Hash[*path.dynamic_parts.inject([]){|a, dynamic_part| a.concat([dynamic_part.name, params.shift || raise(MissingParameterException.new("got #{given_size}, expected #{path.dynamic_parts.size} parameters"))]); a}]
86
+ params.merge!(extra_params) if extra_params
87
+ end
88
+
89
+ result = ''
90
+ path.parts.each do |part|
91
+ case part
92
+ when Route::Variable::Glob
93
+ value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
94
+ value.each_with_index do |current_value, index|
95
+ part.valid!(current_value)
96
+ result << current_value.to_s
97
+ result << '/' if index != value.size - 1
98
+ end
99
+ when Route::Variable
100
+ value = (params && params.delete(part.name)) || part.default_value || raise(MissingParameterException.new)
101
+ part.valid!(value)
102
+ result << value.to_s
103
+ else
104
+ result << part
105
+ end
106
+ end
107
+ result = Rack::Utils.uri_escape(result)
108
+
109
+ unless params.nil? || params.empty?
110
+ has_query = result[??]
111
+ params.each do |k,v|
112
+ case v
113
+ when Array
114
+ v.each do |v_part|
115
+ result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape("#{k.to_s}[]") << '=' << Rack::Utils.escape(v_part.to_s)
116
+ end
117
+ else
118
+ result << (has_query ? '&' : has_query = true && '?') << Rack::Utils.escape(k.to_s) << '=' << Rack::Utils.escape(v.to_s)
119
+ end
120
+ end
121
+ end
122
+ result
123
+ end
124
+
125
+ end
126
+
127
+ end
128
+ end
129
+ end