camping 1.4 → 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,5 +1,16 @@
1
+ = 1.4.2
2
+ === 10th May, 2006
3
+
4
+ * Efficient file uploads for multipart/form-data POSTs.
5
+ * Camping tool now uses Mongrel, if available. If not, sticks with WEBrick.
6
+
7
+ = 1.4.1
8
+ === 3rd May, 2006
9
+
10
+ * Streaming HTTP support. If body is IO, will simply pass to the controller. Mongrel, in particular, supports this nicely.
11
+
1
12
  = 1.4
2
- === 17th February, 2006
13
+ === 11th April, 2006
3
14
 
4
15
  * Moved Camping::Controllers::Base to Camping::Base.
5
16
  * Moved Camping::Controllers::R to Camping::R.
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2006 why the lucky stiff
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,89 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/rdoctask'
5
+ require 'fileutils'
6
+ include FileUtils
7
+
8
+ NAME = "camping"
9
+ VERS = "1.4.2"
10
+ CLEAN.include ['**/.*.sw?', '*.gem', '.config']
11
+ RDOC_OPTS = ['--quiet', '--title', "Camping, the Documentation",
12
+ "--template", "extras/flipbook_rdoc.rb",
13
+ "--opname", "index.html",
14
+ "--line-numbers",
15
+ "--main", "README",
16
+ "--inline-source"]
17
+
18
+ desc "Packages up Camping."
19
+ task :default => [:package]
20
+ task :package => [:clean]
21
+
22
+ task :doc => [:before_doc, :rdoc, :after_doc]
23
+
24
+ task :before_doc do
25
+ mv "lib/camping.rb", "lib/camping-mural.rb"
26
+ mv "lib/camping-unabridged.rb", "lib/camping.rb"
27
+ end
28
+
29
+ Rake::RDocTask.new do |rdoc|
30
+ rdoc.rdoc_dir = 'doc'
31
+ rdoc.options += RDOC_OPTS
32
+ rdoc.template = "extras/flipbook_rdoc.rb"
33
+ rdoc.main = "README"
34
+ rdoc.title = "Camping, the Documentation"
35
+ rdoc.rdoc_files.add ['README', 'CHANGELOG', 'COPYING', 'lib/camping.rb']
36
+ end
37
+
38
+ task :after_doc do
39
+ mv "lib/camping.rb", "lib/camping-unabridged.rb"
40
+ mv "lib/camping-mural.rb", "lib/camping.rb"
41
+ cp "extras/Camping.gif", "doc/"
42
+ cp "extras/permalink.gif", "doc/"
43
+ sh %{scp -r doc/* #{ENV['USER']}@rubyforge.org:/var/www/gforge-projects/camping/}
44
+ end
45
+
46
+ spec =
47
+ Gem::Specification.new do |s|
48
+ s.name = NAME
49
+ s.version = VERS
50
+ s.platform = Gem::Platform::RUBY
51
+ s.has_rdoc = true
52
+ s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"]
53
+ s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)\/', '--exclude', 'lib/camping.rb']
54
+ s.summary = "minature rails for stay-at-home moms"
55
+ s.description = s.summary
56
+ s.author = "why the lucky stiff"
57
+ s.email = 'why@ruby-lang.org'
58
+ s.homepage = 'http://code.whytheluckystiff.net/camping/'
59
+ s.executables = ['camping']
60
+
61
+ s.add_dependency('activerecord', '>=1.14.2')
62
+ s.add_dependency('markaby', '>=0.4')
63
+ s.add_dependency('metaid')
64
+ s.required_ruby_version = '>= 1.8.2'
65
+
66
+ s.files = %w(COPYING README Rakefile) +
67
+ Dir.glob("{bin,doc/rdoc,test,lib,extras}/**/*") +
68
+ Dir.glob("ext/**/*.{h,c,rb}") +
69
+ Dir.glob("examples/**/*.rb") +
70
+ Dir.glob("tools/*.rb")
71
+
72
+ s.require_path = "lib"
73
+ # s.extensions = FileList["ext/**/extconf.rb"].to_a
74
+ s.bindir = "bin"
75
+ end
76
+
77
+ Rake::GemPackageTask.new(spec) do |p|
78
+ p.need_tar = true
79
+ p.gem_spec = spec
80
+ end
81
+
82
+ task :install do
83
+ sh %{rake package}
84
+ sh %{sudo gem install pkg/#{NAME}-#{VERS}}
85
+ end
86
+
87
+ task :uninstall => [:clean] do
88
+ sh %{sudo gem uninstall #{NAME}}
89
+ end
data/bin/camping CHANGED
@@ -2,62 +2,220 @@
2
2
 
3
3
  # this line prevents other db adapters from being loaded (oci8 was
4
4
  # causing some pain.)
5
- RAILS_CONNECTION_ADAPTERS = %w[sqlite]
5
+ unless Object.const_defined? :RAILS_CONNECTION_ADAPTERS
6
+ RAILS_CONNECTION_ADAPTERS = []
7
+ end
8
+ RAILS_CONNECTION_ADAPTERS.replace %w[sqlite]
6
9
 
10
+ require 'delegate'
11
+ require 'optparse'
7
12
  require 'stringio'
8
- require 'webrick/httpserver'
9
- require 'camping/webrick'
13
+ require 'rubygems'
14
+ require 'camping'
15
+
16
+ host = '0.0.0.0'
17
+ port = 3301
18
+
19
+ db = nil
20
+ homes = []
21
+ homes << File.join( ENV['HOME'], '.camping.db' ) if ENV['HOME']
22
+ homes << File.join( ENV['APPDATA'], 'Camping.db' ) if ENV['APPDATA']
23
+ homes.each do |db|
24
+ break if File.exists?( db )
25
+ end
26
+
27
+
28
+ opts = OptionParser.new do |opts|
29
+ opts.banner = "Usage: camping app1.rb, app2.rb..."
30
+ opts.define_head "#{File.basename($0)}, the microframework ON-button for ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
31
+ opts.separator ""
32
+ opts.separator "Specific options:"
33
+
34
+ opts.on("-h", "--host HOSTNAME", "Host for web server to bind to (default is all IPs)") do |h|
35
+ host = h
36
+ end
10
37
 
11
- (puts <<USAGE; exit) if ARGV.length == 0
12
- #{File.basename($0)}, the microframework ON-button for ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]
13
- Usage: #{File.basename($0)} your.camping.rb [your.camping.db]
14
- USAGE
38
+ opts.on("-p", "--port NUM", "Port for web server (defaults to #{port})") do |p|
39
+ port = p
40
+ end
15
41
 
16
- script, db = ARGV[0..-1]
42
+ opts.on("-d", "--database FILE", "Database file (defaults to #{db})") do |d|
43
+ db = d
44
+ end
17
45
 
18
- unless db
19
- homes = []
20
- homes << File.join( ENV['HOME'], '.camping.db' ) if ENV['HOME']
21
- homes << File.join( ENV['APPDATA'], 'Camping.db' ) if ENV['APPDATA']
22
- homes.each do |db|
23
- break if File.exists?( db )
46
+ opts.separator ""
47
+ opts.separator "Common options:"
48
+
49
+ # No argument, shows at tail. This will print an options summary.
50
+ # Try it and see!
51
+ opts.on_tail("-h", "--help", "Show this message") do
52
+ puts opts
53
+ exit
24
54
  end
55
+
56
+ # Another typical switch to print the version.
57
+ opts.on_tail("--version", "Show version") do
58
+ class << Gem; attr_accessor :loaded_specs; end
59
+ puts Gem.loaded_specs['camping'].version
60
+ exit
61
+ end
62
+ end
63
+
64
+ opts.parse! ARGV
65
+ if ARGV.length < 1
66
+ puts opts
67
+ exit
25
68
  end
26
69
 
27
70
  Camping::Models::Base.establish_connection :adapter => 'sqlite3', :database => db
28
71
 
29
- class WEBrick::CampingHandler
30
- attr_accessor :klass, :mtime
31
- end
72
+ class CampingReloader
73
+ attr_accessor :klass, :mtime, :mount
74
+
75
+ def initialize(script)
76
+ @script = script
77
+ @mount = File.basename(script, '.rb')
78
+ load_app
79
+ end
32
80
 
33
- # Load the script, locate the module
34
- def camp_load(server, script, klass = nil)
35
- Object.instance_eval { remove_const klass.name } if klass
36
- mtime = File.mtime(script)
37
- load script
38
- klass = Object.const_get(Object.constants.grep(/^#{File.basename(script)[/^(\w+)/,1]}$/i)[0]) rescue nil
39
- klass ||= Camping
40
- klass.create if klass.respond_to? :create
41
-
42
- brick = WEBrick::CampingHandler.new(server, klass)
43
- brick.mtime = mtime
44
- brick
81
+ def load_app
82
+ @mtime = File.mtime(@script)
83
+ title = File.basename(@script)[/^(\w+)/,1]
84
+ begin
85
+ load @script
86
+ rescue Exception => e
87
+ puts "!! trouble loading #{title}: [#{e.class}] #{e.message}"
88
+ @klass = nil
89
+ return
90
+ end
91
+
92
+ @klass = Object.const_get(Object.constants.grep(/^#{title}$/i)[0]) rescue nil
93
+ unless @klass.const_defined? :C
94
+ puts "!! trouble loading #{title}: not a Camping app"
95
+ @klass = nil
96
+ return
97
+ end
98
+ @klass.create if @klass.respond_to? :create
99
+ @klass
100
+ end
101
+
102
+ # Load the script, locate the module
103
+ def reload_app
104
+ newtime = File.mtime( @script )
105
+ return if @klass and @mtime and newtime <= @mtime
106
+
107
+ k = @klass
108
+ Object.instance_eval { remove_const k.name } if k
109
+ load_app
110
+ end
111
+
112
+ def run(*a)
113
+ reload_app
114
+ if @klass
115
+ @klass.run(*a)
116
+ else
117
+ Camping.run(*a)
118
+ end
119
+ end
120
+
121
+ def view_source
122
+ File.read(@script)
123
+ end
45
124
  end
46
125
 
47
- # Mount the root
48
- s = WEBrick::HTTPServer.new(:BindAddress => '0.0.0.0', :Port => 3301)
49
- brick = camp_load( s, script )
50
- s.mount_proc("/") do |req, resp|
51
- newtime = File.mtime( script )
52
- if newtime > brick.mtime
53
- brick = camp_load( s, script, brick.klass )
126
+ apps = ARGV.map { |script| CampingReloader.new(script) }
127
+ def apps.index_page
128
+ welcome = "You are Camping"
129
+ apps = self
130
+ b = Markaby::Builder.new({}, {})
131
+ b = b.instance_eval do
132
+ html do
133
+ head do
134
+ title welcome
135
+ style <<-END, :type => 'text/css'
136
+ body {
137
+ font-family: verdana, arial, sans-serif;
138
+ padding: 10px 40px;
139
+ margin: 0;
140
+ }
141
+ h1, h2, h3, h4, h5, h6 {
142
+ font-family: utopia, georgia, serif;
143
+ }
144
+ END
145
+ end
146
+ body do
147
+ h1 welcome
148
+ p %{Good day. These are the Camping apps you've mounted.}
149
+ ul do
150
+ apps.each do |app|
151
+ next unless app.klass
152
+ li do
153
+ h3(:style => "display: inline") { a app.klass.name, :href => "/#{app.mount}" }
154
+ small { text " / " ; a "View Source", :href => "/code/#{app.mount}" }
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
54
160
  end
55
- brick.do_GET(req, resp)
56
- nil
161
+ b.to_s
57
162
  end
58
163
 
59
- # Server up
60
- trap(:INT) do
61
- s.shutdown
164
+ begin
165
+ require 'mongrel'
166
+ require 'mongrel/camping'
167
+ class IndexHandler < Mongrel::HttpHandler
168
+ def initialize(apps)
169
+ @apps = apps
170
+ end
171
+ def process(req, res)
172
+ res.start(200) do |head, out|
173
+ out << @apps.index_page
174
+ end
175
+ end
176
+ end
177
+ class ViewSource < Mongrel::HttpHandler
178
+ def initialize(app)
179
+ @app = app
180
+ end
181
+ def process(req, res)
182
+ res.start(200) do |head, out|
183
+ head['Content-Type'] = 'text/plain'
184
+ out << @app.view_source
185
+ end
186
+ end
187
+ end
188
+ config = Mongrel::Configurator.new :host => host do
189
+ listener :port => port do
190
+ apps.each do |app|
191
+ uri "/#{app.mount}", :handler => Mongrel::Camping::CampingHandler.new(app)
192
+ uri "/code/#{app.mount}", :handler => ViewSource.new(app)
193
+ end
194
+ uri "/", :handler => IndexHandler.new(apps)
195
+ uri "/favicon.ico", :handler => Mongrel::Error404Handler.new("")
196
+ trap("INT") { stop }
197
+ run
198
+ end
199
+ end
200
+ config.join
201
+ rescue LoadError
202
+ require 'webrick/httpserver'
203
+ require 'camping/webrick'
204
+
205
+ # Mount the root
206
+ s = WEBrick::HTTPServer.new(:BindAddress => host, :Port => port)
207
+ apps.each do |app|
208
+ s.mount "/#{app.mount}", WEBrick::CampingHandler, app
209
+ s.mount_proc("/code/#{app.mount}") do |req, resp|
210
+ resp['Content-Type'] = 'text/plain'
211
+ resp.body = app.view_source
212
+ end
213
+ end
214
+ s.mount_proc("/") { |req, resp| resp.body = apps.index_page }
215
+
216
+ # Server up
217
+ trap(:INT) do
218
+ s.shutdown
219
+ end
220
+ s.start
62
221
  end
63
- s.start
@@ -123,6 +123,7 @@ module Camping
123
123
  raise NoMethodError, "#{m}"
124
124
  end
125
125
  end
126
+ alias_method :u, :regular_update
126
127
  end
127
128
 
128
129
  # Helpers contains methods available in your controllers and views. You may add
@@ -252,7 +253,7 @@ module Camping
252
253
  def URL c='/',*a
253
254
  c = R(c, *a) if c.respond_to? :urls
254
255
  c = self/c
255
- c = "http://"+@env.HTTP_HOST+c if c[/^\//]
256
+ c = "//"+@env.HTTP_HOST+c if c[/^\//]
256
257
  URI(c)
257
258
  end
258
259
  end
@@ -356,21 +357,37 @@ module Camping
356
357
  qs = C.qs_parse(e.QUERY_STRING)
357
358
  @in = r
358
359
  if %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)|n.match(e.CONTENT_TYPE)
359
- b = "--#$1"
360
- @in.read.split(/(?:\r?\n|\A)#{ Regexp::quote( b ) }(?:--)?\r\n/m).each { |pt|
361
- h,v=pt.split("\r\n\r\n",2);fh={}
362
- [:name, :filename].each { |x|
363
- fh[x] = $1 if h =~ /^Content-Disposition: form-data;.*(?:\s#{x}="([^"]+)")/m
364
- }
365
- fn = fh[:name]
366
- if fh[:filename]
367
- fh[:type]=$1 if h =~ /^Content-Type: (.+?)(\r\n|\Z)/m
368
- fh[:tempfile]=Tempfile.new(:C).instance_eval {binmode;write v;rewind;self}
360
+ b = /(?:\r?\n|\A)#{Regexp::quote("--#$1")}(?:--)?\r$/
361
+ until @in.eof?
362
+ fh=H[]
363
+ for l in @in
364
+ case l
365
+ when "\r\n": break
366
+ when /^Content-Disposition: form-data;/
367
+ fh.u H[*$'.scan(/(?:\s(\w+)="([^"]+)")/).flatten]
368
+ when /^Content-Type: (.+?)(\r$|\Z)/m
369
+ puts "=> fh[type] = #$1"
370
+ fh[:type] = $1
371
+ end
372
+ end
373
+ fn=fh[:name]
374
+ o=if fh[:filename]
375
+ o=fh[:tempfile]=Tempfile.new(:C)
376
+ o.binmode
369
377
  else
370
- fh=v
378
+ fh=""
379
+ end
380
+ while l=@in.read(16384)
381
+ if l=~b
382
+ o<<$`.chomp
383
+ @in.seek(-$'.size,IO::SEEK_CUR)
384
+ break
385
+ end
386
+ o<<l
371
387
  end
372
388
  qs[fn]=fh if fn
373
- }
389
+ fh[:tempfile].rewind if fh.is_a?H
390
+ end
374
391
  elsif @method == "post"
375
392
  qs.merge!(C.qs_parse(@in.read))
376
393
  end
@@ -540,11 +557,11 @@ module Camping
540
557
  # #=> {'post' => {'id' => '1', 'user' => '_why'}}
541
558
  #
542
559
  def qs_parse(qs, d = '&;')
543
- m = proc {|_,o,n|o.merge(n,&m)rescue([*o]<<n)}
560
+ m = proc {|_,o,n|o.u(n,&m)rescue([*o]<<n)}
544
561
  (qs||'').
545
562
  split(/[#{d}] */n).
546
563
  inject(H[]) { |h,p| k, v=un(p).split('=',2)
547
- h.merge(k.split(/[\]\[]+/).reverse.
564
+ h.u(k.split(/[\]\[]+/).reverse.
548
565
  inject(v) { |x,i| H[i,x] },&m)
549
566
  }
550
567
  end