sinatra-named-routes 0.0.1 → 0.1.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/.travis.yml ADDED
@@ -0,0 +1 @@
1
+ rvm: 1.9.2
data/Gemfile CHANGED
@@ -4,11 +4,12 @@ source 'http://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  group :development, :test do
7
+ gem 'rake'
7
8
  gem 'sinatra-contrib'
8
9
  gem 'rspec'
9
10
  gem 'guard-rspec'
10
11
  end
11
12
 
12
13
  group :test do
13
- gem 'simplecov', require: false
14
+ gem 'simplecov', :require => false
14
15
  end
data/Gemfile.lock CHANGED
@@ -2,6 +2,7 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  sinatra-named-routes (0.0.1)
5
+ sinatra
5
6
 
6
7
  GEM
7
8
  remote: http://rubygems.org/
@@ -9,31 +10,34 @@ GEM
9
10
  backports (2.3.0)
10
11
  diff-lcs (1.1.3)
11
12
  eventmachine (0.12.10)
12
- guard (0.8.8)
13
+ ffi (1.0.11)
14
+ guard (1.0.0)
15
+ ffi (>= 0.5.0)
13
16
  thor (~> 0.14.6)
14
- guard-rspec (0.5.2)
15
- guard (>= 0.8.4)
16
- multi_json (1.0.3)
17
- rack (1.3.5)
18
- rack-protection (1.1.4)
17
+ guard-rspec (0.6.0)
18
+ guard (>= 0.10.0)
19
+ multi_json (1.0.4)
20
+ rack (1.4.1)
21
+ rack-protection (1.2.0)
19
22
  rack
20
23
  rack-test (0.6.1)
21
24
  rack (>= 1.0)
22
- rspec (2.7.0)
23
- rspec-core (~> 2.7.0)
24
- rspec-expectations (~> 2.7.0)
25
- rspec-mocks (~> 2.7.0)
26
- rspec-core (2.7.1)
27
- rspec-expectations (2.7.0)
25
+ rake (0.9.2.2)
26
+ rspec (2.8.0)
27
+ rspec-core (~> 2.8.0)
28
+ rspec-expectations (~> 2.8.0)
29
+ rspec-mocks (~> 2.8.0)
30
+ rspec-core (2.8.0)
31
+ rspec-expectations (2.8.0)
28
32
  diff-lcs (~> 1.1.2)
29
- rspec-mocks (2.7.0)
33
+ rspec-mocks (2.8.0)
30
34
  simplecov (0.5.4)
31
35
  multi_json (~> 1.0.3)
32
36
  simplecov-html (~> 0.5.3)
33
37
  simplecov-html (0.5.3)
34
- sinatra (1.3.1)
35
- rack (~> 1.3, >= 1.3.4)
36
- rack-protection (~> 1.1, >= 1.1.2)
38
+ sinatra (1.3.2)
39
+ rack (~> 1.3, >= 1.3.6)
40
+ rack-protection (~> 1.2)
37
41
  tilt (~> 1.3, >= 1.3.3)
38
42
  sinatra-contrib (1.3.1)
39
43
  backports (>= 2.0)
@@ -50,8 +54,8 @@ PLATFORMS
50
54
 
51
55
  DEPENDENCIES
52
56
  guard-rspec
57
+ rake
53
58
  rspec
54
59
  simplecov
55
- sinatra
56
60
  sinatra-contrib
57
61
  sinatra-named-routes!
data/README.md CHANGED
@@ -1,3 +1,3 @@
1
- # Sinatra Named Routes
2
-
1
+ # Sinatra Named Routes [![Build Status](https://secure.travis-ci.org/ckhampus/sinatra-named-routes.png)](http://travis-ci.org/ckhampus/sinatra-named-routes)
2
+
3
3
  This gem allows the use of named routes in Sinatra applications.
data/Rakefile CHANGED
@@ -1 +1,5 @@
1
1
  require 'bundler/gem_tasks'
2
+
3
+ task(:spec) { ruby '-S rspec spec -c' }
4
+ task(:test => :spec)
5
+ task(:default => :spec)
@@ -1,103 +1,52 @@
1
- require_relative 'version'
1
+ require File.join(File.dirname(__FILE__), 'version')
2
+ require File.join(File.dirname(__FILE__), 'route_parser')
2
3
 
3
4
  module Sinatra
4
5
  module NamedRoutes
5
- @@routes = {}
6
+ module Helpers
6
7
 
7
- #def uri(addr = nil, absolute = true, add_script_name = true, params = {})
8
- def uri(*args)
9
- path = args.shift if args.first.is_a? Symbol
10
- params = args.pop if args.last.is_a? Array or args.last.is_a? Hash
8
+ # def uri(addr = nil, absolute = true, add_script_name = true, params = {})
9
+ def uri(*args)
10
+ path = args.shift if args.first.is_a? Symbol
11
+ params = args.pop if args.last.is_a? Array or args.last.is_a? Hash
11
12
 
12
- if path
13
- addr = get_path(path, params)
13
+ if path
14
+ addr = NamedRoutes.get_path(path, params)
14
15
 
15
- super(addr, *args)
16
- else
17
- super(*args)
18
- end
16
+ super(addr, *args)
17
+ else
18
+ super(*args)
19
+ end
19
20
 
21
+ end
22
+ alias :to :uri
23
+ alias :url :uri
20
24
  end
21
25
 
22
- alias :to :uri
23
- alias :url :uri
24
-
25
26
  def map(name, path)
26
- route = {}
27
- route[:path] = path
28
-
29
- if path.is_a? String
30
- named = path.scan(/(?<=:)[^\.\/]*/).map { |item| item.to_sym }
31
- splat = path.scan(/\*/)
32
-
33
- params = { named: named, splat: splat }
34
- elsif path.is_a? Regexp
35
- regexp = path.source.scan(/\([^\)]*\)/)
36
-
37
- params = { regexp: regexp }
38
- end
39
-
40
- route[:params] = params
41
-
42
- @@routes[name] = route
27
+ NamedRoutes.routes[name] = Route.new path
43
28
  end
44
29
 
45
30
  private
46
31
 
47
- def get_path(name, params = {})
48
- route = @@routes[name]
49
-
50
- path = route[:path]
51
-
52
- if params.is_a? Hash
53
-
54
- # Turn string keys into symbols
55
- params = params.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
56
-
57
- if path.is_a? String
58
- route[:params][:named].each do |key|
59
- if params.has_key? key
60
- path = path.sub(key.inspect, params[key])
61
- else
62
- raise ArgumentError.new
63
- end
64
- end
65
- end
66
- elsif params.is_a? Array
67
- if path.is_a? String
68
- if route[:params][:splat].length != params.length
69
- raise ArgumentError.new
70
- end
71
-
72
- params.each do |value|
73
- path = path.sub('*', value)
74
- end
75
- elsif path.is_a? Regexp
76
- if route[:params][:regexp].length != params.length
77
- raise ArgumentError.new
78
- end
79
-
80
- path = path.source
81
-
82
- params.each_index do |index|
83
- path = path.sub(route[:params][:regexp][index], params[index])
84
- end
85
- end
86
- end
87
-
88
- path
89
- end
90
-
91
32
  def route(verb, path, options={}, &block)
92
33
  if path.is_a?(Symbol)
93
- path = @@routes[path][:path]
34
+ path = NamedRoutes.routes[path].source
94
35
  end
95
36
 
96
37
  super(verb, path, options, &block)
97
38
  end
98
39
 
40
+ def self.get_path(name, params = {})
41
+ NamedRoutes.routes[name].build params
42
+ end
43
+
44
+ def self.routes
45
+ @@routes ||= {}
46
+ end
47
+
99
48
  def self.registered(app)
100
- app.helpers NamedRoutes
49
+ app.helpers NamedRoutes::Helpers
101
50
  end
102
51
  end
103
52
  end
@@ -0,0 +1,164 @@
1
+ class Route
2
+ attr_reader :source
3
+
4
+ def initialize(route)
5
+ route = route.source if route.is_a? Regexp
6
+
7
+ @source = route
8
+ @input = StringScanner.new(route)
9
+ @output = []
10
+
11
+ parse
12
+ end
13
+
14
+ # Public: Build URL with parameters.
15
+ #
16
+ # params - The hash or array of parameters to pass to URL.
17
+ #
18
+ # Returns the URL as a String.
19
+ def build(params = {})
20
+ path = []
21
+ params = {} if params.nil?
22
+
23
+ @output.each_index do |index|
24
+ item = @output[index]
25
+ next_item = @output.fetch(index + 1, nil)
26
+
27
+ case item[:token]
28
+ when :slash
29
+ @trailing_slash = item[:optional]
30
+ path << '/'
31
+ when :dot
32
+ @trailing_dot = item[:optional]
33
+ path << '.'
34
+ when :splat
35
+ if params.is_a? Hash
36
+ raise ArgumentError, 'No parameters passed.' if params[:splat].empty?
37
+ path << params[:splat].shift
38
+ else
39
+ raise ArgumentError, 'No enough parameters passed.' if params.empty?
40
+ path << params.shift
41
+ end
42
+ when :path
43
+ path << item[:value]
44
+ when :named_param
45
+ item_key = item[:value]
46
+
47
+ if params.has_key? item_key
48
+ path << params.delete(item_key)
49
+ else
50
+ raise ArgumentError, "No value passed for '#{item_key.to_s}'" unless item[:optional]
51
+ end
52
+ when :regexp
53
+ name = /#{item[:value]}/.names
54
+
55
+ if name.any?
56
+ name = name.first.to_sym
57
+
58
+ if params.has_key? name
59
+ path << params.delete(name)
60
+ else
61
+ raise ArgumentError, "No value passed for '#{name.to_s}'" unless item[:optional]
62
+ end
63
+ else
64
+ if params.is_a? Hash
65
+ raise ArgumentError, 'No enough parameters passed.' if params[:captures].empty? and !item[:optional]
66
+ path << params[:captures].shift
67
+ else
68
+ raise ArgumentError, 'No enough parameters passed.' if params.empty?
69
+ path << params.shift
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ path = path.join
76
+
77
+ if @trailing_dot
78
+ path = path.chomp '.'
79
+ end
80
+
81
+ if @trailing_slash
82
+ path = path.chomp '/'
83
+ end
84
+
85
+ path
86
+ end
87
+
88
+ private
89
+
90
+ def is_optional?
91
+ @output.last[:optional] = @input.scan(/\?/) ? true : false
92
+ end
93
+
94
+ def parse
95
+ while token = parse_slash || parse_path || parse_named_param ||
96
+ parse_dot || parse_splat || parse_regexp
97
+ @output << token
98
+ is_optional?
99
+ end
100
+ end
101
+
102
+ def parse_slash
103
+ if @input.scan(/\//)
104
+ {
105
+ :token => :slash
106
+ }
107
+ else
108
+ nil
109
+ end
110
+ end
111
+
112
+ def parse_dot
113
+ if @input.scan(/\./)
114
+ {
115
+ :token => :dot
116
+ }
117
+ else
118
+ nil
119
+ end
120
+ end
121
+
122
+ def parse_splat
123
+ if @input.scan(/\*/)
124
+ {
125
+ :token => :splat
126
+ }
127
+ else
128
+ nil
129
+ end
130
+ end
131
+
132
+ def parse_path
133
+ if @input.scan(/\w+/)
134
+ {
135
+ :token => :path,
136
+ :value => @input.matched
137
+ }
138
+ else
139
+ nil
140
+ end
141
+ end
142
+
143
+ def parse_named_param
144
+ if @input.scan(/:[^\W]*/)
145
+ {
146
+ :token => :named_param,
147
+ :value => @input.matched.sub(':', '').to_sym
148
+ }
149
+ else
150
+ nil
151
+ end
152
+ end
153
+
154
+ def parse_regexp
155
+ if @input.scan(/\([^\)]*\)/)
156
+ {
157
+ :token => :regexp,
158
+ :value => @input.matched
159
+ }
160
+ else
161
+ nil
162
+ end
163
+ end
164
+ end
@@ -1,5 +1,5 @@
1
1
  module Sinatra
2
2
  module NamedRoutes
3
- VERSION = '0.0.1'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
@@ -1,147 +1,105 @@
1
- require_relative 'spec_helper'
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
2
 
3
3
  describe Sinatra::NamedRoutes do
4
4
  def helper_route(&block)
5
- result = nil
6
- @helper_app.get('/helper_route') do
7
- result = instance_eval(&block)
8
- 'ok'
9
- end
10
- get '/helper_route'
11
- last_response.should be_ok
12
- body.should be == 'ok'
13
- result
14
- end
15
-
16
- before do
17
- app = nil
5
+ time = Time.now.usec
18
6
 
19
7
  mock_app do
20
8
  register Sinatra::NamedRoutes
21
9
 
22
- map(:hello, '/hello')
23
-
24
- map(:path_named, '/hello/:name')
25
- map(:path_multi_named, '/hello/:name.:format')
26
- map(:path_splat, '/hello/*')
27
- map(:path_multi_splat, '/hello/*.*')
28
- map(:path_regexp, %r{/hello/([\w]+)})
29
- map(:path_multi_regexp, %r{/hello/([\w]+).([\w]+)})
10
+ map(:path_named_params, '/hello/:name.:format')
11
+ map(:path_splats, '/hello/*.*')
12
+ map(:path_regexp, %r{/hello/([\w]+).([\w]+)})
13
+ map(:path_named_captures, %r{/hello/(?<person>[^/?#]+)})
14
+ map(:path_optional_named_captures, %r{/page(?<format>.[^/?#]+)?})
30
15
 
31
- get('/') { 'hello' }
32
- get(:hello) { 'hello world' }
33
- get('/hello/:name') { |name| "hello #{name}" }
34
-
35
- app = self
16
+ get "/#{time}" do
17
+ instance_eval(&block)
18
+ end
36
19
  end
37
20
 
38
- @helper_app = app
21
+ get "/#{time}"
39
22
  end
40
23
 
41
- describe :helper_route do
42
- it 'runs the block' do
43
- ran = false
44
- helper_route { ran = true }
45
- ran.should be_true
46
- end
47
-
48
- it 'returns the block result' do
49
- helper_route { 42 }.should be == 42
50
- end
24
+ describe :routing do
25
+ it 'does allow routing with symbols as paths' do
26
+ mock_app do
27
+ register Sinatra::NamedRoutes
51
28
 
52
- end
29
+ map :test, '/test'
53
30
 
54
- describe :routing do
55
- it 'does still allow normal routing' do
56
- get('/').should be_ok
57
- body.should be == 'hello'
31
+ get :test do
32
+ 'symbols work'
33
+ end
34
+ end
58
35
 
59
- get('/hello/cristian').should be_ok
60
- body.should be == 'hello cristian'
36
+ get('/test').should be_ok
37
+ body.should be == 'symbols work'
61
38
  end
62
39
 
63
- it 'does still allow routing with symbols as paths' do
64
- get('/hello').should be_ok
65
- body.should be == 'hello world'
40
+ it 'does not break normal routing' do
41
+ mock_app do
42
+ register Sinatra::NamedRoutes
43
+
44
+ get '/normal' do
45
+ 'still works'
46
+ end
47
+ end
48
+
49
+ get('/normal').should be_ok
50
+ body.should be == 'still works'
66
51
  end
67
52
 
68
53
  end
69
54
 
70
55
  describe :path_helper do
71
56
 
72
- it 'does not break normal behavior' do
73
- helper_route do
74
- url '/route_one', false
75
- end.should be == '/route_one'
76
- end
77
-
78
- it 'ignores params if path is not a symbol' do
79
- helper_route do
80
- url '/route_one', false, name: 'cristian'
81
- end.should be == '/route_one'
82
- end
57
+ it 'does not break normal helper behavior' do
58
+ mock_app do
59
+ register Sinatra::NamedRoutes
83
60
 
84
- describe :named do
85
- it 'returns the correct path if passed a hash with symbols as keys' do
86
- helper_route do
87
- url :path_multi_named, false, name: 'cristian', format: 'json'
88
- end.should be == '/hello/cristian.json'
61
+ get '/' do
62
+ url '/route', false
63
+ end
89
64
  end
90
65
 
91
- it 'returns the correct path if passed a hash with strings as keys' do
92
- helper_route do
93
- url :path_multi_named, false, 'name' => 'cristian', 'format' => 'json'
94
- end.should be == '/hello/cristian.json'
95
- end
66
+ get('/').should be_ok
67
+ body.should be == '/route'
68
+ end
96
69
 
97
- it 'ignores keys that are left' do
98
- helper_route do
99
- url :path_multi_named, false, name: 'cristian', format: 'json', color: 'blue'
100
- end.should be == '/hello/cristian.json'
101
- end
70
+ it 'ignores params if path is not a symbol' do
71
+ mock_app do
72
+ register Sinatra::NamedRoutes
102
73
 
103
- it 'throws an exception if required keys do not exist' do
104
- expect do
105
- helper_route do
106
- url :path_multi_named, false, name: 'cristian', color: 'blue'
107
- end
108
- end.to raise_exception ArgumentError
74
+ get '/' do
75
+ url '/route', false, :name => 'cristian'
76
+ end
109
77
  end
110
78
 
79
+ get('/').should be_ok
80
+ body.should be == '/route'
111
81
  end
112
82
 
113
- describe :splat do
114
- it 'returns the correct url for path with splats' do
115
- helper_route do
116
- url :path_multi_splat, false, ['cristian', 'json']
117
- end.should be == '/hello/cristian.json'
118
- end
83
+ it 'returns the correct urls' do
84
+ mock_app do
85
+ register Sinatra::NamedRoutes
119
86
 
120
- it 'throws exception if params does not match number of splats' do
121
- expect do
122
- helper_route do
123
- url :path_multi_splat, false, ['cristian']
124
- end
125
- end.to raise_exception ArgumentError
126
- end
87
+ map :path, '/hello/?:person?'
127
88
 
128
- end
89
+ get '/test1' do
90
+ url :path, false, :person => 'cristian'
91
+ end
129
92
 
130
- describe :regular_expression do
131
- it 'returns the correct url for path with regular expressions' do
132
- helper_route do
133
- url :path_multi_regexp, false, ['cristian', 'json']
134
- end.should be == '/hello/cristian.json'
93
+ get '/test2' do
94
+ url :path, false
95
+ end
135
96
  end
136
97
 
137
- it 'throws exception if params does not match number of captures' do
138
- expect do
139
- helper_route do
140
- url :path_multi_regexp, false, ['cristian']
141
- end
142
- end.to raise_exception ArgumentError
143
- end
98
+ get('/test1').should be_ok
99
+ body.should be == '/hello/cristian'
144
100
 
101
+ get('/test2').should be_ok
102
+ body.should be == '/hello'
145
103
  end
146
104
 
147
105
  end
@@ -0,0 +1,72 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Route do
4
+ it 'supports named parameters' do
5
+ route = Route.new('/hello/:person.:format')
6
+ url = route.build :person => 'cristian', :format => 'json'
7
+ url.should eql '/hello/cristian.json'
8
+ end
9
+
10
+ it 'throws exception if required named params are missing' do
11
+ expect do
12
+ route = Route.new('/hello/:person.:format')
13
+ url = route.build :person => 'cristian'
14
+ end.to raise_exception ArgumentError
15
+ end
16
+
17
+ it 'supports splats' do
18
+ route = Route.new('/hello/*.*')
19
+ url = route.build ['cristian', 'json']
20
+ url.should eql '/hello/cristian.json'
21
+ end
22
+
23
+ it 'throws exception if required splats are missing' do
24
+ expect do
25
+ route = Route.new('/hello/*.*')
26
+ url = route.build ['cristian']
27
+ end.to raise_exception ArgumentError
28
+ end
29
+
30
+ it 'supports splats mixed wih named parameters' do
31
+ route = Route.new('/hello/:person.*')
32
+ url = route.build :person => 'cristian', :splat => ['json']
33
+ url.should eql '/hello/cristian.json'
34
+ end
35
+
36
+ it 'supports regular expressions' do
37
+ route = Route.new(%r{/hello/([\w]+).([\w]+)})
38
+ url = route.build ['cristian', 'html']
39
+ url.should eql '/hello/cristian.html'
40
+ end
41
+
42
+ it 'supports optional regular expressions mixed with named params' do
43
+ route = Route.new(%r{/hello/:lang/([\w]+).?([\w]+)?})
44
+ url = route.build :lang => 'en', :captures => ['cristian', 'html']
45
+ url.should eql '/hello/en/cristian.html'
46
+
47
+ url = route.build :lang => 'en', :captures => ['cristian']
48
+ url.should eql '/hello/en/cristian'
49
+ end
50
+
51
+ it 'supports named capture groups' do
52
+ next if RUBY_VERSION < '1.9'
53
+
54
+ route = Route.new(%r{/hello/(?<person>[^/?#]+)})
55
+ url = route.build :person => 'cristian'
56
+ url.should eql '/hello/cristian'
57
+ end
58
+
59
+ it 'supports optional named capture groups' do
60
+ next if RUBY_VERSION < '1.9'
61
+
62
+ route = Route.new(%r{/page(?<format>.[^/?#]+)?})
63
+ url = route.build
64
+ url.should eql '/page'
65
+
66
+ url = route.build :format => '.html'
67
+ url.should eql '/page.html'
68
+
69
+ url = route.build :format => '.xml'
70
+ url.should eql '/page.xml'
71
+ end
72
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-named-routes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-11-13 00:00:00.000000000Z
12
+ date: 2012-02-11 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
16
- requirement: &22756416 !ruby/object:Gem::Requirement
16
+ requirement: &70197364991660 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *22756416
24
+ version_requirements: *70197364991660
25
25
  description: Allows the use of named routes in Sinatra applications.
26
26
  email:
27
27
  - contact@cristianhampus.se
@@ -30,15 +30,18 @@ extensions: []
30
30
  extra_rdoc_files: []
31
31
  files:
32
32
  - .gitignore
33
+ - .travis.yml
33
34
  - Gemfile
34
35
  - Gemfile.lock
35
36
  - Guardfile
36
37
  - README.md
37
38
  - Rakefile
38
39
  - lib/sinatra/named_routes.rb
40
+ - lib/sinatra/route_parser.rb
39
41
  - lib/sinatra/version.rb
40
42
  - sinatra-named-routes.gemspec
41
43
  - spec/named_routes_spec.rb
44
+ - spec/route_parser_spec.rb
42
45
  - spec/spec_helper.rb
43
46
  homepage: https://github.com/ckhampus/sinatra-named-routes
44
47
  licenses: []
@@ -60,8 +63,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
63
  version: '0'
61
64
  requirements: []
62
65
  rubyforge_project: sinatra-named-routes
63
- rubygems_version: 1.7.2
66
+ rubygems_version: 1.8.15
64
67
  signing_key:
65
68
  specification_version: 3
66
69
  summary: Named Routes for Sinatra
67
- test_files: []
70
+ test_files:
71
+ - spec/named_routes_spec.rb
72
+ - spec/route_parser_spec.rb
73
+ - spec/spec_helper.rb