codebutler 0.0.3

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/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
+