infod 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/bin/infod +37 -0
  2. data/config.ru +3 -0
  3. data/infod/Es/fs.rb +154 -0
  4. data/infod/Es/groonga.rb +101 -0
  5. data/infod/Es/redis.rb +3 -0
  6. data/infod/Es/sqlite.rb +3 -0
  7. data/infod/Es.rb +67 -0
  8. data/infod/H.rb +29 -0
  9. data/infod/K.rb +197 -0
  10. data/infod/N.rb +248 -0
  11. data/infod/Rb.rb +71 -0
  12. data/infod/Th/404.rb +55 -0
  13. data/infod/Th/500.rb +10 -0
  14. data/infod/Th/GET.rb +132 -0
  15. data/infod/Th/HEAD.rb +5 -0
  16. data/infod/Th/PATCH.rb +5 -0
  17. data/infod/Th/POST.rb +19 -0
  18. data/infod/Th/local.rb +22 -0
  19. data/infod/Th/uid.rb +24 -0
  20. data/infod/Th.rb +110 -0
  21. data/infod/W/audio.rb +56 -0
  22. data/infod/W/blog.rb +3 -0
  23. data/infod/W/cal.rb +110 -0
  24. data/infod/W/chat.rb +81 -0
  25. data/infod/W/color.rb +28 -0
  26. data/infod/W/core.rb +77 -0
  27. data/infod/W/css.rb +24 -0
  28. data/infod/W/csv.rb +13 -0
  29. data/infod/W/du.rb +35 -0
  30. data/infod/W/edit.rb +8 -0
  31. data/infod/W/examine/examine.rb +59 -0
  32. data/infod/W/examine/exhibit.rb +34 -0
  33. data/infod/W/examine/hist.rb +55 -0
  34. data/infod/W/examine/history.rb +19 -0
  35. data/infod/W/examine/normal.rb +31 -0
  36. data/infod/W/examine/protovis.rb +30 -0
  37. data/infod/W/examine/sw.rb +114 -0
  38. data/infod/W/examine/time/graph.rb +86 -0
  39. data/infod/W/examine/time/line.rb +24 -0
  40. data/infod/W/feed.rb +116 -0
  41. data/infod/W/find.rb +24 -0
  42. data/infod/W/forum.rb +3 -0
  43. data/infod/W/grep.rb +27 -0
  44. data/infod/W/html.rb +143 -0
  45. data/infod/W/image.rb +61 -0
  46. data/infod/W/json.rb +44 -0
  47. data/infod/W/kv.rb +66 -0
  48. data/infod/W/ls.rb +50 -0
  49. data/infod/W/mail.rb +248 -0
  50. data/infod/W/page.rb +30 -0
  51. data/infod/W/pdf.rb +16 -0
  52. data/infod/W/post.rb +9 -0
  53. data/infod/W/rdf.rb +32 -0
  54. data/infod/W/schema.rb +172 -0
  55. data/infod/W/search.rb +33 -0
  56. data/infod/W/shell.rb +30 -0
  57. data/infod/W/source.rb +35 -0
  58. data/infod/W/table.rb +87 -0
  59. data/infod/W/text.rb +94 -0
  60. data/infod/W/tree.rb +26 -0
  61. data/infod/W/vfs.rb +175 -0
  62. data/infod/W/wiki.rb +18 -0
  63. data/infod/W.rb +34 -0
  64. data/infod/Y.rb +17 -0
  65. data/infod/infod.rb +13 -0
  66. data/infod.rb +13 -0
  67. metadata +129 -0
data/infod/N.rb ADDED
@@ -0,0 +1,248 @@
1
+ %w{base64 cgi shellwords}.each{|r|require(r)}
2
+
3
+ def E e
4
+ return e if e.class == E
5
+ return e unless e
6
+ E.new e
7
+ end
8
+
9
+ class E
10
+ def E.[] u; E u end
11
+ def E e=uri; super e end
12
+
13
+ attr_reader :uri
14
+ def initialize uri; @uri = uri.to_s end
15
+
16
+ def base
17
+ File.basename path
18
+ end
19
+
20
+ def bare
21
+ base.sub(/\.#{ext}$/,'')
22
+ rescue
23
+ base
24
+ end
25
+
26
+ def docBase
27
+ readlink.uri.
28
+ split(/#/)[0].E.
29
+ do{|d|
30
+ d.dirname.as d.bare}
31
+ end
32
+
33
+ def ef; @ef ||= docBase.a('.e') end
34
+ def nt; @nt ||= docBase.a('.nt') end
35
+ def ttl; @ttl ||= docBase.a('.ttl') end
36
+
37
+ def docBaseURI
38
+ u = URI uri
39
+ s = u.scheme
40
+ p = u.path
41
+ p = '/' if p.empty?
42
+ ((s ? s + '://' : '') + # scheme
43
+ u.host + # host
44
+ File.dirname(p).t + # path
45
+ File.basename(p)[0..-(File.extname(p).size+1)]).E # doc
46
+ end
47
+
48
+ def frag
49
+ uri.frag
50
+ end
51
+
52
+ def docs
53
+ (e ? [self] : []). # directly-referenced
54
+ concat(docBase.glob ".{e,html,n3,nt,owl,rdf,ttl}"). # docs
55
+ concat((d? && uri[-1]=='/') ? c : []) # trailing slash -> children
56
+ end
57
+
58
+ def dirname
59
+ no.dirname.E
60
+ end
61
+
62
+ # generate URL for non-URL identifier (mail ID, Tag URI..)
63
+ def url
64
+ path? ? uri : Prefix + (CGI.escape uri)
65
+ end
66
+
67
+ # URI extension :: E -> string
68
+ def ext
69
+ File.extname(uri).tail||''
70
+ end
71
+
72
+ def label
73
+ uri.label
74
+ end
75
+
76
+ def expand
77
+ uri.expand.E
78
+ end
79
+
80
+ # concatenate URIs with separator
81
+ # s :: E -> E
82
+ def s b
83
+ u.a E(b).path
84
+ end
85
+
86
+ def prependURI s
87
+ (s + uri).E
88
+ end
89
+
90
+ def appendURI s
91
+ (uri + s).E
92
+ end
93
+ alias_method :a, :appendURI
94
+ alias_method :+, :appendURI
95
+
96
+ def appendSlashURI s
97
+ E uri.t + s
98
+ end
99
+ alias_method :as, :appendSlashURI
100
+
101
+ # path? :: E -> Bool
102
+ def path?
103
+ uri.path?
104
+ end
105
+
106
+ # path :: E -> String
107
+ def path
108
+ @path ||=
109
+ path? ? (uri.match(/^\//) ?
110
+ uri : '/'+uri) :
111
+ '/E/'+uri.h.dive[0..5]+(Base64.urlsafe_encode64 uri)
112
+ end
113
+
114
+ def u
115
+ @u ||= E (f ? dirname + '/.' + File.basename(path) : path.t + E::S)
116
+ end
117
+
118
+ # E (_ _ o) -> E o
119
+ def ro
120
+ uri.split(/#{E::S}\//)[-1].unpath false
121
+ end
122
+
123
+ def sh
124
+ d.force_encoding('UTF-8').sh
125
+ end
126
+
127
+ # literal -> URI
128
+ def literal o
129
+ return literalBlob o unless o.class == String
130
+ return literalURI o if (Literal[uri] || o.size<=88) && !o.match(/\//)
131
+ return E o if o.match %r{\A[a-z]+://[^\s]+\Z}
132
+ literalBlob o
133
+ end
134
+
135
+ # pathname for short literals
136
+ def literalURI o
137
+ E "/u/"+(Literal[uri] && o.gsub(/[\.:\-T+]/,'/'))+'/'+o
138
+ end
139
+
140
+ def literalBlobURI o
141
+ if o.class == String
142
+ E "/blob/"+o.h.dive
143
+ else
144
+ E "/json/"+[o].to_json.h.dive
145
+ end
146
+ end
147
+
148
+ # spaceship
149
+ def <=> c
150
+ to_s <=> c.to_s
151
+ end
152
+
153
+ # example: E('wiggly').to_s -> "wiggly"
154
+ def to_s # string
155
+ uri
156
+ end
157
+
158
+ end
159
+
160
+ class Hash
161
+ def uri
162
+ self["uri"]
163
+ end
164
+ def url; self.E.url end
165
+ def label
166
+ self[E::Label] || uri.label
167
+ end
168
+ def E
169
+ E.new uri
170
+ end
171
+ end
172
+
173
+ class Array
174
+ def E
175
+ self[0].E if self[0].class==Hash
176
+ end
177
+ end
178
+
179
+ class String
180
+ def dive
181
+ self[0..1]+'/'+
182
+ self[2..3]+'/'+
183
+ self[4..-1]
184
+ end
185
+
186
+ # expand qname-style identifier to URI
187
+ Expand={}
188
+ def expand
189
+ # memoize lookups
190
+ (Expand.has_key? self) ?
191
+ Expand[self] :
192
+ (Expand[self] =
193
+ match(/([^:]+):([^\/].*)/).do{|e|
194
+ (E::Abbrev[e[1]]||e[1]+':')+e[2]} ||
195
+ self )
196
+ end
197
+
198
+ def sh
199
+ Shellwords.escape self
200
+ end
201
+
202
+ BaseLen = E::FSbase.size.succ
203
+
204
+ def pathToURI r = true
205
+ self[BaseLen..-1].unpath r
206
+ end
207
+
208
+ # string -> E || literal
209
+ def unpath r=true # dereference literal?
210
+
211
+ if m=(match /^([a-z]+:)\/+(.*)/) # URL
212
+ (m[1]+'//'+m[2]).E
213
+
214
+ elsif match /^blob/ # string
215
+ r ? ('/'+self).E.r : ('/'+self).E
216
+
217
+ elsif match /^json/ # JSON
218
+ r ? (('/'+self).E.r true) : ('/'+self).E
219
+
220
+ elsif match /^u\// # trie
221
+ r ? (File.basename self) : ('/'+self).E
222
+
223
+ elsif match /^E\/..\/..\// # !fs-compatible URI
224
+ self[8..-1].match(/([^.]+)(.*)/).do{|c|
225
+ (Base64.urlsafe_decode64 c[1]) + c[2]
226
+ }.E
227
+ else # path
228
+ ('/'+self).E
229
+ end
230
+ end
231
+
232
+ def E
233
+ E.new self
234
+ end
235
+
236
+ def path?
237
+ (match /^(\.|\/|https?:\/)/) && true || false
238
+ end
239
+
240
+ def frag
241
+ split(/#/).pop()
242
+ end
243
+
244
+ def label
245
+ split(/[\/#]/)[-1]
246
+ end
247
+
248
+ end
data/infod/Rb.rb ADDED
@@ -0,0 +1,71 @@
1
+ # Rb Rubidium
2
+
3
+ class Array
4
+ def head; self[0] end
5
+ def tail; self[1..-1] end
6
+ def snd; self[1] end
7
+ def r; self[rand length] end
8
+ def h; join.h end
9
+ def intersperse i
10
+ inject([]){|a,b|a << b << i}[0..-2]
11
+ end
12
+ def sum
13
+ inject 0, &:+
14
+ end
15
+ def cr
16
+ intersperse "\n"
17
+ end
18
+ end
19
+
20
+ class Hash
21
+ def except *ks
22
+ clone.do{|h|
23
+ ks.map{|k|h.delete k}
24
+ h}
25
+ end
26
+ def has_keys ks; ks.each{|k|
27
+ return false unless has_key? k
28
+ }; true
29
+ end
30
+ def has_any_key ks; ks.each{|k|
31
+ return true if has_key? k
32
+ }; false
33
+ end
34
+ end
35
+
36
+ class Float
37
+ def max i; i > self ? self : i end
38
+ def min i; i < self ? self : i end
39
+ end
40
+
41
+ class Fixnum
42
+ def max i; i > self ? self : i end
43
+ def min i; i < self ? self : i end
44
+ end
45
+
46
+ class Object
47
+ def id; self end
48
+ def do; yield self end
49
+ end
50
+
51
+ class String
52
+ def h; Digest::SHA1.hexdigest self end
53
+ def hsub h; map{|e|h[e]||e} end
54
+ def map; each_char.map{|l| yield l}.join end
55
+ def tail; self[1..-1] end
56
+ def to_utf8; encode('UTF-8', undef: :replace) end
57
+ def t; match(/\/$/) ? self : self+'/' end
58
+ end
59
+ class FalseClass
60
+ def do; nil end
61
+ def to_s; "" end
62
+ alias_method :to_str,:to_s
63
+ end
64
+
65
+ class NilClass
66
+ def do; nil end
67
+ def to_ary; [] end
68
+ def to_s; "" end
69
+ alias_method :to_str,:to_s
70
+ def method_missing *a; nil; end
71
+ end
data/infod/Th/404.rb ADDED
@@ -0,0 +1,55 @@
1
+ class E
2
+
3
+ # 404 response URI
4
+ E404 = 'req/'+HTTP+'404'
5
+
6
+ # 404 response function
7
+ fn E404,->e,r{
8
+ u = e.uri # response URI
9
+ g = {u => {}} # response graph
10
+ s = g[u] # resource pointer
11
+ # add request data to response graph
12
+ r.map{|k,v| s[k] = [v] }
13
+ s[Type] = [E[HTTP+'404']]
14
+ s['uri'] = u
15
+ s['QUERY'] = [r.q]
16
+ s['ACCEPT']= [r.accept]
17
+ s['SERVER_SOFTWARE']=[('//'+r['SERVER_NAME']).E]
18
+ s['http://buzzword.org.uk/rdf/personal-link-types#edit']=[E[u+'?view=edit&graph=_']]
19
+ %w{CHARSET LANGUAGE ENCODING}.map{|a|s['ACCEPT_'+a] = [(r.accept_ '_' + a)]}
20
+ # output
21
+ r.q.delete 'view' # use 404 view if HTML
22
+ [404,{'Content-Type'=> r.format},[e.render(r.format,g,r)]]}
23
+
24
+ # qs y=404 to force a 404 response
25
+ F['req/404'] = F[E404]
26
+
27
+ fn 'view/'+HTTP+'404',->d,e{
28
+ [H.css('/css/404'),{_: :h1, c: '404'},d.html]}
29
+
30
+ # 404.css if fs content is missing
31
+ fn '/css/404.css/GET',->e,r{
32
+ [200,{'Content-Type'=>'text/css'},
33
+ ["body {background-color:#000;color:#fff; font-family: sans-serif}
34
+ a {font-size:1.05em;background-color:#1ef;color:#000;text-decoration:none;padding:.1em}
35
+ td.key {text-align:right}
36
+ td.key .frag {font-weight:bold;background-color:#0f0;color:#000;padding-left:.2em;border-radius:.38em 0 0 .38em}
37
+ td.key .abbr {color:#eee;font-size:.92em}
38
+ td.val {border-style:dotted;border-width:0 0 .1em 0;border-color:#00f;}"]]}
39
+
40
+ # show response-codes for a list of URIs
41
+ def checkURIs
42
+ r = uris.select{|u|u.to_s.match /^http/}.map{|u|
43
+ c = [`curl -IsA 404? "#{u}"`.lines.to_a[0].match(/\d{3}/)[0].to_i,u] # HEAD
44
+ #c = [`curl -s -o /dev/null -w %{http_code} "#{u}"`.chomp.to_i,u] # GET
45
+ puts c.join ' '
46
+ c # status, uri tuple
47
+ }
48
+ puts "\n\n"
49
+ r.map{|c|
50
+ # show anomalies
51
+ puts c.join(' ') unless c[0] == 200
52
+ }
53
+ end
54
+
55
+ end
data/infod/Th/500.rb ADDED
@@ -0,0 +1,10 @@
1
+ #watch __FILE__
2
+ class E
3
+
4
+ fn 'backtrace',->x,r{
5
+ [500,{'Content-Type'=>'text/html'},
6
+ ['<html><head><title>500</title></head><body><h1>500</h1><pre>',
7
+ [x.class.to_s,x.message,*x.backtrace].join("\n").hrefs,
8
+ '</pre></body></html>']]}
9
+
10
+ end
data/infod/Th/GET.rb ADDED
@@ -0,0 +1,132 @@
1
+ class E
2
+
3
+ def GET
4
+ a = @r.accept.values.flatten # acceptable MIME types
5
+ send(f ? (if (@r.q.has_any_key(['format','graph','view']) || # user-specified view
6
+ (MIMEcook[mime] && !@r.q.has_key?('raw')) || # view for MIME-type
7
+ !(a.empty?||a.member?(mime)||a.member?('*/*'))) # file MIME not accepted
8
+ :GET_resource # invoke resource handler
9
+ else
10
+ :GET_img # continue to file handler
11
+ end) :
12
+ :GET_resource)
13
+ rescue Exception => x
14
+ $stderr.puts 500,x.message,x.backtrace
15
+ Fn 'backtrace',x,@r
16
+ end
17
+
18
+ def maybeSend m,b,lH=false
19
+ send? ? # agent already has this version?
20
+ b.().do{|b| # continue
21
+ h = {'Content-Type'=> m, 'ETag'=> @r['ETag']} # response header
22
+ m.match(/^(audio|image|video)/) && # media MIME-type?
23
+ h.update({'Cache-Control' => 'no-transform'}) # no further compression
24
+ h.update({'MS-Author-Via' => 'DAV, SPARQL'}) # authoring
25
+ lH && h.update({'Link' => '<' + (URI.escape uri) + '?format=text/n3>; rel=meta'}) # Link Header - full URI variant
26
+ b.class == E ? (Nginx ? # nginx env-var
27
+ [200,h.update({'X-Accel-Redirect' => '/fs' + b.path}),[]] : # Nginx file-handler
28
+ Apache ? # Apache env-var
29
+ [200,h.update({'X-Sendfile' => b.d}),[]] : # Apache file-handler
30
+ (r = Rack::File.new nil # create Rack file-handler
31
+ r.instance_variable_set '@path',b.d # set path
32
+ r.serving(@r).do{|s,m,b|[s,m.update(h),b]}) # Rack file-handler
33
+ ) :
34
+ [200, h, b]} : # response triple
35
+ [304,{},[]] # not modified
36
+ end
37
+
38
+ def send?
39
+ !((m=@r['HTTP_IF_NONE_MATCH']) && m.strip.split(/\s*,\s*/).include?(@r['ETag']))
40
+ end
41
+
42
+ def GET_file
43
+ @r['ETag'] = [m,size].h
44
+ maybeSend mime,->{self},:link
45
+ end
46
+
47
+ def GET_img
48
+ (thumb? ? thumb : self).GET_file
49
+ end
50
+
51
+ def GET_resource # for
52
+ (F['req/'+@r.q['y']] || # any URI
53
+ F[@r['REQUEST_PATH'].t+('GET')]||# specific path
54
+ F[uri.t+('GET')] # specific URI
55
+ ).do{|y|y.(self,@r)} || # custom handler
56
+ as('index.html').do{|i| # HTML index
57
+ i.e && # exists?
58
+ ((uri[-1]=='/') ? i.env(@r).GET_file : # are we inside dir?
59
+ [301, {Location: uri.t}] )} || # rebase to index dir
60
+ response
61
+ end
62
+
63
+ # graph constructor
64
+ fn 'graph/',->e,q,m{
65
+ F['set/' + q['set']][e, q, m]. # doc set
66
+ map{|u|m[u.uri] ||= u}}
67
+
68
+ # document set constructor
69
+ fn 'set/',->d,e,m{d.docs}
70
+
71
+ # construct HTTP response
72
+ def response
73
+
74
+ # request arguments
75
+ q = @r.q # query-string
76
+ g = q['graph'] # graph-generation function selector
77
+
78
+ # request graph
79
+ m = {}
80
+
81
+ # add resources to request graph
82
+ F['graph/' + g][self,q,m]
83
+
84
+ # empty graph -> 404
85
+ return F[E404][self,@r] if m.empty?
86
+
87
+ # inspect request-graph
88
+ if q.has_key? 'debug'
89
+ puts "docs #{m.keys.join ' '}"
90
+ puts "resources #{m['frag']['res']}" if m['frag']
91
+ end
92
+
93
+ # request-graph identifier
94
+ s = (q.has_key?('nocache') ? rand.to_s : # random identifier
95
+ m.sort.map{|u,r|[u, r.respond_to?(:m) && r.m]}).h # canonicalized set signature
96
+
97
+ # response identifier
98
+ @r['ETag'] ||= [s, q, @r.format].h
99
+
100
+ # check if client has response
101
+ maybeSend @r.format, ->{
102
+
103
+ # cached response identifier
104
+ r = E'/E/req/' + @r['ETag'].dive
105
+
106
+ if r.e # response already generated
107
+ r # cached response
108
+ else
109
+
110
+ # cached graph identifier
111
+ c = E '/E/graph/' + s.dive
112
+
113
+ if c.e # cached graph exists
114
+ m.merge! c.r true # read cache
115
+ else
116
+ # construct response graph
117
+ m.values.map{|r|
118
+ r.env(@r).graphFromFile m}
119
+
120
+ # cache response graph
121
+ c.w m,true
122
+ end
123
+
124
+ # response graph sorting/filtering
125
+ E.filter q, m, self
126
+
127
+ # response body
128
+ r.w render @r.format, m, @r
129
+ end }
130
+ end
131
+
132
+ end
data/infod/Th/HEAD.rb ADDED
@@ -0,0 +1,5 @@
1
+ class E
2
+ def HEAD
3
+ self.GET.do{|s,h,b|[s,h,[]]}
4
+ end
5
+ end
data/infod/Th/PATCH.rb ADDED
@@ -0,0 +1,5 @@
1
+ class E
2
+ def PATCH
3
+
4
+ end
5
+ end
data/infod/Th/POST.rb ADDED
@@ -0,0 +1,19 @@
1
+ class E
2
+ def POST
3
+ as('POST').y(self,@r) || basicPOST
4
+ rescue Exception => x
5
+ Fn 'backtrace',x,@r
6
+ end
7
+
8
+ def basicPOST
9
+
10
+ end
11
+
12
+ # mint URI to POSTs here
13
+ fn '/post/POST',->e,r{
14
+
15
+
16
+ }
17
+
18
+
19
+ end
data/infod/Th/local.rb ADDED
@@ -0,0 +1,22 @@
1
+ class E
2
+ =begin
3
+ this code is not loaded by default
4
+
5
+ server looks in hostname-root paths for *.rb files, reporting found code:
6
+ site config http://data.whats-your.name/data.rb
7
+
8
+ =end
9
+
10
+ # disallow custom-query crawling on all sites
11
+ fn '/robots.txt/GET',->e,r{
12
+ [200,{'Content-Type'=>'text/plain'},["User-agent: *\nDisallow: /*?*\n"]]}
13
+
14
+ # schema search forward from site root
15
+ fn 'http://data.whats-your.name/GET',->e,r{
16
+ [302,{'Location'=>'/schema'},[]]}
17
+
18
+ # webize PS(1)
19
+ fn '/ps/GET',->e,r{
20
+ [200,{'Content-Type'=>'text/plain'},[`ps aux`]]}
21
+
22
+ end
data/infod/Th/uid.rb ADDED
@@ -0,0 +1,24 @@
1
+ #watch __FILE__
2
+
3
+ module Th
4
+ FingerprintKeys = %w{
5
+ HTTP_ACCEPT
6
+ HTTP_ACCEPT_CHARSET
7
+ HTTP_ACCEPT_LANGUAGE
8
+ HTTP_ACCEPT_ENCODING
9
+ HTTP_USER_AGENT
10
+ HTTP_ORIGIN_ADDR
11
+ REMOTE_ADDR
12
+ }
13
+
14
+ def uid
15
+ ('/u/'+FingerprintKeys.map{|i|self[i]}.h.dive).E
16
+ end
17
+ end
18
+
19
+ class E
20
+
21
+ fn '/whoami/GET',->e,r{
22
+ [302,{Location: '/@'+r.uid.uri},[]]}
23
+
24
+ end
data/infod/Th.rb ADDED
@@ -0,0 +1,110 @@
1
+ %w{GET HEAD POST PATCH uid 404 500}.map{|i|require_relative 'Th/' + i}
2
+ require 'rack'
3
+
4
+ class String
5
+ # parse querystring
6
+ def qp
7
+ d={}
8
+ split(/&/).map{|e|
9
+ k,v=e.split(/=/,2).map{|x|
10
+ CGI.unescape x}
11
+ d[k]=v}
12
+ d
13
+ end
14
+ def hR
15
+ [200,{'Content-Type'=>'text/html'},[self]]
16
+ end
17
+ end
18
+
19
+ module Tb
20
+ def q
21
+ @q ||= self.().qp
22
+ end
23
+ end
24
+
25
+ module Th
26
+ def qs
27
+ (['GET','HEAD'].member? fn) ? self['QUERY_STRING'] : self['rack.input'].read
28
+ end
29
+ # read querystring
30
+ def q
31
+ @q ||= (qs||'').qp.do{|q|
32
+ (q['?']).do{|d|
33
+ E::F['?'][d].do{|g| # expand aliases
34
+ g.merge q
35
+ } || q } || q}
36
+ end
37
+ # read Accept header
38
+ def accept_ k=''
39
+ d={}
40
+ self['HTTP_ACCEPT'+k].do{|k|
41
+ k.split(/,/).map{|e|
42
+ f,q=e.split(/;/)
43
+ i=q&&q.split(/=/)[1].to_f||1
44
+ d[i]||=[]
45
+ d[i].push f}}
46
+ d
47
+ end
48
+
49
+ def format
50
+ @format ||= conneg
51
+ end
52
+
53
+ def conneg
54
+ # choose a preferred content-type
55
+ return q['format'] if q['format'] && E::F[E::Render+q['format']]
56
+ accept.sort.reverse.map{|p|p[1].map{|mime|
57
+ return mime if E::F[E::Render+mime]
58
+ }}
59
+ 'text/html'
60
+ end
61
+
62
+ def accept; @accept ||= accept_ end
63
+
64
+ def fn
65
+ # request method (Symbol) getter
66
+ self['REQUEST_METHOD']
67
+ end
68
+ end
69
+
70
+ class Hash
71
+ def qs
72
+ '?'+map{|k,v|k.to_s+'='+(v ? (CGI.escape [*v][0].to_s) : '')}.intersperse("&").join('')
73
+ end
74
+ def env r # thread environment through to children
75
+ @r = r
76
+ self
77
+ end
78
+ end
79
+
80
+ class E
81
+
82
+ F['?'] ||= {}
83
+
84
+ # access or update request environment
85
+ def env r=nil
86
+ r ? (@r = r
87
+ self) : @r
88
+ end
89
+
90
+ def E.call e
91
+ dev # check for changed source code
92
+ e.extend Th # enable request-related utility functions
93
+ e['HTTP_X_FORWARDED_HOST'].do{|h| e['SERVER_NAME'] = h } # hostname
94
+ (e['REQUEST_PATH'].force_encoding('UTF-8').do{|u| # path
95
+ CGI.unescape(u.index(Prefix)==0 ? u[Prefix.size..-1] : # non-local or non-HTTP URI
96
+ 'http://' + e['SERVER_NAME'] + u.gsub('+','%2B')) # HTTP URI
97
+ }.E.env(e).jail.do{|r| # valid path?
98
+ e['uri']=r.uri; r.send e.fn # update URI and continue
99
+ } || [403,{},['invalid path']]). # reject
100
+ do{|response| puts [ # inspect
101
+ e.fn, response[0],['http://', e['SERVER_NAME'], e['REQUEST_URI']].join,e['HTTP_USER_AGENT'],e['HTTP_REFERER']].join ' '
102
+ response }
103
+ end
104
+
105
+ # load site-specific code-base
106
+
107
+ E['http:/*/*.rb'].glob.map{|s| puts "site config #{s}"
108
+ require s.d}
109
+
110
+ end