patchbay 0.0.1.pre

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.
Files changed (2) hide show
  1. data/lib/patchbay.rb +231 -0
  2. metadata +46 -0
data/lib/patchbay.rb ADDED
@@ -0,0 +1,231 @@
1
+ require 'rack/mime'
2
+ require 'rack/handler'
3
+
4
+ class Patchbay
5
+ class Route < Struct.new(:handler, :url_parts, :verb)
6
+ end
7
+
8
+ NoRoute_Message = <<EOT
9
+ <html><body>
10
+ <h1>404</h1><p>Patchbay: Routing Error</p>
11
+ </body></html>
12
+ EOT
13
+
14
+ Forbidden_Message = <<EOT
15
+ <html><body>
16
+ <h1>403</h1>
17
+ <p>Forbidden</p>
18
+ </body></html>
19
+ EOT
20
+
21
+ Exception_Message = <<EOT
22
+ <html><body>
23
+ <h1>500</h1>
24
+ <p>Internal Server Error</p>
25
+ </body></html>
26
+ EOT
27
+
28
+ def path_is_subdir_of(path_to_test, root_path)
29
+ File.fnmatch(File.join(root_path, '**'), File.realpath(path_to_test))
30
+ end
31
+
32
+ class Router
33
+ def initialize
34
+ @routes = []
35
+ end
36
+
37
+ def matches?(route, url_parts)
38
+ if route.url_parts.size != url_parts.size
39
+ return false
40
+ end
41
+
42
+ params = { }
43
+
44
+ route.url_parts.zip(url_parts) do |parts|
45
+ route_url_part = parts[0]
46
+ url_part = parts[1]
47
+
48
+ if route_url_part =~ /:(.+)/
49
+ params[$1.intern] = url_part
50
+ else
51
+ if route_url_part != url_part
52
+ return false
53
+ end
54
+ end
55
+ end
56
+
57
+ return params
58
+ end
59
+
60
+ def match(verb, url)
61
+ parts = url.gsub(/^\/+/,'').split('/')
62
+
63
+ @routes.each do |route|
64
+ if route.verb == verb
65
+ result = matches?(route, parts)
66
+ if result
67
+ return [ route.handler, result ]
68
+ end
69
+ end
70
+ end
71
+
72
+ return [ nil, nil ]
73
+ end
74
+
75
+ def add(verb, route, handler)
76
+ parts = route.gsub(/^\/+/,'').split('/')
77
+ routeObj = Route.new
78
+ routeObj.url_parts = parts
79
+ routeObj.handler = handler
80
+ routeObj.verb = verb
81
+ @routes << routeObj
82
+ end
83
+ end
84
+
85
+ def self.router
86
+ @router ||= Router.new
87
+ @router
88
+ end
89
+
90
+ def router
91
+ self.class.router
92
+ end
93
+
94
+ def self.get(route, &action)
95
+ router.add('GET', route, action)
96
+ end
97
+
98
+ def self.put(route, &action)
99
+ router.add('PUT', route, action)
100
+ end
101
+
102
+ def self.post(route, &action)
103
+ router.add('POST', route, action)
104
+ end
105
+
106
+ def self.delete(route, &action)
107
+ router.add('DELETE', route, action)
108
+ end
109
+
110
+ def self.files_dir
111
+ @files_dir
112
+ end
113
+
114
+ def self.files_dir=(new_dir)
115
+ @files_dir = File.realpath(new_dir)
116
+ end
117
+
118
+ def files_dir
119
+ self.class.files_dir
120
+ end
121
+
122
+ def environment
123
+ @environment
124
+ end
125
+
126
+ def params
127
+ @params
128
+ end
129
+
130
+ def handle_file
131
+ url_parts = environment['PATH_INFO'].split('/')
132
+ file_path = File.join(files_dir, url_parts)
133
+
134
+ if File.exists?(file_path) and path_is_subdir_of(file_path, files_dir)
135
+ if File.readable?(file_path)
136
+ send_file(file_path)
137
+ else
138
+ handle_forbidden
139
+ end
140
+ elsif File.exists?(file_path)
141
+ handle_no_route
142
+ else
143
+ handle_no_route
144
+ end
145
+ end
146
+
147
+ def send_file(file_path)
148
+ mime_type = Rack::Mime.mime_type(File.extname(file_path))
149
+ @response = [200, { "Content-Type" => mime_type }, File.new(file_path)]
150
+ end
151
+
152
+ def call(env)
153
+ # find a handler for this request
154
+ handler, route_params = router.match(env['REQUEST_METHOD'], env['PATH_INFO'])
155
+
156
+ @params = route_params
157
+
158
+ @environment = env
159
+
160
+ @response = nil
161
+
162
+ begin
163
+ if handler
164
+ self.instance_eval(&handler)
165
+ else
166
+ if files_dir
167
+ handle_file
168
+ else
169
+ handle_no_route
170
+ end
171
+ end
172
+
173
+ unless @response
174
+ fail "No response was generated"
175
+ end
176
+ rescue Exception => e
177
+ handle_exception(e)
178
+ end
179
+
180
+ @response
181
+ end
182
+
183
+ def render(options={})
184
+ if @response
185
+ fail "can only render once per request"
186
+ end
187
+
188
+ @response = [200, { "Content-Type" => "text/html" }, []]
189
+ options.each do |key, value|
190
+ case key
191
+ when :error
192
+ @response[0] = value
193
+ else
194
+ @response[1]["Content-Type"] = Rack::Mime.mime_type("."+key.to_s)
195
+ @response[2] << value
196
+ end
197
+ end
198
+ end
199
+
200
+ def handle_no_route
201
+ @response = [404, { "Content-Type" => "text/html" }, [NoRoute_Message]]
202
+ end
203
+
204
+ def handle_exception(e)
205
+ @response = [500, { "Content-Type" => "text/html" }, [Exception_Message]]
206
+ $stderr.puts e.inspect
207
+ e.backtrace.each { |bt| $stderr.puts bt }
208
+ end
209
+
210
+ def handle_forbidden
211
+ @response = [403, { "Content-Type" => "text/html" }, [Forbidden_Message]]
212
+ end
213
+
214
+ def run(options={})
215
+ handler = find_rack_handler
216
+ handler_name = handler.name.gsub(/.*::/,'')
217
+ $stderr.puts "Patchbay starting, using #{handler_name}"
218
+ handler.run(self, options)
219
+ end
220
+
221
+ private
222
+ def find_rack_handler
223
+ servers = %w/thin mongrel webrick/
224
+ servers.each do |server|
225
+ begin
226
+ return Rack::Handler.get(server)
227
+ rescue LoadError
228
+ end
229
+ end
230
+ end
231
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: patchbay
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.pre
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Andrew Armenia
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-13 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: ! "Patchbay is the web framework for non-web apps. \nIt's designed for
15
+ simplicity, minimalism, and easy integration.\n"
16
+ email: andrew@asquaredlabs.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/patchbay.rb
22
+ homepage: http://rubygems.org/gems/patchbay
23
+ licenses: []
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ! '>'
38
+ - !ruby/object:Gem::Version
39
+ version: 1.3.1
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 1.8.6
43
+ signing_key:
44
+ specification_version: 3
45
+ summary: Embed HTTP APIs in non-web apps easily
46
+ test_files: []