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