kontrol 0.3

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/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
+