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/bin/infod ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ case ARGV[0]
4
+ when 'apache'
5
+ ARGV.shift
6
+ ENV['apache']='y'
7
+ when /^(lo|local)$/
8
+ ARGV.shift
9
+ ARGV.unshift *%w{-p 80 -a 127.0.0.1}
10
+ when 'nginx'
11
+ ARGV.shift
12
+ ENV['nginx']='y'
13
+ else
14
+ ARGV.unshift *%w{-p 80}
15
+ end
16
+ ARGV.unshift *%w{--threaded}
17
+ unless ARGV[-1].match /(start|stop|restart|config|install)/
18
+ ARGV.push 'start'
19
+ end
20
+
21
+ %w{infod thin}.map{|l| require l}
22
+
23
+ module Rack
24
+ module Adapter
25
+ def self.guess _
26
+ :rack
27
+ end
28
+ def self.load _
29
+ Rack::Builder.new {
30
+ use Rack::Deflater
31
+ run E
32
+ }.to_app
33
+ end
34
+ end
35
+ end
36
+
37
+ Thin::Runner.new(ARGV).run!
data/config.ru ADDED
@@ -0,0 +1,3 @@
1
+ require 'infod'
2
+ use Rack::Deflater
3
+ run E
data/infod/Es/fs.rb ADDED
@@ -0,0 +1,154 @@
1
+ class E
2
+
3
+ # POSIX-filesystem index for triples
4
+ #
5
+
6
+ # index a triple
7
+ def index p,o
8
+ # normalize predicate typeclass (accept URI string or resources)
9
+ indexEdit E(p),
10
+ # literal -> URI conversion
11
+ (o.class == E ? o : E(p).literal(o)),
12
+ nil
13
+ end
14
+
15
+ # index a triple - no input-cleanup
16
+ def indexEdit p,o,a
17
+ return if @noIndex
18
+ p.pIndex.noIndex[o,self,a]
19
+ end
20
+ def noIndex
21
+ @noIndex = 1
22
+ self
23
+ end
24
+
25
+ # subtree traverse
26
+ fn 'set/subtree',->d,r,m{
27
+ c =(r['c'].do{|c|c.to_i + 1} || 3).max(100) # one extra for start of next-page
28
+ o = r['d'] =~ /^a/ ? :asc : :desc # direction
29
+ ('/'.E.take c, o, d.uri).do{|s| # take subtree
30
+ desc, asc = o == :desc ? # orient pagination hints
31
+ [s.pop, s[0]] : [s[0], s.pop]
32
+ m['prev'] = {'uri' => 'prev', 'url' => desc.url,'d' => 'desc'}
33
+ m['next'] = {'uri' => 'next', 'url' => asc.url, 'd' => 'asc'}
34
+ s }}
35
+
36
+ # subtree traverse index on p+o cursor
37
+ fn 'set/index',->d,r,m,f=:rangePO{
38
+ (# predicate
39
+ (f == :rangeP ? d : r['p']).expand.E.
40
+ # query
41
+ send f,
42
+ # count
43
+ (r['c']&&
44
+ r['c'].to_i.max(808)+1 || 22),
45
+ # direction
46
+ (r['d']&&
47
+ r['d'].match(/^(a|de)sc$/) &&
48
+ r['d'].to_sym ||
49
+ :desc),
50
+ # offset
51
+ r['offset'],
52
+ # object
53
+ (d if f == :rangePO)
54
+ ).do{|s|
55
+ # pagination pointers
56
+ a,b = s[0], s.size > 1 && s.pop
57
+ desc,asc = r['d'] && r['d']=='asc' && [a,b]||[b,a]
58
+ # insert pointers in response-graph
59
+ m['prev']={'uri' => 'prev','url' => d.url,'d' => 'desc','offset' => desc.uri} if desc
60
+ m['next']={'uri' => 'next','url' => d.url,'d' => 'asc', 'offset' => asc.uri} if asc
61
+ s }}
62
+ F['set/indexPO']=F['set/index']
63
+ fn 'set/indexP',->d,r,m{Fn 'set/index',d,r,m,:rangeP}
64
+
65
+ # predicate index
66
+ def pIndex
67
+ '/index'.E.s self
68
+ end
69
+
70
+ # predicate-object index
71
+ def poIndex o
72
+ pIndex.s o
73
+ end
74
+
75
+ # predicate-object index lookup
76
+ def po o
77
+ pIndex[o.class == E ? o : literal(o)]
78
+ end
79
+
80
+ # range query - predicate
81
+ def rangeP n=8,d=:desc,s=nil,o=nil
82
+ puts "rangeP #{uri} count #{n} dir #{d} cursor #{s}"
83
+ pIndex.subtree(n,d,s).map &:ro
84
+ end
85
+
86
+ # range query - predicate-object
87
+ def rangePO n=8,d=:desc,s=nil,o
88
+ puts "rangePO #{uri} #{o} count #{n} dir #{d} cursor #{s}"
89
+ poIndex(o).subtree(n,d,s).map &:ro
90
+ end
91
+
92
+ # E -> [node]
93
+ def subtree *a
94
+ u.take *a
95
+ end
96
+
97
+ # E -> [E]
98
+ def take *a
99
+ no.take(*a).map &:E
100
+ end
101
+
102
+ end
103
+
104
+
105
+ class Pathname
106
+
107
+ # take N els from fs tree in sorted, depth-first order
108
+ def take count=1000, direction=:desc, offset=nil
109
+
110
+ # construct offset-path
111
+ offset = to_s + offset.gsub(/\/+/,'/').E.path if offset
112
+
113
+ # in-range indicator
114
+ ok = false
115
+
116
+ # result set
117
+ set=[]
118
+
119
+ # asc/desc operators
120
+ v,m={asc: [:id,:>=],
121
+ desc: [:reverse,:<=]}[direction]
122
+
123
+ # visitation function
124
+ visit=->nodes{
125
+
126
+ # sort nodes in asc or desc order
127
+ nodes.sort_by(&:to_s).send(v).each{|n|
128
+ ns = n.to_s
129
+ # have we got enough nodes?
130
+ return if 0 >= count
131
+
132
+ # continue if
133
+ (# already in-range
134
+ ok ||
135
+ # no offset specified
136
+ !offset ||
137
+ # offset satisfies in-range operator
138
+ (sz = [ns,offset].map(&:size).min
139
+ ns[0..sz-1].send(m,offset[0..sz-1]))) && (
140
+ if !(c = n.c).empty? # has children?
141
+ visit.(c) # visit children
142
+ else
143
+ count = count - 1 # decrement wanted-nodes count
144
+ set.push n # add node to result-set
145
+ ok = true # iterator is now within range
146
+ end )}}
147
+
148
+ visit.(c) # start
149
+
150
+ # result set
151
+ set
152
+ end
153
+
154
+ end
@@ -0,0 +1,101 @@
1
+ #watch __FILE__
2
+ class E
3
+
4
+ # http://groonga.org/ http://ranguba.org/
5
+ # https://github.com/groonga/groonga
6
+ # https://github.com/ranguba/rroonga
7
+ # default DB
8
+ def E.groonga
9
+ @groonga ||= (require 'groonga'
10
+ E['/E/groonga'].groonga
11
+ Groonga["E"] )
12
+ end
13
+
14
+ # load or create groongaDB at URI
15
+ def groonga
16
+ return Groonga::Database.open d if e # open db
17
+ dirname.dir # create containing dir
18
+ Groonga::Database.create(:path => d) # create db
19
+ Groonga::Schema.define{|s| # create schema
20
+ s.create_table("E",:type => :hash,:key_type => "ShortText"){|t|
21
+ t.short_text "uri"
22
+ t.short_text "graph"
23
+ t.text "content"
24
+ t.time "time" }
25
+ s.create_table("Bigram",
26
+ :type => :patricia_trie,
27
+ :key_normalize => true,
28
+ :default_tokenizer => "TokenBigram"){|t|
29
+ %w{graph content}.map{|c| t.index("E." + c) }}}
30
+ end
31
+
32
+ # index resource
33
+ def roonga graph="global", m = self.graph
34
+ g = E.groonga # db
35
+ m.map{|u,i|
36
+ r = g[u] || g.add(u) # create or load entry
37
+ r.uri = u # update data
38
+ r.graph = graph.to_s
39
+ r.content = i.to_s
40
+ r.time = i[E::Date][0].to_time
41
+ }
42
+ self
43
+ rescue Exception => x
44
+ $stderr.puts x,x.message
45
+ end
46
+
47
+ # remove
48
+ def unroonga
49
+ g = E.groonga
50
+ graph.keys.push(uri).map{|u|g[u].delete}
51
+ end
52
+
53
+ # query
54
+ fn 'graph/roonga',->d,e,m{
55
+
56
+ # db
57
+ ga = E.groonga
58
+
59
+ # search expression
60
+ q = e['q']
61
+
62
+ # context
63
+ g = e["context"] || d.env['SERVER_NAME']
64
+
65
+ # offset
66
+ start = e['start'].do{|c|c.to_i} || 0
67
+
68
+ # number of results
69
+ c = e['c'].do{|c|c.to_i.max(1000).min(0)} || 8
70
+
71
+ # exec expression
72
+ r = q ? ga.select{|r|(r['graph'] == g) & r["content"].match(q)} : # expression if exists
73
+ ga.select{|r| r['graph'] == g} # ordered set (index date-range)
74
+
75
+ # are further results traversible?
76
+ down = r.size > start+c
77
+ up = !(start<=0)
78
+
79
+ # sort results
80
+ r = r.sort(e.has_key?('score') ? [["_score"]] : [["time", "descending"]],:offset => start,:limit => c)
81
+
82
+ # pagination resources
83
+ m['prev']={'uri' => 'prev','url' => '/search','start' => start + c, 'c' => c} if down
84
+ m['next']={'uri' => 'next','url' => '/search','start' => start - c, 'c' => c} if up
85
+
86
+ # search-result identifiers
87
+ r = r.map{|r| r['.uri'].E }
88
+
89
+ # fragment identifiers
90
+ m['frag'] = {'uri' => 'frag', 'res' => r}
91
+
92
+ # containing documents
93
+ r.map(&:docs).flatten.uniq.map{|r| m[r.uri] = r.env e}
94
+
95
+ # no 404 on 0 results - searchbox view
96
+ m['/s']={'uri'=>'/s'} if m.empty?
97
+
98
+ m # result set
99
+ }
100
+
101
+ end
data/infod/Es/redis.rb ADDED
@@ -0,0 +1,3 @@
1
+ class E
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ class E
2
+
3
+ end
data/infod/Es.rb ADDED
@@ -0,0 +1,67 @@
1
+ %w{fs groonga redis}.map{|e|require_relative 'Es/'+e}
2
+
3
+ class E
4
+
5
+ # accumulate a graph recursively along set-membership arc
6
+ def walk p,m={}
7
+ graph m # accumulative graph
8
+ o = [] # resources to visit
9
+ o.concat m[uri][p] # outgoing arc targets
10
+ o.concat (E p).po self # incoming arc sources
11
+ o.map{|r| # walk
12
+ r.E.walk p,m unless m[r.uri]}
13
+ m
14
+ end
15
+
16
+ # random leaf
17
+ def randomLeaf
18
+ c.empty? && self || c.r.randomLeaf
19
+ end
20
+ fn 'set/randomLeaf',->d,e,m{[d.randomLeaf]}
21
+ fn 'req/randomLeaf',->e,r{[302, {Location: e.randomLeaf.uri},[]]}
22
+
23
+ # Graph -> [Predicate]
24
+ def E.graphProperties g
25
+ g.values.select{|v|v.respond_to? :keys}.map(&:keys).flatten.uniq
26
+ end
27
+
28
+ fn 'filter/p',->e,m,_{
29
+ a=Hash[*e['p'].split(/,/).map(&:expand).map{|p|[p,true]}.flatten]
30
+ m.values.map{|r|
31
+ r.delete_if{|p,o|!a[p]}}}
32
+
33
+ fn 'filter/frag',->e,m,r{
34
+ f = [r.uri].concat m['frag']['res']
35
+ m.keys.map{|u|
36
+ m.delete u unless f.member? u}}
37
+
38
+ fn 'filter/basic',->o,m,_{
39
+ d=m.values
40
+ o['match'] && (p=o['matchP'].expand
41
+ d=d.select{|r|r[p].do{|p|(p.class==Array ? p[0] : p).to_s.match o['match']}})
42
+ o['min'] && (min=o['min'].to_f
43
+ p=o['minP'].expand
44
+ d=d.select{|r|r[p].do{|p|(p.class==Array ? p[0] : p).to_f >= min }})
45
+ o['max'] && (max=o['max'].to_f
46
+ p=o['maxP'].expand
47
+ d=d.select{|r|r[p].do{|p|(p.class==Array ? p[0] : p).to_f <= max }})
48
+ o['sort'] && (p=o['sort'].expand
49
+ _ = d.partition{|r|r[p]}
50
+ d =_[0].sort_by{|r|r[p]}.concat _[1] rescue d)
51
+ o['sortN'] && (p=o['sortN'].expand
52
+ _ = d.partition{|r|r[p]}
53
+ d =_[0].sort_by{|r|
54
+ (r[p].class==Array && r[p] || [r[p]])[0].do{|d|
55
+ d.class==String && d.to_i || d
56
+ }
57
+ }.concat _[1])
58
+ o.has_key?('reverse') && d.reverse!
59
+ m.clear;d.map{|r|m[r['uri']]=r}}
60
+
61
+ def self.filter o,m,r
62
+ o['filter'].do{|f|f.split(/,/).map{|f|Fn 'filter/'+f,o,m,r}}
63
+ Fn'filter/basic',o,m,r if o.has_any_key ['reverse','sort','max','min','match']
64
+ m
65
+ end
66
+
67
+ end
data/infod/H.rb ADDED
@@ -0,0 +1,29 @@
1
+ def H _
2
+ case _
3
+ when Hash then
4
+ '<'+(_[:_]||:div).to_s+(_.keys-[:_,:c]).map{|a|
5
+ ' '+a.to_s+'='+"'"+
6
+ _[a].to_s.hsub({"'"=>'%27',
7
+ '>'=>'%3E',
8
+ '<'=>'%3C'})+"'"}.join+'>'+
9
+ (_[:c] ? (H _[:c]) : '')+
10
+ (_[:_] == :link ? '' : ('</'+(_[:_]||:div).to_s+'>'))
11
+ when Array then
12
+ _.map{|n|H n}.join
13
+ else
14
+ _.to_s
15
+ end
16
+ end
17
+
18
+ class H
19
+
20
+ def H.js a,inline=false
21
+ p=a+'.js'
22
+ inline ? {_: :script, c: p.E.r} :
23
+ {_: :script, type: "text/javascript", src: p} end
24
+
25
+ def H.once e,n,*h
26
+ return if e[n]
27
+ e[n]=true
28
+ h end
29
+ end
data/infod/K.rb ADDED
@@ -0,0 +1,197 @@
1
+ class E
2
+
3
+ FSbase = `pwd`.chomp
4
+ Prefix = '/@' # resolver for non-local and non-HTTP URIs
5
+ S = '<>' # path separator
6
+
7
+ # frequently-used URIs
8
+ W3 = 'http://www.w3.org/'
9
+ Purl = 'http://purl.org/'
10
+ FOAF = "http://xmlns.com/foaf/0.1/"
11
+ SIOC = 'http://rdfs.org/sioc/ns#'
12
+ SIOCt = 'http://rdfs.org/sioc/types#'
13
+ MIMEtype = 'http://www.iana.org/assignments/media-types/'
14
+ DC = Purl + 'dc/terms/'
15
+ Date = DC + 'date'
16
+ Modified = DC + 'modified'
17
+ Title = DC + 'title'
18
+ Name = FOAF + 'name'
19
+ To = SIOC + 'addressed_to'
20
+ Creator = SIOC + 'has_creator'
21
+ Content = SIOC + 'content'
22
+ Type = W3 + "1999/02/22-rdf-syntax-ns#type"
23
+ RDFs = W3 + '2000/01/rdf-schema#'
24
+ HTTP = W3 + '2011/http#'
25
+ Posix = W3 + 'ns/posix/'
26
+ Stat = Posix + 'stat#'
27
+ Label = RDFs + 'label'
28
+ EXIF = 'http://www.w3.org/2003/12/exif/ns#'
29
+ Audio = 'http://www.semanticdesktop.org/ontologies/nid3/#'
30
+
31
+ # file-name extension -> MIME type
32
+ MIME={
33
+ aif: 'audio/aif',
34
+ ans: 'text/ansi',
35
+ atom: 'application/atom+xml',
36
+ avi: 'video/avi',
37
+ e: 'application/json+rdf',
38
+ coffee: 'text/plain',
39
+ css: 'text/css',
40
+ csv: 'text/comma-separated-values',
41
+ doc: 'application/word',
42
+ flv: 'video/flv',
43
+ for: 'application/fortran',
44
+ gemspec: 'application/ruby',
45
+ gif: 'image/gif',
46
+ hs: 'application/haskell',
47
+ html: 'text/html',
48
+ ico: 'image/x-ico',
49
+ jpeg: 'image/jpeg',
50
+ jpg: 'image/jpeg',
51
+ js: 'application/javascript',
52
+ json: 'application/json',
53
+ log: 'text/log',
54
+ markdown: 'application/markdown',
55
+ m4a: 'audio/mp4',
56
+ md: 'application/markdown',
57
+ mkv: 'video/matroska',
58
+ mp3: 'audio/mpeg',
59
+ mp4: 'video/mp4',
60
+ mpg: 'video/mpg',
61
+ n3: 'text/rdf+n3',
62
+ nfo: 'text/nfo',
63
+ nt: 'text/ntriples',
64
+ ntriples: 'text/ntriples',
65
+ owl: 'application/rdf+xml',
66
+ pdf: 'application/pdf',
67
+ png: 'image/png',
68
+ py: 'application/python',
69
+ rb: 'application/ruby',
70
+ ru: 'application/ruby',
71
+ rdf: 'application/rdf+xml',
72
+ rtf: 'text/rtf',
73
+ ssv: 'text/semicolon-separated-values',
74
+ textile: 'application/textile',
75
+ tsv: 'text/tab-separated-values',
76
+ ttl: 'text/turtle',
77
+ txt: 'text/plain',
78
+ u: 'application/uri',
79
+ wav: 'audio/wav',
80
+ wmv: 'video/wmv',
81
+ xlsx: 'application/excel',
82
+ }
83
+
84
+ # MIME type -> triplrFn
85
+ MIMEsource={
86
+ 'application/atom+xml' => [:triplrFeed],
87
+ 'application/markdown' => [:triplrMarkdown],
88
+ 'application/org' => [:triplrOrg],
89
+ 'application/rdf+xml' => [:triplrRDFformats,:rdfxml],
90
+ 'application/json' => [:triplrJSON],
91
+ 'application/pdf' => [:triplrPDF],
92
+ 'application/textile' => [:triplrTextile],
93
+ 'application/uri' => [:triplrUriList],
94
+ 'application/word' => [:triplrWord],
95
+ 'audio/mp4' => [:triplrStdOut,'faad -i',Audio],
96
+ 'audio/mpeg' => [:triplrStdOut,'id3info',Audio,/\((.*?)\)$/],
97
+ 'audio' => [:triplrStdOut,'sndfile-info',Audio],
98
+ 'image' => [:triplrStdOut,'exiftool',EXIF],
99
+ 'message/rfc822' => [:triplrMail],
100
+ 'text/ansi' => [:triplrANSI],
101
+ 'text/comma-separated-values'=>[:triplrCSV,/,/],
102
+ 'text/html' => [:triplrRDFformats, :rdfa],
103
+ 'text/log' => [:triplrLog],
104
+ 'text/nfo' => [:triplrHref,'cp437'],
105
+ 'text/ntriples' => [:triplrRDFformats, :ntriples],
106
+ 'text/plain' => [:triplrHref],
107
+ 'text/rtf' => [:triplrRTF],
108
+ 'text/semicolon-separated-values'=>[:triplrCSV,/;/],
109
+ 'text/tab-separated-values'=>[:triplrCSV,/\t/],
110
+ 'text/turtle' => [:triplrRDFformats,:turtle],
111
+ }
112
+
113
+ # MIME type -> formatted content
114
+ Render='render/'
115
+ fn Render+'application/ld+json',->d,_=nil{E.renderRDF d, :jsonld}
116
+ fn Render+'application/rdf+xml',->d,_=nil{E.renderRDF d, :rdfxml}
117
+ fn Render+'text/ntriples',->d,_=nil{E.renderRDF d, :ntriples}
118
+ fn Render+'text/turtle', ->d,_=nil{E.renderRDF d, :turtle}
119
+ fn Render+'text/rdf+n3', ->d,_=nil{E.renderRDF d, :n3}
120
+ fn Render+'text/n3', ->d,_=nil{E.renderRDF d, :n3}
121
+
122
+ # render a view even if requested file exists
123
+ MIMEcook={
124
+ 'application/atom+xml' => true,
125
+ 'application/markdown' => true,
126
+ 'application/json' => true,
127
+ 'application/json+rdf' => true,
128
+ 'application/org' => true,
129
+ 'application/textile' => true,
130
+ 'application/word' => true,
131
+ 'message/rfc822'=> true,
132
+ 'text/ansi'=>true,
133
+ 'text/log'=>true,
134
+ 'text/nfo'=>true,
135
+ 'text/rtf'=>true,
136
+ }
137
+ %w{c ruby haskell php python}.map{|t|
138
+ %w{application/ text/x-}.map{|m|
139
+ MIMEcook[m+t] = true
140
+ }}
141
+
142
+ # short -> full URI
143
+ Abbrev={
144
+ "dc" => DC,
145
+ "foaf" => FOAF,
146
+ "rdf" => W3+"1999/02/22-rdf-syntax-ns#",
147
+ "rdfs" => RDFs,
148
+ "sioc" => SIOC,
149
+ "stat" => Stat,
150
+ }
151
+
152
+ # expose these literals as a path-name
153
+ Literal={}
154
+ [Purl+'dc/elements/1.1/date',
155
+ Date,
156
+ DC+'created',
157
+ Modified,
158
+ ].map{|f|Literal[f]=true}
159
+
160
+ def mime
161
+ @mime ||= (# dereferenced symlink
162
+ f = readlink
163
+
164
+ # filename extension
165
+ x = f.ext.downcase.to_sym
166
+
167
+ # directory?
168
+ if d?
169
+ "inode/directory"
170
+ # local MIME-types table
171
+ elsif MIME[x]
172
+ # puts "found mime for #{x} -> #{MIME[x]}"
173
+ MIME[x]
174
+ # Rack MIME-types table
175
+ elsif Rack::Mime::MIME_TYPES[t = '.' + x.to_s]
176
+ Rack::Mime::MIME_TYPES[t]
177
+ # procmail uses a prefix not an extension
178
+ elsif f.base.index('msg.')==0
179
+ "message/rfc822"
180
+ # ask FILE(1)
181
+ elsif f.e
182
+ `file --mime-type -b #{f.sh}`.chomp
183
+ # default
184
+ else
185
+ "application/octet-stream"
186
+ end)
187
+ end
188
+
189
+ def == u
190
+ to_s == u.to_s
191
+ end
192
+
193
+ Nginx = ENV['nginx']
194
+ Apache = ENV['apache']
195
+ Version = 'http://web.whats-your.name/www/'
196
+
197
+ end