codebutler 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENCE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2008 Markus Prinz
2
+ Sinatra is Copyright (c) 2007 Blake Mizerany
3
+
4
+ Permission is hereby granted, free of charge, to any person
5
+ obtaining a copy of this software and associated documentation
6
+ files (the "Software"), to deal in the Software without
7
+ restriction, including without limitation the rights to use,
8
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the
10
+ Software is furnished to do so, subject to the following
11
+ conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23
+ OTHER DEALINGS IN THE SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,6 @@
1
+ bin/codebutler
2
+ lib/codebutler/sinatra.rb
3
+ lib/codebutler.rb
4
+ LICENCE
5
+ Manifest
6
+ README
data/README ADDED
@@ -0,0 +1,47 @@
1
+ = CodeButler
2
+
3
+ * http://rubyforge.org/projects/codebutler/
4
+ * http://github.com/cypher/codebutler/tree/master
5
+
6
+ == DESCRIPTION:
7
+
8
+ CodeButler scans a given directory for supported sourcecode, and then serves them highlighted by Coderay on a local webserver.
9
+
10
+ == REQUIREMENTS:
11
+
12
+ * Coderay >= 0.7.4
13
+ * mongrel >= 1.0.1
14
+ * rack >= 0.2.0
15
+
16
+ Since a version of Sinatra is required that is not yet available as a gem, it is included with CodeButler for now
17
+
18
+ == INSTALL:
19
+
20
+ gem install codebutler
21
+
22
+ == LICENSE:
23
+
24
+ (The MIT License)
25
+
26
+ Copyright (c) 2008 Markus Prinz
27
+
28
+ Sinatra is Copyright (c) 2007 Blake Mizerany
29
+
30
+ Permission is hereby granted, free of charge, to any person obtaining
31
+ a copy of this software and associated documentation files (the
32
+ 'Software'), to deal in the Software without restriction, including
33
+ without limitation the rights to use, copy, modify, merge, publish,
34
+ distribute, sublicense, and/or sell copies of the Software, and to
35
+ permit persons to whom the Software is furnished to do so, subject to
36
+ the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be
39
+ included in all copies or substantial portions of the Software.
40
+
41
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
42
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
43
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
44
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
45
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
46
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
47
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/codebutler ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Markus Prinz on 2008-4-3.
4
+ # Copyright (c) 2008. All rights reserved.
5
+ #
6
+ # Available options:
7
+ # -p port
8
+ # -h
9
+
10
+ require 'rubygems'
11
+ require 'codebutler'
12
+
13
+ CodeButler.serve
@@ -0,0 +1,49 @@
1
+
2
+ # Gem::Specification for Codebutler-0.0.3
3
+ # Originally generated by Echoe
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{codebutler}
7
+ s.version = "0.0.3"
8
+
9
+ s.specification_version = 2 if s.respond_to? :specification_version=
10
+
11
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
+ s.authors = ["Markus Prinz"]
13
+ s.date = %q{2008-04-04}
14
+ s.default_executable = %q{codebutler}
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
+ s.email = %q{markus.prinz@qsig.org}
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"]
20
+ s.has_rdoc = true
21
+ s.homepage = %q{http://codebutler.rubyforge.org}
22
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Codebutler", "--main", "README"]
23
+ s.require_paths = ["lib"]
24
+ s.rubyforge_project = %q{codebutler}
25
+ s.rubygems_version = %q{1.1.0}
26
+ s.summary = %q{Easily serve your highlighted codefiles from a local webserver}
27
+
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"])
31
+ end
32
+
33
+
34
+ # # Original Rakefile source (requires the Echoe gem):
35
+ #
36
+ # require 'rubygems'
37
+ # require 'rake'
38
+ # require 'echoe'
39
+ #
40
+ # Echoe.new("codebutler") do |p|
41
+ # p.author = "Markus Prinz"
42
+ # p.email = "markus.prinz@qsig.org"
43
+ # p.summary = "Easily serve your highlighted codefiles from a local webserver"
44
+ # 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
+ # 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"]
48
+ # end
49
+ #
data/lib/codebutler.rb ADDED
@@ -0,0 +1,71 @@
1
+ require 'find'
2
+ require 'coderay'
3
+ require 'uri'
4
+
5
+ # We bring our own version of sinatra since it's not available as a gem yet
6
+ require 'codebutler/sinatra'
7
+
8
+ module CodeButler
9
+
10
+ SUPPORTED_LANGUAGES = {
11
+ '.rb' => :ruby,
12
+ '.c' => :c,
13
+ '.cc' => :c,
14
+ '.h' => :c,
15
+ '.html' => :html,
16
+ '.rhtml' => :rhtml,
17
+ }
18
+
19
+ # Ignore all command line arguments except -p
20
+ #
21
+ # Also clears ARGV to prevent sinatra from parsing
22
+ def self.clean_argv
23
+ if ARGV.include? "-h"
24
+ puts <<-HELP
25
+ Supported Arguments:
26
+ -h Print help (You're looking at it)
27
+ -p port Start the webserver on the specified port
28
+ HELP
29
+ exit
30
+ elsif ARGV.include?("-p") && ARGV[ARGV.index("-p") + 1] =~ /^[0-9]+$/
31
+ port = ARGV[ARGV.index("-p") + 1]
32
+ ARGV.clear
33
+ ARGV.unshift ['-p', port.to_s]
34
+ else
35
+ ARGV.clear
36
+ end
37
+ end
38
+
39
+ # Scan the current working directory for files, and serve them on localhost
40
+ def self.serve
41
+ clean_argv
42
+
43
+ # Create a regex that matches all supported file endings
44
+ regex = Regexp.new("(#{SUPPORTED_LANGUAGES.keys.map{|key| Regexp.escape(key)}.join('|')})$")
45
+
46
+ working_dir = Dir.getwd
47
+ file_list = []
48
+
49
+ Find.find(working_dir) do |path|
50
+ if match = path.match(regex)
51
+ relative_path = path.slice( working_dir.length..-1 )
52
+
53
+ file_list << relative_path
54
+
55
+ # use Sinatra for easy hosting
56
+ # We need to escape the path to catch names like "c++test.rb" and spaces
57
+ get(Regexp.escape(URI.escape(relative_path))) do
58
+ CodeRay.scan(File.readlines(path).join, SUPPORTED_LANGUAGES[match[1]]).div
59
+ end
60
+ end
61
+ end
62
+
63
+ # index:
64
+ get('/') do
65
+ file_list.sort.map { |file|
66
+ %Q(<a href="#{file}">#{file}</a>)
67
+ }.join("<br/>")
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,1060 @@
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'
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: codebutler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Markus Prinz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-04-04 00:00:00 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: coderay
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.7.4
23
+ version:
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
35
+ version_requirement:
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.0
41
+ version:
42
+ 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.
43
+ email: markus.prinz@qsig.org
44
+ executables:
45
+ - codebutler
46
+ extensions: []
47
+
48
+ extra_rdoc_files:
49
+ - bin/codebutler
50
+ - lib/codebutler/sinatra.rb
51
+ - lib/codebutler.rb
52
+ - README
53
+ files:
54
+ - bin/codebutler
55
+ - lib/codebutler/sinatra.rb
56
+ - lib/codebutler.rb
57
+ - LICENCE
58
+ - Manifest
59
+ - README
60
+ - codebutler.gemspec
61
+ has_rdoc: true
62
+ homepage: http://codebutler.rubyforge.org
63
+ post_install_message:
64
+ rdoc_options:
65
+ - --line-numbers
66
+ - --inline-source
67
+ - --title
68
+ - Codebutler
69
+ - --main
70
+ - README
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ version:
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ requirements: []
86
+
87
+ rubyforge_project: codebutler
88
+ rubygems_version: 1.1.0
89
+ signing_key:
90
+ specification_version: 2
91
+ summary: Easily serve your highlighted codefiles from a local webserver
92
+ test_files: []
93
+