camping 2.1.531 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +72 -53
  3. data/Rakefile +25 -20
  4. data/bin/camping +1 -0
  5. data/book/01_introduction.md +6 -6
  6. data/book/02_getting_started.md +348 -267
  7. data/book/03_more_about_controllers.md +124 -0
  8. data/book/04_more_about_views.md +118 -0
  9. data/book/05_more_about_markaby.md +173 -0
  10. data/book/06_more_about_models.md +58 -0
  11. data/book/06_rules_of_thumb.md +143 -0
  12. data/book/07_philosophy.md +23 -0
  13. data/book/08_publishing_an_app.md +118 -0
  14. data/book/09_upgrade_notes.md +96 -0
  15. data/book/10_middleware.md +69 -0
  16. data/book/11_gear.md +50 -0
  17. data/examples/blog.rb +38 -38
  18. data/lib/camping/ar.rb +20 -5
  19. data/lib/camping/commands.rb +388 -0
  20. data/lib/camping/gear/filters.rb +48 -0
  21. data/lib/camping/gear/inspection.rb +32 -0
  22. data/lib/camping/gear/kuddly.rb +178 -0
  23. data/lib/camping/gear/nancy.rb +170 -0
  24. data/lib/camping/loads.rb +15 -0
  25. data/lib/camping/mab.rb +2 -2
  26. data/lib/camping/reloader.rb +22 -17
  27. data/lib/camping/server.rb +145 -70
  28. data/lib/camping/session.rb +8 -5
  29. data/lib/camping/template.rb +1 -2
  30. data/lib/camping/tools.rb +43 -0
  31. data/lib/camping/version.rb +6 -0
  32. data/lib/camping-unabridged.rb +360 -133
  33. data/lib/camping.rb +78 -47
  34. data/lib/campingtrip.md +341 -0
  35. data/test/app_camping_gear.rb +121 -0
  36. data/test/app_camping_tools.rb +1 -0
  37. data/test/app_config.rb +30 -0
  38. data/test/app_cookies.rb +1 -1
  39. data/test/app_file.rb +3 -3
  40. data/test/app_goes_meta.rb +23 -0
  41. data/test/app_inception.rb +39 -0
  42. data/test/app_markup.rb +5 -20
  43. data/test/app_migrations.rb +16 -0
  44. data/test/app_partials.rb +1 -1
  45. data/test/app_prefixed.rb +88 -0
  46. data/test/app_reloader.rb +1 -2
  47. data/test/app_route_generating.rb +69 -2
  48. data/test/app_sessions.rb +24 -2
  49. data/test/app_simple.rb +18 -18
  50. data/test/apps/migrations.rb +82 -82
  51. data/test/apps/misc.rb +1 -1
  52. data/test/gear/gear_nancy.rb +129 -0
  53. data/test/test_helper.rb +69 -12
  54. metadata +152 -92
  55. data/CHANGELOG +0 -145
  56. data/book/51_upgrading.md +0 -110
data/lib/camping.rb CHANGED
@@ -1,55 +1,86 @@
1
- require "uri";require "rack";class Object;def meta_def m,&b;(class<<self;self
2
- end).send:define_method,m,&b end end;module Camping;C=self;S=IO.read(__FILE__
3
- )rescue nil;P="<h1>Cam\ping Problem!</h1><h2>%s</h2>";U=Rack::Utils;O={};Apps=[];
4
- SK=:camping;class H<Hash;def method_missing m,*a;m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.
5
- to_s]:super end;undef id,type if ??==63 end;class Cookies<H;attr_accessor :_p;
6
- def _n;@n||={}end;alias :_s :[]=;def set k,v,o={};_s(j=k.to_s,v);_n[j]=
7
- {:value=>v,:path=>_p}.update o;end;def []=(k,v)set(k,v,v.is_a?(Hash)?v:{})end
8
- end;module Helpers;def R c,*g;p,h=
9
- /\(.+?\)/,g.grep(Hash);g-=h;raise"bad route"unless u=c.urls.find{|x|break x if
1
+ require "cam\ping/loads";E||="content-type";Z||="text/html"
2
+ class Object;def meta_def m,&b;(class<<self;self
3
+ end).send:define_method,m,&b end;end
4
+ module Camping;C=self;S=IO.read(__FILE__)rescue nil
5
+ P="<h1>Cam\ping Problem!</h1><h2>%s</h2>";U=Rack::Utils;Apps=[];SK="camping";G=[]
6
+ class H<Hash;def method_missing m,*a;m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m
7
+ .to_s]:super end;undef id,type if ??==63;end;O=H.new;O[:url_prefix]=""
8
+ class Cookies<H;attr_accessor :_p
9
+ def _n;@n||={}end;alias _s []=;def set k,v,o={};_s(j=k.to_s,v);_n[j] =
10
+ {:value=>v,:path=>_p}.update o;end;def []=(k,v)set k,v,v.is_a?(Hash)?v:{} end
11
+ end
12
+ module Helpers;def R c,*g;p,h=
13
+ /\(.+?\)/,g.grep(Hash);g-=h;raise"bad route"if !u=c.urls.find{|x|break x if
10
14
  x.scan(p).size==g.size&&/^#{x}\/?$/=~(x=g.inject(x){|x,a|x.sub p,U.escape((a.
11
- to_param rescue a))}.gsub(/\\(.)/){$1})};h.any?? u+"?"+U.build_query(h[0]):u
12
- end;def / p;p[0]==?/?@root+p :p end;def URL c='/',*a;c=R(c,*a) if c.respond_to?(
13
- :urls);c=self/c;c=@request.url[/.{8,}?(?=\/|$)/]+c if c[0]==?/;URI c end end
15
+ to_param rescue a))}.gsub(CampTools.descape){$1})};h.any?? u+"?"+U.build_query(h[0]) : u
16
+ end;def /(p) p[0]==?/ ?(@root+@url_prefix.dup.prepend("/").chop+p) : p end
17
+ def URL c='/',*a;c=R(c,*a)if c.respond_to?(
18
+ :urls);c=self/c;c=@request.url[/.{8,}?(?=\/|$)/]+c if c[0]==?/;URI c end
19
+ def app_name;"Camping"end end
14
20
  module Base;attr_accessor:env,:request,:root,:input,:cookies,:state,:status,
15
- :headers,:body;T={};L=:layout;def lookup n;T.fetch(n.to_sym){|k|t=Views.
21
+ :headers,:body,:url_prefix;T={};L=:layout
22
+ def lookup n;T.fetch(n.to_sym){|k|t=Views.
16
23
  method_defined?(k)||(t=O[:_t].keys.grep(/^#{n}\./)[0]and Template[t].new{
17
24
  O[:_t][t]})||(f=Dir[[O[:views]||"views","#{n}.*"]*'/'][0])&&Template.
18
- new(f,O[f[/\.(\w+)$/,1].to_sym]||{});O[:dynamic_templates]?t:T[k]=t} end
19
- def render v,*a,&b;if t=lookup(v);r,@_r=@_r,o=Hash===a[-1]?a.pop: {};s=(t==true)?mab{
25
+ new(f,O[f[/\.(\w+)$/,1].to_sym]||{});O[:dynamic_templates]?t: T[k]=t}end
26
+ def render v,*a,&b;if t=lookup(v);r=@_r;@_r=o=Hash===a[-1]?a.pop: {};s=(t==true)?mab{
20
27
  send v,*a,&b}: t.render(self,o[:locals]||{},&b);s=render(L,o.merge(L=>false)){s
21
- }if o[L]or o[L].nil?&&lookup(L)&&!r&&v.to_s[0]!=?_;s;else;raise"no template: #{v}"
22
- end;end;def mab &b;extend(Mab);mab(&b) end;def r s,b,h={};b,h=
23
- h,b if Hash===b;@status=s;@headers.merge!(h);@body=b end;def redirect *a;r 302,
24
- '','Location'=>URL(*a).to_s end;def r404 p;P%"#{p} not found"end;def r500 k,m,e
25
- raise e end;def r501 m;P%"#{m.upcase} not implemented"end;def serve(p,c)
26
- (t=Rack::Mime.mime_type p[/\..*$/],"text/html")&&@headers["Content-Type"]=t;c;end;def to_a;@env[
27
- 'rack.session'][SK]=Hash[@state];r=Rack::Response.new(@body,@status,@headers)
28
- @cookies._n.each{|k,v|r.set_cookie k,v};r.to_a end;def initialize env,m
29
- r=@request=Rack:: Request.new(@env=env);@root,@input,@cookies,@state,@headers,
30
- @status,@method=r.script_name.sub(/\/$/,''),n(r.params),Cookies[r.cookies],
31
- H[r.session[SK]||{}],{'Content-Type'=>'text/html'},m=~/r(\d+)/?$1.to_i: 200,m;@cookies._p=self/"/" end
32
- def n h;Hash===h ?h.inject(H[]){|m,(k,v)|m[k]=
33
- n(v);m}: h end;def service *a;r=catch(:halt){send(@method,*a)};@body||=r;self
34
- end end;module Controllers;@r=[];class<<self;def R *u;r=@r;Class.
35
- new{meta_def(:urls){u};meta_def(:inherited){|x|r<<x}}end;def D p,m,e;p='/'if
28
+ } if o[L] or o[L].nil?&&lookup(L)&&!r&&v.to_s[0]!=?_;s else raise "no template: #{v}"
29
+ end end
30
+ def mab &b;extend Mab;mab &b;end
31
+ def r s,b,h={};b,h=h,b if Hash===b;@status=s;@headers.merge!(h);@body=b end
32
+ def redirect *a;r 302,'','Location'=>URL(*a).to_s;end
33
+ def r404 p;P%"#{p} not found"end
34
+ def r500 k,m,e;raise e end
35
+ def r501 m;P % "#{m.upcase} not implemented"end
36
+ def serve p,c;t=Rack::Mime.mime_type(p[/\..*$/], Z)and@headers[E]=t;c;end
37
+ def to_a;@env['rack.session'][SK]=Hash[@state];r=Rack::Response.new(@body,
38
+ @status,@headers);@cookies._n.each{|k,v|r.set_cookie k,v};r.to_a end
39
+ def initialize env,m,p
40
+ r=@request=Rack::Request.new(@env=env);@root,@input,@cookies,@state,@headers,
41
+ @status,@method,@url_prefix=r.script_name.sub(/\/$/,''),n(r.params),
42
+ Cookies[r.cookies],H[r.session[SK]||{}],{E=>Z},m=~/r(\d+)/?$1.to_i: 200,m,p
43
+ @cookies._p=self/"/";end
44
+ def n h;Hash===h ?h.inject(H[]){|m,(k,v)|m[k]=n(v);m}: h end
45
+ def service *a;r=catch(:halt){send(@method,*a)};@body||=r;self end end
46
+ module Controllers;@r=[];class Camper end;class<<self
47
+ def R *u;r,uf=@r,u.first;Class.new((uf.is_a?(Class)&&
48
+ (uf.ancestors.include?(Camper))) ? u.shift : Camper) {
49
+ meta_def(:urls){u};meta_def(:inherited){|x|r<< x} } end
50
+ def v;@r.map(&:urls);end
51
+ def D p,m,e;p='/'if
36
52
  !p||!p[0];(a=O[:_t].find{|n,_|n==p}) and return [I,:serve,*a]
37
53
  @r.map{|k|k.urls.map{|x|return(k.method_defined? m)?[k,m,*$~[1..-1].map{|x|U.unescape x}]:
38
- [I, 'r501',m]if p=~/^#{x}\/?$/}};[I,'r404',p] end;N=H.new{|_,x|x.downcase}.
39
- merge!("N"=>'(\d+)',"X"=>'([^/]+)',"Index"=>'');def M;def M;end;constants.
40
- map{|c|k=const_get(c);k.send:include,C,X,Base,Helpers,Models
41
- @r=[k]+@r if @r-[k]==@r;k.meta_def(:urls){["/#{c.to_s.scan(/.[^A-Z]*/).map(&
42
- N.method(:[]))*'/'}"]}if !k.respond_to?:urls}end end;I=R()end;X=
43
- Controllers;class<<self;def
44
- goes m,g=TOPLEVEL_BINDING;Apps<<a=eval(S.gsub(/Camping/,m.to_s),g);caller[0]=~/:/
45
- IO.read(a.set:__FILE__,$`)=~/^__END__/&&(b=$'.split /^@@\s*(.+?)\s*\r?\n/m).shift rescue nil
46
- a.set :_t,H[*b||[]];end;def call e;X.M
47
- k,m,*a=X.D e["PATH_INFO"],e['REQUEST_METHOD'].
48
- downcase,e;k.new(e,m).service(*a).to_a;rescue;r500(:I,k,m,$!,:env=>e).to_a end
49
- def method_missing m,c,*a;X.M;h=Hash===a[-1]?a.pop: {};e=H[Rack::MockRequest.
50
- env_for('',h.delete(:env)||{})];k=X.const_get(c).new(e,m.to_s);h.each{|i,v|k.
51
- send"#{i}=",v};k.service(*a) end;def use*a,&b;m=a.shift.new(method(:call),*a,&b)
52
- meta_def(:call){|e|m.call(e)}end;def options;O end;def set k,v;O[k]=v end end
53
- module Views;include X,Helpers end;module Models;autoload:Base,'camping/ar'
54
+ [I, 'r501',m]if p=~/^#{x}\/?$/}};[I,'r404',p] end;
55
+ A=->(c,u,p){d=p.dup;d.chop! if u=='';u.prepend("/"+d) if !["I"].
56
+ include? c.to_s
57
+ if c.to_s=="Index";while d[-1]=="/";d.chop! end;u.prepend("/"+d)end;u}
58
+ N=H.new{|_,x|x.downcase}.merge! "N"=>'(\d+)',"X"=>'([^/]+)',"Index"=>''
59
+ def M p;def M p;end
60
+ constants.filter{|c|c.to_s!='Camper'}.map{|c|k=const_get(c);
61
+ k.send:include,C,X,Base,Helpers,Models
62
+ @r=[k]+@r if @r-[k]==@r;mu=false;ka=k.ancestors
63
+ if (k.respond_to?(:urls) && ka[1].respond_to?(:urls)) && (k.urls == ka[1].urls)
64
+ mu = true unless ka[1].name == nil end
65
+ k.meta_def(:urls){[A.(k,"#{c.to_s.scan(/.[^A-Z]*/).map(&N.method(:[]))*'/'}",p)]} if (!k
66
+ .respond_to?(:urls) || mu==true)};end end;I=R()end;X=Controllers
67
+ class<<self;def make_camp;X.M prx;Apps.map(&:make_camp) end;def routes;(Apps.map(&:routes)<<X.v).flatten end
68
+ def prx;@_prx||=CampTools.normalize_slashes(O[:url_prefix])end
69
+ def call e;make_camp;k,m,*a=X.D e["PATH_INFO"],e['REQUEST_METHOD'].
70
+ downcase,e;k.new(e,m,prx).service(*a).to_a;rescue;r500(:I,k,m,$!,:env=>e).to_a end
71
+ def method_missing m,c,*a;h=Hash===a[-1]?a.pop : {};e=H[Rack::MockRequest.
72
+ env_for('',h.delete(:env)||{})];k=X.const_get(c).new(e,m.to_s,prx);h.each{|i,v|
73
+ k.send"#{i}=",v};k.service(*a)end
74
+ def use*a,&b;m=a.shift.new(method(:call),*a,&b);meta_def(:call){|e|m.call(e)}end
75
+ def pack*a,&b;G<< g=a.shift;include g;g.setup(self,*a,&b)end
76
+ def gear;G end;def options;O;end;def set k,v;O[k]=v end
77
+ def goes m,g=TOPLEVEL_BINDING;sp=caller[0].split('`')[0].split(":");fl,ln,pr=
78
+ sp[0],sp[1].to_i,nil;Apps<< a=eval(S.gsub(/Camping/,m.to_s),g,fl,ln);caller[0]=~/:/
79
+ IO.read(a.set:__FILE__,$`)=~/^__END__/&&(b=$'.split(/^@@\s*(.+?)\s*\r?\n/m)
80
+ ).shift rescue nil;a.set :_t,H[*b||[]]
81
+ a.set :_meta, H[file: fl, line_number: ln, parent: self,
82
+ root: (name != "Cam\ping" ? '/' + CampTools.to_snake(name) : '/')];C.configure(a)end end
83
+ module Views;include X,Helpers end;module Models
54
84
  Helpers.send:include,X,self end;autoload:Mab,'camping/mab'
55
- autoload:Template,'camping/template';C end
85
+ autoload:Template,'camping/template';pack Gear::Inspection;pack Gear::Filters
86
+ pack Gear::Nancy;pack Gear::Kuddly;C end
@@ -0,0 +1,341 @@
1
+ # How does Camping work?
2
+ This is an academic document written to help people, but mostly me, understand what Camping is doing and in what sequence. Why? Because I want to make camping better, but how do you make it better unless you understand what you've got?
3
+
4
+ # Start
5
+ Camping starts off with some simple code you type: `camping nuts.rb` into your terminal and the camping gem is loaded and executed. This assumes that you've installed camping via ruby gems: `gem install camping`. Gems are then given a binary command you can use on the command line, in our case **camping**. The camping command accepts a file as an argument and maybe some options. This command calls a *binary* that's found the camping gem, this is what it looks like:
6
+ ```ruby
7
+ #!/usr/bin/env ruby
8
+
9
+ $:.unshift File.dirname(__FILE__) + "/../lib"
10
+
11
+ require 'camping'
12
+ require 'camping/server'
13
+
14
+ begin
15
+ Camping::Server.start
16
+ rescue OptionParser::ParseError => ex
17
+ STDERR.puts "!! #{ex.message}"
18
+ puts "** use `#{File.basename($0)} --help` for more details..."
19
+ exit 1
20
+ end
21
+ ```
22
+
23
+ First wee see `$:.unshift File.dirname(__FILE__) + "/../lib"`, `$:`, is a global variable, that contains the loadpath for scripts. Every ruby file is a script, you may be more familiar with `$LOAD_PATH` which is an alias for the `$:` global. Next `unshift` is an array method that prepends an item to the beginning of an array. `File` is a builtin ruby class that lets you work with files. `File.dirname(file)` returns a string of the complete file path of the file given, except for the file's name. In our case we're using `__FILE__` to return the current file name, which is the binary file in the camping gem. the last portion: `+ "/../lib"` appends the string to the directory path that we just got. All of this is done to ensure that the `lib` folder where all of camping's code resides is added to the script load path.
24
+
25
+ Next we require camping:
26
+ ```ruby
27
+ require 'camping'
28
+ require 'camping/server'
29
+ ```
30
+
31
+ Which loads camping and it's server code in to the current script context.
32
+
33
+ ```ruby
34
+ Camping::Server.start
35
+ ```
36
+
37
+ The above code finally Starts the camping server. So let's take a look at the server:
38
+
39
+ ```ruby
40
+ require 'irb'
41
+ require 'erb'
42
+ require 'rack'
43
+ require 'camping/reloader'
44
+ require 'camping/commands'
45
+
46
+ # == The Camping Server (for development)
47
+ #
48
+ # Camping includes a pretty nifty server which is built for development.
49
+ # It follows these rules:
50
+ #
51
+ # * Load all Camping apps in a file.
52
+ # * Mount those apps according to their name. (e.g. Blog is mounted at /blog.)
53
+ # * Run each app's <tt>create</tt> method upon startup.
54
+ # * Reload the app if its modification time changes.
55
+ # * Reload the app if it requires any files under the same directory and one
56
+ # of their modification times changes.
57
+ # * Support the X-Sendfile header.
58
+ #
59
+ # Run it like this:
60
+ #
61
+ # camping blog.rb # Mounts Blog at /
62
+ #
63
+ # And visit http://localhost:3301/ in your browser.
64
+ module Camping
65
+ class Server < Rack::Server
66
+ class Options
67
+ if home = ENV['HOME'] # POSIX
68
+ DB = File.join(home, '.camping.db')
69
+ RC = File.join(home, '.campingrc')
70
+ elsif home = ENV['APPDATA'] # MSWIN
71
+ DB = File.join(home, 'Camping.db')
72
+ RC = File.join(home, 'Campingrc')
73
+ else
74
+ DB = nil
75
+ RC = nil
76
+ end
77
+
78
+ HOME = File.expand_path(home) + '/'
79
+
80
+ def parse!(args)
81
+ args = args.dup
82
+
83
+ options = {}
84
+
85
+ opt_parser = OptionParser.new("", 24, ' ') do |opts|
86
+ opts.banner = "Usage: camping my-camping-app.rb"
87
+ opts.define_head "#{File.basename($0)}, the microframework ON-button for ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
88
+ opts.separator ""
89
+ opts.separator "Specific options:"
90
+
91
+ opts.on("-h", "--host HOSTNAME",
92
+ "Host for web server to bind to (default is all IPs)") { |v| options[:Host] = v }
93
+
94
+ opts.on("-p", "--port NUM",
95
+ "Port for web server (defaults to 3301)") { |v| options[:Port] = v }
96
+
97
+ db = DB.sub(HOME, '~/') if DB
98
+ opts.on("-d", "--database FILE",
99
+ "SQLite3 database path (defaults to #{db ? db : '<none>'})") { |db_path| options[:database] = db_path }
100
+
101
+ opts.on("-C", "--console",
102
+ "Run in console mode with IRB") { options[:server] = "console" }
103
+
104
+ server_list = ["thin", "webrick", "console"]
105
+ opts.on("-s", "--server NAME",
106
+ "Server to force (#{server_list.join(', ')})") { |v| options[:server] = v }
107
+
108
+ opts.separator ""
109
+ opts.separator "Common options:"
110
+
111
+ # No argument, shows at tail. This will print an options summary.
112
+ # Try it and see!
113
+ opts.on("-?", "--help", "Show this message") do
114
+ puts opts
115
+ exit
116
+ end
117
+
118
+ # Another typical switch to print the version.
119
+ opts.on("-m", "--mounting", "Shows Mounting Guide") do
120
+ puts "Mounting Guide"
121
+ puts ""
122
+ puts "To mount your horse, hop up on the side and put it."
123
+ exit
124
+ end
125
+
126
+ # Another typical switch to print the version.
127
+ opts.on("-v", "--version", "Show version") do
128
+ puts Gem.loaded_specs['camping'].version
129
+ exit
130
+ end
131
+
132
+ end
133
+
134
+ opt_parser.parse!(args)
135
+
136
+ # If no Arguments were called.
137
+ if args.empty?
138
+ args << "cabin.rb" # adds cabin.rb as a default camping entrance file
139
+ end
140
+
141
+ # Parses the first argument as the script to load into the server.
142
+ options[:script] = args.shift
143
+ options
144
+ end
145
+ end
146
+
147
+ def initialize(*)
148
+ super
149
+ @reloader = Camping::Reloader.new(options[:script]) do |app|
150
+ if !app.options.has_key?(:dynamic_templates)
151
+ app.options[:dynamic_templates] = true
152
+ end
153
+
154
+ if !Camping::Models.autoload?(:Base) && options[:database]
155
+ Camping::Models::Base.establish_connection(
156
+ :adapter => 'sqlite3',
157
+ :database => options[:database]
158
+ )
159
+ end
160
+ end
161
+ end
162
+
163
+ def opt_parser
164
+ Options.new
165
+ end
166
+
167
+ def default_options
168
+ super.merge({
169
+ :Port => 3301,
170
+ :database => Options::DB
171
+ })
172
+ end
173
+
174
+ def middleware
175
+ h = super
176
+ h["development"] << [XSendfile]
177
+ h
178
+ end
179
+
180
+ def start
181
+ if options[:server] == "console"
182
+ puts "** Starting console"
183
+ @reloader.reload!
184
+ r = @reloader
185
+ eval("self", TOPLEVEL_BINDING).meta_def(:reload!) { r.reload!; nil }
186
+ ARGV.clear
187
+ IRB.start
188
+ exit
189
+ else
190
+ name = server.name[/\w+$/]
191
+ puts "** Starting #{name} on #{options[:Host]}:#{options[:Port]}"
192
+ super
193
+ end
194
+ end
195
+
196
+ # defines the public directory to be /public
197
+ def public_dir
198
+ File.expand_path('../public', @reloader.file)
199
+ end
200
+
201
+ # add the public directory as a Rack app serving files first, then the
202
+ # current value of self, which is our camping apps, as an app.
203
+ def app
204
+ Rack::Cascade.new([Rack::Files.new(public_dir), self], [405, 404, 403])
205
+ end
206
+
207
+ # path_matches?
208
+ # accepts a regular expression string
209
+ # in our case our apps and controllers
210
+ def path_matches?(path, *reg)
211
+ reg.each do |r|
212
+ return true if Regexp.new(r).match? path
213
+ end
214
+ false
215
+ end
216
+
217
+ # call(env) res
218
+ # == How routing works
219
+ #
220
+ # The first app added using Camping.goes is set at the root, we walk through
221
+ # the defined routes of the first app to see if there is a match.
222
+ # With no match we then walk through every other defined app.
223
+ # Each subsequent app defined is loaded at a directory named after them:
224
+ #
225
+ # camping.goes :Nuts # Mounts Nuts at /
226
+ # camping.goes :Auth # Mounts Auth at /auth/
227
+ # camping.goes :Blog # Mounts Blog at /blog/
228
+ #
229
+ def call(env)
230
+ @reloader.reload
231
+ apps = @reloader.apps
232
+
233
+ # our switch statement iterates through possible app outcomes, no apps
234
+ # loaded, one app loaded, or multiple apps loaded.
235
+ case apps.length
236
+ when 0
237
+ [200, {'Content-Type' => 'text/html'}, ["I'm sorry but no apps were found."]]
238
+ when 1
239
+ apps.values.first.call(env) # When we have one
240
+ else
241
+ # 2 and up get special treatment
242
+ count = 0
243
+ apps.each do |name, app|
244
+ if count == 0
245
+ app.routes.each do |r|
246
+ if (path_matches?(env['PATH_INFO'], r))
247
+ next
248
+ end
249
+ return app.call(env) unless !(path_matches?(env['PATH_INFO'], r))
250
+ end
251
+ else
252
+ mount = name.to_s.downcase
253
+ case env["PATH_INFO"]
254
+ when %r{^/#{mount}}
255
+ env["SCRIPT_NAME"] = env["SCRIPT_NAME"] + $&
256
+ env["PATH_INFO"] = $'
257
+ return app.call(env)
258
+ when %r{^/code/#{mount}}
259
+ return [200, {'Content-Type' => 'text/plain', 'X-Sendfile' => @reloader.file}, []]
260
+ end
261
+ end
262
+ count += 1
263
+ end
264
+
265
+ # Just return the first app if we didn't find a match.
266
+ return apps.values.first.call(env)
267
+ end
268
+ end
269
+
270
+ class XSendfile
271
+ def initialize(app)
272
+ @app = app
273
+ end
274
+
275
+ def call(env)
276
+ status, headers, body = @app.call(env)
277
+
278
+ if key = headers.keys.grep(/c-sendfile/i).first
279
+ filename = headers[key]
280
+ content = open(filename,'rb') { | io | io.read}
281
+ headers['content-length'] = size(content).to_s
282
+ body = [content]
283
+ end
284
+
285
+ return status, headers, body
286
+ end
287
+
288
+ if "".respond_to?(:bytesize)
289
+ def size(str)
290
+ str.bytesize
291
+ end
292
+ else
293
+ def size(str)
294
+ str.size
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end
300
+ ```
301
+
302
+ The beginning of Server loads the required code to get Camping started, and then opens the `Camping` module:
303
+
304
+ ```ruby
305
+ module Camping
306
+ class Server < Rack::Server
307
+ end
308
+ end
309
+ ```
310
+
311
+ `Server` inherits from `Rack::Server`. Camping is Rack based to give ourselves a predictable interface for our web server code. Consequentally a lot of utilities useful for webservers are just baked into Rack, It gives Camping the chance to do what it does best, magic!
312
+
313
+ The first class declared in `Server` is called `Options`. It's in charge of parsing the command line options supplied to camping, and then supplying those options as a hash for the program further down the line. This class is declared inside of the `Server` class so that we can encapsulate the behaviour of options within the server. Which is pretty nice. Next is initialize:
314
+
315
+ ```ruby
316
+ def initialize(*)
317
+ super
318
+ @reloader = Camping::Reloader.new(options[:script]) do |app|
319
+ if !app.options.has_key?(:dynamic_templates)
320
+ app.options[:dynamic_templates] = true
321
+ end
322
+
323
+ if !Camping::Models.autoload?(:Base) && options[:database]
324
+ Camping::Models::Base.establish_connection(
325
+ :adapter => 'sqlite3',
326
+ :database => options[:database]
327
+ )
328
+ end
329
+ end
330
+ end
331
+ ```
332
+
333
+ `initialize` is the method called whenever you instantiate a new object of a class. You'll notice that the line of code in the method is a call to `super`. Because `Camping::Server` is a subclass of `Rack::Server`, and to get things rolling we first call `Rack::Server`'s initialize. Afterwards we setup the reloader, and optionally include the database.
334
+
335
+ When you call super naked like that it passes along whatever arguments were sent to the method that called super. In our case It's a splat: `initialize(*)`, so everything is sent along.
336
+
337
+
338
+
339
+
340
+
341
+ Remember earlier from the Camping binary where we start the server? : `Camping::Server.start`, You may notice that this is a call to a class method `start`, but we don't declare any class methods in `Camping::Server` only instance methods.
@@ -0,0 +1,121 @@
1
+ require 'test_helper'
2
+ require 'camping'
3
+
4
+ Camping.goes :Packing
5
+
6
+ module Camping
7
+ module Gear
8
+
9
+ # Basically copied from Cuba, will probably modify later.
10
+ module CSRF
11
+
12
+ # Package Class Methods
13
+ module ClassMethods
14
+ # define class methods
15
+ def secret_token
16
+ @_secret_token ||= SecureRandom.base64(64)
17
+ end
18
+
19
+ def erase_token
20
+ @_secret_token = nil
21
+ end
22
+
23
+ def set_secret(secret)
24
+ @_secret_token = secret
25
+ end
26
+ end
27
+
28
+ # Run a setup routine with this Gear.
29
+ def self.setup(app, *a, &block)
30
+ @app = app
31
+ @app.set :secret_token, "top_secret_code"
32
+ end
33
+
34
+ def self.included(mod)
35
+ mod.extend(ClassMethods)
36
+ end
37
+
38
+ # Adds an instance method csrf
39
+ def csrf
40
+ @csrf ||= Camping::Gear::CSRF::Helper.new(@state, @request)
41
+ end
42
+
43
+ class Helper
44
+ attr_accessor :req
45
+ attr_accessor :state
46
+
47
+ def initialize(state, request)
48
+ @state = state
49
+ @req = request
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ module Packing
57
+ pack Camping::Gear::CSRF
58
+
59
+ before :Home do
60
+ @great = "This is great"
61
+ end
62
+
63
+ after :Work do
64
+ @body = "This is nice"
65
+ end
66
+
67
+ module Controllers
68
+ class Home < R '/'
69
+ def get
70
+ (@great == "This is great").to_s
71
+ end
72
+ end
73
+
74
+ class Work < R '/work'
75
+ def get
76
+ (@nice == "This is great").to_s
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ class Packing::Test < TestCase
83
+
84
+ def test_gear_packed
85
+ list = Packing::G
86
+ assert (list.length == 5), "Proper number of Gear was not packed! Gear: #{list.length}"
87
+ end
88
+
89
+ def test_right_gear_packed
90
+ csrf_gear = "Camping::Gear::CSRF"
91
+ assert Packing::G.map(&:to_s).include?(csrf_gear), "The correct Gear was not packed! Gear: #{csrf_gear}"
92
+ end
93
+
94
+ def test_instance_methods_packed
95
+ im = Packing.instance_methods.map(&:to_s)
96
+ assert (im.include? "csrf"), "Gear instance methods were not included: #{im}"
97
+ end
98
+
99
+ def test_class_methods_packed
100
+ [:secret_token, :erase_token, :set_secret].each { |sym|
101
+ assert (Packing.methods.include? sym), "Gear class methods were not packed, missing #{sym.to_s}."
102
+ }
103
+ end
104
+
105
+ def test_setup_callback
106
+ secret = Packing.options[:secret_token]
107
+ assert (secret == "top_secret_code"), "Gear setup callback failed: \"#{secret}\" should be \"top_secret_code\"."
108
+ end
109
+
110
+ # Maybe move the Camping Filters tests somewhere else later.
111
+ def test_before_filter
112
+ get '/'
113
+ assert_body "true", "Before filter did not work."
114
+ end
115
+
116
+ def test_after_filter
117
+ get '/work'
118
+ assert (body() == "This is nice"), "After filter did not work."
119
+ end
120
+
121
+ end
@@ -0,0 +1 @@
1
+ # Test Camping tools here.
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+ require 'camping'
3
+
4
+ module Config end
5
+
6
+ class Config::Test < TestCase
7
+
8
+ def setup
9
+ write_config()
10
+ Camping.goes :Config
11
+ @options = Camping::Apps.select{|a| a.name == "Config" }.first.options
12
+ super
13
+ end
14
+
15
+ def teardown
16
+ trash_config()
17
+ super
18
+ end
19
+
20
+ def test_config
21
+ assert @options.has_key? :hostname
22
+ assert @options.has_key? :friends
23
+ assert_equal @options[:friends].first, "_why", "_why isn't here?"
24
+ assert_equal @options[:friends].length, 3, "Where are all our friends?"
25
+ assert_equal @options.has_key?(:database), true, "By Golly the Database settings are missing."
26
+ assert_equal @options[:database].length, 4, "We're missing some database settings."
27
+ assert_equal @options[:database][:adapter], "sqlite3", "sqlite is missing. Not good."
28
+ end
29
+
30
+ end
data/test/app_cookies.rb CHANGED
@@ -55,7 +55,7 @@ class Cookies::Test < TestCase
55
55
  def test_path
56
56
  get '/one', {}, 'SCRIPT_NAME' => '/mnt'
57
57
  assert_body '["42", "43", "past"]'
58
- assert_equal 3, last_response.headers["Set-Cookie"].scan('path=/mnt/').size
58
+ assert_equal 3, last_response.headers["set-cookie"].map{|d|d.include?('path=/mnt/')}.size, "We were expecting a different number of 'path=/mnt/' declarations in the array. #{last_response.headers['set-cookie']}"
59
59
  end
60
60
  end
61
61
 
data/test/app_file.rb CHANGED
@@ -20,15 +20,15 @@ class FileSource::Test < TestCase
20
20
  def test_file
21
21
  get '/style.css'
22
22
  assert_body "* { margin: 0; padding: 0 }"
23
- assert_equal "text/css", last_response.headers['Content-Type']
23
+ assert_equal "text/css", last_response.headers['content-type']
24
24
 
25
25
  get '/test.foo'
26
26
  assert_body "Hello"
27
- assert_equal "text/html", last_response.headers['Content-Type']
27
+ assert_equal "text/html", last_response.headers['content-type']
28
28
 
29
29
  get '/test'
30
30
  assert_body "No extension"
31
- assert_equal "text/html", last_response.headers['Content-Type']
31
+ assert_equal "text/html", last_response.headers['content-type']
32
32
  end
33
33
  end
34
34