usher 0.4.8 → 0.5.1

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.
@@ -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