SqueezeBox 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,42 @@
1
+ SqueezeBox is a Ruby-language web framework for small websites.
2
+
3
+ Sometimes Rails is too sophisticated to set up something simple like a poll or quiz for livejournal. Squeeze Box is like 1995; it routes by the file system. Squeeze Box makes large projects painful but small ones simple.
4
+
5
+ = Using SqueezeBox
6
+ Create a directory for your web page. There are two things to put in there
7
+ * initialize.rb, will be called on to load external libraries, models, or anything else needed to use your site.
8
+ * public/ the document root directory
9
+
10
+ Inside of public/ you place .erb files. The additionally you may place a .rb file of the same basename which contains code that will be called before your erb file loads.
11
+
12
+ Example:
13
+ Local Web
14
+ /mywebsite/public/happy.erb http://mywebsite.com/happy
15
+ /mywebsite/public/happy.rb
16
+
17
+ /mywebsite/public/index.erb http://mywebsite.com/
18
+ /mywebsite/public/index.rb
19
+
20
+ /mywebsite/public/blah/index.erb http://mywebsite.com/blah
21
+ /mywebsite/public/blah/index.rb
22
+
23
+ The last line of code of the .rb file, if it exists, is tells the SqueezeBox what to do with the request. Typically this command will be 'render' which means 'render the .erb file with the same basename' but it could just be a string of JSON or a redirect.
24
+
25
+ = Example Application
26
+ Please look in examples/quotes for a complete application. To start this run the squeeze_box command with the examples/quotes directory as argument.
27
+ Something like this:
28
+
29
+ lakshmi:~/Projects/squeeze_box% bin/squeeze_box -p 3000 examples/quotes
30
+ Squeeze Box running at http://0.0.0.0:3000
31
+ Root: /Users/ry/Projects/squeeze_box/examples/quotes
32
+
33
+
34
+ = Future
35
+ My plan is to add the basic features necessary for every web application, cookies, sessions, logging. I love haml and will add support for it. Beyond that I want to follow the thin server philosophy and not touch anything else; no plug-in framework, no generators, and certainly no authorization schemes.
36
+ I don't even want to add ActiveRecord as a dependency.
37
+ This is to be a spartan and dirty interface to mongrel.
38
+ The SVN repo is at
39
+ http://tinyclouds.org/svn/squeeze_box/trunk
40
+
41
+ Bug reports, patches, or comments are gladly accepted at
42
+ ry@tinyclouds.org
@@ -0,0 +1,104 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/testtask'
6
+ require 'spec/rake/spectask'
7
+ require 'fileutils'
8
+ def __DIR__
9
+ File.dirname(__FILE__)
10
+ end
11
+
12
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
13
+
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = 'SqueezeBox'
17
+ s.version = "0.0.1"
18
+ s.summary = "A Web Framework Without Fur or Limestone"
19
+ s.description = s.summary
20
+ s.email = 'ry@tinyclouds.org'
21
+ s.author = 'ry dahl'
22
+ s.has_rdoc = true
23
+ s.extra_rdoc_files = %w(README)
24
+ s.homepage = 'http://squeezebox.rubyforge.org'
25
+ s.rubyforge_project = 'squeezebox'
26
+ s.files = FileList['Rakefile', 'lib/**/*', 'bin/*', 'specs/**/*',
27
+ 'examples/**/*']
28
+ s.executables << 'squeezebox'
29
+ s.test_files = Dir['specs/**/*_spec.rb']
30
+
31
+ s.add_dependency 'mongrel'
32
+ s.rdoc_options = ['--title', "SqueezeBox",
33
+ '--main', 'README',
34
+ '--line-numbers', '--inline-source']
35
+ end
36
+
37
+ Rake::RDocTask.new do |rdoc|
38
+ files =['README', 'TODO', 'lib/**/*.rb']
39
+ rdoc.rdoc_files.add(files)
40
+ rdoc.main = "README" # page to start on
41
+ rdoc.title = "SqueezeBox"
42
+ rdoc.rdoc_dir = 'doc' # rdoc output folder
43
+ rdoc.options << '--line-numbers' << '--inline-source'
44
+ end
45
+
46
+ Rake::GemPackageTask.new(spec) do |p|
47
+ p.need_tar = true
48
+ p.gem_spec = spec
49
+ end
50
+
51
+ desc "Run specs"
52
+ Spec::Rake::SpecTask.new('specs') do |t|
53
+ t.spec_opts = ["--format", "specdoc"]
54
+ t.spec_files = Dir['specs/*_spec.rb'].sort
55
+ end
56
+
57
+ desc "RCov"
58
+ Spec::Rake::SpecTask.new('rcov') do |t|
59
+ t.spec_opts = ["--format", "specdoc"]
60
+ t.spec_files = Dir['specs/*_spec.rb'].sort
61
+ t.libs = ['lib' ]
62
+ t.rcov = true
63
+ end
64
+
65
+ task :deploy => :rdoc do
66
+ sh %(scp -r website/*.{html,css,png} rydahl@rubyforge.org:/var/www/gforge-projects/squeezebox/)
67
+ sh %(scp -r doc/ rydahl@rubyforge.org:/var/www/gforge-projects/squeezebox/rdoc/)
68
+ end
69
+
70
+ desc 'Tag release'
71
+ task :tag do
72
+ svn_root = 'http://ry@tinyclouds.org/svn/squeeze_box'
73
+ sh %(svn cp #{svn_root}/trunk #{svn_root}/tags/rel-#{spec.version} -m "Tag #{spec.name} release #{spec.version}")
74
+ end
75
+
76
+ task :confirm_release do
77
+ print "Releasing version #{spec.version}. Are you sure you want to proceed? [Yn] "
78
+ abort if STDIN.getc == ?n
79
+ end
80
+
81
+ package_name = lambda {|specification| File.join('pkg', "#{specification.name}-#{specification.version}")}
82
+
83
+
84
+ desc 'Push a release to rubyforge'
85
+ task :release => [:confirm_release, :clean, :package] do
86
+ require 'rubyforge'
87
+ package = package_name[spec]
88
+
89
+ rubyforge = RubyForge.new
90
+ rubyforge.login
91
+
92
+ # version_already_released = lambda do
93
+ # releases = rubyforge.userconfig['rubyforge']['release_ids']
94
+ # releases.has_key?(spec.name) && releases[spec.name][spec.version]
95
+ # end
96
+ #
97
+ # abort("Release #{spec.version} already exists!") if version_already_released.call
98
+ #
99
+ if release_id = rubyforge.add_release(spec.rubyforge_project, spec.name, spec.version, "#{package}.tar.gz")
100
+ rubyforge.add_file(spec.rubyforge_project, spec.name, release_id, "#{package}.gem")
101
+ else
102
+ puts 'Release failed!'
103
+ end
104
+ end
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'ostruct'
4
+ require 'rubygems'
5
+
6
+ # this is so dumb that the following doesn't work
7
+ #autoload(:ActiveRecord, 'active_record')
8
+ #autoload(:Debugger, 'ruby-debug')
9
+ require 'active_record'
10
+ require 'ruby-debug'
11
+ require File.dirname(__FILE__) + '/../lib/squeeze_box'
12
+
13
+ include SqueezeBox
14
+
15
+ opts = OptionParser.new do |x|
16
+ x.banner = "Usage: squeeze_box [pde] path/to/website"
17
+
18
+ x.on('-p', '--port PORT') do |p|
19
+ OPTIONS.port = p.to_i
20
+ end
21
+
22
+ x.on('-d', '--debug') do
23
+ Debugger.start
24
+ $mongrel_debug_client = true
25
+ OPTIONS.debug = true
26
+ LOGGER.level = Logger::DEBUG
27
+ end
28
+
29
+ environments = %w{development test production}
30
+ x.on('-e', '--environment ENVIRONMENT', environments.join(' | ')) do |e|
31
+ if environments.include?(e.downcase!)
32
+ OPTIONS.environment = e
33
+ else
34
+ $stderr.puts "Invalid environment: #{e}"
35
+ exit 1
36
+ end
37
+ end
38
+
39
+ x.on("-h", "--help") do
40
+ puts x
41
+ exit
42
+ end
43
+ end
44
+ opts.parse!(ARGV)
45
+
46
+ unless(ARGV.length == 1)
47
+ puts opts
48
+ exit 1
49
+ end
50
+
51
+ OPTIONS.root = File.expand_path ARGV.shift
52
+
53
+ # Add the root dir to include path
54
+ $: << OPTIONS.root
55
+
56
+ # Change working dir
57
+ Dir.chdir( OPTIONS.root )
58
+
59
+
60
+ # Load Initialize Script
61
+ init_file = File.join(OPTIONS.root, 'initialize.rb')
62
+ require init_file if File.exists?(init_file)
63
+
64
+ # Load database.yml
65
+ db_config_file = File.join(OPTIONS.root, 'database.yml')
66
+ if File.exists?(db_config_file)
67
+ db_config = YAML::load(File.open(db_config_file))[OPTIONS.environment || 'development']
68
+ ActiveRecord::Base.establish_connection(db_config)
69
+ end
70
+
71
+ server = Mongrel::HttpServer.new('0.0.0.0', OPTIONS.port)
72
+ server.register("/", SqueezeBox::Handler.new)
73
+
74
+ LOGGER.info "Squeeze Box running at http://0.0.0.0:#{OPTIONS.port}"
75
+ LOGGER.info "Root: #{OPTIONS.root}"
76
+ trap("INT") do
77
+ LOGGER.info "Shutting down"
78
+ server.graceful_shutdown
79
+ exit
80
+ end
81
+ server.run.join
82
+
@@ -0,0 +1,3 @@
1
+ development:
2
+ adapter: sqlite3
3
+ database: database.db
@@ -0,0 +1,14 @@
1
+
2
+ class Quote < ActiveRecord::Base
3
+ end
4
+
5
+
6
+ set_layouts(
7
+ :default => 'layouts/default.erb'
8
+ )
9
+
10
+ def find_quote(quote_id)
11
+ quote = Quote.find(:first, :conditions => { :id => quote_id })
12
+ raise(NotFound, "Cannot find quote with id = #{quote_id}") if quote.nil?
13
+ quote
14
+ end
@@ -0,0 +1,13 @@
1
+ <html>
2
+ <head>
3
+ <title>Quotes <%= ' | ' + @page_title if @page_title %></title>
4
+ <link type="text/css" rel="stylesheet" href="/site.css" media="screen"/>
5
+ </head>
6
+ <body>
7
+ <h1 id="pageTitle"><%= @page_title ? @page_title : 'Quotes' %></h1>
8
+ <div id="mainContent">
9
+ <%= content %>
10
+ </div>
11
+ <div id="footer">:D</div>
12
+ </body>
13
+ </html>
@@ -0,0 +1,9 @@
1
+ raise MethodNotAllowed unless @request.post?
2
+
3
+ quote = Quote.new(@params['quote'])
4
+
5
+ if quote.save
6
+ redirect_to '/'
7
+ else
8
+ redirect_to '/new'
9
+ end
@@ -0,0 +1,5 @@
1
+ raise MethodNotAllowed unless @request.post?
2
+
3
+ @quote = find_quote(@params['id'])
4
+ @quote.destroy
5
+ redirect_to '/'
@@ -0,0 +1,17 @@
1
+ <% @page_title = 'Edit Quote' %>
2
+ <form action="/update?id=<%= @quote.id %>" method="post">
3
+ <p>
4
+ <label for="author">Author</label>
5
+ <input type="text" name="quote[author]" id="author" value="<%= @quote.author %>"/>
6
+ </p>
7
+
8
+ <p>
9
+ <label for="content">Content</label>
10
+ <textarea name="quote[content]" id="content" rows="5"><%= @quote.content %></textarea>
11
+ </p>
12
+
13
+ <input type="submit" value="Update"/>
14
+ </form>
15
+ <form action="/delete?id=<%= @quote.id %>" method="post">
16
+ <input type="submit" value="Delete"/>
17
+ </form>
@@ -0,0 +1,2 @@
1
+ @quote = find_quote(@params['id'])
2
+ render
@@ -0,0 +1,5 @@
1
+ <h1>NOT FOUND!@#!@#</h1>
2
+ <h2>omg</h2>
3
+
4
+ <p> you were trying to go to <code><%= @request.params['REQUEST_URI'] %></code>
5
+ but it's not there</p>
@@ -0,0 +1,9 @@
1
+ <% @page_title = 'All Quotes' %>
2
+ <a href="/new" class="action">new</a>
3
+ <% @quotes.each_with_index do |quote, i| %>
4
+ <div class='quote <%= i%2==0 ? 'even' : 'odd' %>'>
5
+ <h2 class="author"><%= quote.author %></h2>
6
+ <div class="content"><%= quote.content %></div>
7
+ <a href="/edit?id=<%= quote.id %>">edit</a>
8
+ </div>
9
+ <% end %>
@@ -0,0 +1,2 @@
1
+ @quotes = Quote.find(:all, :order => 'author')
2
+ render # <-- required
@@ -0,0 +1,14 @@
1
+ <% @page_title = 'New Quote' %>
2
+ <form action="/create" method="post">
3
+ <p>
4
+ <label for="author">Author</label>
5
+ <input type="text" name="quote[author]" id="author" />
6
+ </p>
7
+
8
+ <p>
9
+ <label for="content">Content</label>
10
+ <textarea name="quote[content]" id="content" rows="5"></textarea>
11
+ </p>
12
+
13
+ <input type="submit" value="Create"/>
14
+ </form>
@@ -0,0 +1,5 @@
1
+ <% @page_title = 'params' %>
2
+ <p><b>@params =</b> <code><%= @params.inspect %></code></p>
3
+
4
+
5
+ <p><b>@path =</b> <code><%= @path.inspect %></code></p>
@@ -0,0 +1,41 @@
1
+ body {
2
+ font-family: Arial;
3
+ margin: 0;
4
+ }
5
+
6
+ h1#pageTitle {
7
+ text-align: center; padding: 1em;
8
+ background: #ffa; border-bottom: 1px solid #999;
9
+ }
10
+
11
+ label { display: block; }
12
+
13
+ #mainContent {
14
+ margin: 1em;
15
+ }
16
+
17
+ #footer {
18
+ margin: 3em 0;
19
+ border-top: 1px dotted #aaa;
20
+ font-family: Courier;
21
+ text-align: center;
22
+ font-size: 20pt;
23
+ }
24
+
25
+ .action { display: block; margin: 1em; text-align: center;}
26
+
27
+ .quote {
28
+ padding: 5em;
29
+ }
30
+
31
+ .author {
32
+ font-size: inherit;
33
+ margin: 0;
34
+ }
35
+
36
+ .content {
37
+ margin: 0;
38
+ }
39
+
40
+ .even { background: #eef; }
41
+ .odd { }
@@ -0,0 +1,9 @@
1
+ raise MethodNotAllowed unless @request.post?
2
+
3
+ @quote = find_quote(@params['id'])
4
+
5
+ if @quote.update_attributes(@params['quote'])
6
+ redirect_to '/'
7
+ else
8
+ redirect_to '/new'
9
+ end
@@ -0,0 +1,31 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
4
+ <head>
5
+ <style>
6
+ body { margin: 1em; font-family: Courier; color: #444;}
7
+ #httpStatus { text-align: center; margin: 0.2em; }
8
+ #httpStatus h1, #httpStatus h2 {
9
+ padding: 0.0em; line-height: 90%; margin: 0;
10
+ }
11
+ #httpStatus h1 { font-size: 130pt; }
12
+ #httpStatus h2 { font-size: 20pt; }
13
+ li { margin: 1em 0;}
14
+ </style>
15
+ <title><%= @status %> <%= @name %></title>
16
+ </head>
17
+ <body>
18
+ <div id="httpStatus">
19
+ <h1><%= @status %></h1>
20
+ <h2><%= @message %></h2>
21
+ </div>
22
+ <% if @backtrace and SqueezeBox::OPTIONS.environment == 'development' %>
23
+ <h2>Backtrace</h2>
24
+ <ol>
25
+ <% @backtrace.each do |level| %>
26
+ <li><%= level %></li>
27
+ <% end %>
28
+ </ol>
29
+ <% end %>
30
+ </body>
31
+ <html>
@@ -0,0 +1,349 @@
1
+ require 'rubygems'
2
+ require 'mongrel'
3
+ require 'erubis'
4
+ require 'logger'
5
+
6
+ module SqueezeBox
7
+ LOGGER = Logger.new(STDOUT)
8
+ OPTIONS = OpenStruct.new(
9
+ :debug => false,
10
+ :port => 4200,
11
+ :environment => 'development'
12
+ )
13
+ module Const
14
+ include Mongrel::Const
15
+ MIME_TYPES = Mongrel::DirHandler::MIME_TYPES
16
+ EXCEPTION_TEMPLATE = Erubis::Eruby.new(File.read(
17
+ File.join(File.dirname(__FILE__), 'http_error.erb') # lib/http_error.erb
18
+ ))
19
+ HTTP_COOKIE = 'HTTP_COOKIE'.freeze
20
+ QUERY_STRING = 'QUERY_STRING'.freeze
21
+ end
22
+
23
+ module HTTPErrorMixin
24
+ def resource_path
25
+ "/exceptions/#{self.class.name.split('::').last.snake_case}"
26
+ end
27
+ def status; self.class::STATUS; end
28
+ end
29
+
30
+ class Unauthorized < StandardError
31
+ include HTTPErrorMixin
32
+ STATUS = 401
33
+ end
34
+ class Forbidden < StandardError
35
+ include HTTPErrorMixin
36
+ STATUS = 403
37
+ end
38
+ class NotFound < StandardError
39
+ include HTTPErrorMixin
40
+ STATUS = 404
41
+ end
42
+ class MethodNotAllowed < StandardError
43
+ include HTTPErrorMixin
44
+ STATUS = 405
45
+ end
46
+
47
+ # Alias this method from Resource for easy access
48
+ def set_layouts(layouts); Resource.set_layouts(layouts); end
49
+
50
+ class Resource
51
+ @@cache = {} # {normalized_path => resource}
52
+ @@layouts = {} # { symbol => Erubis template}. :default layout is default
53
+
54
+ def self.root
55
+ File.expand_path(File.join(OPTIONS.root, 'public'))
56
+ end
57
+
58
+ def self.set_layouts(layouts)
59
+ layouts.each do |sym, filename|
60
+ path = File.join(OPTIONS.root, filename)
61
+ @@layouts[sym] = Erubis::Eruby.new(File.read(path))
62
+ end
63
+ end
64
+
65
+ # Finds a cached Resource in the cache
66
+ def self.find(path)
67
+ path = normalize_path(path)
68
+ if @@cache.has_key?(path)
69
+ resource = @@cache[path]
70
+ resource.reload unless OPTIONS.environment == 'production'
71
+ resource
72
+ else # the resource doesn't exist yet
73
+ nil
74
+ end
75
+ end
76
+
77
+ def self.clear_cache; @@cache = {}; end
78
+
79
+ # Takes a path relative to Resource.root or a full filesystem path
80
+ # and returns a normalized path.
81
+ def self.normalize_path(path)
82
+ path = File.expand_path(path)
83
+ full_path = path.index(root) == 0 ? path : File.join(root, path)
84
+ full_path = File.join(full_path, 'index') if File.directory?(full_path)
85
+ File.extensionless_path(full_path)
86
+ end
87
+
88
+ attr_reader :path
89
+
90
+ # =Options=
91
+ # path: full path to the resource. this path should not have
92
+ # any extensions e.g. /home/ry/document_root/images/background
93
+ # not /home/ry/document_root/images/background.jpg
94
+ # default_template: a default template to use for rendering in the absence
95
+ # of a resource in the file system (compiled Erubis tempalte)
96
+ def initialize(path, opts={})
97
+ LOGGER.info "Resource initialized for #{path}"
98
+ @path = self.class.normalize_path(path)
99
+
100
+ @default_template = opts[:default_template] if opts.has_key? :default_template
101
+
102
+ @default_content_type = "application/octet-stream".freeze
103
+ reload()
104
+
105
+ @@cache[@path] = self
106
+ end
107
+
108
+ # reload the .rb and .erb files
109
+ def reload
110
+ @static_files = []
111
+ Dir.glob(@path + '*').each do |file|
112
+ case File.extname(file)
113
+ when '.rb'; @logic_file = file
114
+ when '.erb'; @template_file = file
115
+ else; @static_files.push(file)
116
+ end
117
+ end
118
+
119
+ if @logic_file
120
+ @to_eval = File.read(@logic_file) # !!!
121
+
122
+ # Another option is to try to load this data into a method. If we have
123
+ # users put 'def serve; code; end' in the sever_file, then we could
124
+ # do something like this:
125
+ # instance_eval { load(@logic_file)
126
+ end
127
+
128
+ @template =
129
+ if @template_file
130
+ Erubis::Eruby.new(File.read(@template_file))
131
+ else
132
+ @default_template
133
+ end
134
+ end
135
+
136
+ # Call this to get the response of an HTTP request. The parameter is a
137
+ # Mongrel::HttpRequest object
138
+ def execute(request)
139
+ @cookies = request.cookies
140
+ @params = request.cgi_params
141
+
142
+ # TODO clean up (is this really nessessary?)
143
+ path_info = Mongrel::HttpRequest.unescape(request.params[Const::PATH_INFO])
144
+ @request_extension = File.extname(path_info)
145
+
146
+ @header = { Const::CONTENT_TYPE => 'text/html'}
147
+ @request = request
148
+
149
+ # before_filter?
150
+ # Default is to just render
151
+ body = @to_eval ? eval(@to_eval) : render()
152
+ # after_filter?
153
+
154
+ rtn = [@status || 200, @header, body]
155
+ # <TODO> clean this up...
156
+ # (evidence that a new class to handle requests is needed)
157
+ # clear variables:
158
+ @status = @header = @request_extension = @request = @params = @cookies = nil
159
+ # </TODO>
160
+ rtn
161
+ end
162
+
163
+ private
164
+
165
+ def redirect_to(path)
166
+ @status = 302
167
+ @header['Location'] = path
168
+ nil
169
+ end
170
+
171
+ def render(options = {})
172
+ # if request is actual file (in @static_files) then serve it
173
+ if file = @static_files.detect { |f| File.extname(f) == @request_extension }
174
+ # <TODO> serve with X-SendFile
175
+ file_status = File.stat(file)
176
+ @status = 200
177
+
178
+ content_type =
179
+ if dot_at = file.rindex('.')
180
+ Const::MIME_TYPES[file[dot_at .. -1]] || @default_content_type
181
+ else
182
+ @default_content_type
183
+ end
184
+
185
+ etag = Const::ETAG_FORMAT % [file_status.mtime.to_i, file_status.size, file_status.ino]
186
+
187
+ @header.update(
188
+ Const::LAST_MODIFIED => file_status.mtime.httpdate,
189
+ Const::ETAG => etag,
190
+ Const::CONTENT_LENGTH => file_status.size,
191
+ Const::CONTENT_TYPE => content_type
192
+ )
193
+ body = File.new(file, 'r')
194
+ # </TODO>
195
+
196
+ # otherwise render the template
197
+ elsif @template
198
+ @header[Const::CONTENT_TYPE] = 'text/html'
199
+ content = @template.result(binding)
200
+
201
+ # TODO clean up
202
+ body =
203
+ if options[:layout]
204
+ @@layouts[options[:layout]].result(binding)
205
+ elsif @@layouts.has_key?(:default) and options[:layout] != false
206
+ @@layouts[:default].result(binding)
207
+ else
208
+ content
209
+ end
210
+
211
+ # otherwise raise 404.
212
+ else
213
+ raise NotFound
214
+ end
215
+ end
216
+ end
217
+
218
+ class ExceptionResource < Resource
219
+ def initialize(path)
220
+ @default_template = Const::EXCEPTION_TEMPLATE
221
+ super(path)
222
+ end
223
+
224
+ def set_exception(e)
225
+ @status = e.respond_to?(:status) ? e.status : 500
226
+ @message = e.message
227
+ @backtrace = e.backtrace
228
+ @name = e.class.name
229
+ end
230
+
231
+ def render(options = {})
232
+ unless options.has_key?(:layout)
233
+ options[:layout] = false
234
+ end
235
+ super(options)
236
+ end
237
+ end
238
+
239
+ class Handler < Mongrel::HttpHandler
240
+ def process(request, response)
241
+ path_info = Mongrel::HttpRequest.unescape(request.params[Const::PATH_INFO])
242
+ LOGGER.info "request: #{path_info}"
243
+ begin
244
+ resource = Resource.find(path_info) || Resource.new(path_info)
245
+ serve_resource(resource, request, response)
246
+ rescue StandardError => e
247
+ LOGGER.debug "Exception #{e} raised"
248
+ resource = Resource.find(e.resource_path) || ExceptionResource.new(e.resource_path)
249
+ resource.set_exception(e)
250
+ serve_resource(resource, request, response)
251
+ end
252
+ end
253
+
254
+ def serve_resource(resource, request, response)
255
+ response.status, resource_headers, body = resource.execute(request)
256
+
257
+ resource_headers[Const::CONTENT_LENGTH] = body.size if body.respond_to?(:size)
258
+ response.squeeze_box_send_status
259
+ # Send headers TODO clean up headers
260
+ resource_headers.each_pair { |k,v| response.header[k] = v }
261
+ response.send_header
262
+
263
+ if resource_headers.has_key?('X-Sendfile')
264
+ # we're done
265
+ @body_sent = true
266
+ elsif body.respond_to? :read
267
+ while chunk = body.read(Const::CHUNK_SIZE) and chunk.length > 0
268
+ begin
269
+ response.write(chunk)
270
+ rescue Object => exc
271
+ break
272
+ end
273
+ end
274
+ body.close if body.respond_to? :close
275
+ # response.write(Const::LINE_END)
276
+ @body_sent = true
277
+ else
278
+ response.write(body || ' ')
279
+ @body_sent = true
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+
286
+ ### Extensions
287
+
288
+ class StandardError
289
+ def resource_path; "/exceptions/index"; end
290
+ end
291
+
292
+ module Mongrel
293
+ class HttpRequest
294
+ # parses a query string or the payload of a POST
295
+ # request into the params hash. So for example:
296
+ # /foo?bar=nik&post[title]=heya&post[body]=whatever
297
+ # parses into:
298
+ # {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever'}}
299
+ def self.query_parse(qs, d = '&;')
300
+ m = proc {|_,o,n|o.update(n,&m)rescue([*o]<<n)}
301
+ (qs||'').split(/[#{d}] */n).inject(Hash[]) do |h,p|
302
+ k, v=unescape(p).split('=',2)
303
+ h.update(k.split(/[\]\[]+/).reverse.inject(v) { |x,i| Hash[i,x] },&m)
304
+ end
305
+ end
306
+ # ^--- by Ezra Zygmuntowicz; from merb/mixins/controller.rb
307
+
308
+ def post?; params['REQUEST_METHOD'].upcase == 'POST'.upcase; end
309
+
310
+ def get?; params['REQUEST_METHOD'].upcase == 'GET'.upcase; end
311
+
312
+ def cgi_params
313
+ unless @_cgi_params
314
+ @_cgi_params = HttpRequest.query_parse(@params[SqueezeBox::Const::QUERY_STRING] || '')
315
+ @_cgi_params.update( HttpRequest.query_parse(body.read) ) if post?
316
+ end
317
+ @_cgi_params
318
+ end
319
+
320
+ def cookies
321
+ @_cookies ||= HttpRequest.query_parse(@params[SqueezeBox::Const::HTTP_COOKIE], ';,')
322
+ end
323
+ end
324
+
325
+ class HttpResponse
326
+ def squeeze_box_send_status
327
+ if not @status_sent
328
+ # Do not write Content-Length to header (Bad for streaming)
329
+ #@header['Content-Length'] = content_length unless @status == 304
330
+ write(Const::STATUS_FORMAT % [@status, HTTP_STATUS_CODES[@status]])
331
+ @status_sent = true
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ class File
338
+ def self.extensionless_path(path)
339
+ dirname = File.dirname(path)
340
+ extensionless_basename = File.basename(path).sub(/\.[^.]+$/,'')
341
+ File.join(dirname, extensionless_basename)
342
+ end
343
+ end
344
+
345
+ class String
346
+ def snake_case
347
+ gsub(/\B[A-Z]/, '_\&').downcase
348
+ end
349
+ end
@@ -0,0 +1,4 @@
1
+ $global_variable = "hello"
2
+
3
+
4
+ # Reloader.watch( other_file )
@@ -0,0 +1 @@
1
+ Hello World!
@@ -0,0 +1,8 @@
1
+ <html>
2
+ <body>
3
+ <h1>Impressive Test</h1>
4
+ <p>a = <%= @a %></p>
5
+ <p>b = <%= @b %></p>
6
+ <p>global variabe = <%= $global_variable %></p>
7
+ </body>
8
+ </html>
@@ -0,0 +1,6 @@
1
+ def action
2
+ @b = 200
3
+ @a = "string"
4
+ render
5
+ end
6
+
@@ -0,0 +1 @@
1
+ Resource Not Found
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'ruby-debug'
4
+ Debugger.start
5
+ SQUEEZE_BOX_ROOT = File.expand_path(File.dirname(__FILE__) + '/fixtures')
6
+ require '../lib/squeeze_box'
7
+
8
+ include SqueezeBox
9
+
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe HTTPError do
4
+ it "should initialize" do
5
+ x = Unauthorized.new
6
+ x.class.should == Unauthorized
7
+ end
8
+
9
+ it "should build_resource" do
10
+ x = NotFound.new
11
+ resource = x.build_resource
12
+ resource.class.should == Resource
13
+ resource.path.should == File.join(SQUEEZE_BOX_ROOT,'public/http_errors/not_found')
14
+ end
15
+
16
+ it "should have default template which works" do
17
+ @backtrace = ['a', 'b', 'c']
18
+ @status = 404
19
+ @message = 'Not Found'
20
+ lambda do
21
+ out = HTTPError::DEFAULT_TEMPLATE.result(binding)
22
+ out.should =~ /html/i
23
+ out.should =~ /Not Found/
24
+ out.should =~ /404/
25
+ end.should_not raise_error
26
+ end
27
+
28
+ end
@@ -0,0 +1,33 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ describe Resource do
4
+ it "should instantiate with relative path" do
5
+ resource = Resource.new('/some_path')
6
+ resource.class.should == Resource
7
+ resource.path.should == File.join(SQUEEZE_BOX_ROOT, 'public/some_path')
8
+ end
9
+
10
+ it "should instantiate with full path" do
11
+ path = File.join(SQUEEZE_BOX_ROOT, 'public/some_path')
12
+ resource = Resource.new(path)
13
+ resource.class.should == Resource
14
+ resource.path.should == path
15
+ end
16
+
17
+ it "should get cached by relative path" do
18
+ Resource.find('/some_path').should be_nil
19
+ resource = Resource.new('/some_path')
20
+ Resource.find('/some_path').should == resource
21
+ end
22
+
23
+ it "should get cached by full path" do
24
+ path = File.join(SQUEEZE_BOX_ROOT, 'public/some_path')
25
+ Resource.find(path).should be_nil
26
+ resource = Resource.new('/some_path')
27
+ Resource.find(path).should == resource
28
+ end
29
+
30
+ after(:each) do
31
+ Resource.clear_cache
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.2
3
+ specification_version: 1
4
+ name: SqueezeBox
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.0.1
7
+ date: 2007-08-16 00:00:00 +02:00
8
+ summary: A Web Framework Without Fur or Limestone
9
+ require_paths:
10
+ - lib
11
+ email: ry@tinyclouds.org
12
+ homepage: http://squeezebox.rubyforge.org
13
+ rubyforge_project: squeezebox
14
+ description: A Web Framework Without Fur or Limestone
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - ry dahl
31
+ files:
32
+ - Rakefile
33
+ - lib/http_error.erb
34
+ - lib/squeeze_box.rb
35
+ - bin/squeezebox
36
+ - specs/fixtures
37
+ - specs/fixtures/enviroment.rb
38
+ - specs/fixtures/public
39
+ - specs/fixtures/public/hello.txt
40
+ - specs/fixtures/public/images
41
+ - specs/fixtures/public/images/background.jpg
42
+ - specs/fixtures/public/images/background.rb
43
+ - specs/fixtures/public/index.erb
44
+ - specs/fixtures/public/index.rb
45
+ - specs/fixtures/public/internal_server_error.erb
46
+ - specs/fixtures/public/not_found.erb
47
+ - specs/helper.rb
48
+ - specs/http_error_spec.rb
49
+ - specs/resource_spec.rb
50
+ - examples/quotes
51
+ - examples/quotes/database.db
52
+ - examples/quotes/database.yml
53
+ - examples/quotes/initialize.rb
54
+ - examples/quotes/layouts
55
+ - examples/quotes/layouts/default.erb
56
+ - examples/quotes/public
57
+ - examples/quotes/public/create.rb
58
+ - examples/quotes/public/delete.rb
59
+ - examples/quotes/public/edit.erb
60
+ - examples/quotes/public/edit.rb
61
+ - examples/quotes/public/exceptions
62
+ - examples/quotes/public/exceptions/not_found.erb
63
+ - examples/quotes/public/index.erb
64
+ - examples/quotes/public/index.rb
65
+ - examples/quotes/public/new.erb
66
+ - examples/quotes/public/params.erb
67
+ - examples/quotes/public/site.css
68
+ - examples/quotes/public/update.rb
69
+ - README
70
+ test_files:
71
+ - specs/http_error_spec.rb
72
+ - specs/resource_spec.rb
73
+ rdoc_options:
74
+ - --title
75
+ - SqueezeBox
76
+ - --main
77
+ - README
78
+ - --line-numbers
79
+ - --inline-source
80
+ extra_rdoc_files:
81
+ - README
82
+ executables:
83
+ - squeezebox
84
+ extensions: []
85
+
86
+ requirements: []
87
+
88
+ dependencies:
89
+ - !ruby/object:Gem::Dependency
90
+ name: mongrel
91
+ version_requirement:
92
+ version_requirements: !ruby/object:Gem::Version::Requirement
93
+ requirements:
94
+ - - ">"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.0.0
97
+ version: