camping 1.4 → 1.4.2

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