tap-server 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/lib/tap/server.rb ADDED
@@ -0,0 +1,219 @@
1
+ require 'rack'
2
+ require 'rack/mock'
3
+
4
+ require 'tap'
5
+ require 'tap/server_error'
6
+
7
+ module Tap
8
+ Env.manifest(:controllers) do |env|
9
+ entries = env.root.glob(:controllers, "*_controller.rb").collect do |path|
10
+ const_name = File.basename(path).chomp('.rb').camelize
11
+ Support::Constant.new(const_name, path)
12
+ end
13
+
14
+ Support::Manifest.intern(entries) do |manifest, const|
15
+ const.basename.chomp('_controller')
16
+ end
17
+ end
18
+
19
+ # Server is a Rack application that dispatches calls to other Rack apps, most
20
+ # commonly a Tap::Controller.
21
+ #
22
+ # == Routes
23
+ #
24
+ # Routing is fixed and very simple:
25
+ #
26
+ # /:controller/path/to/resource
27
+ #
28
+ # Server dispatches the request to the controller keyed by :controller after
29
+ # shifting the key from PATH_INFO to SCRIPT_NAME.
30
+ #
31
+ # server = Server.new
32
+ # server.controllers['sample'] = lambda do |env|
33
+ # [200, {}, ["Sample got #{env['SCRIPT_NAME']} : #{env['PATH_INFO']}"]]
34
+ # end
35
+ #
36
+ # req = Rack::MockRequest.new(server)
37
+ # req.get('/sample/path/to/resource').body # => "Sample got /sample : /path/to/resource"
38
+ #
39
+ # Server automatically maps unknown keys to a controller by searching
40
+ # env.controllers. As a result '/example' maps to the ExampleController
41
+ # defined in 'controllers/example_controller.rb'.
42
+ #
43
+ # # [controllers/example_controller.rb] => %q{
44
+ # # class ExampleController
45
+ # # def self.call(env)
46
+ # # [200, {}, ["ExampleController got #{env['SCRIPT_NAME']} : #{env['PATH_INFO']}"]]
47
+ # # end
48
+ # # end
49
+ # # }
50
+ #
51
+ # req.get('/example/path/to/resource').body # => "ExampleController got /example : /path/to/resource"
52
+ #
53
+ # If desired, controllers can be set with aliases to map a path key to a
54
+ # lookup key.
55
+ #
56
+ # server.controllers['sample'] = 'example'
57
+ # req.get('/sample/path/to/resource').body # => "ExampleController got /sample : /path/to/resource"
58
+ #
59
+ # If no controller can be found, the request is routed using the
60
+ # default_controller_key and the request is NOT adjusted.
61
+ #
62
+ # server.default_controller_key = 'app'
63
+ # server.controllers['app'] = lambda do |env|
64
+ # [200, {}, ["App got #{env['SCRIPT_NAME']} : #{env['PATH_INFO']}"]]
65
+ # end
66
+ #
67
+ # req.get('/unknown/path/to/resource').body # => "App got : /unknown/path/to/resource"
68
+ #
69
+ class Server
70
+ include Rack::Utils
71
+ include Configurable
72
+
73
+ config :environment, :development
74
+ config :server, %w[thin mongrel webrick]
75
+ config :host, 'localhost'
76
+ config :port, 8080, &c.integer
77
+
78
+ config :views_dir, :views
79
+ config :public_dir, :public
80
+ config :controllers, {}
81
+ config :default_controller_key, 'app'
82
+
83
+ attr_reader :env
84
+
85
+ def initialize(env=Env.new, app=Tap::App.instance, config={})
86
+ @env = env
87
+ @app = app
88
+ @cache = {}
89
+ initialize_config(config)
90
+ end
91
+
92
+ # Currently a stub for initializing a session. initialize_session returns
93
+ # an integer session id.
94
+ def initialize_session
95
+ id = 0
96
+ session_app = app(id)
97
+ log_path = session_app.prepare(:log, 'server.log')
98
+ session_app.logger = Logger.new(log_path)
99
+
100
+ session_app.on_complete do |_result|
101
+ # find the template
102
+ class_name = _result.key.class.to_s.underscore
103
+ pattern = "#{class_name}/result\.*"
104
+ template = nil
105
+ env.each do |e|
106
+ templates = e.root.glob(views_dir, pattern)
107
+ unless templates.empty?
108
+ template = templates[0]
109
+ break
110
+ end
111
+ end
112
+
113
+ if template
114
+ extname = File.extname(template)
115
+ session_app.prepare(:results, id.to_s, "#{class_name}#{extname}") do |file|
116
+ file << Support::Templater.new(File.read(template)).build(:_result => _result)
117
+ end
118
+ end
119
+ end
120
+
121
+ id
122
+ end
123
+
124
+ # Returns the app provided during initialization. In the future this
125
+ # method may be extended to provide a session-specific App, hence it
126
+ # has been stubbed with an id input.
127
+ def app(id=nil)
128
+ @app
129
+ end
130
+
131
+ # Returns true if environment is :development.
132
+ def development?
133
+ environment == :development
134
+ end
135
+
136
+ # The {Rack}[http://rack.rubyforge.org/doc/] interface method.
137
+ def call(rack_env)
138
+ if development?
139
+ env.reset
140
+ @cache.clear
141
+ end
142
+
143
+ # route to a controller
144
+ blank, key, path_info = rack_env['PATH_INFO'].split("/", 3)
145
+ controller = lookup(unescape(key))
146
+
147
+ if controller
148
+ # adjust env if key routes to a controller
149
+ rack_env['SCRIPT_NAME'] = ["#{rack_env['SCRIPT_NAME'].chomp('/')}/#{key}"]
150
+ rack_env['PATH_INFO'] = ["/#{path_info}"]
151
+ else
152
+ # use default controller key
153
+ controller = lookup(default_controller_key)
154
+
155
+ unless controller
156
+ raise ServerError.new("404 Error: could not route to controller", 404)
157
+ end
158
+ end
159
+
160
+ # handle the request
161
+ rack_env['tap.server'] = self
162
+ controller.call(rack_env)
163
+ rescue ServerError
164
+ $!.response
165
+ rescue Exception
166
+ ServerError.response($!)
167
+ end
168
+
169
+ #--
170
+ # TODO: implement caching for path content
171
+ def content(path)
172
+ File.read(path)
173
+ end
174
+
175
+ #--
176
+ # TODO: implement caching for public_paths
177
+ def public_path(path)
178
+ env.search(public_dir, path) {|public_path| File.file?(public_path) }
179
+ end
180
+
181
+ #--
182
+ # TODO: implement caching for template_paths
183
+ def template_path(path)
184
+ env.search(views_dir, path) {|template_path| File.file?(template_path) }
185
+ end
186
+
187
+ protected
188
+
189
+ # a helper method for routing a key to a controller
190
+ def lookup(key) # :nodoc:
191
+ return @cache[key] if @cache.has_key?(key)
192
+ minikey = controllers[key] || key
193
+
194
+ # return registered controllers
195
+ if minikey.respond_to?(:call)
196
+ @cache[key] = minikey
197
+ return minikey
198
+ end
199
+
200
+ # return if no controller can be found
201
+ unless const = env.controllers.search(minikey)
202
+ @cache[key] = nil
203
+ return nil
204
+ end
205
+
206
+ # load the require_path in dev mode so that
207
+ # controllers will be reloaded each time
208
+ if development? && const.require_path
209
+ if Object.const_defined?(const.const_name)
210
+ Object.send(:remove_const, const.const_name)
211
+ end
212
+
213
+ load const.require_path
214
+ end
215
+
216
+ @cache[key] = const.constantize
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,34 @@
1
+ module Tap
2
+
3
+ # A special type of error used for specifiying controller errors.
4
+ class ServerError < RuntimeError
5
+ class << self
6
+
7
+ # A helper to format a non-ServerError into a ServerError response.
8
+ def response(err)
9
+ new("500 #{err.class}: #{err.message}\n#{err.backtrace.join("\n")}").response
10
+ end
11
+ end
12
+
13
+ # The error status
14
+ attr_reader :status
15
+
16
+ # Headers for the error response
17
+ attr_reader :headers
18
+
19
+ # The error response body
20
+ attr_reader :body
21
+
22
+ def initialize(body="500 Server Error", status=500, headers={'Content-Type' => ['text/plain']})
23
+ @body = body
24
+ @status = status
25
+ @headers = headers
26
+ super(body)
27
+ end
28
+
29
+ # Formats self as a rack response array (ie [status, headers, body]).
30
+ def response
31
+ [status, headers, [body]]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ module Tap
2
+ module Tasks
3
+
4
+ # ::manifest
5
+ class Echo < Tap::Task
6
+ def process(*args)
7
+ args = args.flatten
8
+ args << name
9
+ log name, args.inspect
10
+ args
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,30 @@
1
+ require 'tap/server'
2
+
3
+ module Tap
4
+ module Tasks
5
+
6
+ # ::manifest
7
+ class Server < Tap::Task
8
+
9
+ nest(:env, Tap::Env) do |config|
10
+ case config
11
+ when Tap::Env then config
12
+ else Tap::Env.new(config)
13
+ end
14
+ end
15
+
16
+ nest_attr(:server, Tap::Server) do |config|
17
+ @server = Tap::Server.new(env, config)
18
+ end
19
+
20
+ def process(method='get', uri="/")
21
+ uri = URI(uri[0] == ?/ ? uri : "/#{uri}")
22
+ uri.host ||= server.host
23
+ uri.port ||= server.port
24
+
25
+ mock = Rack::MockRequest.new(server)
26
+ mock.request(method, uri.to_s)
27
+ end
28
+ end
29
+ end
30
+ end