codebutler 0.0.3 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Manifest CHANGED
@@ -1,6 +1,5 @@
1
1
  bin/codebutler
2
- lib/codebutler/sinatra.rb
3
2
  lib/codebutler.rb
4
3
  LICENCE
5
- Manifest
6
4
  README
5
+ Manifest
@@ -1,33 +1,32 @@
1
1
 
2
- # Gem::Specification for Codebutler-0.0.3
2
+ # Gem::Specification for Codebutler-0.1.0
3
3
  # Originally generated by Echoe
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = %q{codebutler}
7
- s.version = "0.0.3"
7
+ s.version = "0.1.0"
8
8
 
9
9
  s.specification_version = 2 if s.respond_to? :specification_version=
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.authors = ["Markus Prinz"]
13
- s.date = %q{2008-04-04}
13
+ s.date = %q{2008-04-12}
14
14
  s.default_executable = %q{codebutler}
15
15
  s.description = %q{Simply run codebutler in a directory of your choice, and it will serve all the code files it supports on a local webserver, hilighted.}
16
16
  s.email = %q{markus.prinz@qsig.org}
17
17
  s.executables = ["codebutler"]
18
- s.extra_rdoc_files = ["bin/codebutler", "lib/codebutler/sinatra.rb", "lib/codebutler.rb", "README"]
19
- s.files = ["bin/codebutler", "lib/codebutler/sinatra.rb", "lib/codebutler.rb", "LICENCE", "Manifest", "README", "codebutler.gemspec"]
18
+ s.extra_rdoc_files = ["bin/codebutler", "lib/codebutler.rb", "README"]
19
+ s.files = ["bin/codebutler", "lib/codebutler.rb", "LICENCE", "README", "Manifest", "codebutler.gemspec"]
20
20
  s.has_rdoc = true
21
21
  s.homepage = %q{http://codebutler.rubyforge.org}
22
22
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Codebutler", "--main", "README"]
23
23
  s.require_paths = ["lib"]
24
24
  s.rubyforge_project = %q{codebutler}
25
- s.rubygems_version = %q{1.1.0}
25
+ s.rubygems_version = %q{1.1.1}
26
26
  s.summary = %q{Easily serve your highlighted codefiles from a local webserver}
27
27
 
28
28
  s.add_dependency(%q<coderay>, [">= 0.7.4"])
29
- s.add_dependency(%q<mongrel>, [">= 1.0.1"])
30
- s.add_dependency(%q<rack>, [">= 0.2.0"])
29
+ s.add_dependency(%q<sinatra>, [">= 0.2.0"])
31
30
  end
32
31
 
33
32
 
@@ -43,7 +42,7 @@ end
43
42
  # p.summary = "Easily serve your highlighted codefiles from a local webserver"
44
43
  # p.description = "Simply run codebutler in a directory of your choice, and it will serve all the code files it supports on a local webserver, hilighted."
45
44
  # p.url = "http://codebutler.rubyforge.org"
46
- # p.version = '0.0.3'
47
- # p.dependencies = ["coderay >=0.7.4", "mongrel >=1.0.1", "rack >=0.2.0"]
45
+ # p.version = '0.1.0'
46
+ # p.dependencies = ["coderay >=0.7.4", "sinatra >=0.2.0"]
48
47
  # end
49
48
  #
@@ -1,10 +1,8 @@
1
- require 'find'
1
+ require 'sinatra'
2
2
  require 'coderay'
3
+ require 'find'
3
4
  require 'uri'
4
5
 
5
- # We bring our own version of sinatra since it's not available as a gem yet
6
- require 'codebutler/sinatra'
7
-
8
6
  module CodeButler
9
7
 
10
8
  SUPPORTED_LANGUAGES = {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: codebutler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Prinz
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-04-04 00:00:00 +02:00
12
+ date: 2008-04-12 00:00:00 +02:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -22,16 +22,7 @@ dependencies:
22
22
  version: 0.7.4
23
23
  version:
24
24
  - !ruby/object:Gem::Dependency
25
- name: mongrel
26
- version_requirement:
27
- version_requirements: !ruby/object:Gem::Requirement
28
- requirements:
29
- - - ">="
30
- - !ruby/object:Gem::Version
31
- version: 1.0.1
32
- version:
33
- - !ruby/object:Gem::Dependency
34
- name: rack
25
+ name: sinatra
35
26
  version_requirement:
36
27
  version_requirements: !ruby/object:Gem::Requirement
37
28
  requirements:
@@ -47,16 +38,14 @@ extensions: []
47
38
 
48
39
  extra_rdoc_files:
49
40
  - bin/codebutler
50
- - lib/codebutler/sinatra.rb
51
41
  - lib/codebutler.rb
52
42
  - README
53
43
  files:
54
44
  - bin/codebutler
55
- - lib/codebutler/sinatra.rb
56
45
  - lib/codebutler.rb
57
46
  - LICENCE
58
- - Manifest
59
47
  - README
48
+ - Manifest
60
49
  - codebutler.gemspec
61
50
  has_rdoc: true
62
51
  homepage: http://codebutler.rubyforge.org
@@ -85,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
74
  requirements: []
86
75
 
87
76
  rubyforge_project: codebutler
88
- rubygems_version: 1.1.0
77
+ rubygems_version: 1.1.1
89
78
  signing_key:
90
79
  specification_version: 2
91
80
  summary: Easily serve your highlighted codefiles from a local webserver
@@ -1,1060 +0,0 @@
1
- require 'rubygems'
2
- require 'metaid'
3
- require 'uri'
4
- require 'thread'
5
- require 'time'
6
-
7
- if ENV['SWIFT']
8
- require 'swiftcore/swiftiplied_mongrel'
9
- puts "Using Swiftiplied Mongrel"
10
- elsif ENV['EVENT']
11
- require 'swiftcore/evented_mongrel'
12
- puts "Using Evented Mongrel"
13
- end
14
-
15
- require 'rack'
16
- require 'ostruct'
17
-
18
- class Class
19
- def dslify_writter(*syms)
20
- syms.each do |sym|
21
- class_eval <<-end_eval
22
- def #{sym}(v=nil)
23
- self.send "#{sym}=", v if v
24
- v
25
- end
26
- end_eval
27
- end
28
- end
29
- end
30
-
31
- module Rack #:nodoc:
32
-
33
- class Request #:nodoc:
34
-
35
- # Set of request method names allowed via the _method parameter hack. By default,
36
- # all request methods defined in RFC2616 are included, with the exception of
37
- # TRACE and CONNECT.
38
- POST_TUNNEL_METHODS_ALLOWED = %w( PUT DELETE OPTIONS HEAD )
39
-
40
- # Return the HTTP request method with support for method tunneling using the POST
41
- # _method parameter hack. If the real request method is POST and a _method param is
42
- # given and the value is one defined in +POST_TUNNEL_METHODS_ALLOWED+, return the value
43
- # of the _method param instead.
44
- def request_method
45
- if post_tunnel_method_hack?
46
- params['_method'].upcase
47
- else
48
- @env['REQUEST_METHOD']
49
- end
50
- end
51
-
52
- def user_agent
53
- env['HTTP_USER_AGENT']
54
- end
55
-
56
- private
57
-
58
- # Return truthfully if and only if the following conditions are met: 1.) the
59
- # *actual* request method is POST, 2.) the request content-type is one of
60
- # 'application/x-www-form-urlencoded' or 'multipart/form-data', 3.) there is a
61
- # "_method" parameter in the POST body (not in the query string), and 4.) the
62
- # method parameter is one of the verbs listed in the POST_TUNNEL_METHODS_ALLOWED
63
- # list.
64
- def post_tunnel_method_hack?
65
- @env['REQUEST_METHOD'] == 'POST' &&
66
- POST_TUNNEL_METHODS_ALLOWED.include?(self.POST.fetch('_method', '').upcase)
67
- end
68
-
69
- end
70
-
71
- module Utils
72
- extend self
73
- end
74
-
75
- end
76
-
77
- module Sinatra
78
- extend self
79
-
80
- module Version
81
- MAJOR = '0'
82
- MINOR = '2'
83
- REVISION = '0'
84
- def self.combined
85
- [MAJOR, MINOR, REVISION].join('.')
86
- end
87
- end
88
-
89
- class NotFound < RuntimeError; end
90
- class ServerError < RuntimeError; end
91
-
92
- Result = Struct.new(:block, :params, :status) unless defined?(Result)
93
-
94
- def options
95
- application.options
96
- end
97
-
98
- def application
99
- unless @app
100
- @app = Application.new
101
- Sinatra::Environment.setup!
102
- end
103
- @app
104
- end
105
-
106
- def application=(app)
107
- @app = app
108
- end
109
-
110
- def port
111
- application.options.port
112
- end
113
-
114
- def env
115
- application.options.env
116
- end
117
-
118
- def build_application
119
- Rack::CommonLogger.new(application)
120
- end
121
-
122
- def run
123
-
124
- begin
125
- puts "== Sinatra has taken the stage on port #{port} for #{env}"
126
- require 'pp'
127
- Rack::Handler::Mongrel.run(build_application, :Port => port) do |server|
128
- trap(:INT) do
129
- server.stop
130
- puts "\n== Sinatra has ended his set (crowd applauds)"
131
- end
132
- end
133
- rescue Errno::EADDRINUSE => e
134
- puts "== Someone is already performing on port #{port}!"
135
- end
136
-
137
- end
138
-
139
- class Event
140
-
141
- URI_CHAR = '[^/?:,&#\.]'.freeze unless defined?(URI_CHAR)
142
- PARAM = /:(#{URI_CHAR}+)/.freeze unless defined?(PARAM)
143
- SPLAT = /(.*?)/
144
- attr_reader :path, :block, :param_keys, :pattern, :options
145
-
146
- def initialize(path, options = {}, &b)
147
- @path = path
148
- @block = b
149
- @param_keys = []
150
- @options = options
151
- regex = @path.to_s.gsub(PARAM) do
152
- @param_keys << $1
153
- "(#{URI_CHAR}+)"
154
- end
155
-
156
- regex.gsub!('*', SPLAT.to_s)
157
-
158
- @pattern = /^#{regex}$/
159
- end
160
-
161
- def invoke(request)
162
- params = {}
163
- if agent = options[:agent]
164
- return unless request.user_agent =~ agent
165
- params[:agent] = $~[1..-1]
166
- end
167
- if host = options[:host]
168
- return unless host === request.host
169
- end
170
- return unless pattern =~ request.path_info.squeeze('/')
171
- params.merge!(param_keys.zip($~.captures.map(&:from_param)).to_hash)
172
- Result.new(block, params, 200)
173
- end
174
-
175
- end
176
-
177
- class Error
178
-
179
- attr_reader :code, :block
180
-
181
- def initialize(code, &b)
182
- @code, @block = code, b
183
- end
184
-
185
- def invoke(request)
186
- Result.new(block, {}, 404)
187
- end
188
-
189
- end
190
-
191
- class Static
192
-
193
- def invoke(request)
194
- return unless File.file?(
195
- Sinatra.application.options.public + request.path_info
196
- )
197
- Result.new(block, {}, 200)
198
- end
199
-
200
- def block
201
- Proc.new do
202
- send_file Sinatra.application.options.public + request.path_info,
203
- :disposition => nil
204
- end
205
- end
206
-
207
- end
208
-
209
- # Adapted from actionpack
210
- # Methods for sending files and streams to the browser instead of rendering.
211
- module Streaming
212
- DEFAULT_SEND_FILE_OPTIONS = {
213
- :type => 'application/octet-stream'.freeze,
214
- :disposition => 'attachment'.freeze,
215
- :stream => true,
216
- :buffer_size => 4096
217
- }.freeze
218
-
219
- class MissingFile < RuntimeError; end
220
-
221
- class FileStreamer
222
-
223
- attr_reader :path, :options
224
-
225
- def initialize(path, options)
226
- @path, @options = path, options
227
- end
228
-
229
- def to_result(cx, *args)
230
- self
231
- end
232
-
233
- def each
234
- File.open(path, 'rb') do |file|
235
- while buf = file.read(options[:buffer_size])
236
- yield buf
237
- end
238
- end
239
- end
240
-
241
- end
242
-
243
- protected
244
- # Sends the file by streaming it 4096 bytes at a time. This way the
245
- # whole file doesn't need to be read into memory at once. This makes
246
- # it feasible to send even large files.
247
- #
248
- # Be careful to sanitize the path parameter if it coming from a web
249
- # page. send_file(params[:path]) allows a malicious user to
250
- # download any file on your server.
251
- #
252
- # Options:
253
- # * <tt>:filename</tt> - suggests a filename for the browser to use.
254
- # Defaults to File.basename(path).
255
- # * <tt>:type</tt> - specifies an HTTP content type.
256
- # Defaults to 'application/octet-stream'.
257
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
258
- # Valid values are 'inline' and 'attachment' (default). When set to nil, the
259
- # Content-Disposition and Content-Transfer-Encoding headers are omitted entirely.
260
- # * <tt>:stream</tt> - whether to send the file to the user agent as it is read (true)
261
- # or to read the entire file before sending (false). Defaults to true.
262
- # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
263
- # Defaults to 4096.
264
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
265
- # * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See Time#httpdate)
266
- # indicating the last modified time of the file. If the request includes an
267
- # If-Modified-Since header that matches this value exactly, a 304 Not Modified response
268
- # is sent instead of the file. Defaults to the file's last modified
269
- # time.
270
- #
271
- # The default Content-Type and Content-Disposition headers are
272
- # set to download arbitrary binary files in as many browsers as
273
- # possible. IE versions 4, 5, 5.5, and 6 are all known to have
274
- # a variety of quirks (especially when downloading over SSL).
275
- #
276
- # Simple download:
277
- # send_file '/path/to.zip'
278
- #
279
- # Show a JPEG in the browser:
280
- # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
281
- #
282
- # Show a 404 page in the browser:
283
- # send_file '/path/to/404.html, :type => 'text/html; charset=utf-8', :status => 404
284
- #
285
- # Read about the other Content-* HTTP headers if you'd like to
286
- # provide the user with more information (such as Content-Description).
287
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
288
- #
289
- # Also be aware that the document may be cached by proxies and browsers.
290
- # The Pragma and Cache-Control headers declare how the file may be cached
291
- # by intermediaries. They default to require clients to validate with
292
- # the server before releasing cached responses. See
293
- # http://www.mnot.net/cache_docs/ for an overview of web caching and
294
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
295
- # for the Cache-Control header spec.
296
- def send_file(path, options = {}) #:doc:
297
- raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
298
-
299
- options[:length] ||= File.size(path)
300
- options[:filename] ||= File.basename(path)
301
- options[:type] ||= Rack::File::MIME_TYPES[File.extname(options[:filename])[1..-1]] || 'text/plain'
302
- options[:last_modified] ||= File.mtime(path).httpdate
303
- send_file_headers! options
304
-
305
- if options[:stream]
306
- throw :halt, [options[:status] || 200, FileStreamer.new(path, options)]
307
- else
308
- File.open(path, 'rb') { |file| throw :halt, [options[:status] || 200, file.read] }
309
- end
310
- end
311
-
312
- # Send binary data to the user as a file download. May set content type, apparent file name,
313
- # and specify whether to show data inline or download as an attachment.
314
- #
315
- # Options:
316
- # * <tt>:filename</tt> - Suggests a filename for the browser to use.
317
- # * <tt>:type</tt> - specifies an HTTP content type.
318
- # Defaults to 'application/octet-stream'.
319
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
320
- # Valid values are 'inline' and 'attachment' (default).
321
- # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
322
- # * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See Time#httpdate)
323
- # indicating the last modified time of the response entity. If the request includes an
324
- # If-Modified-Since header that matches this value exactly, a 304 Not Modified response
325
- # is sent instead of the data.
326
- #
327
- # Generic data download:
328
- # send_data buffer
329
- #
330
- # Download a dynamically-generated tarball:
331
- # send_data generate_tgz('dir'), :filename => 'dir.tgz'
332
- #
333
- # Display an image Active Record in the browser:
334
- # send_data image.data, :type => image.content_type, :disposition => 'inline'
335
- #
336
- # See +send_file+ for more information on HTTP Content-* headers and caching.
337
- def send_data(data, options = {}) #:doc:
338
- send_file_headers! options.merge(:length => data.size)
339
- throw :halt, [options[:status] || 200, data]
340
- end
341
-
342
- private
343
- def send_file_headers!(options)
344
- options = DEFAULT_SEND_FILE_OPTIONS.merge(options)
345
- [:length, :type, :disposition].each do |arg|
346
- raise ArgumentError, ":#{arg} option required" unless options.key?(arg)
347
- end
348
-
349
- # Send a "304 Not Modified" if the last_modified option is provided and matches
350
- # the If-Modified-Since request header value.
351
- if last_modified = options[:last_modified]
352
- header 'Last-Modified' => last_modified
353
- throw :halt, [ 304, '' ] if last_modified == request.env['HTTP_IF_MODIFIED_SINCE']
354
- end
355
-
356
- headers(
357
- 'Content-Length' => options[:length].to_s,
358
- 'Content-Type' => options[:type].strip # fixes a problem with extra '\r' with some browsers
359
- )
360
-
361
- # Omit Content-Disposition and Content-Transfer-Encoding headers if
362
- # the :disposition option set to nil.
363
- if !options[:disposition].nil?
364
- disposition = options[:disposition].dup || 'attachment'
365
- disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
366
- headers 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary'
367
- end
368
-
369
- # Fix a problem with IE 6.0 on opening downloaded files:
370
- # If Cache-Control: no-cache is set (which Rails does by default),
371
- # IE removes the file it just downloaded from its cache immediately
372
- # after it displays the "open/save" dialog, which means that if you
373
- # hit "open" the file isn't there anymore when the application that
374
- # is called for handling the download is run, so let's workaround that
375
- header('Cache-Control' => 'private') if headers['Cache-Control'] == 'no-cache'
376
- end
377
- end
378
-
379
- module ResponseHelpers
380
-
381
- def redirect(path, *args)
382
- status(302)
383
- headers 'Location' => path
384
- throw :halt, *args
385
- end
386
-
387
- def headers(header = nil)
388
- @response.headers.merge!(header) if header
389
- @response.headers
390
- end
391
- alias :header :headers
392
-
393
- end
394
-
395
- module RenderingHelpers
396
-
397
- def render(renderer, template, options={})
398
- m = method("render_#{renderer}")
399
- result = m.call(resolve_template(renderer, template, options), options)
400
- if layout = determine_layout(renderer, template, options)
401
- result = m.call(resolve_template(renderer, layout, options), options) { result }
402
- end
403
- result
404
- end
405
-
406
- def determine_layout(renderer, template, options)
407
- return if options[:layout] == false
408
- layout_from_options = options[:layout] || :layout
409
- resolve_template(renderer, layout_from_options, options, false)
410
- end
411
-
412
- private
413
-
414
- def resolve_template(renderer, template, options, scream = true)
415
- case template
416
- when String
417
- template
418
- when Proc
419
- template.call
420
- when Symbol
421
- if proc = templates[template]
422
- resolve_template(renderer, proc, options, scream)
423
- else
424
- read_template_file(renderer, template, options, scream)
425
- end
426
- else
427
- nil
428
- end
429
- end
430
-
431
- def read_template_file(renderer, template, options, scream = true)
432
- path = File.join(
433
- options[:views_directory] || Sinatra.application.options.views,
434
- "#{template}.#{renderer}"
435
- )
436
- unless File.exists?(path)
437
- raise Errno::ENOENT.new(path) if scream
438
- nil
439
- else
440
- File.read(path)
441
- end
442
- end
443
-
444
- def templates
445
- Sinatra.application.templates
446
- end
447
-
448
- end
449
-
450
- module Erb
451
-
452
- def erb(content, options={})
453
- require 'erb'
454
- render(:erb, content, options)
455
- end
456
-
457
- private
458
-
459
- def render_erb(content, options = {})
460
- ::ERB.new(content).result(binding)
461
- end
462
-
463
- end
464
-
465
- module Haml
466
-
467
- def haml(content, options={})
468
- require 'haml'
469
- render(:haml, content, options)
470
- end
471
-
472
- private
473
-
474
- def render_haml(content, options = {}, &b)
475
- ::Haml::Engine.new(content).render(options[:scope] || self, options[:locals] || {}, &b)
476
- end
477
-
478
- end
479
-
480
- # Generating conservative XML content using Builder templates.
481
- #
482
- # Builder templates can be inline by passing a block to the builder method, or in
483
- # external files with +.builder+ extension by passing the name of the template
484
- # to the +builder+ method as a Symbol.
485
- #
486
- # === Inline Rendering
487
- #
488
- # If the builder method is given a block, the block is called directly with an
489
- # +XmlMarkup+ instance and the result is returned as String:
490
- # get '/who.xml' do
491
- # builder do |xml|
492
- # xml.instruct!
493
- # xml.person do
494
- # xml.name "Francis Albert Sinatra",
495
- # :aka => "Frank Sinatra"
496
- # xml.email 'frank@capitolrecords.com'
497
- # end
498
- # end
499
- # end
500
- #
501
- # Yields the following XML:
502
- # <?xml version='1.0' encoding='UTF-8'?>
503
- # <person>
504
- # <name aka='Frank Sinatra'>Francis Albert Sinatra</name>
505
- # <email>Frank Sinatra</email>
506
- # </person>
507
- #
508
- # === Builder Template Files
509
- #
510
- # Builder templates can be stored in separate files with a +.builder+
511
- # extension under the view path. An +XmlMarkup+ object named +xml+ is automatically
512
- # made available to template.
513
- #
514
- # Example:
515
- # get '/bio.xml' do
516
- # builder :bio
517
- # end
518
- #
519
- # The "views/bio.builder" file might contain the following:
520
- # xml.instruct! :xml, :version => '1.1'
521
- # xml.person do
522
- # xml.name "Francis Albert Sinatra"
523
- # xml.aka "Frank Sinatra"
524
- # xml.aka "Ol' Blue Eyes"
525
- # xml.aka "The Chairman of the Board"
526
- # xml.born 'date' => '1915-12-12' do
527
- # xml.text! "Hoboken, New Jersey, U.S.A."
528
- # end
529
- # xml.died 'age' => 82
530
- # end
531
- #
532
- # And yields the following output:
533
- # <?xml version='1.1' encoding='UTF-8'?>
534
- # <person>
535
- # <name>Francis Albert Sinatra</name>
536
- # <aka>Frank Sinatra</aka>
537
- # <aka>Ol&apos; Blue Eyes</aka>
538
- # <aka>The Chairman of the Board</aka>
539
- # <born date='1915-12-12'>Hoboken, New Jersey, U.S.A.</born>
540
- # <died age='82' />
541
- # </person>
542
- #
543
- # NOTE: Builder must be installed or a LoadError will be raised the first time an
544
- # attempt is made to render a builder template.
545
- #
546
- # See http://builder.rubyforge.org/ for comprehensive documentation on Builder.
547
- module Builder
548
-
549
- def builder(content=nil, options={}, &block)
550
- options, content = content, nil if content.is_a?(Hash)
551
- content = Proc.new { block } if content.nil?
552
- render(:builder, content, options)
553
- end
554
-
555
- private
556
-
557
- def render_builder(content, options = {}, &b)
558
- require 'builder'
559
- xml = ::Builder::XmlMarkup.new(:indent => 2)
560
- case content
561
- when String
562
- eval(content, binding, '<BUILDER>', 1)
563
- when Proc
564
- content.call(xml)
565
- end
566
- xml.target!
567
- end
568
-
569
- end
570
-
571
- class EventContext
572
-
573
- include ResponseHelpers
574
- include Streaming
575
- include RenderingHelpers
576
- include Erb
577
- include Haml
578
- include Builder
579
-
580
- attr_accessor :request, :response
581
-
582
- dslify_writter :status, :body
583
-
584
- def initialize(request, response, route_params)
585
- @request = request
586
- @response = response
587
- @route_params = route_params
588
- @response.body = nil
589
- end
590
-
591
- def params
592
- @params = @route_params.merge(@request.params)
593
- end
594
-
595
- def stop(*args)
596
- throw :halt, args
597
- end
598
-
599
- def complete(returned)
600
- @response.body || returned
601
- end
602
-
603
- private
604
-
605
- def method_missing(name, *args, &b)
606
- @response.send(name, *args, &b)
607
- end
608
-
609
- end
610
-
611
- class Application
612
-
613
- attr_reader :events, :errors, :templates, :filters
614
- attr_reader :clearables, :reloading
615
-
616
- attr_writer :options
617
-
618
- def self.default_options
619
- @@default_options ||= {
620
- :run => true,
621
- :port => 4567,
622
- :env => :development,
623
- :root => Dir.pwd,
624
- :views => Dir.pwd + '/views',
625
- :public => Dir.pwd + '/public'
626
- }
627
- end
628
-
629
- def default_options
630
- self.class.default_options
631
- end
632
-
633
-
634
- ##
635
- # Load all options given on the command line
636
- # NOTE: Ignores --name so unit/spec tests can run individually
637
- def load_options!
638
- require 'optparse'
639
- OptionParser.new do |op|
640
- op.on('-p port') { |port| default_options[:port] = port }
641
- op.on('-e env') { |env| default_options[:env] = env }
642
- op.on('-x') { |env| default_options[:mutex] = true }
643
- end.parse!(ARGV.dup.select { |o| o !~ /--name/ })
644
- end
645
-
646
- # Called immediately after the application is initialized or reloaded to
647
- # register default events. Events added here have dibs on requests since
648
- # they appear first in the list.
649
- def load_default_events!
650
- events[:get] << Static.new
651
- end
652
-
653
- def initialize
654
- @clearables = [
655
- @events = Hash.new { |hash, key| hash[key] = [] },
656
- @errors = Hash.new,
657
- @filters = Hash.new { |hash, key| hash[key] = [] },
658
- @templates = Hash.new
659
- ]
660
- load_options!
661
- load_default_events!
662
- end
663
-
664
- def define_event(method, path, options = {}, &b)
665
- events[method] << event = Event.new(path, options, &b)
666
- event
667
- end
668
-
669
- def define_template(name=:layout, &b)
670
- templates[name] = b
671
- end
672
-
673
- def define_error(code, options = {}, &b)
674
- errors[code] = Error.new(code, &b)
675
- end
676
-
677
- def define_filter(type, &b)
678
- filters[:before] << b
679
- end
680
-
681
- # Visits and invokes each handler registered for the +request_method+ in
682
- # definition order until a Result response is produced. If no handler
683
- # responds with a Result, the NotFound error handler is invoked.
684
- #
685
- # When the request_method is "HEAD" and no valid Result is produced by
686
- # the set of handlers registered for HEAD requests, an attempt is made to
687
- # invoke the GET handlers to generate the response before resorting to the
688
- # default error handler.
689
- def lookup(request)
690
- method = request.request_method.downcase.to_sym
691
- events[method].eject(&[:invoke, request]) ||
692
- (events[:get].eject(&[:invoke, request]) if method == :head) ||
693
- errors[NotFound].invoke(request)
694
- end
695
-
696
- def options
697
- @options ||= OpenStruct.new(default_options)
698
- end
699
-
700
- def development?
701
- options.env == :development
702
- end
703
-
704
- def reload!
705
- @reloading = true
706
- clearables.each(&:clear)
707
- load_default_events!
708
- Kernel.load $0
709
- @reloading = false
710
- Environment.setup!
711
- end
712
-
713
- def mutex
714
- @@mutex ||= Mutex.new
715
- end
716
-
717
- def run_safely
718
- if options.mutex
719
- mutex.synchronize { yield }
720
- else
721
- yield
722
- end
723
- end
724
-
725
- def call(env)
726
- reload! if development?
727
- request = Rack::Request.new(env)
728
- result = lookup(request)
729
- context = EventContext.new(
730
- request,
731
- Rack::Response.new,
732
- result.params
733
- )
734
- context.status(result.status)
735
- begin
736
- returned = run_safely do
737
- catch(:halt) do
738
- filters[:before].each { |f| context.instance_eval(&f) }
739
- [:complete, context.instance_eval(&result.block)]
740
- end
741
- end
742
- body = returned.to_result(context)
743
- rescue => e
744
- request.env['sinatra.error'] = e
745
- context.status(500)
746
- result = (errors[e.class] || errors[ServerError]).invoke(request)
747
- returned = run_safely do
748
- catch(:halt) do
749
- [:complete, context.instance_eval(&result.block)]
750
- end
751
- end
752
- body = returned.to_result(context)
753
- end
754
- body = '' unless body.respond_to?(:each)
755
- body = '' if request.request_method.upcase == 'HEAD'
756
- context.body = body.kind_of?(String) ? [*body] : body
757
- context.finish
758
- end
759
-
760
- end
761
-
762
-
763
- module Environment
764
- extend self
765
-
766
- def setup!
767
- configure do
768
- error do
769
- raise request.env['sinatra.error'] if Sinatra.options.raise_errors
770
- '<h1>Internal Server Error</h1>'
771
- end
772
- not_found { '<h1>Not Found</h1>'}
773
- end
774
-
775
- configures :development do
776
-
777
- get '/sinatra_custom_images/:image.png' do
778
- File.read(File.dirname(__FILE__) + "/../images/#{params[:image]}.png")
779
- end
780
-
781
- not_found do
782
- %Q(
783
- <style>
784
- body {
785
- text-align: center;
786
- color: #888;
787
- font-family: Arial;
788
- font-size: 22px;
789
- margin: 20px;
790
- }
791
- #content {
792
- margin: 0 auto;
793
- width: 500px;
794
- text-align: left;
795
- }
796
- </style>
797
- <html>
798
- <body>
799
- <h2>Sinatra doesn't know this diddy.</h2>
800
- <img src='/sinatra_custom_images/404.png'></img>
801
- <div id="content">
802
- Try this:
803
- <pre>#{request.request_method.downcase} "#{request.path_info}" do
804
- .. do something ..
805
- end<pre>
806
- </div>
807
- </body>
808
- </html>
809
- )
810
- end
811
-
812
- error do
813
- @error = request.env['sinatra.error']
814
- %Q(
815
- <html>
816
- <body>
817
- <style type="text/css" media="screen">
818
- body {
819
- font-family: Verdana;
820
- color: #333;
821
- }
822
-
823
- #content {
824
- width: 700px;
825
- margin-left: 20px;
826
- }
827
-
828
- #content h1 {
829
- width: 99%;
830
- color: #1D6B8D;
831
- font-weight: bold;
832
- }
833
-
834
- #stacktrace {
835
- margin-top: -20px;
836
- }
837
-
838
- #stacktrace pre {
839
- font-size: 12px;
840
- border-left: 2px solid #ddd;
841
- padding-left: 10px;
842
- }
843
-
844
- #stacktrace img {
845
- margin-top: 10px;
846
- }
847
- </style>
848
- <div id="content">
849
- <img src="/sinatra_custom_images/500.png" />
850
- <div class="info">
851
- Params: <pre>#{params.inspect}
852
- </div>
853
- <div id="stacktrace">
854
- <h1>#{Rack::Utils.escape_html(@error.class.name + ' - ' + @error.message)}</h1>
855
- <pre><code>#{Rack::Utils.escape_html(@error.backtrace.join("\n"))}</code></pre>
856
- </div>
857
- </body>
858
- </html>
859
- )
860
- end
861
- end
862
- end
863
- end
864
-
865
- end
866
-
867
- def get(path, options ={}, &b)
868
- Sinatra.application.define_event(:get, path, options, &b)
869
- end
870
-
871
- def post(path, options ={}, &b)
872
- Sinatra.application.define_event(:post, path, options, &b)
873
- end
874
-
875
- def put(path, options ={}, &b)
876
- Sinatra.application.define_event(:put, path, options, &b)
877
- end
878
-
879
- def delete(path, options ={}, &b)
880
- Sinatra.application.define_event(:delete, path, options, &b)
881
- end
882
-
883
- def before(&b)
884
- Sinatra.application.define_filter(:before, &b)
885
- end
886
-
887
- def helpers(&b)
888
- Sinatra::EventContext.class_eval(&b)
889
- end
890
-
891
- def error(type = Sinatra::ServerError, options = {}, &b)
892
- Sinatra.application.define_error(type, options, &b)
893
- end
894
-
895
- def not_found(options = {}, &b)
896
- Sinatra.application.define_error(Sinatra::NotFound, options, &b)
897
- end
898
-
899
- def layout(name = :layout, &b)
900
- Sinatra.application.define_template(name, &b)
901
- end
902
-
903
- def template(name, &b)
904
- Sinatra.application.define_template(name, &b)
905
- end
906
-
907
- def use_in_file_templates!
908
- require 'stringio'
909
- templates = IO.read(caller.first.split(':').first).split('__FILE__').last
910
- data = StringIO.new(templates)
911
- current_template = nil
912
- data.each do |line|
913
- if line =~ /^##\s?(.*)/
914
- current_template = $1.to_sym
915
- Sinatra.application.templates[current_template] = ''
916
- elsif current_template
917
- Sinatra.application.templates[current_template] << line
918
- end
919
- end
920
- end
921
-
922
- def configures(*envs, &b)
923
- yield if !Sinatra.application.reloading &&
924
- (envs.include?(Sinatra.application.options.env) ||
925
- envs.empty?)
926
- end
927
- alias :configure :configures
928
-
929
- def set_options(opts)
930
- Sinatra::Application.default_options.merge!(opts)
931
- Sinatra.application.options = nil
932
- end
933
-
934
- def mime(ext, type)
935
- Rack::File::MIME_TYPES[ext.to_s] = type
936
- end
937
-
938
- ### Misc Core Extensions
939
-
940
- module Kernel
941
-
942
- def silence_warnings
943
- old_verbose, $VERBOSE = $VERBOSE, nil
944
- yield
945
- ensure
946
- $VERBOSE = old_verbose
947
- end
948
-
949
- end
950
-
951
- class String
952
-
953
- # Converts +self+ to an escaped URI parameter value
954
- # 'Foo Bar'.to_param # => 'Foo%20Bar'
955
- def to_param
956
- URI.escape(self)
957
- end
958
-
959
- # Converts +self+ from an escaped URI parameter value
960
- # 'Foo%20Bar'.from_param # => 'Foo Bar'
961
- def from_param
962
- URI.unescape(self)
963
- end
964
-
965
- end
966
-
967
- class Hash
968
-
969
- def to_params
970
- map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
971
- end
972
-
973
- def symbolize_keys
974
- self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
975
- end
976
-
977
- def pass(*keys)
978
- reject { |k,v| !keys.include?(k) }
979
- end
980
-
981
- end
982
-
983
- class Symbol
984
-
985
- def to_proc
986
- Proc.new { |*args| args.shift.__send__(self, *args) }
987
- end
988
-
989
- end
990
-
991
- class Array
992
-
993
- def to_hash
994
- self.inject({}) { |h, (k, v)| h[k] = v; h }
995
- end
996
-
997
- def to_proc
998
- Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
999
- end
1000
-
1001
- end
1002
-
1003
- module Enumerable
1004
-
1005
- def eject(&block)
1006
- find { |e| result = block[e] and break result }
1007
- end
1008
-
1009
- end
1010
-
1011
- ### Core Extension results for throw :halt
1012
-
1013
- class Proc
1014
- def to_result(cx, *args)
1015
- cx.instance_eval(&self)
1016
- args.shift.to_result(cx, *args)
1017
- end
1018
- end
1019
-
1020
- class String
1021
- def to_result(cx, *args)
1022
- args.shift.to_result(cx, *args)
1023
- self
1024
- end
1025
- end
1026
-
1027
- class Array
1028
- def to_result(cx, *args)
1029
- self.shift.to_result(cx, *self)
1030
- end
1031
- end
1032
-
1033
- class Symbol
1034
- def to_result(cx, *args)
1035
- cx.send(self, *args)
1036
- end
1037
- end
1038
-
1039
- class Fixnum
1040
- def to_result(cx, *args)
1041
- cx.status self
1042
- args.shift.to_result(cx, *args)
1043
- end
1044
- end
1045
-
1046
- class NilClass
1047
- def to_result(cx, *args)
1048
- ''
1049
- end
1050
- end
1051
-
1052
- at_exit do
1053
- raise $! if $!
1054
- if Sinatra.application.options.run
1055
- Sinatra.run
1056
- end
1057
- end
1058
-
1059
- mime :xml, 'application/xml'
1060
- mime :js, 'application/javascript'