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/W/audio.rb ADDED
@@ -0,0 +1,56 @@
1
+ #watch __FILE__
2
+ class E
3
+
4
+ F["?"]||={}; F["?"].update({'af'=>{'graph' => 'audioFind','view' => 'audioplayer'},
5
+ 'a'=>{'set' => 'audio','view' => 'audioplayer'},
6
+ 'v'=>{'set' => 'video','view' => 'audioplayer','video'=>true}})
7
+
8
+ def audioNodes;take.select &:audioNode end;def audioNode;true if ext.match /(aif|wav|flac|mp3|m4a|aac|ogg)/i end
9
+ def videoNodes;take.select &:videoNode end;def videoNode;true if ext.match /(avi|flv|mkv|mpg|mp4|wmv)/i end
10
+
11
+ fn 'set/audio',->d,e,m{d.audioNodes}
12
+ fn 'set/video',->d,e,m{d.videoNodes}
13
+
14
+ fn 'graph/audioFind',->e,q,m{
15
+ t=q['day'] && q['day'].match(/^\d+$/) && '-ctime -'+q['day']
16
+ s=q['size'] && q['size'].match(/^\d+$/) && '-size +'+q['size']+'M'
17
+ r=(q['find'] ? '.*'+q['find'].gsub(/[^a-zA-Z0-9\.\ ]+/,'.*') : '') + '.*.\(aif\|flac\|m4a\|mp3\|aac\|ogg\|wav\)'
18
+ `find #{e.sh} #{t} #{s} -iregex "#{r}"`.lines.map{|p|p.pathToURI.do{|a|m[a.uri]=a}}}
19
+
20
+ fn 'view/audioplayer/item',->m,e{
21
+ {_: :a,class: :entry, href: '#'+m.uri.gsub('%','%25').gsub('#','%23'),
22
+ c: m.E.bare+" \n"}}
23
+
24
+ fn 'view/audioplayer/base',->d,e,c=nil{
25
+ [{_: :span, id: :jump,c: 'Ⴢ   '},{_: :span, id: :rand,r: :true,c: :r},
26
+ (H.once e,:mu,(H.js '/js/mu')),(H.js '/js/audio'),H.css('/css/audio'),H.css('/css/table'),
27
+ {_: e.q.has_key?('video') ? :video : :audio, id: :player, controls: true},
28
+ {id: :data},
29
+ {id: :playlist,
30
+ c: {:class => :playlistItems,c: c.()}}]}
31
+
32
+ fn 'view/audioplayer',->d,e{i=F['view/audioplayer/item']
33
+ Fn 'view/audioplayer/base',d,e,->{
34
+ d.group_by{|u,r|
35
+ u.split('/')[0..-2].join '/'}.map{|b,g|
36
+ [{_: :a, class: :directory, href: b, c: ['<br>',b,'<br>']},"\n",
37
+ g.map{|r|i.(r[1],e)},"\n"]}}}
38
+
39
+ def audioScan
40
+ a('.png').e ||
41
+ (e = ext; d = sh
42
+ un = e.match /^(aif|wav)$/i # compressed?
43
+ a = un ? d : d+'.wav' # analyze
44
+ (case e
45
+ when 'flac'
46
+ `flac -d #{d} -o #{a}`
47
+ when 'mp3'
48
+ `lame --decode #{d} #{a}`
49
+ when 'ogg'
50
+ `oggdec #{d} -o #{a}`
51
+ end) unless un
52
+ `waveformgen #{a} #{d}.png -l`
53
+ `sndfile-spectrogram --no-border #{a} 1280 800 #{d}.spec.png`
54
+ `rm #{a}` unless un) end
55
+
56
+ end
data/infod/W/blog.rb ADDED
@@ -0,0 +1,3 @@
1
+ class E
2
+
3
+ end
data/infod/W/cal.rb ADDED
@@ -0,0 +1,110 @@
1
+ #watch __FILE__
2
+ class E
3
+ # tripleStream -> tripleStream
4
+ def dateNorm *f
5
+ send(*f){|s,p,o|
6
+ yield *({'CreationDate' => true,
7
+ 'Date' => true,
8
+ RSS+'pubDate' => true,
9
+ Date => true,
10
+ Purl+'dc/elements/1.1/date' => true,
11
+ Atom+'published' => true,
12
+ Atom+'updated' => true
13
+ }[p] ?
14
+ [s,
15
+ Date,
16
+ Time.parse(o).utc.iso8601] :[s,p,o])} end
17
+
18
+ fn 'cal/day',->{Time.now.strftime '%Y/%m/%d/'}
19
+ fn 'cal/month',->{Time.now.strftime '%Y/%m/'}
20
+
21
+ # y=day forwards to current day's directory
22
+ %w{day month}.map{|i|
23
+ fn 'req/'+i,->e,r{
24
+ [303,{'Location'=>e.send(i).uri+r.q.except('y').qs},[]]}}
25
+
26
+ def day; as Fn 'cal/day' end
27
+ def month; as Fn 'cal/month' end
28
+
29
+ fn 'graph/cal',->d,e,m{
30
+ DateTime.parse(e['s']||'2011-03-03').
31
+ upto(e['f'].do{|f|DateTime.parse f} || DateTime.now).
32
+ map{|d|m[d.iso8601]={Date=>[d]}}
33
+ m }
34
+
35
+ fn 'table/year',->d{ m={}
36
+ d.map{|u,r|
37
+ r[Date][0].do{|t|
38
+ t = Time.parse t unless t.time?
39
+ o=12*t.year + t.month - 1
40
+ x=o/3
41
+ y=o%3
42
+ m[x] ||= {}
43
+ m[x][y] ||= {}
44
+ m[x][y][u] = r
45
+ m[x][y][:t] = t
46
+ }}
47
+ m }
48
+
49
+ fn 'table/day',->d{ m={}
50
+ d.map{|u,r|
51
+ r[Date][0].do{|t|
52
+ t = Time.parse t unless t.time?
53
+ h=t.hour
54
+ s=h/12
55
+ h12=h%12
56
+ m[h12] ||= {}
57
+ m[h12][s] ||= {}
58
+ m[h12][s][u] = r
59
+ m[h12][s][:t] = t
60
+ }}
61
+ m }
62
+
63
+ fn 'table/month',->d{ m={}
64
+ d.map{|u,r|
65
+ r[Date][0].do{|t|
66
+ t = DateTime.parse t unless t.time?
67
+ w=t.strftime('%Y%W').to_i
68
+ d=t.cwday
69
+ m[w] ||= {}
70
+ m[w][d] ||= {}
71
+ m[w][d][u] = r
72
+ m[w][d][:t] = t
73
+ }}
74
+ m }
75
+
76
+ fn 'view/year',->d,e{[(H.css '/css/cal'),(Fn 'view/t',d,e,'year','month.label')]}
77
+ fn 'view/month',->d,e{Fn 'view/t',d,e,'month','day.label'}
78
+ fn 'view/day',->d,e{Fn 'view/t',d,e,'day','hour'}
79
+
80
+ fn 'view/hour',->d,e{
81
+ t = d.delete :t
82
+ {style: 'background-color:#'+(t.hour % 2 == 1 ? 'ccc' : 'fff'),c:[{_: :b,style:'float:left;font-size:1.3em', c: [t.hour==0 && {_: :span, style: 'font-size:.8em;color:white;background-color:#ff%02xff' % rand(256),c: t.strftime('%e %B')},t.hour]},
83
+ (Fn 'view/'+(e.q['hourv']||'title'),d,e)
84
+ ]}}
85
+
86
+ fn 'view/month.label',->d,e{
87
+ t = d.delete :t
88
+ {class: :month, style: 'background-color:#bb%02xff'%(rand(64)+192), c:
89
+ ['<b>',(t.month==1 && ['<span class=year>',t.year,'</span> ']),t.strftime('%B'),'</b>',(Fn 'view/'+(e.q['monthv']||'month'),d,e)]}}
90
+
91
+ fn 'view/day.label',->d,e{
92
+ t = d.delete :t
93
+ e[:m]||={}
94
+ c=(e[:m][t.month]||='00%02xff' % (64+rand(192)))
95
+ {style: 'padding:.4em;background-color:#'+c,c:[{_: :b,property: Date, c: [t.day, t.day==1 && {_: :span, style: 'font-size:.5em',c: t.strftime('%b')}]},
96
+ (Fn 'view/'+(e.q['dayv']||'title'),d,e)
97
+ ]}}
98
+
99
+ end
100
+
101
+ class Object
102
+ def time?
103
+ (self.class == Time) || (self.class == DateTime)
104
+ end
105
+ def to_time
106
+ time? ? self : Time.parse(self)
107
+ rescue
108
+ nil
109
+ end
110
+ end
data/infod/W/chat.rb ADDED
@@ -0,0 +1,81 @@
1
+ #watch __FILE__
2
+ class E
3
+
4
+ def triplrLog &f
5
+ i=-1
6
+ day = dirname.uri.split('/')[-3..-1].join('-')
7
+ doc = uri.sub '#','%23'
8
+ chan = bare
9
+ yield doc,'chan',chan
10
+ r.scan(/(\d\d):(\d\d) \[[\s@]*([^\(\]]+)[^\]]*\] (.*)/){|m|
11
+ s = doc + '#' + doc + ':' + (i+=1).to_s
12
+ yield s,Date,day+'T'+m[0]+':'+m[1]+':00'
13
+ yield s,'chan',chan
14
+ yield s,Creator,m[2]
15
+ yield s,Content,m[3].hrefs(true)
16
+ yield s,Type,E[SIOCt+'InstantMessage']
17
+ yield s,'hasLink',(m[3].match(/http:\//) ? 'true' : 'false')
18
+ yield s,'hasNum','true' if m[3].match(/\d/)
19
+ } rescue nil
20
+ yield doc,Date,day
21
+ end
22
+
23
+ def tw g='m'
24
+ no.readlines.shuffle.each_slice(22){|s|
25
+ E['https://twitter.com/search/realtime?q='+s.map{|u|'from:'+u.chomp}.intersperse('+OR+').join].addJSON :triplrTweets, g}
26
+ end
27
+
28
+ def triplrTweets
29
+ base = 'http://twitter.com'
30
+ nokogiri.css('div.tweet').map{|t|
31
+ s = base + t.css('a.details').attr('href') # subject URI
32
+ yield s, Type, E[SIOCt+'MicroblogPost']
33
+ yield s, Creator, E(base+'/'+t.css('.username b')[0].inner_text)
34
+ yield s, SIOC+'name',t.css('.fullname')[0].inner_text
35
+ yield s, Atom+"/link/image", E(t.css('.avatar')[0].attr('src'))
36
+ yield s, Date, Time.at(t.css('[data-time]')[0].attr('data-time').to_i).iso8601
37
+ content = t.css('.tweet-text')[0]
38
+ content.css('a').map{|a|
39
+ u = a.attr 'href'
40
+ a.set_attribute('href',base + u) if u.match /^\//}
41
+ yield s, Content, content.inner_html
42
+ }
43
+ end
44
+
45
+ fn 'head/chat',->d,e{
46
+ t = d.map{|_,r|r[Date]}.flatten.compact.map &:to_time
47
+ c = d.map{|_,r|r[Content]}.compact.size
48
+ [{_: :title, c: "#{c} post#{c!=1 && 's'} #{t.min} -> #{t.max}"},
49
+ (Fn 'head.formats',e)]}
50
+
51
+ fn 'view/chat',->d,e{
52
+ Fn'view/chat/base',d,e,->{d.map{|u,r|Fn 'view/chat/item',r,e}}}
53
+
54
+ fn 'view/chat/item',->r,e{
55
+ line = r.E.frag
56
+ r[Type] && r[Type].map(&:uri).include?(SIOCt+'MailMessage') && r[:mail]=true
57
+ r[Content] &&
58
+ [{_: :a, id: line},
59
+ {_: :a, :class => :date, href: r.url, c: r[Date][0].match(/T([0-9:]{5})/)[1]},
60
+ {_: :span, :class => :nick, c: {_: :a, href: r[Atom+'/link/alternate'].do{|a|a[0].uri}||r.url,
61
+ c: [{_: :img, class: :a, src: r[Atom+"/link/image"][0].uri},
62
+ {_: :span, c: r[SIOC+'name']||r[Creator]||'#'}]}},' ',
63
+ {_: :span, :class => :tw,
64
+ c: [r[Atom+'/link/media'].do{|a|
65
+ a.map{|a|{_: :a, href: r.url, c: {_: :img, src: a.uri}}}},
66
+ ((r[Title].to_s==r[Content].to_s || r.uri.match(/twitter/)) && '' ||
67
+ {_: :a, href: r.url, c: r[Title],:class => r[:mail] ? :titleMail : :title}),
68
+ r[:mail] ? (r[Content].map{|c|c.lines.to_a.grep(/^[^&@_]+$/)[0..21]}) : r[Content],
69
+ ]},' ',
70
+ {_: :a, class: :line, href: '#'+line, c: '&nbsp;'},
71
+ "<br>\n"] if r.uri}
72
+
73
+ fn 'view/chat/base',->d,e,c{
74
+ [(H.once e,'chat.head',(H.css '/css/tw'),{_: :style, c: "body, span.nick span, a {background-color: #{E.c}}\n"}),
75
+ {:class => :ch, c: c.()},
76
+ (H.once e,'chat.tail',{id: :b})]}
77
+
78
+ F['view/'+SIOCt+'InstantMessage']=F['view/chat']
79
+ F['view/'+SIOCt+'MicroblogPost']=F['view/chat']
80
+
81
+ end
data/infod/W/color.rb ADDED
@@ -0,0 +1,28 @@
1
+ #watch __FILE__
2
+ class E
3
+
4
+ def E.c; '#%06x' % rand(16777216)end
5
+
6
+ fn 'view/color',->d,e{
7
+
8
+ n = (e.q['n']||42).to_i.max 255
9
+
10
+ hsv2rgb=->h,s,v{
11
+ i = h.floor
12
+ f = h - i
13
+ p = v * (1 - s)
14
+ q = v * (1 - (s * f))
15
+ t = v * (1 - (s * (1 - f)))
16
+ r,g,b=[[v,t,p],
17
+ [q,v,p],
18
+ [p,v,t],
19
+ [p,q,v],
20
+ [t,p,v],
21
+ [v,p,q]][i].map{|q|q*255.0}}
22
+
23
+ [H.css('/css/color'),
24
+ {style: 'display:table;width:100%',
25
+ c: [(1..10).map{|s| {class: :row, c: (0..n-1).map{|h| {class: :c,style: 'background-color:#%02x%02x%02x' % hsv2rgb.(h/(n/6.0),s/10.0,1.0)}}}},
26
+ (1..10).to_a.reverse.map{|v| {class: :row, c: (0..n-1).map{|h| {class: :c,style: 'background-color:#%02x%02x%02x' % hsv2rgb.(h/(n/6.0),1.0,v/10.0)}}}}]}]}
27
+
28
+ end
data/infod/W/core.rb ADDED
@@ -0,0 +1,77 @@
1
+
2
+ class Hash
3
+ def graph g
4
+ g.merge!({uri=>self})
5
+ end
6
+ %w{graphFromFile q}.map{|m|alias_method m,:graph}
7
+ def mergeGraph g
8
+ g.values.each do |r|
9
+ r.triples do |s,p,o|
10
+ self[s] = {'uri' => s} unless self[s].class == Hash
11
+ self[s][p] ||= []
12
+ self[s][p].push o unless self[s][p].member? o
13
+ end
14
+ end
15
+ self
16
+ end
17
+ def attr p;map{|_,r|r[p].do{|o|return o}}; nil end
18
+ # Hash -> tripleStream
19
+ def triples
20
+ s = uri
21
+ map{|p,o|
22
+ o.class == Array ? o.each{|o| yield s,p,o} : yield(s,p,o) unless p=='uri'}
23
+ end
24
+ end
25
+
26
+ class E
27
+
28
+ # fromStream :: Graph -> tripleStream -> Graph
29
+ def fromStream m,*i
30
+ send(*i) do |s,p,o|
31
+ m[s] = {'uri' => s} unless m[s].class == Hash
32
+ m[s][p] ||= []
33
+ m[s][p].push o unless m[s][p].member? o
34
+ end; m
35
+ end
36
+
37
+ # tripleStream transformer stack
38
+ fn 'graph/|',->e,_,m{e.fromStream m, *_['|'].split(/,/)}
39
+
40
+ def E.graphFromStream s
41
+ fn 'graph/'+s.to_s,->e,_,m{e.fromStream m, s}
42
+ end
43
+
44
+ # placeholder to circumvent empty-graph 404
45
+ fn 'graph/_',->d,_,m{ m[d.uri] = {} }
46
+
47
+ # to_h :: -> Hash
48
+ def to_h
49
+ {'uri'=>uri}
50
+ end
51
+
52
+ def triplrMIMEdispatch &b;mime.do{|mime|
53
+ yield uri,E::Type,(E MIMEtype+mime)
54
+ (MIMEsource[mime]||
55
+ MIMEsource[mime.split(/\//)[0]]).do{|s|
56
+ send *s,&b }}
57
+ end
58
+
59
+ def graphFromFile g={}
60
+ g.mergeGraph r(true) if ext=='e' # JSON -> graph
61
+ [:triplrInode, # filesystem data
62
+ :triplrMIMEdispatch].# format-specific tripleStream
63
+ each{|i| fromStream g,i } # tripleStream -> Graph
64
+ g
65
+ end
66
+
67
+ def graph g={}
68
+ docs.map{|d|d.graphFromFile g} # tripleStream -> graph
69
+ g
70
+ end
71
+
72
+ # render :: MIME, Graph, env -> String
73
+ def render mime, d, e
74
+ E[Render+mime].y d,e
75
+ end
76
+
77
+ end
data/infod/W/css.rb ADDED
@@ -0,0 +1,24 @@
1
+ class E
2
+
3
+ def nokogiri; require 'nokogiri'; Nokogiri::HTML.parse read end
4
+
5
+ def triplrCSSselect
6
+ glob.select(&:f).do{|f|(f.empty? ? [self] : f).map{|r|r.nokogiri.do{|c|c.css(sel).map{|e|
7
+ yield r.uri+'#css:'+sel,Content,e.to_s
8
+ }}}} end
9
+
10
+ fn 'graph/css',->d,e,m{
11
+ d.fromStream m,:triplrCSSselect,e['selector']}
12
+
13
+ MIMEsource['text/css'] ||= [:triplrSourceCode]
14
+
15
+ end
16
+
17
+ class H
18
+
19
+ def H.css a,inline=false
20
+ p=a+'.css'
21
+ inline ? {_: :style, c: p.E.r} :
22
+ {_: :link, href: p, rel: :stylesheet, type: E::MIME[:css]} end
23
+
24
+ end
data/infod/W/csv.rb ADDED
@@ -0,0 +1,13 @@
1
+ class E
2
+
3
+ # CSV -> tripleStream
4
+ def triplrCSV d
5
+ d = @r.q['delim']||d
6
+ open(node).readlines.map{|l|l.chomp.split(d) rescue []}.do{|t|
7
+ t[0].do{|x|
8
+ t[1..-1].each_with_index{|r,ow|r.each_with_index{|v,i|
9
+ yield uri+'#r'+ow.to_s,x[i],v
10
+ }}}}
11
+ end
12
+
13
+ end
data/infod/W/du.rb ADDED
@@ -0,0 +1,35 @@
1
+ #watch __FILE__
2
+ class E
3
+
4
+ F["?"]||={}
5
+ F["?"].update({'du'=>{
6
+ 'graph' => 'du',
7
+ 'view' => 'protovis',
8
+ 'protovis.data' => 'protovis/du',
9
+ 'protovis.view' => 'starburst'
10
+ }})
11
+
12
+ fn 'graph/du',->e,_,m{
13
+ `du -a #{e.sh}`.lines.to_a[0..-2].map{|p|
14
+ begin
15
+ s,p = p.split /\t/ # size, path
16
+ p = p.pathToURI # path -> URI
17
+ m[p.uri]={'uri'=>p.uri,'size'=>s.to_i}
18
+ rescue
19
+ nil
20
+ end
21
+ }
22
+ m}
23
+
24
+ fn 'protovis/du',->e,c{
25
+ m={} # model
26
+ e.map{|u,r| # each resource
27
+ s = u.sub(/http:..[^\/]+./,'').split '/' # split path
28
+ p = m # pointer
29
+ s[0..-2].map{|s| # each path segment
30
+ p[s] = {} if !p[s] || p[s].class != Hash # create branch
31
+ p = p[s]} # advance pointer down tree
32
+ p[s[-1]]||=r['size']} # append size to leaf
33
+ m}
34
+
35
+ end
data/infod/W/edit.rb ADDED
@@ -0,0 +1,8 @@
1
+ #watch __FILE__
2
+ class E
3
+
4
+ fn 'view/edit',->d,env{
5
+ {_: :form, name: :editor, c: 'edit'}
6
+ }
7
+
8
+ end
@@ -0,0 +1,59 @@
1
+ #watch __FILE__
2
+ %w{exhibit hist history normal protovis sw time/graph time/line}.each{|e|require_relative e}
3
+ class E
4
+
5
+ fn 'view/examine',->a,m,e{
6
+ a=Hash[(a.split ',').map{|a|[a,{}]}] # facets
7
+
8
+ # facet stats
9
+ m.map{|s,r| a.map{|p,_|
10
+ r[p].do{|o|
11
+ (o.class==Array ? o : [o]).map{|o|
12
+ a[p][o]=(a[p][o]||0)+1}}}}
13
+
14
+
15
+ # facet identifiers
16
+ i={}; c=-1
17
+ n=->o{
18
+ i[o]||='f'+(c+=1).to_s}
19
+
20
+ view=F['view/'+ (e.q['ev'] || 'divine') + '/item']
21
+
22
+ resources=->{
23
+ m.map{|u,r| # each resource
24
+ a.map{|p,_| # each facet
25
+ [n.(p),r[p].do{|o| # value
26
+ (o.class==Array ? o : [o]).map{|o|
27
+ n.(o.to_s) # identifier
28
+ }}].join ' '
29
+ }.do{|f|
30
+ [f.map{|o|'<div class="'+o+'">'}, # facet wrapper
31
+ view.(r,e), # resource
32
+ (0..f.size-1).map{|c|'</div>'}]}}}
33
+
34
+ [(H.css'/css/examine'),(H.js'/js/examine'),(H.js'/js/mu'),
35
+
36
+ a.map{|b,_|{_: :style, class: n.(b)}},
37
+
38
+ # facet selection
39
+ {class: :sidebar, c: a.map{|f,v|
40
+ {class: :facet, title: f, facet: n.(f), # predicate
41
+ c: [f.label,
42
+ v.sort_by{|k,v|v}.reverse.map{|k,v| # sort by popularity
43
+ k.respond_to?(:label) &&
44
+ {facet: n.(k.to_s), # predicate-object tuple
45
+ c: [{_: :span, class: :count, c: v},
46
+ {_: :span, class: :name, c: k.label}]}}]}}},
47
+
48
+ (F['view/'+e.q['ev']+'/base']||
49
+ ->m,e,r{r.()}).(m,e,resources)]}
50
+
51
+ fn 'view/examine/selectFacets',->m,e{
52
+ [(H.js '/js/examine.selectFacet'),(H.js '/js/mu'),(H.css '/css/examine'),
53
+ E.graphProperties(m).map{|e|[{c: e},' ']},
54
+ {_: 'button', c: 'Go'}]}
55
+
56
+ fn 'view/e',->m,e{
57
+ e.q['a'].do{|a|Fn 'view/examine',a,m,e} ||
58
+ (Fn 'view/examine/selectFacets',m,e)}
59
+ end
@@ -0,0 +1,34 @@
1
+ class E
2
+ fn Render+'application/json+exhibit',->d,e{
3
+ fields=e.q['f'].do{|f|f.split /,/}
4
+ {items: d.values.map{|r|
5
+ r.keys.-(['uri']).map{|k|
6
+ f=k.frag.do{|f|(f.gsub /\W/,'').downcase} # alphanumeric id restriction
7
+ if !fields || (fields.member? f)
8
+ r[f]=r[k][0].to_s # rename fieldnames, unwrap value
9
+ r.delete k unless f==k # cleanup unless id same as before
10
+ else
11
+ r.delete k
12
+ end}
13
+ r[:label]=r.delete 'uri' # requires label only
14
+ r
15
+ }}.to_json}
16
+
17
+ fn 'head/exhibit',->d,e{
18
+ '<link href="'+e['REQUEST_PATH']+e.q.merge({'format' => 'application/json+exhibit'}).qs+'" type="application/json" rel="exhibit/data" />
19
+ <script src="http://api.simile-widgets.org/exhibit/2.2.0/exhibit-api.js?autoCreate=false" type="text/javascript"></script>
20
+ <script>SimileAjax.jQuery(document).ready(function(){var fDone=function(){
21
+ window.exhibit = Exhibit.create()
22
+ window.exhibit.configureFromDOM()
23
+ database.getAllProperties().map(function(f){
24
+ if (!f.match(/^(label|content)$/)){
25
+ var a=document.createElement("div")
26
+ document.getElementById("sidebar").appendChild(a)
27
+ var x=Exhibit.ListFacet.create({"expression": "."+f},a,exhibit.getUIContext())
28
+ exhibit.setComponent("facet-"+f, x)}})}
29
+ window.database = Exhibit.Database.create()
30
+ window.database.loadDataLinks(fDone)})</script>'}
31
+
32
+ fn 'view/exhibit',->d,e{'<table width="100%"><tr valign="top"><td width="25%" id=sidebar></td><td><div ex:role="view"></div></td></tr></table>'}
33
+
34
+ end
@@ -0,0 +1,55 @@
1
+ #watch __FILE__
2
+ class E
3
+
4
+ # histogram
5
+ # ?view=h&a=dc:date
6
+ fn 'view/h',->d,e{
7
+ a=e.q['a'].do{|e|e.expand} # a : attribute to chart
8
+ !a && 'attribute required' || (
9
+ n=e.q['bins']&&e.q['bins'].to_f.max(999.0).min(2)||42.0 # n : number of bins
10
+ v=F['view/'+(e&&e.q['hv']||'tab')] # hv : bin template
11
+ (Fn 'u/hist',d,a,n).do{|h| # construct histogram bins
12
+ [H.css('/css/hist'),H.js('/js/hist'),(Fn 'view/hist',h),
13
+ h.map{|b,r|{style: 'display:none',
14
+ :class => 's'+b.to_s.sub(/\./,'_'),
15
+ c: v.(r,e)}}]})}
16
+
17
+ # hist :: Graph, property, numBins -> {bin -> Graph}
18
+ fn 'u/hist',->m,p,nb=32.0{
19
+ h={}; bw=0; max=0; min=0
20
+ m.map{|u,r|
21
+ r[p]
22
+ }.flatten.do{|v|
23
+ v=v.compact.map{|v|v.to_time.to_f}# values
24
+ bw = (v.max - v.min) / nb.min(1)} # bin width
25
+ m.map{|u,r|
26
+ r[p].do{|v|v.each{|v|
27
+ b=(v.to_time.to_f/bw).floor*bw # bin selector
28
+ h[b] ||= {}
29
+ h[b][u]=r}}} # append
30
+ h} # histogram
31
+
32
+ # histTable :: hist -> htmlTable
33
+ fn 'view/hist',->h{
34
+ scale = 255 / h.map{|b,r|r.keys.size}.max.to_f
35
+ b=h.keys.sort
36
+ span=(b.size / 8).min 1
37
+ i=-1
38
+ '<table cellspacing=0 style="width:100%;max-width:100%"><tr class=histLegend>'+
39
+ H(b.select{|b|
40
+ i = i + 1
41
+ i % span == 0
42
+ }.map{|b|
43
+ {_: :td,
44
+ :class => :histLegendPt,
45
+ colspan: span,
46
+ c: {_: :span, :class => :histLabel, c: b > 1 ? b.to_i : b}}})+
47
+ '</tr><tr class=hist>'+
48
+ H(b.map{|b|{_: :td, title:b.to_s.sub(/\./,'_'),style: 'background-color:#'+
49
+ ('%02x' % (255-(h[b].do{|p|
50
+ p.keys.size * scale
51
+ }||0))).do{|x|
52
+ 'ff'+x+x}}})+
53
+ '</tr></table>'}
54
+
55
+ end
@@ -0,0 +1,19 @@
1
+ class E
2
+
3
+ def triplrMozHist
4
+ c = @r.q['c'].match(/[0-9]+/) ? @r.q['c'] : '18'
5
+ q = @r.q['match'].do{|m|m.match(/[a-zA-Z\-_\.\/]+/) && "and p.url like '#{@r.q.has_key?('full')?'':'%'}#{m}%'"}
6
+ t = @r.q['t'].do{|t| "and h.visit_date < #{Time.parse(t).to_f*1e6}"}
7
+
8
+ `sqlite3 -separator "\t" #{sh} "select p.url, r.url, h.visit_date, hr.visit_date from moz_places as p,moz_places as r, moz_historyvisits as h, moz_historyvisits as hr where p.id = h.place_id and r.id = hr.place_id and hr.id = h.from_visit #{q} #{t} order by h.visit_date desc limit #{c}"`.lines.to_a.map{|i|i.split "\t"}.do{|l|
9
+
10
+ yield 'prev','url',@r['REQUEST_PATH']
11
+ yield 'prev','t',Time.at(l[-1][2].to_f/1e6).iso8601
12
+
13
+ l.map{|i|
14
+ yield i[0], Date, Time.at(i[2].to_f/1e6)
15
+ yield i[1], Date, Time.at(i[3].to_f/1e6)
16
+ yield i[0],'referer',i[1].E }
17
+
18
+ } end
19
+ end
@@ -0,0 +1,31 @@
1
+ #watch __FILE__
2
+ class E
3
+ fn 'filter/map',->o,m,_{
4
+ o.except('filter','graph','view').map{|p,n|
5
+ p=p.expand
6
+ n=n.expand
7
+ p!=n &&
8
+ m.values.map{|r|
9
+ r[p].do{|o|
10
+ r[n]=o
11
+ r.delete p }}}}
12
+
13
+ fn 'view/map',->d,e{
14
+ [H.js('/js/normal'),(H.once e,:mu,(H.js '/js/mu')),
15
+ '<style>.b {display:inline-block;font-weight:bold;padding-right:.8em;text-align:right;min-width:12em}
16
+ .exerpt {display:inline-block;max-height:1em;overflow:hidden;max-width:44em;font-size: .9em} </style>',
17
+ {_: :form, c:
18
+ [d.values.map(&:keys).flatten.uniq.-(['uri']).do{|ps|
19
+ ps.map{|p|
20
+ [{class: :b, c: p},{_: :select, name: p, c:
21
+ (ps + [Date,Creator,Content,Title]).map{|q|
22
+ {_: :option, c: q}.
23
+ update(p==q ? {selected: :selected}:{})}},
24
+ {class: :exerpt, c: d.values.map{|r|r[p]}.flatten.uniq.html},
25
+ '<br>']}},
26
+ {_: :input, type: :hidden, name: :view, value: :tab},
27
+ {_: :input, type: :hidden, name: :filter, value: :map},
28
+ {_: :input, type: :submit}
29
+ ]}]}
30
+
31
+ end
@@ -0,0 +1,30 @@
1
+ #watch __FILE__
2
+ class E
3
+
4
+ F["?"]||={}
5
+ F["?"].update({'arc'=>{
6
+ 'view' => 'protovis',
7
+ 'protovis.data' => 'protovis/net',
8
+ 'protovis.view' => 'arc'
9
+ }})
10
+
11
+ fn 'protovis/net',->d,e{
12
+ i=-1
13
+ x={} # uri -> index
14
+ a=[] # arcs
15
+ d.values.each{|r|
16
+ r.triples{|s,p,o|
17
+ o.respond_to?(:uri) &&
18
+ o.uri.do{|o|
19
+ x[s]||=i+=1
20
+ x[o]||=i+=1
21
+ a.push({source: x[s], target: x[o], value: 3})}}}
22
+ {nodes: x.map{|u,_|{nodeName: d[u][Title]||u.label, group: 0}},
23
+ links: a}}
24
+
25
+ fn 'view/protovis',->d,e{
26
+ [H.js('/js/protovis/protovis-r3.2'),{id: :fig},
27
+ {_: :script,type: 'text/javascript+protovis',
28
+ c: ['var d='+(Fn e.q['protovis.data'],d,e).to_json,
29
+ E['http://'+e['SERVER_NAME']+'/js/protovis/'+e.q['protovis.view']+'.js'].r].cr}]}
30
+ end