hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

Wednesday

2006.01.18

Camping is a Microframework #

by why in bits

First, as a mural. For friends here who like to pick at the weave.

 %w[rubygems active_record markaby metaid ostruct].each {|lib| require lib}
 module Camping;C=self;module Models;end;Models::Base=ActiveRecord::Base
 module Helpers;def R c,*args;p=/\(.+?\)/;args.inject(c.urls.detect{|x|x.
 scan(p).size==args.size}.dup){|str,a|str.gsub(p,(a.method(a.class.primary_key
 )[]rescue a).to_s)};end;def / p;File.join(@root,p) end;end;module Controllers
 module Base;include Helpers;attr_accessor :input,:cookies,:headers,:body,
 :status,:root;def method_missing(m,*args,&blk);str=m==:render ? markaview(
 *args,&blk):eval("markaby.#{m}(*args,&blk)");str=markaview(:layout){str
 }rescue nil;r(200,str.to_s);end;def r(s,b,h={});@status=s;@headers.merge!(h)
 @body=b;end;def redirect(c,*args);c=R(c,*args)if c.respond_to?:urls;r(302,'',
 'Location'=>self/c);end;def service(r,e,m,a);@status,@headers,@root=200,{},e[
 'SCRIPT_NAME'];@cookies=C.cookie_parse(e['HTTP_COOKIE']||e['COOKIE']);cook=
 @cookies.marshal_dump.dup;if ("POST"==e['REQUEST_METHOD'])and %r|\Amultipart\
 /form-data.*boundary=\"?([^\";,]+)\"?|n.match(e['CONTENT_TYPE']);return r(500,
 "No multipart/form-data supported.")else;@input=C.qs_parse(e['REQUEST_METHOD'
 ]=="POST"?r.read(e['CONTENT_LENGTH'].to_i):e['QUERY_STRING']);end;@body=
 method(m.downcase).call(*a);@headers["Set-Cookie"]=@cookies.marshal_dump.map{
 |k,v|"#{k}=#{C.escape(v)}; path=/"if v != cook[k]}.compact;self;end;def to_s
 "Status: #{@status}\n#{{'Content-Type'=>'text/html'}.merge(@headers).map{|k,v|
 v.to_a.map{|v2|"#{k}: #{v2}"}}.flatten.join("\n")}\n\n#{@body}";end;def \
 markaby;Class.new(Markaby::Builder){@root=@root;include Views;def tag!(*g,&b)
 [:href,:action].each{|a|(g.last[a]=self./(g.last[a]))rescue 0};super end}.new(
 instance_variables.map{|iv|[iv[1..-1].intern,instance_variable_get(iv)]},{})
 end;def markaview(m,*args,&blk);markaby.instance_eval{Views.instance_method(m
 ).bind(self).call(*args, &blk);self}.to_s;end;end;class R;include Base end
 class NotFound<R;def get(p);r(404,div{h1("#{C} Problem!")+h2("#{p} not found")
 });end end;class ServerError<R;def get(k,m,e);r(500,markaby.div{h1 "#{C} Prob\
 lem!";h2 "#{k}.#{m}";h3 "#{e.class} #{e.message}:";ul{e.backtrace.each{|bt|li(
 bt)}}})end end;class<<self;def R(*urls);Class.new(R){meta_def(:inherited){|c|
 c.meta_def(:urls){urls}}};end;def D(path);constants.each{|c|k=const_get(c)
 return k,$~[1..-1] if (k.urls rescue "/#{c.downcase}").find {|x|path=~/^#{x}\
 \/?$/}};[NotFound,[path]];end end end;class<<self;def escape(s);s.to_s.gsub(
 /([^ a-zA-Z0-9_.-]+)/n){'%'+$1.unpack('H2'*$1.size).join('%').upcase}.tr(' ',
 '+') end;def unescape(s);s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){[$1.
 delete('%')].pack('H*')} end;def qs_parse(qs,d='&;');OpenStruct.new((qs||'').
 split(/[#{d}] */n).inject({}){|hsh,p|k,v=p.split('=',2).map{|v|unescape(v)}
 hsh[k]=v unless v.empty?;hsh}) end;def cookie_parse(s);c=qs_parse(s,';,') end
 def run(r=$stdin,w=$stdout);w<<begin;k,a=Controllers.D "/#{ENV['PATH_INFO']}".
 gsub(%r!/+!,'/');m=ENV['REQUEST_METHOD']||"GET";k.class_eval{include C
 include Controllers::Base;include Models};o=k.new;o.service(r,ENV,m,a);rescue\
 =>e;Controllers::ServerError.new.service(r,ENV,"GET",[k,m,e]);end;end;end
 module Views; include Controllers; include Helpers end;end

  1. gem install camping --source http://code.whytheluckystiff.net/
    • Installs the regular camping.rb.
    • ... and dependencies activerecord, markaby and metaid.
  2. For an example, see the blog.

Update:

Update: Field Office Outpost Bravo Bravo reports.

Update: Ranger Douglas has sent word.

Many rangers, myself included, returned from their morning jaunt to find this tiny yarn insignia fastened to their ranger doors without explanation. It seems peaceful, like something Camping would do, but you never know. Radicals often portray future animal executions in the simplest of strokes.

Camping has not had the time or inclination to rate and classify this message, so it is being displayed in its true, unfiltered form.

Update: Reader Justin Heat sent in this awful photo.

He says, “At my aunt’s viewing. Snapped a pic for you with my Treo 650.”

Off-topic. And NOT funny. Let’s start focusing on getting through this day before we talk about this kind of stuff. For crying out loud.

Update: What luck, this month’s Boy’s Life has a feature story for PDF subscribers only. Very in-depth. An excerpt:

Update: from #camping.

 20:11 <goalie> can camping do e-commerce?
 20:11 <_why> no, use rails
 20:11 <RangerCliff> heh wtf?
 20:13 <goalie> it's just a small store, we only have two products
 20:13 <goalie> root beer lip balm and cream soda lip balm
 20:13 * _why contemplates
 20:14 <goalie> mail-order only
 20:14 <_why> now you're talking!!

Update: 2006 uniforms are in.

Update:

Update: You may now clip the following article from your handbooks, the Camping Phys Ed staff has ejected it from the curriculum!

Update:

If you’ve come to this site looking for the Dog Scouts, or if you’ve never heard of Dog Scouts and would like to sign your dog (or dogs) up for Dog Scouts, please click on the image of the Geocaching 3 Merit Badge for Dogs (to your right, my left) and you will be whisked away to the correct site.

If you are looking for Doggles, they can be found here.

Matthew Cartwright from Aberdeen, WA, writes in:

My nine-year-old daughter has been captivated with Camping over the last day or so. We even got her a little tackle box to put all her supplies in. Her excitement really came to a crescendo when she read your story about Ranger Cliff hanging out in IRC. She’s been non-stop Ranger Cliff all morning.

Anyway, she’s now filming a stop-motion film about the wedding of Ranger Cliff, I’m including a still frame I just snapped over her shoulder. Ranger Cliff is the little, rotund, dark fellow.

Great work, Katy! You’ve nailed the Ranger. His balance is abyssmal.

Update: Photos are piling up on Flickr now that everyone’s back from CampingCamp, held last night at Ranger Mayhew’s house in Minneapolis. Jealous.

Alex Chanticlair took the following photo and adds:

Troops 142 and 146 were in attendance. Many a glowstick was had. I know it looks alot like a rave, but believe me, it’s a very safe, very loving environment, sort of like a rave, but with root beer and cream soda lip balms instead of Roofies or Vitamin K. Actually, Roofies are still pretty common at conventions, but usually those people are under the table and don’t pollute the general mood of self-photography.

Update: 100 bytes have been shaved from Camping 1.1. Update through gems or wherever.

Two steps to get Camping working under Lighttpd+FastCGI. At the bottom of your application script (blog.rb):

 if __FILE__ == $0
   require 'fcgi'
   Dir.chdir(File.dirname(__FILE__))
   Camping::Models::Base.establish_connection 
     :adapter => 'sqlite3', :database => 'blog3.db'
   Camping::Models::Base.logger = Logger.new('camping.log')
   FCGI.each do |req|
     ENV.replace req.env
     Camping.run(req.in, req.out)
     req.finish
   end
 end

A suitable lighttpd.conf would look like:

 server.port                 = 3044
 server.bind                 = "127.0.0.1" 
 server.modules              = ( "mod_fastcgi" )
 server.document-root        = "/var/camping/blog/" 
 server.errorlog             = "/var/camping/blog/error.log" 

 #### fastcgi module
 fastcgi.server = ( "/" => ( 
   "localhost" => ( 
     "socket" => "/tmp/camping-blog.socket",
     "bin-path" => "/var/camping/blog/blog.rb",
     "check-local" => "disable",
     "max-procs" => 1 ) ) )

The check-local directive is vital to make sure lighttpd passes all the requests into blog.rb without throwing a 404.

Update:

Update: Reader Bill Atley writes in:

This afternoon I was at my grandfather’s house (I’m his primary caretaker) and midway through a corn feeding, I look over and this little mouse has taken the stage on a bit of carpet over by the television, no idea where he got the little mike, but he started belting away—Walk This Way, Sweet Child of Mine—classic stuff! I swear, it has to be related!

Thanks, Bill. It has to be related.

Hot tip! Give the new symmetrical R is for Routing method a try, it’s in trunk or the 1.1.18 gem on http://code.whytheluckystiff.net/ (still at 3.2k!)

Pave routes with Controllers::R:

 module Camping::Controllers
   class BlogEdit < R '/edit/(\d+)'; end
 edn

Find a Route backwards inside controller and view methods with Helpers::R:

 def Camping::Views.all_posts
   for post in @posts
     a :href => R(BlogEdit, post)
   end
 end

Also works in redirects:

 redirect R(BlogEdit, 2)

One breakage overnight: if a controller and a model are named identically, you’ll need to refer to the model as Models::Blog inside the controller. See the blogging app.

Update: The Florida Friends of Camping want to send out an alert to the vast throngs of bedraggled and disenfranchised coders who don’t fathom Camping, don’t care about it and are hurt that it is even trying to do anything.

Many dogs are out of work and remain unemployed for years on end. Their prospects are weak and many humans lack the patience to help the canine hunt through Craigslist. (The mouse peripheral was specifically designed without paws in mind.) Yet, many of these dogs are our best asset when faced with that initial first stab through Camping. Call us! Our dogs are lined up right now, outside the facility, with their honorary green scarves blowing in the wind, ready to embark on a walking tour of up to 4k. It’s your enjoyment that’s at stake here!

Update: