kontrol 0.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2008 Matthias Georgi <http://www.matthias-georgi.de>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,201 @@
1
+ Kontrol - a micro framework
2
+ ===========================
3
+
4
+ Kontrol is a small web framework written in Ruby, which runs directly
5
+ on [Rack][1]. Basically you can mount a class as rack handler and
6
+ attach a set of named routes, which will be used for route recognition
7
+ and generation.
8
+
9
+ All examples can be found in the examples folder of the kontrol
10
+ project, which is hosted on [github][2].
11
+
12
+ ## Quick Start
13
+
14
+ We will create a simple Kontrol application with exactly one route
15
+ named 'root'. Routes are defined within a block insider your
16
+ application class. Each route has a name, a pattern and a block. The
17
+ name must be defined to generate paths pointing to this route.
18
+
19
+ `hello_world.ru`:
20
+
21
+ class HelloWorld < Kontrol::Application
22
+
23
+ def time
24
+ Time.now.strftime "%H:%M:%S"
25
+ end
26
+
27
+ map do
28
+ root '/' do
29
+ text "<h1>Hello World at #{time}</h1>"
30
+ end
31
+ end
32
+ end
33
+
34
+ run HelloWorld.new
35
+
36
+ Now run:
37
+
38
+ rackup hello_world.ru
39
+
40
+ Browse to `http://localhost:9292` and you will see "Hello World".
41
+
42
+
43
+ ## Basics
44
+
45
+ A Kontrol application is a class, which provides some context to the
46
+ defined actions. You will probably use these methods:
47
+
48
+ - __request__: the Rack request object
49
+ - __response__: the Rack response object
50
+ - __params__: union of GET and POST parameters
51
+ - __cookies__: shortcut to request.cookies
52
+ - __session__: shortcut to `request.env['rack.session']`
53
+ - __redirect(path)__: renders a redirect response to specified path
54
+ - __render(file, variables)__: render a template with specified variables
55
+ - __text(string)__: render a string
56
+
57
+
58
+ ## Routing
59
+
60
+ Routing is just as simple as using regular expressions with
61
+ groups. Each group will be provided as argument to the block.
62
+
63
+ Create a file named `routing.ru`:
64
+
65
+ require 'kontrol'
66
+
67
+ class Routing < Kontrol::Application
68
+ map do
69
+ pages '/pages/(.*)' do |name|
70
+ text "The path is #{ pages_path name }! "
71
+ end
72
+
73
+ archive '/(\d*)/(\d*)' do |year, month|
74
+ text "The path is #{ archive_path year, month }! "
75
+ end
76
+ end
77
+ end
78
+
79
+ run Routing.new
80
+
81
+ Now run this application:
82
+
83
+ rackup routing.ru
84
+
85
+ You will now see, how regex groups and parameters are related. For
86
+ example if you browse to `localhost:9292/2008/12`, the app will
87
+ display `The path is /2008/12`.
88
+
89
+ The inverse operation to route recognition is route generation. That
90
+ means a route with one or more groups can generate a url, which will
91
+ be recognized this very route.
92
+
93
+ For example the route `/page/(.*)` named page will recognize the path
94
+ `/page/about`, which can be generated by using `page_path('about')`.
95
+
96
+
97
+ ## Templates
98
+
99
+ Rendering templates is as simple as calling a template file with some
100
+ parameters, which are accessible inside the template as instance
101
+ variables. Additionally you will need a layout template.
102
+
103
+ Create a template named `templates/layout.rhtml`:
104
+
105
+ <html>
106
+ <body>
107
+ <%= @content %>
108
+ </body>
109
+ </html>
110
+
111
+ And now another template named `templates/page.rhtml`:
112
+
113
+ <h1><%= @title %></h1>
114
+ <%= @body %>
115
+
116
+ Create a templates.ru file:
117
+
118
+ require 'kontrol'
119
+
120
+ class Templates < Kontrol::Application
121
+ map do
122
+ page '/(.*)' do |name|
123
+ render "page.rhtml", :title => name.capitalize, :body => "This is the body!"
124
+ end
125
+ end
126
+ end
127
+
128
+ run Templates.new
129
+
130
+ Now run this example:
131
+
132
+ rackup templates.ru
133
+
134
+ If you browse to any path on `localhost:9292`, you will see the
135
+ rendered template. Note that the title and body parameters have been
136
+ passed to the `render` call.
137
+
138
+
139
+ ## Using GitStore
140
+
141
+ [GitStore][3] is another library, which allows you to store code and
142
+ data in a convenient way in a git repository. The repository is
143
+ checked out into memory and any data may be saved back into the
144
+ repository.
145
+
146
+ Install [GitStore][] by:
147
+
148
+ $ gem sources -a http://gems.github.com
149
+ $ sudo gem install georgi-git_store
150
+
151
+ We create a Markdown file name `index.md`:
152
+
153
+ Hello World
154
+ ===========
155
+
156
+ This is the **Index** page!
157
+
158
+ We have now a simple page, which should be rendered as response. We
159
+ create a simple app in a file `git_app.ru`:
160
+
161
+ require 'kontrol'
162
+ require 'bluecloth'
163
+ require 'git_store'
164
+
165
+ class GitApp < Kontrol::Application
166
+
167
+ def initialize(path)
168
+ super
169
+ @store = GitStore.new(path)
170
+ end
171
+
172
+ map do
173
+ page '/(.*)' do |name|
174
+ text BlueCloth.new(@store[name + '.md']).to_html
175
+ end
176
+ end
177
+ end
178
+
179
+ run GitApp.new
180
+
181
+
182
+ Add all the page to your git repository:
183
+
184
+ git init
185
+ git add index.md
186
+ git commit -m 'init'
187
+
188
+ Run the app:
189
+
190
+ rackup git_app.ru
191
+
192
+ Browse to `http://localhost:9292/index` and you will see the rendered
193
+ page generated from the markdown file.
194
+
195
+ This application runs straight from the git repository. You can even
196
+ delete the page and it will still show up over the web.
197
+
198
+
199
+ [1]: http://github.com/chneukirchen/rack
200
+ [2]: http://github.com/georgi/kontrol
201
+ [3]: http://github.com/georgi/git_store
@@ -0,0 +1,19 @@
1
+ require 'kontrol'
2
+ require 'bluecloth'
3
+ require 'git_store'
4
+
5
+ class GitApp < Kontrol::Application
6
+
7
+ def initialize(path)
8
+ super
9
+ @store = GitStore.new(path)
10
+ end
11
+
12
+ map do
13
+ page '/(.*)' do |name|
14
+ text BlueCloth.new(@store[name]).to_html
15
+ end
16
+ end
17
+ end
18
+
19
+ run GitApp.new
@@ -0,0 +1,16 @@
1
+ require 'kontrol'
2
+
3
+ class HelloWorld < Kontrol::Application
4
+
5
+ def time
6
+ Time.now.strftime "%H:%M:%S"
7
+ end
8
+
9
+ map do
10
+ root '/' do
11
+ text "<h1>Hello World at #{time}</h1>"
12
+ end
13
+ end
14
+ end
15
+
16
+ run HelloWorld.new
@@ -0,0 +1,15 @@
1
+ require 'kontrol'
2
+
3
+ class Routing < Kontrol::Application
4
+ map do
5
+ pages '/pages/(.*)' do |name|
6
+ text "The path is #{ pages_path name }! "
7
+ end
8
+
9
+ archive '/(\d*)/(\d*)' do |year, month|
10
+ text "The path is #{ archive_path year, month }! "
11
+ end
12
+ end
13
+ end
14
+
15
+ run Routing.new
@@ -0,0 +1,5 @@
1
+ <html>
2
+ <body>
3
+ <%= @content %>
4
+ </body>
5
+ </html>
@@ -0,0 +1,2 @@
1
+ <h1><%= @title %></h1>
2
+ <%= @body %>
@@ -0,0 +1,11 @@
1
+ require 'kontrol'
2
+
3
+ class Templates < Kontrol::Application
4
+ map do
5
+ page '/(.*)' do |name|
6
+ render "page.rhtml", :title => name.capitalize, :body => "This is the body!"
7
+ end
8
+ end
9
+ end
10
+
11
+ run Templates.new
@@ -0,0 +1,155 @@
1
+ require 'digest/sha1'
2
+
3
+ module Kontrol
4
+
5
+ class Application
6
+ include Helpers
7
+
8
+ attr_reader :path
9
+
10
+ class << self
11
+ attr_accessor :router
12
+
13
+ def map(&block)
14
+ @router = Router.new(&block)
15
+ end
16
+ end
17
+
18
+ def initialize(path = '.')
19
+ @path = File.expand_path(path)
20
+ end
21
+
22
+ def load_template(file)
23
+ ERB.new(File.read("#{self.path}/templates/#{file}"))
24
+ end
25
+
26
+ # Render template with given variables.
27
+ def render_template(file, vars)
28
+ template = load_template(file) or raise "not found: #{path}"
29
+ Template.render(template, self, "#{self.path}/templates/#{file}", vars)
30
+ end
31
+
32
+ # Render named template and insert into layout with given variables.
33
+ def render(name, options = {})
34
+ options = options.merge(:request => request, :params => params)
35
+ content = render_template(name, options)
36
+ layout = options.delete(:layout)
37
+
38
+ if name[0, 1] == '_'
39
+ return content
40
+
41
+ elsif layout == false
42
+ response.body = content
43
+ else
44
+ options.merge!(:content => content)
45
+ response.body = render_template(layout || "layout.rhtml", options)
46
+ end
47
+
48
+ response['Content-Length'] = response.body.size.to_s
49
+ end
50
+
51
+ def etag(string)
52
+ Digest::SHA1.hexdigest(string)
53
+ end
54
+
55
+ def if_modified_since(time)
56
+ date = time.respond_to?(:httpdate) ? time.httpdate : time
57
+ response['Last-Modified'] = date
58
+
59
+ if request.env['HTTP_IF_MODIFIED_SINCE'] == date
60
+ response.status = 304
61
+ else
62
+ yield
63
+ end
64
+ end
65
+
66
+ def if_none_match(etag)
67
+ response['Etag'] = etag
68
+ if request.env['HTTP_IF_NONE_MATCH'] == etag
69
+ response.status = 304
70
+ else
71
+ yield
72
+ end
73
+ end
74
+
75
+ def request ; Thread.current['request'] end
76
+ def response; Thread.current['response'] end
77
+ def params ; request.params end
78
+ def cookies ; request.cookies end
79
+ def session ; request.env['rack.session'] end
80
+ def post? ; request.post? end
81
+ def get? ; request.get? end
82
+ def put? ; request.put? end
83
+ def delete? ; request.delete? end
84
+ def post ; request.post? and yield end
85
+ def get ; request.get? and yield end
86
+ def put ; request.put? and yield end
87
+ def delete ; request.delete? and yield end
88
+
89
+ def text(s)
90
+ response.body = s
91
+ response['Content-Length'] = response.body.size.to_s
92
+ end
93
+
94
+ def redirect(path)
95
+ response['Location'] = path
96
+ response.status = 301
97
+ end
98
+
99
+ def guess_content_type
100
+ ext = File.extname(request.path_info)[1..-1]
101
+ MIME_TYPES[ext] || 'text/html'
102
+ end
103
+
104
+ def router
105
+ self.class.router
106
+ end
107
+
108
+ def call(env)
109
+ Thread.current['request'] = Rack::Request.new(env)
110
+ Thread.current['response'] = Rack::Response.new([], 200, { 'Content-Type' => '' })
111
+
112
+ route, match = router.__recognize__(request)
113
+
114
+ if route
115
+ method = "process_#{route.name}"
116
+ self.class.send(:define_method, method, &route.block)
117
+ send(method, *match.to_a[1..-1])
118
+ else
119
+ response.body = "<h1>404 - Page Not Found</h1>"
120
+ response['Content-Length'] = response.body.size.to_s
121
+ response.status = 404
122
+ end
123
+
124
+ response['Content-Type'] = guess_content_type if response['Content-Type'].empty?
125
+ response.finish
126
+ end
127
+
128
+ def inspect
129
+ "#<#{self.class.name} @path=#{path}>"
130
+ end
131
+
132
+ def respond_to?(name)
133
+ if match = name.to_s.match(/^(.*)_path$/)
134
+ router.__find__(match[1])
135
+ else
136
+ super
137
+ end
138
+ end
139
+
140
+ def method_missing(name, *args, &block)
141
+ if match = name.to_s.match(/^(.*)_path$/)
142
+ if route = router.__find__(match[1])
143
+ route.generate(*args)
144
+ else
145
+ super
146
+ end
147
+ else
148
+ super
149
+ end
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+
@@ -0,0 +1,41 @@
1
+ module Kontrol
2
+
3
+ module Helpers
4
+
5
+ # Render a HTML tag with given name.
6
+ # The last argument specifies the attributes of the tag.
7
+ # The second argument may be the content of the tag.
8
+ def tag(name, *args)
9
+ text, attr = args.first.is_a?(Hash) ? [nil, args.first] : args
10
+ attributes = attr.map { |k, v| %Q{#{k}="#{v}"} }.join(' ')
11
+ "<#{name} #{attributes}>#{text}</#{name}>"
12
+ end
13
+
14
+ # Render a link
15
+ def link_to(text, path, options = {})
16
+ tag :a, text, options.merge(:href => path)
17
+ end
18
+
19
+ def markdown(text, *args)
20
+ BlueCloth.new(text, *args).to_html
21
+ rescue => e
22
+ "#{text}<br/><br/><strong style='color:red'>#{e.message}</strong>"
23
+ end
24
+
25
+ def strip_tags(str)
26
+ str.to_s.gsub(/<\/?[^>]*>/, "")
27
+ end
28
+
29
+ def urlify(string)
30
+ string.downcase.gsub(/[ -]+/, '-').gsub(/[^-a-z0-9_]+/, '')
31
+ end
32
+
33
+ HTML_ESCAPE = { '&' => '&amp;', '"' => '&quot;', '>' => '&gt;', '<' => '&lt;', ' ' => '&nbsp;' }
34
+
35
+ def h(s)
36
+ s.to_s.gsub(/[ &"><]/) { |special| HTML_ESCAPE[special] }
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,62 @@
1
+ module Kontrol
2
+
3
+ MIME_TYPES = {
4
+ "ai" => "application/postscript",
5
+ "asc" => "text/plain",
6
+ "avi" => "video/x-msvideo",
7
+ "bin" => "application/octet-stream",
8
+ "bmp" => "image/bmp",
9
+ "class" => "application/octet-stream",
10
+ "cer" => "application/pkix-cert",
11
+ "crl" => "application/pkix-crl",
12
+ "crt" => "application/x-x509-ca-cert",
13
+ "css" => "text/css",
14
+ "dms" => "application/octet-stream",
15
+ "doc" => "application/msword",
16
+ "dvi" => "application/x-dvi",
17
+ "eps" => "application/postscript",
18
+ "etx" => "text/x-setext",
19
+ "exe" => "application/octet-stream",
20
+ "gif" => "image/gif",
21
+ "htm" => "text/html",
22
+ "html" => "text/html",
23
+ "ico" => "image/x-icon",
24
+ "jpe" => "image/jpeg",
25
+ "jpeg" => "image/jpeg",
26
+ "jpg" => "image/jpeg",
27
+ "js" => "text/javascript",
28
+ "lha" => "application/octet-stream",
29
+ "lzh" => "application/octet-stream",
30
+ "mov" => "video/quicktime",
31
+ "mp3" => "audio/mpeg",
32
+ "mpe" => "video/mpeg",
33
+ "mpeg" => "video/mpeg",
34
+ "mpg" => "video/mpeg",
35
+ "pbm" => "image/x-portable-bitmap",
36
+ "pdf" => "application/pdf",
37
+ "pgm" => "image/x-portable-graymap",
38
+ "png" => "image/png",
39
+ "pnm" => "image/x-portable-anymap",
40
+ "ppm" => "image/x-portable-pixmap",
41
+ "ppt" => "application/vnd.ms-powerpoint",
42
+ "ps" => "application/postscript",
43
+ "qt" => "video/quicktime",
44
+ "ras" => "image/x-cmu-raster",
45
+ "rb" => "text/plain",
46
+ "rd" => "text/plain",
47
+ "rtf" => "application/rtf",
48
+ "rss" => "application/rss+xml",
49
+ "sgm" => "text/sgml",
50
+ "sgml" => "text/sgml",
51
+ "tif" => "image/tiff",
52
+ "tiff" => "image/tiff",
53
+ "txt" => "text/plain",
54
+ "xbm" => "image/x-xbitmap",
55
+ "xls" => "application/vnd.ms-excel",
56
+ "xml" => "text/xml",
57
+ "xpm" => "image/x-xpixmap",
58
+ "xwd" => "image/x-xwindowdump",
59
+ "zip" => "application/zip",
60
+ }
61
+
62
+ end
@@ -0,0 +1,30 @@
1
+ module Kontrol
2
+
3
+ class Route
4
+ attr_accessor :name, :pattern, :options, :block
5
+
6
+ def initialize(name, pattern, options, block)
7
+ @name = name
8
+ @pattern = pattern
9
+ @block = block
10
+ @options = options || {}
11
+ @format = pattern.gsub(/\(.*?\)/, '%s')
12
+ @regexp = /^#{pattern}/
13
+ end
14
+
15
+ def recognize(request)
16
+ match = request.path_info.match(@regexp)
17
+ valid = @options.all? { |key, val| request.send(key).match(val) }
18
+
19
+ return match if match and valid
20
+ end
21
+
22
+ def generate(*args)
23
+ @format % args.map { |arg|
24
+ arg.respond_to?(:to_param) ? arg.to_param : arg.to_s
25
+ }
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,35 @@
1
+ module Kontrol
2
+
3
+ class Router
4
+
5
+ def initialize(&block)
6
+ @routes = []
7
+ @map = {}
8
+
9
+ instance_eval(&block) if block
10
+ end
11
+
12
+ def __find__(name)
13
+ @map[name.to_sym]
14
+ end
15
+
16
+ def __recognize__(request)
17
+ @routes.each do |route|
18
+ if match = route.recognize(request)
19
+ return route, match
20
+ end
21
+ end
22
+
23
+ return nil
24
+ end
25
+
26
+ def method_missing(name, pattern, *args, &block)
27
+ route = Route.new(name, pattern, args.first, block)
28
+
29
+ @routes << route
30
+ @map[name] = route
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,42 @@
1
+ module Kontrol
2
+
3
+ # This class renders an ERB template for a set of attributes, which
4
+ # are accessible as instance variables.
5
+ class Template
6
+ include Helpers
7
+
8
+ # Initialize this template with an ERB instance.
9
+ def initialize(app, vars)
10
+ @__app__ = app
11
+
12
+ vars.each do |k, v|
13
+ instance_variable_set "@#{k}", v
14
+ end
15
+ end
16
+
17
+ def __binding__
18
+ binding
19
+ end
20
+
21
+ def self.render(erb, app, file, vars)
22
+ template = Template.new(app, vars)
23
+
24
+ return erb.result(template.__binding__)
25
+
26
+ rescue => e
27
+ e.backtrace.each do |s|
28
+ s.gsub!('(erb)', file)
29
+ end
30
+ raise e
31
+ end
32
+
33
+ def method_missing(id, *args, &block)
34
+ if @__app__.respond_to?(id)
35
+ return @__app__.send(id, *args, &block)
36
+ end
37
+ super
38
+ end
39
+
40
+ end
41
+
42
+ end
data/lib/kontrol.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'rubygems'
2
+ require 'rack'
3
+ require 'erb'
4
+ require 'yaml'
5
+ require 'logger'
6
+
7
+ require 'kontrol/helpers'
8
+ require 'kontrol/mime_types'
9
+ require 'kontrol/template'
10
+ require 'kontrol/application'
11
+ require 'kontrol/router'
12
+ require 'kontrol/route'
@@ -0,0 +1,121 @@
1
+ require 'kontrol'
2
+ require 'rack/mock'
3
+
4
+ describe Kontrol::Application do
5
+
6
+ before do
7
+ @class = Class.new(Kontrol::Application)
8
+ @app = @class.new
9
+ @request = Rack::MockRequest.new(@app)
10
+
11
+ def @app.load_template(file)
12
+ if file == "layout.rhtml"
13
+ ERB.new '<html><%= @content %></html>'
14
+ else
15
+ ERB.new '<p><%= @body %></p>'
16
+ end
17
+ end
18
+ end
19
+
20
+ def get(*args)
21
+ @request.get(*args)
22
+ end
23
+
24
+ def map(&block)
25
+ @class.map(&block)
26
+ end
27
+
28
+ it "should do simple pattern matching" do
29
+ map do
30
+ one '/one' do
31
+ response.body = 'one'
32
+ end
33
+
34
+ two '/two' do
35
+ response.body = 'two'
36
+ end
37
+ end
38
+
39
+ get("/one").body.should == 'one'
40
+ get("/two").body.should == 'two'
41
+ end
42
+
43
+ it "should have a router" do
44
+ map do
45
+ root '/'
46
+ end
47
+
48
+ @class.router.should_not be_nil
49
+ end
50
+
51
+ it "should generate paths" do
52
+ map do
53
+ root '/'
54
+ about '/about'
55
+ page '/page/(.*)'
56
+ end
57
+
58
+ @app.root_path.should == '/'
59
+ @app.about_path.should == '/about'
60
+ @app.page_path('world').should == '/page/world'
61
+ end
62
+
63
+ it "should redirect" do
64
+ map do
65
+ root '/' do
66
+ redirect 'x'
67
+ end
68
+ end
69
+
70
+ get('/')['Location'].should == 'x'
71
+ get('/').status.should == 301
72
+ end
73
+
74
+ it "should respond with not modified" do
75
+ map do
76
+ assets '/assets/(.*)' do
77
+ script = "script"
78
+ if_none_match(etag(script)) do
79
+ text script
80
+ end
81
+ end
82
+ end
83
+
84
+ get("/assets/test.js").body.should == 'script'
85
+
86
+ etag = get("/assets/file")['Etag']
87
+ get("/assets/file", 'HTTP_IF_NONE_MATCH' => etag).status.should == 304
88
+ end
89
+
90
+ it "should render text" do
91
+ map do
92
+ index '/' do
93
+ text "Hello"
94
+ end
95
+ end
96
+
97
+ get('/').body.should == 'Hello'
98
+ get('/')['Content-Length'].should == '5'
99
+ end
100
+
101
+ it "should render no layout" do
102
+ map do
103
+ index '/' do
104
+ render 'index.rhtml', :body => 'BODY', :layout => false
105
+ end
106
+ end
107
+
108
+ get('/').body.should == '<p>BODY</p>'
109
+ end
110
+
111
+ it "should render templates" do
112
+ map do
113
+ index '/' do
114
+ render 'index.rhtml', :body => 'BODY'
115
+ end
116
+ end
117
+
118
+ get('/').body.should == '<html><p>BODY</p></html>'
119
+ end
120
+
121
+ end
@@ -0,0 +1,50 @@
1
+ require 'kontrol'
2
+
3
+ describe Kontrol::Route do
4
+
5
+ it "should recognize a request" do
6
+ route = Kontrol::Route.new(:test, "/test", nil, nil)
7
+ request = Rack::Request.new('PATH_INFO' => '/test')
8
+
9
+ match = route.recognize(request)
10
+
11
+ match.should_not be_nil
12
+ match[0].should == '/test'
13
+ end
14
+
15
+ it "should recognize a request by options" do
16
+ route = Kontrol::Route.new(:test, "/test", { :request_method => 'GET' }, nil)
17
+ request = Rack::Request.new('PATH_INFO' => '/test', 'REQUEST_METHOD' => 'GET')
18
+
19
+ match = route.recognize(request)
20
+
21
+ match.should_not be_nil
22
+ match[0].should == '/test'
23
+ end
24
+
25
+ it "should recognize a request with groups" do
26
+ route = Kontrol::Route.new(:test, "/test/(.*)/(.*)", nil, nil)
27
+ request = Rack::Request.new('PATH_INFO' => '/test/me/here')
28
+
29
+ match = route.recognize(request)
30
+
31
+ match.should_not be_nil
32
+ match[0].should == '/test/me/here'
33
+ match[1].should == 'me'
34
+ match[2].should == 'here'
35
+ end
36
+
37
+ it "should generate a path" do
38
+ route = Kontrol::Route.new(:test, "/test", nil, nil)
39
+
40
+ route.generate.should == '/test'
41
+ end
42
+
43
+ it "should generate a path with groups" do
44
+ route = Kontrol::Route.new(:test, "/test/(.*)/me/(\d\d)", nil, nil)
45
+
46
+ route.generate(1, 22).should == '/test/1/me/22'
47
+ end
48
+
49
+ end
50
+
@@ -0,0 +1,53 @@
1
+ require 'kontrol'
2
+
3
+ describe Kontrol::Router do
4
+
5
+ before :each do
6
+ @router = Kontrol::Router.new
7
+ end
8
+
9
+ def request(env)
10
+ Rack::Request.new(env)
11
+ end
12
+
13
+ it "should find a route" do
14
+ @router.test '/test'
15
+ route = @router.__find__(:test)
16
+
17
+ route.name.should == :test
18
+ route.pattern.should == '/test'
19
+ end
20
+
21
+ it "should recognize a route" do
22
+ request = request('PATH_INFO' => '/test')
23
+
24
+ @router.test '/test'
25
+ route, match = @router.__recognize__(request)
26
+
27
+ route.name.should == :test
28
+ route.pattern.should == '/test'
29
+ end
30
+
31
+ it "should recognize routes in right order" do
32
+ request = request('PATH_INFO' => '/test')
33
+
34
+ @router.root '/'
35
+ @router.test '/test'
36
+
37
+ route, match = @router.__recognize__(request)
38
+
39
+ route.name.should == :root
40
+ route.pattern.should == '/'
41
+ end
42
+
43
+ it "should not recognize a not matching route" do
44
+ request = request('PATH_INFO' => '/test')
45
+
46
+ @router.root '/other'
47
+
48
+ route, match = @router.__recognize__(request)
49
+
50
+ route.should be_nil
51
+ end
52
+
53
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kontrol
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.3"
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Georgi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-07 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Small web framework running on top of rack.
17
+ email: matti.georgi@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.md
24
+ files:
25
+ - LICENSE
26
+ - README.md
27
+ - examples/git_app.ru
28
+ - examples/hello_world.ru
29
+ - examples/routing.ru
30
+ - examples/templates.ru
31
+ - examples/templates/layout.rhtml
32
+ - examples/templates/page.rhtml
33
+ - lib/kontrol.rb
34
+ - lib/kontrol/application.rb
35
+ - lib/kontrol/helpers.rb
36
+ - lib/kontrol/mime_types.rb
37
+ - lib/kontrol/route.rb
38
+ - lib/kontrol/router.rb
39
+ - lib/kontrol/template.rb
40
+ - test/application_spec.rb
41
+ - test/route_spec.rb
42
+ - test/router_spec.rb
43
+ has_rdoc: true
44
+ homepage: http://github.com/georgi/kontrol
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options: []
49
+
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.3.5
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: a micro framework
71
+ test_files: []
72
+