georgi-kontrol 0.1.6 → 0.2

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/README.md CHANGED
@@ -2,21 +2,31 @@ Kontrol - a micro framework
2
2
  ===========================
3
3
 
4
4
  Kontrol is a small web framework written in Ruby, which runs directly
5
- on [Rack][5]. It uses regular expressions for routing.
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.
6
8
 
7
- All examples can be found in the [examples folder][3] of the kontrol
8
- project, which is hosted on [this github page][4].
9
+ All examples can be found in the examples folder of the kontrol
10
+ project, which is hosted on [github][2].
9
11
 
10
12
  ## Quick Start
11
13
 
12
- Create a file named `hello_world.ru`:
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`:
13
20
 
14
- require 'kontrol'
15
-
16
21
  class HelloWorld < Kontrol::Application
22
+
23
+ def time
24
+ Time.now.strftime "%H:%M:%S"
25
+ end
26
+
17
27
  map do
18
- get '/' do
19
- "Hello World!"
28
+ root '/' do
29
+ text "<h1>Hello World at #{time}</h1>"
20
30
  end
21
31
  end
22
32
  end
@@ -30,21 +40,19 @@ Now run:
30
40
  Browse to `http://localhost:9292` and you will see "Hello World".
31
41
 
32
42
 
33
- ## Features
34
-
35
- Kontrol is just a thin layer on top of Rack. It provides a routing
36
- algorithm, a simple template mechanism and some convenience stuff to
37
- work with [GitStore][1].
43
+ ## Basics
38
44
 
39
45
  A Kontrol application is a class, which provides some context to the
40
46
  defined actions. You will probably use these methods:
41
47
 
42
- * request: the Rack request object
43
- * response: the Rack response object
44
- * params: union of GET and POST parameters
45
- * cookies: shortcut to request.cookies
46
- * session: shortcut to `request.env['rack.session']`
47
- * redirect(path): renders a redirect response to specified path
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
48
56
 
49
57
 
50
58
  ## Routing
@@ -58,12 +66,12 @@ Create a file named `routing.ru`:
58
66
 
59
67
  class Routing < Kontrol::Application
60
68
  map do
61
- get '/pages/(.*)' do |name|
62
- "This is the page #{name}!"
69
+ pages '/pages/(.*)' do |name|
70
+ text "The path is #{ pages_path name }! "
63
71
  end
64
-
65
- get '/(\d*)/(\d*)' do |year, month|
66
- "Archive for #{year}/#{month}"
72
+
73
+ archive '/(\d*)/(\d*)' do |year, month|
74
+ text "The path is #{ archive_path year, month }! "
67
75
  end
68
76
  end
69
77
  end
@@ -74,52 +82,17 @@ Now run this application:
74
82
 
75
83
  rackup routing.ru
76
84
 
77
-
78
85
  You will now see, how regex groups and parameters are related. For
79
86
  example if you browse to `localhost:9292/2008/12`, the app will
80
- display `Archive for 2008/12`.
87
+ display `The path is /2008/12`.
81
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.
82
92
 
83
- ## Nested Routes
93
+ For example the route `/page/(.*)` named page will recognize the path
94
+ `/page/about`, which can be generated by using `page_path('about')`.
84
95
 
85
- Routes can be nested. This way you can avoid repeating patterns and
86
- define handlers for a set of HTTP verbs. Each handler will be called
87
- with the same arguments.
88
-
89
- require 'kontrol'
90
-
91
- class Nested < Kontrol::Application
92
- map do
93
- map '/blog' do
94
- get '/archives' do
95
- "The archives!"
96
- end
97
- end
98
-
99
- map '/(.*)' do
100
- get do |path|
101
- "<form method='post'><input type='submit'/></form>"
102
- end
103
-
104
- post do |path|
105
- "You posted to #{path}"
106
- end
107
- end
108
- end
109
- end
110
-
111
- run Nested.new
112
-
113
- Now run this app like:
114
-
115
- rackup nested.ru
116
-
117
- The second route catches all paths except the `/blog` route. Inside
118
- the second route there are two different handlers for `GET` and `POST`
119
- actions.
120
-
121
- So if you browse to `/something`, you will see a submit button. After
122
- submitting you will see the result of the second handler.
123
96
 
124
97
  ## Templates
125
98
 
@@ -146,7 +119,7 @@ Create a templates.ru file:
146
119
 
147
120
  class Templates < Kontrol::Application
148
121
  map do
149
- get '/(.*)' do |name|
122
+ page '/(.*)' do |name|
150
123
  render "page.rhtml", :title => name.capitalize, :body => "This is the body!"
151
124
  end
152
125
  end
@@ -165,17 +138,17 @@ passed to the `render` call.
165
138
 
166
139
  ## Using GitStore
167
140
 
168
- [GitStore][1] is another library, which allows you to store code and
141
+ [GitStore][3] is another library, which allows you to store code and
169
142
  data in a convenient way in a git repository. The repository is
170
143
  checked out into memory and any data may be saved back into the
171
144
  repository.
172
145
 
173
- Install [GitStore][1] and [Grit][2] by:
146
+ Install [GitStore][] by:
174
147
 
175
- $ gem sources -a http://gems.github.com (you only have to do this once)
176
- $ sudo gem install mojombo-grit georgi-git_store
148
+ $ gem sources -a http://gems.github.com
149
+ $ sudo gem install georgi-git_store
177
150
 
178
- We create a Markdown file name `pages/index.md`:
151
+ We create a Markdown file name `index.md`:
179
152
 
180
153
  Hello World
181
154
  ===========
@@ -185,23 +158,24 @@ We create a Markdown file name `pages/index.md`:
185
158
  We have now a simple page, which should be rendered as response. We
186
159
  create a simple app in a file `git_app.ru`:
187
160
 
188
- require 'kontrol'
189
- require 'bluecloth'
190
-
191
161
  class GitApp < Kontrol::Application
162
+
163
+ def initialize(path)
164
+ super
165
+ @store = GitStore.new(path)
166
+ end
167
+
192
168
  map do
193
- get '/(.*)' do |name|
194
- BlueCloth.new(store['pages', name + '.md']).to_html
169
+ page '/(.*)' do |name|
170
+ text BlueCloth.new(@store[name + '.md']).to_html
195
171
  end
196
172
  end
197
173
  end
198
-
199
- run GitApp.new
200
174
 
201
- Add all these files to your repo:
175
+ Add all the page to your git repository:
202
176
 
203
177
  git init
204
- git add pages/index.md
178
+ git add index.md
205
179
  git commit -m 'init'
206
180
 
207
181
  Run the app:
@@ -211,15 +185,10 @@ Run the app:
211
185
  Browse to `http://localhost:9292/index` and you will see the rendered
212
186
  page generated from the markdown file.
213
187
 
214
- This application runs straight from the git repository. You can delete
215
- all files except the rackup file and the app will still serve the page
216
- from your repo.
188
+ This application runs straight from the git repository. You can even
189
+ delete the page and it will still show up over the web.
217
190
 
218
191
 
219
- [1]: http://github.com/georgi/git_store
220
- [2]: http://github.com/mojombo/grit
221
- [3]: http://github.com/georgi/kontrol/tree/master/examples
222
- [4]: http://github.com/georgi/kontrol
223
- [5]: http://github.com/chneukirchen/rack
224
- [6]: http://github.com/chneukirchen/rack/tree/master/lib/rack/request.rb
225
- [7]: http://github.com/chneukirchen/rack/tree/master/lib/rack/response.rb
192
+ [1]: http://github.com/chneukirchen/rack
193
+ [2]: http://github.com/georgi/kontrol
194
+ [3]: http://github.com/georgi/git_store
data/examples/git_app.ru CHANGED
@@ -7,10 +7,10 @@ class GitApp < Kontrol::Application
7
7
  super
8
8
  @store = GitStore.new(path)
9
9
  end
10
-
10
+
11
11
  map do
12
- get '/(.*)' do |name|
13
- BlueCloth.new(@store['pages', name + '.md']).to_html
12
+ page '/(.*)' do |name|
13
+ text BlueCloth.new(@store[name + '.md']).to_html
14
14
  end
15
15
  end
16
16
  end
@@ -1,9 +1,14 @@
1
1
  require 'kontrol'
2
2
 
3
3
  class HelloWorld < Kontrol::Application
4
+
5
+ def time
6
+ Time.now.strftime "%H:%M:%S"
7
+ end
8
+
4
9
  map do
5
- get '/' do
6
- "Hello World!"
10
+ root '/' do
11
+ text "<h1>Hello World at #{time}</h1>"
7
12
  end
8
13
  end
9
14
  end
data/examples/routing.ru CHANGED
@@ -2,12 +2,12 @@ require 'kontrol'
2
2
 
3
3
  class Routing < Kontrol::Application
4
4
  map do
5
- get '/pages/(.*)' do |name|
6
- "This is the page #{name}!"
5
+ pages '/pages/(.*)' do |name|
6
+ text "The path is #{ pages_path name }! "
7
7
  end
8
8
 
9
- get '/(\d*)/(\d*)' do |year, month|
10
- "Archive for #{year}/#{month}"
9
+ archive '/(\d*)/(\d*)' do |year, month|
10
+ text "The path is #{ archive_path year, month }! "
11
11
  end
12
12
  end
13
13
  end
@@ -2,7 +2,7 @@ require 'kontrol'
2
2
 
3
3
  class Templates < Kontrol::Application
4
4
  map do
5
- get '/(.*)' do |name|
5
+ page '/(.*)' do |name|
6
6
  render "page.rhtml", :title => name.capitalize, :body => "This is the body!"
7
7
  end
8
8
  end
data/lib/kontrol.rb CHANGED
@@ -8,5 +8,5 @@ require 'kontrol/helpers'
8
8
  require 'kontrol/mime_types'
9
9
  require 'kontrol/template'
10
10
  require 'kontrol/application'
11
- require 'kontrol/builder'
12
11
  require 'kontrol/router'
12
+ require 'kontrol/route'
@@ -3,25 +3,18 @@ require 'digest/sha1'
3
3
  module Kontrol
4
4
 
5
5
  class Application
6
+ include Helpers
7
+
8
+ attr_reader :path
6
9
 
7
10
  class << self
11
+ attr_accessor :router
12
+
8
13
  def map(&block)
9
- if block
10
- @map = Builder.new(&block)
11
- else
12
- @map
13
- end
14
+ @router = Router.new(&block)
14
15
  end
15
-
16
- def call(env)
17
- @map.call(env)
18
- end
19
16
  end
20
17
 
21
- include Helpers
22
-
23
- attr_reader :path
24
-
25
18
  def initialize(path = '.')
26
19
  @path = File.expand_path(path)
27
20
  end
@@ -35,20 +28,20 @@ module Kontrol
35
28
  Template.render(load_template(file), self, file, vars)
36
29
  end
37
30
 
38
- def render_layout(vars)
39
- render_template(vars[:layout] || "layout.rhtml", vars)
40
- end
41
-
42
31
  # Render named template and insert into layout with given variables.
43
32
  def render(name, vars = {})
44
33
  content = render_template(name, vars)
34
+ layout = vars[:layout] || "layout.rhtml"
45
35
 
46
- if name[0, 1] == '_' or vars[:layout] == false
47
- content
36
+ if name[0, 1] == '_'
37
+ return content
38
+ elsif layout == false
39
+ response.body = content
48
40
  else
49
- vars.merge!(:content => content)
50
- render_layout(vars)
41
+ response.body = render_template(layout, vars.merge(:content => content))
51
42
  end
43
+
44
+ response['Content-Length'] = response.body.size.to_s
52
45
  end
53
46
 
54
47
  def etag(string)
@@ -75,30 +68,24 @@ module Kontrol
75
68
  end
76
69
  end
77
70
 
78
- def request
79
- Thread.current['request']
80
- end
81
-
82
- def response
83
- Thread.current['response']
84
- end
85
-
86
- def params
87
- request.params
88
- end
89
-
90
- def cookies
91
- request.cookies
92
- end
71
+ def request ; Thread.current['request'] end
72
+ def response; Thread.current['response'] end
73
+ def params ; request.params end
74
+ def cookies ; request.cookies end
75
+ def session ; request.env['rack.session'] end
76
+ def post? ; request.post? end
77
+ def get? ; request.get? end
78
+ def put? ; request.put? end
79
+ def delete? ; request.delete? end
93
80
 
94
- def session
95
- request.env['rack.session']
81
+ def text(s)
82
+ response.body = s
83
+ response['Content-Length'] = response.body.size.to_s
96
84
  end
97
85
 
98
86
  def redirect(path)
99
87
  response['Location'] = path
100
88
  response.status = 301
101
- response.body = "Redirect to: #{path}"
102
89
  end
103
90
 
104
91
  def guess_content_type
@@ -106,31 +93,42 @@ module Kontrol
106
93
  MIME_TYPES[ext] || 'text/html'
107
94
  end
108
95
 
96
+ def router
97
+ self.class.router
98
+ end
99
+
109
100
  def call(env)
110
101
  Thread.current['request'] = Rack::Request.new(env)
111
- Thread.current['response'] = Rack::Response.new([], nil, { 'Content-Type' => '' })
112
-
113
- env['kontrol.app'] = self
102
+ Thread.current['response'] = Rack::Response.new([], 200, { 'Content-Type' => '' })
114
103
 
115
- status, header, body = self.class.call(env)
104
+ route, match = router.__recognize__(request)
116
105
 
117
- response.status = status if response.status.nil?
118
- response.header.merge!(header)
119
- response.body = body if response.body.empty?
120
-
121
- if response.status != 304
122
- response.body ||= ''
106
+ if route
107
+ method = "process_#{route.name}"
108
+ self.class.send(:define_method, method, &route.block)
109
+ send(method, *match.to_a[1..-1])
110
+ else
111
+ response.body = "<h1>404 - Page Not Found</h1>"
123
112
  response['Content-Length'] = response.body.size.to_s
113
+ response.status = 404
124
114
  end
125
-
126
- response['Content-Type'] = guess_content_type if response['Content-Type'].empty?
127
115
 
116
+ response['Content-Type'] = guess_content_type if response['Content-Type'].empty?
128
117
  response.finish
129
118
  end
130
119
 
131
120
  def inspect
132
121
  "#<#{self.class.name} @path=#{path}>"
133
122
  end
123
+
124
+ def method_missing(name, *args, &block)
125
+ if match = name.to_s.match(/^(.*)_path$/)
126
+ router.__find__(match[1]).generate(*args)
127
+ else
128
+ super
129
+ end
130
+ end
131
+
134
132
  end
135
133
 
136
134
  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,28 @@
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
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -1,47 +1,33 @@
1
1
  module Kontrol
2
2
 
3
3
  class Router
4
+
5
+ def initialize(&block)
6
+ @routes = []
7
+ @map = {}
4
8
 
5
- def initialize
6
- @routing = []
9
+ instance_eval(&block) if block
7
10
  end
8
11
 
9
- def map(pattern, block, options = {})
10
- @routing << [pattern, block, options]
12
+ def __find__(name)
13
+ @map[name.to_sym]
11
14
  end
12
15
 
13
- OPTION_MAPPING = {
14
- :method => 'REQUEST_METHOD',
15
- :port => 'SERVER_PORT',
16
- :host => 'HTTP_HOST',
17
- :accept => 'HTTP_ACCEPT',
18
- :query => 'QUERY_STRING',
19
- :content_type => 'CONTENT_TYPE'
20
- }
21
-
22
- def options_match(env, options)
23
- options.all? do |name, pattern|
24
- value = env[OPTION_MAPPING[name] || name]
25
- value and pattern.match(value)
26
- end
27
- end
28
-
29
- def call(env)
30
- path = env["PATH_INFO"].to_s.squeeze("/")
31
-
32
- @routing.each do |pattern, app, options|
33
- if (match = path.match(/^#{pattern}/)) && options_match(env, options)
34
- env = env.dup
35
- (env['kontrol.args'] ||= []).concat(match.to_a[1..-1])
36
- if match[0] == pattern
37
- env["SCRIPT_NAME"] += match[0]
38
- env["PATH_INFO"] = path[match[0].size..-1]
39
- end
40
- return app.call(env)
16
+ def __recognize__(request)
17
+ @routes.each do |route|
18
+ if match = route.recognize(request)
19
+ return route, match
41
20
  end
42
21
  end
22
+
23
+ return nil
24
+ end
43
25
 
44
- [404, {"Content-Type" => "text/plain"}, ["Not Found: #{env['REQUEST_URI']}"]]
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
45
31
  end
46
32
 
47
33
  end
@@ -6,8 +6,12 @@ module Kontrol
6
6
  include Helpers
7
7
 
8
8
  # Initialize this template with an ERB instance.
9
- def initialize(app)
9
+ def initialize(app, vars)
10
10
  @__app__ = app
11
+
12
+ vars.each do |k, v|
13
+ instance_variable_set "@#{k}", v
14
+ end
11
15
  end
12
16
 
13
17
  def __binding__
@@ -15,19 +19,9 @@ module Kontrol
15
19
  end
16
20
 
17
21
  def self.render(erb, app, file, vars)
18
- template = Template.new(app)
19
-
20
- for name, value in vars
21
- template.send :instance_variable_set, "@#{name}", value
22
- end
23
-
24
- result = erb.result(template.__binding__)
25
-
26
- for name in template.instance_variables
27
- vars[name[1..-1]] = template.send(:instance_variable_get, name) unless name == '@__app__'
28
- end
29
-
30
- return result
22
+ template = Template.new(app, vars)
23
+
24
+ return erb.result(template.__binding__)
31
25
 
32
26
  rescue => e
33
27
  e.backtrace.each do |s|
@@ -1,135 +1,111 @@
1
1
  require 'kontrol'
2
- require 'git_store'
3
2
  require 'rack/mock'
4
3
 
5
- describe Kontrol::Builder do
6
-
7
- REPO = File.exist?('/tmp') ? '/tmp/test' : File.expand_path(File.dirname(__FILE__) + '/repo')
4
+ describe Kontrol::Application do
8
5
 
9
6
  before do
10
- FileUtils.rm_rf REPO
11
- Dir.mkdir REPO
12
- Dir.chdir REPO
13
- `git init`
14
-
15
- ENV['RACK_ENV'] = 'production'
16
-
17
7
  @class = Class.new(Kontrol::Application)
18
- @app = @class.new(REPO)
8
+ @app = @class.new
19
9
  @request = Rack::MockRequest.new(@app)
20
10
  end
21
11
 
22
12
  def get(*args)
23
13
  @request.get(*args)
24
14
  end
25
-
26
- def post(*args)
27
- @request.post(*args)
28
- end
29
-
30
- def delete(*args)
31
- @request.delete(*args)
32
- end
33
-
34
- def put(*args)
35
- @request.put(*args)
36
- end
37
15
 
38
16
  def map(&block)
39
17
  @class.map(&block)
40
18
  end
41
-
42
- def file(file, data)
43
- FileUtils.mkpath(File.dirname(file))
44
- open(file, 'w') { |io| io << data }
45
- `git add #{file}`
46
- `git commit -m 'spec'`
47
- File.unlink(file)
48
- end
49
19
 
50
- it "should understand all verbs" do
51
- map do
52
- get '/one' do 'one' end
53
- post '/one' do 'two' end
54
- delete '/one' do 'three' end
55
- put '/two' do 'four' end
20
+ it "should do simple pattern matching" do
21
+ map do
22
+ one '/one' do
23
+ response.body = 'one'
24
+ end
25
+
26
+ two '/two' do
27
+ response.body = 'two'
28
+ end
56
29
  end
57
30
 
58
31
  get("/one").body.should == 'one'
59
- post("/one").body.should == 'two'
60
- delete("/one").body.should == 'three'
61
- put("/two").body.should == 'four'
32
+ get("/two").body.should == 'two'
62
33
  end
63
34
 
64
- it "should redirect" do
35
+ it "should have a router" do
65
36
  map do
66
- get '/' do redirect 'x' end
37
+ root '/'
67
38
  end
68
39
 
69
- get('/')['Location'].should == 'x'
70
- get('/').status.should == 301
40
+ @class.router.should_not be_nil
71
41
  end
42
+
43
+ it "should generate paths" do
44
+ map do
45
+ root '/'
46
+ about '/about'
47
+ page '/page/(.*)'
48
+ end
72
49
 
73
- it "should reload after a commit" do
74
- file 'file', 'file'
50
+ @app.root_path.should == '/'
51
+ @app.about_path.should == '/about'
52
+ @app.page_path('world').should == '/page/world'
53
+ end
75
54
 
55
+ it "should redirect" do
76
56
  map do
77
- get '/(.*)' do |path|
78
- store[path]
57
+ root '/' do
58
+ redirect 'x'
79
59
  end
80
60
  end
81
61
 
82
- get("/file").body.should == 'file'
83
-
84
- file 'file', 'changed'
85
-
86
- get("/file").body.should == 'changed'
62
+ get('/')['Location'].should == 'x'
63
+ get('/').status.should == 301
87
64
  end
88
65
 
89
- it "should serve assets" do
90
- file 'assets/javascripts/index.js', 'index'
91
- file 'assets/stylesheets/styles.css', 'styles'
92
- file 'assets/file', 'file'
93
-
66
+ it "should respond with not modified" do
94
67
  map do
95
- get '/assets/javascripts\.js' do
96
- scripts = store['assets/javascripts'].to_a.join
97
- if_none_match(etag(scripts)) { scripts }
68
+ assets '/assets/(.*)' do
69
+ script = "script"
70
+ if_none_match(etag(script)) do
71
+ text script
72
+ end
98
73
  end
74
+ end
99
75
 
100
- get '/assets/stylesheets\.css' do
101
- styles = store['assets/stylesheets'].to_a.join
102
- if_none_match(etag(styles)) { styles }
103
- end
76
+ get("/assets/test.js").body.should == 'script'
77
+
78
+ etag = get("/assets/file")['Etag']
79
+ get("/assets/file", 'HTTP_IF_NONE_MATCH' => etag).status.should == 304
80
+ end
104
81
 
105
- get '/assets/(.*)' do |path|
106
- file = store['assets'][path]
107
- if_none_match(etag(file)) { file }
82
+ it "should render text" do
83
+ map do
84
+ index '/' do
85
+ text "Hello"
108
86
  end
109
87
  end
110
88
 
111
- get("/assets/javascripts.js").body.should == 'index'
112
- get("/assets/javascripts.js")['Content-Type'].should == 'text/javascript'
113
- get("/assets/stylesheets.css").body.should == 'styles'
114
- get("/assets/stylesheets.css")['Content-Type'].should == 'text/css'
115
-
116
- get("/assets/file").body.should == 'file'
117
- etag = get("/assets/file")['Etag']
118
-
119
- get("/assets/file", 'HTTP_IF_NONE_MATCH' => etag).status.should == 304
89
+ get('/').body.should == 'Hello'
90
+ get('/')['Content-Length'].should == '5'
120
91
  end
121
92
 
122
93
  it "should render templates" do
123
- file 'templates/layout.rhtml', '<%= @content %>'
124
- file 'templates/index.rhtml', '<%= @body %>'
125
-
94
+ def @app.load_template(file)
95
+ if file == "layout.rhtml"
96
+ ERB.new '<html><%= @content %></html>'
97
+ else
98
+ ERB.new '<p><%= @body %></p>'
99
+ end
100
+ end
101
+
126
102
  map do
127
- get '/' do
103
+ index '/' do
128
104
  render 'index.rhtml', :body => 'BODY'
129
105
  end
130
106
  end
131
107
 
132
- get('/').body.should == 'BODY'
108
+ get('/').body.should == '<html><p>BODY</p></html>'
133
109
  end
134
110
 
135
111
  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 CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: georgi-kontrol
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: "0.2"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthias Georgi
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-04-12 00:00:00 -07:00
12
+ date: 2008-04-13 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: A small web framework running as rack application.
16
+ description: Small web framework running on top of rack.
17
17
  email: matti.georgi@gmail.com
18
18
  executables: []
19
19
 
@@ -22,26 +22,24 @@ extensions: []
22
22
  extra_rdoc_files:
23
23
  - README.md
24
24
  files:
25
- - .gitignore
26
25
  - LICENSE
27
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
28
33
  - lib/kontrol.rb
29
34
  - lib/kontrol/application.rb
30
- - lib/kontrol/builder.rb
31
35
  - lib/kontrol/helpers.rb
36
+ - lib/kontrol/mime_types.rb
37
+ - lib/kontrol/route.rb
32
38
  - lib/kontrol/router.rb
33
39
  - lib/kontrol/template.rb
34
40
  - test/application_spec.rb
35
- - examples/routing.ru
36
- - examples/pages
37
- - examples/pages/index.md
38
- - examples/git_app.ru
39
- - examples/templates.ru
40
- - examples/templates
41
- - examples/templates/page.rhtml
42
- - examples/templates/layout.rhtml
43
- - examples/nested.ru
44
- - examples/hello_world.ru
41
+ - test/route_spec.rb
42
+ - test/router_spec.rb
45
43
  has_rdoc: true
46
44
  homepage: http://github.com/georgi/kontrol
47
45
  post_install_message:
data/.gitignore DELETED
@@ -1,2 +0,0 @@
1
- *~
2
- test/repo
data/examples/nested.ru DELETED
@@ -1,23 +0,0 @@
1
- require 'kontrol'
2
-
3
- class Nested < Kontrol::Application
4
- map do
5
- map '/blog' do
6
- get '/archives' do
7
- "The archives!"
8
- end
9
- end
10
-
11
- map '(.*)' do
12
- get do |path|
13
- "<form method='post'><input type='submit'/></form>"
14
- end
15
-
16
- post do |path|
17
- "You called #{path}"
18
- end
19
- end
20
- end
21
- end
22
-
23
- run Nested.new
@@ -1,5 +0,0 @@
1
- Hello World
2
- ===========
3
-
4
- This is the **Index** page!
5
-
@@ -1,78 +0,0 @@
1
- module Kontrol
2
-
3
- def self.map(&block)
4
- Builder.new(&block)
5
- end
6
-
7
- class Builder
8
-
9
- def initialize(&block)
10
- @router = Router.new
11
- @ins = []
12
- instance_eval(&block) if block
13
- end
14
-
15
- def use(middleware, *args, &block)
16
- @ins << lambda { |app| middleware.new(app, *args, &block) }
17
- end
18
-
19
- def run(app)
20
- @ins << app
21
- end
22
-
23
- def get(*args, &block)
24
- map_method(:get, *args, &block)
25
- end
26
-
27
- def put(*args, &block)
28
- map_method(:put, *args, &block)
29
- end
30
-
31
- def post(*args, &block)
32
- map_method(:post, *args, &block)
33
- end
34
-
35
- def delete(*args, &block)
36
- map_method(:delete, *args, &block)
37
- end
38
-
39
- def map(pattern, &block)
40
- @router.map(pattern, Builder.new(&block))
41
- end
42
-
43
- def call(env)
44
- to_app.call(env)
45
- end
46
-
47
- def to_app
48
- @ins.reverse.inject(@router) { |a, e| e.call(a) }
49
- end
50
-
51
- private
52
-
53
- def method_from_proc(obj, proc)
54
- name = "proc_#{proc.object_id}"
55
- unless obj.respond_to?(name)
56
- singleton = class << obj; self; end
57
- singleton.send(:define_method, name, &proc)
58
- end
59
- obj.method(name)
60
- end
61
-
62
- def map_method(method, pattern = '.*', options = {}, &block)
63
- on(pattern, options.merge(:method => method.to_s.upcase), &block)
64
- end
65
-
66
- def on(pattern = '.*', options = {}, &block)
67
- wrap = lambda do |env|
68
- env['kontrol.app'] or raise "no kontrol.app given"
69
- meth = method_from_proc(env['kontrol.app'], block)
70
- body = meth.call(*env['kontrol.args'])
71
- [200, {}, body]
72
- end
73
- @router.map(pattern, wrap, options)
74
- end
75
-
76
- end
77
-
78
- end