infod 0.0.1

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