infod 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/infod.rb +52 -12
- data/infod/{Th/404.rb → 404.rb} +4 -16
- data/infod/500.rb +53 -0
- data/infod/GET.rb +104 -0
- data/infod/HEAD.rb +23 -0
- data/infod/HTTP.rb +105 -0
- data/infod/{Th/PATCH.rb → PATCH.rb} +0 -0
- data/infod/POST.rb +34 -0
- data/infod/audio.rb +30 -0
- data/infod/blog.rb +34 -0
- data/infod/cal.rb +72 -0
- data/infod/{Es/code.rb → code.rb} +7 -4
- data/infod/constants.rb +55 -0
- data/infod/{Es/css.rb → css.rb} +0 -0
- data/infod/{Es/csv.rb → csv.rb} +0 -0
- data/infod/{Es/du.rb → du.rb} +0 -0
- data/infod/edit.rb +73 -0
- data/infod/{H/facets.rb → facets.rb} +20 -11
- data/infod/{Es/feed.rb → feed.rb} +17 -16
- data/infod/{Es/find.rb → find.rb} +2 -3
- data/infod/forum.rb +13 -0
- data/infod/{Es/fs.rb → fs.rb} +5 -2
- data/infod/glob.rb +26 -0
- data/infod/graph.rb +123 -0
- data/infod/{Es/grep.rb → grep.rb} +2 -2
- data/infod/{Es/groonga.rb → groonga.rb} +41 -33
- data/infod/{H/histogram.rb → histogram.rb} +23 -16
- data/infod/html.rb +231 -0
- data/infod/{Es/image.rb → image.rb} +16 -26
- data/infod/{Es/index.rb → index.rb} +38 -25
- data/infod/infod.rb +52 -12
- data/infod/json.rb +38 -0
- data/infod/{Es/kv.rb → kv.rb} +3 -9
- data/infod/{Y.rb → lambda.rb} +18 -1
- data/infod/ls.rb +49 -0
- data/infod/mail.rb +90 -0
- data/infod/{Es/man.rb → man.rb} +3 -15
- data/infod/{H/microblog.rb → microblog.rb} +22 -31
- data/infod/{K.rb → mime.rb} +68 -52
- data/infod/{N.rb → names.rb} +78 -45
- data/infod/page.rb +13 -0
- data/infod/postscript.rb +26 -0
- data/infod/rdf.rb +51 -0
- data/infod/{Rb.rb → ruby.rb} +18 -33
- data/infod/{Es/schema.rb → schema.rb} +22 -7
- data/infod/{Es/search.rb → search.rb} +5 -11
- data/infod/{Es/sh.rb → sh.rb} +0 -0
- data/infod/{Es/text.rb → text.rb} +33 -29
- data/infod/{H/threads.rb → threads.rb} +17 -27
- data/infod/{H/time.rb → time.rb} +14 -34
- data/infod/{H/who.rb → whois.rb} +6 -4
- data/infod/{H/wiki.rb → wiki.rb} +0 -0
- metadata +54 -64
- data/config.ru +0 -3
- data/infod/Es.rb +0 -31
- data/infod/Es/filter.rb +0 -75
- data/infod/Es/glob.rb +0 -22
- data/infod/Es/html.rb +0 -271
- data/infod/Es/in.rb +0 -68
- data/infod/Es/json.rb +0 -68
- data/infod/Es/ls.rb +0 -58
- data/infod/Es/mail.rb +0 -87
- data/infod/Es/mime.rb +0 -59
- data/infod/Es/out.rb +0 -52
- data/infod/Es/pager.rb +0 -34
- data/infod/Es/pdf.rb +0 -19
- data/infod/Es/rdf.rb +0 -35
- data/infod/H.rb +0 -15
- data/infod/H/audio.rb +0 -19
- data/infod/H/blog.rb +0 -15
- data/infod/H/cal.rb +0 -81
- data/infod/H/edit.rb +0 -88
- data/infod/H/forum.rb +0 -4
- data/infod/H/hf.rb +0 -114
- data/infod/H/mail.rb +0 -92
- data/infod/Th.rb +0 -36
- data/infod/Th/500.rb +0 -41
- data/infod/Th/GET.rb +0 -62
- data/infod/Th/HEAD.rb +0 -5
- data/infod/Th/POST.rb +0 -39
- data/infod/Th/perf.rb +0 -37
- data/infod/Th/uid.rb +0 -24
- data/infod/Th/util.rb +0 -89
data/infod/{Es/fs.rb → fs.rb}
RENAMED
@@ -13,6 +13,10 @@ class E
|
|
13
13
|
end
|
14
14
|
alias_method :no, :node
|
15
15
|
|
16
|
+
def inside
|
17
|
+
node.expand_path.to_s.index(FSbase) == 0
|
18
|
+
end
|
19
|
+
|
16
20
|
def siblings
|
17
21
|
parent.c
|
18
22
|
end
|
@@ -113,7 +117,6 @@ class E
|
|
113
117
|
if f
|
114
118
|
p ? (JSON.parse readFile) : readFile
|
115
119
|
else
|
116
|
-
puts "tried to open #{d}"
|
117
120
|
nil
|
118
121
|
end
|
119
122
|
rescue Exception => e
|
@@ -150,7 +153,7 @@ class Pathname
|
|
150
153
|
end
|
151
154
|
|
152
155
|
def deleteNode
|
153
|
-
FileUtils.send file? ? :rm : :rmdir, self
|
156
|
+
FileUtils.send (file?||symlink?) ? :rm : :rmdir, self
|
154
157
|
parent.deleteNode if parent.c.empty?
|
155
158
|
end
|
156
159
|
|
data/infod/glob.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#watch __FILE__
|
2
|
+
class E
|
3
|
+
|
4
|
+
# glob :: pattern -> [E]
|
5
|
+
def glob p=""
|
6
|
+
(Pathname.glob d + p).map &:E
|
7
|
+
end
|
8
|
+
|
9
|
+
fn 'set/glob',->d,e=nil,_=nil{
|
10
|
+
p = [d,d.pathSegment].compact.map(&:glob).flatten[0..4e2].compact.partition &:inside
|
11
|
+
p[0] }
|
12
|
+
|
13
|
+
fn 'req/randomFile',->e,r{
|
14
|
+
g = F['set/glob'][e]
|
15
|
+
!g.empty? ? [302, {Location: g[rand g.length].uri}, []] : [404]}
|
16
|
+
|
17
|
+
def docs
|
18
|
+
base = docBase
|
19
|
+
[(base if pathSegment!='/' && base.e),
|
20
|
+
(self if base != self && e && uri[-1]!='/'),
|
21
|
+
base.glob(".{e,html,n3,nt,owl,rdf,ttl,txt}"), # docs
|
22
|
+
((d? && uri[-1]=='/' && uri.size>1) ? c : []) # trailing slash = children
|
23
|
+
].flatten.compact
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/infod/graph.rb
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
#watch __FILE__
|
2
|
+
class E
|
3
|
+
=begin
|
4
|
+
graph resolution is two-pass
|
5
|
+
|
6
|
+
protograph/
|
7
|
+
the first-pass will determine if the second-pass needs to run. an eTag will be derived from the return-value and any graph additions preserved for the next pass. ideal fingerprint sources include filestats, mtime checks, extremely trivial SPARQL queries, SHA160 hashes of in-RAM entities. you can define only a second or first-pass and get default behaviour for the other. for more implementation ideas see: http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-25#section-2.3
|
8
|
+
|
9
|
+
graph/
|
10
|
+
a second-pass might query a CBD (concise-bounded description) from a SPARQL store. info"daemon was originally developed as an alternative to fragility & latency of relying on (large, hard-to-implement, must be running, configured & connectable) SPARQL stores by using the filesystem as much as possible, to experiment with hybrids like "touch" a file on successful POSTs so a store only has to be queried occasionally, and to facilitate simply hooking up bits of Ruby code to names rather than try to shoehorn what you're trying to say into some QueryLang where you're additionally without standard library functions thus necessitating more roundtrips and latency via marshalling/unmarshalling, parsing, loopback network-abstraction, nascent RDF-via-SPARQL-as-ORM-libs.. by all means knock yourself out experimenting w/ defining graph-handlers for this stuff though - i know i do!
|
11
|
+
|
12
|
+
triple streams are functions which yield triples
|
13
|
+
s,p - URI in String , o - Literal or URI (object must respond to #uri such as '/path'.E or {'uri' => '/some.n3'}
|
14
|
+
these can be formed into pipelines. the data ingesting/massaging stream-processors in feed.rb are modularized this way
|
15
|
+
|
16
|
+
=end
|
17
|
+
|
18
|
+
def fromStream m,*i
|
19
|
+
send(*i) do |s,p,o|
|
20
|
+
m[s] = {'uri' => s} unless m[s].class == Hash
|
21
|
+
m[s][p] ||= []
|
22
|
+
m[s][p].push o unless m[s][p].member? o
|
23
|
+
end; m
|
24
|
+
end
|
25
|
+
|
26
|
+
=begin
|
27
|
+
* stream triples into graph (memory)
|
28
|
+
* import missing resources to store (fs)
|
29
|
+
* behave as normal triplr to caller, with
|
30
|
+
side-effect of import/indexing to knowledgebase
|
31
|
+
=end
|
32
|
+
def insertDocs triplr, h=nil, p=[], &b
|
33
|
+
graph = fromStream({},triplr)
|
34
|
+
graph.map{|u,r| # stream -> graph
|
35
|
+
e = u.E # resource
|
36
|
+
j = e.ef # doc
|
37
|
+
j.e || # exists?
|
38
|
+
(j.w({u=>r},true) ;puts '< '+u # insert doc
|
39
|
+
p.map{|p| # each indexable property
|
40
|
+
r[p].do{|v| # values exists?
|
41
|
+
v.map{|o| # each value
|
42
|
+
e.index p,o}}} # property index
|
43
|
+
e.roonga h if h)} # full-text index
|
44
|
+
graph.triples &b if b # emit the triples
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# default protograph - identity + resource-thunks
|
49
|
+
# Resource, Query, Graph -> graphID
|
50
|
+
fn 'protograph/',->e,q,g{
|
51
|
+
g['#'] = {'uri' => '#'}
|
52
|
+
set = (q['set'] && F['set/'+q['set']] || F['set/'])[e,q,g]
|
53
|
+
if set.empty?
|
54
|
+
g.delete '#'
|
55
|
+
else
|
56
|
+
g['#'][RDFs+'member'] = set
|
57
|
+
g['#'][Type] = E[HTTP+'Response']
|
58
|
+
set.map{|u| g[u.uri] = u } # thunk
|
59
|
+
end
|
60
|
+
F['docsID'][g,q]}
|
61
|
+
|
62
|
+
fn 'set/',->e,q,g{
|
63
|
+
s = []
|
64
|
+
s.concat e.docs
|
65
|
+
e.pathSegment.do{|p| s.concat p.docs }
|
66
|
+
# day-dir hinted pagination
|
67
|
+
e.env['REQUEST_PATH'].match(/(.*?\/)([0-9]{4})\/([0-9]{2})\/([0-9]{2})(.*)/).do{|m|
|
68
|
+
u = g['#']
|
69
|
+
t = ::Date.parse "#{m[2]}-#{m[3]}-#{m[4]}"
|
70
|
+
pp = m[1] + (t-1).strftime('%Y/%m/%d') + m[5]
|
71
|
+
np = m[1] + (t+1).strftime('%Y/%m/%d') + m[5]
|
72
|
+
u[Prev] = {'uri' => pp} if pp.E.e || E['http://' + e.env['SERVER_NAME'] + pp].e
|
73
|
+
u[Next] = {'uri' => np} if np.E.e || E['http://' + e.env['SERVER_NAME'] + np].e }
|
74
|
+
s }
|
75
|
+
|
76
|
+
# fs-derived ID for a resource-set
|
77
|
+
fn 'docsID',->g,q{
|
78
|
+
[q.has_key?('nocache').do{|_|rand},
|
79
|
+
g.sort.map{|u,r|
|
80
|
+
[u, r.respond_to?(:m) && r.m]}].h }
|
81
|
+
|
82
|
+
# default graph (filesystem store)
|
83
|
+
# to use a different default-graph function (w/o patching here, or querystring param), define a GET handler on / (or a subdir),
|
84
|
+
# update configuration such as q['graph'] = 'hexastore' and return false or call #response..
|
85
|
+
fn 'graph/',->e,q,m{
|
86
|
+
# force thunks
|
87
|
+
m.values.map{|r|(r.env e.env).graphFromFile m if r.class == E }
|
88
|
+
# cleanup unexpanded thunks
|
89
|
+
m.delete_if{|u,r|r.class==E}}
|
90
|
+
|
91
|
+
fn 'filter/set',->e,m,r{
|
92
|
+
# filter to RDFs set-members, gone will be:
|
93
|
+
# data about docs containing the data
|
94
|
+
# other fragments in a doc not matching search when indexed per-fragment
|
95
|
+
f = m['#'].do{|c| c[RDFs+'member'].do{|m| m.map &:uri }} || [] # members
|
96
|
+
m.keys.map{|u| m.delete u unless f.member? u}} # trim
|
97
|
+
|
98
|
+
def graphFromFile g={}
|
99
|
+
if !e
|
100
|
+
puts "missing file! "+d
|
101
|
+
return
|
102
|
+
end
|
103
|
+
_ = self
|
104
|
+
triplr = @r.do{|r|
|
105
|
+
r.q['triplr'].do{|t|
|
106
|
+
respond_to?(t) && t }} || :triplrMIME
|
107
|
+
unless ext=='e' # native graph-format
|
108
|
+
_ = E '/E/rdf/' + [triplr,uri].h.dive
|
109
|
+
unless _.e && _.m > m; # up to date?
|
110
|
+
e = {} ; puts "< #{uri}"
|
111
|
+
[:triplrInode,triplr].each{|t| fromStream e, t }
|
112
|
+
_.w e, true
|
113
|
+
end
|
114
|
+
end
|
115
|
+
g.mergeGraph _.r true
|
116
|
+
end
|
117
|
+
|
118
|
+
def graph g={}
|
119
|
+
docs.map{|d|d.graphFromFile g} # tripleStream -> graph
|
120
|
+
g
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
class E
|
3
3
|
|
4
4
|
fn 'set/grep',->e,q,m{
|
5
|
-
q['i'] ||= true #
|
5
|
+
q['i'] ||= true # case-insensitive by default
|
6
6
|
q['q'].do{|query|
|
7
7
|
[e,e.pathSegment].compact.select(&:e).map{|e|
|
8
|
-
grep = "grep -rl#{q.has_key?('i')
|
8
|
+
grep = "grep -rl#{q.has_key?('i') ? 'i' : ''} #{query.sh} #{e.sh}"
|
9
9
|
`#{grep}`}.map{|r|r.lines.to_a.map{|r|r.chomp.unpathFs}}.flatten}}
|
10
10
|
|
11
11
|
fn 'view/grep',->d,e{
|
@@ -1,10 +1,13 @@
|
|
1
1
|
#watch __FILE__
|
2
2
|
class E
|
3
|
-
|
3
|
+
|
4
|
+
# adaptor for ruby text-search-engine & column-store
|
4
5
|
# http://groonga.org/ http://ranguba.org/
|
5
6
|
|
6
7
|
# query
|
7
8
|
fn 'protograph/roonga',->d,e,m{
|
9
|
+
|
10
|
+
# groonga engine
|
8
11
|
ga = E.groonga
|
9
12
|
|
10
13
|
# search expression
|
@@ -13,38 +16,43 @@ class E
|
|
13
16
|
# context
|
14
17
|
g = e["context"] || d.env['SERVER_NAME']
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
19
|
+
begin
|
20
|
+
# execute
|
21
|
+
r = (q && !q.empty?) ? ga.select{|r|(r['graph'] == g) & r["content"].match(q)} : # expression if exists
|
22
|
+
ga.select{|r| r['graph'] == g} # ordered set (index date-range)
|
23
|
+
|
24
|
+
# offset, size
|
25
|
+
start = e['start'].do{|c| c.to_i.max(r.size - 1).min 0 } || 0
|
26
|
+
c = (e['c']||e['count']).do{|c|c.to_i.max(10000).min(0)} || 8
|
27
|
+
|
28
|
+
# are further results traversible?
|
29
|
+
down = r.size > start+c
|
30
|
+
up = !(start<=0)
|
31
|
+
|
32
|
+
# sort results
|
33
|
+
r = r.sort(e.has_key?('score') ? [["_score"]] : [["time", "descending"]],:offset => start,:limit => c)
|
34
|
+
|
35
|
+
# results -> graph
|
36
|
+
r = r.map{|r| r['.uri'].E }
|
37
|
+
(r.map &:docs).flatten.uniq.map{|r| m[r.uri] = r.env e}
|
38
|
+
|
39
|
+
m['#'] = {'uri' => '#',
|
40
|
+
RDFs+'member' => r,
|
41
|
+
Type=>E[HTTP+'Response']}
|
42
|
+
m['#'][Prev]={'uri' => '/search' + {'q' => q, 'start' => start + c, 'c' => c}.qs} if down
|
43
|
+
m['#'][Next]={'uri' => '/search' + {'q' => q, 'start' => start - c, 'c' => c}.qs} if up
|
44
|
+
m['/search'] = {Type => E[Search]}
|
45
|
+
|
46
|
+
rescue Groonga::SyntaxError => x
|
47
|
+
m['/search'] = {Type => E[Search]}
|
48
|
+
m['#'] = {
|
49
|
+
Type => E[COGS+'Exception'],
|
50
|
+
Title => "bad expression",
|
51
|
+
Content => CGI.escapeHTML(x.message)}
|
52
|
+
e['nocache']=true
|
53
|
+
end
|
54
|
+
|
55
|
+
F['docsID'][m,e]}
|
48
56
|
|
49
57
|
def E.groonga
|
50
58
|
@groonga ||= (require 'groonga'
|
@@ -1,7 +1,11 @@
|
|
1
1
|
#watch __FILE__
|
2
2
|
class E
|
3
3
|
|
4
|
-
fn 'view/histogram',->
|
4
|
+
fn 'view/histogram',->m,e{
|
5
|
+
e.q['a'].do{|a|Fn 'histogram/main',m,e} ||
|
6
|
+
(Fn 'view/facetSelect',m,e)}
|
7
|
+
|
8
|
+
fn 'histogram/main',->d,e{
|
5
9
|
|
6
10
|
# a :: attribute to chart
|
7
11
|
a = e.q['a'].do{|e|e.expand} || Date
|
@@ -10,13 +14,13 @@ class E
|
|
10
14
|
n = e.q['bins'].do{|b| b.to_f.max(999.0).min(1)} || 64.0
|
11
15
|
|
12
16
|
# hv :: bin template
|
13
|
-
v = F['view/'+e.q['hv']]
|
17
|
+
v = F['view/'+(e.q['hv']||'title')]
|
14
18
|
|
15
19
|
# construct histogram bins
|
16
|
-
(Fn '
|
20
|
+
(Fn 'histogram/bins',d,a,n).do{|h,m|
|
17
21
|
|
18
22
|
[H.css('/css/hist'),%w{mu hist}.map{|s|H.js('/js/'+s)},
|
19
|
-
(Fn '
|
23
|
+
(Fn 'histogram',h),{style: "width: 100%; height: 5em"},
|
20
24
|
h.map{|b,r|
|
21
25
|
# skip empty bins
|
22
26
|
r.empty? ? ' ' :
|
@@ -27,21 +31,20 @@ class E
|
|
27
31
|
{ class: 'histBin b'+b.to_s,
|
28
32
|
c: [# label bin
|
29
33
|
{_: :h3, c: from + ' → ' + to },
|
30
|
-
# bin
|
31
|
-
|
34
|
+
# bin-scoped view
|
35
|
+
v[r,e]]})}]}}
|
32
36
|
|
33
37
|
F['view/h']=F['view/histogram']
|
34
38
|
|
35
39
|
# Graph, property, numBins -> {bin -> Graph}
|
36
|
-
fn '
|
40
|
+
fn 'histogram/bins',->m,p,nb{
|
37
41
|
h = {}; bw = 0; min = 0; max = 0
|
38
42
|
m.map{|u,r|
|
39
43
|
# attribute accessor
|
40
44
|
r[p]
|
41
45
|
}.flatten.do{|v|
|
42
46
|
# values
|
43
|
-
v = v.
|
44
|
-
( p == Date ? v.to_time : v ).to_f}
|
47
|
+
v = v.map{|v| p == Date ? v.to_time : v }.select{|v| v.respond_to? :to_f}.map &:to_f
|
45
48
|
max = v.max || 0
|
46
49
|
min = v.min || 0
|
47
50
|
width = (max-min).do{|w| w.zero? ? 1 : w}
|
@@ -56,17 +59,21 @@ class E
|
|
56
59
|
# binnable properties
|
57
60
|
r[p].do{|v|
|
58
61
|
v.each{|v|
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
62
|
+
# date handling
|
63
|
+
v = p == Date ? v.to_time : v
|
64
|
+
if v.respond_to? :to_f
|
65
|
+
# bin select
|
66
|
+
b = ((v.to_f - min) / bw).floor
|
67
|
+
# append
|
68
|
+
h[b][u] = r
|
69
|
+
end
|
70
|
+
}}}
|
64
71
|
|
65
72
|
# histogram model
|
66
73
|
[h, {min: min, max: max, bw: bw}] }
|
67
74
|
|
68
|
-
fn '
|
69
|
-
scale = 255 / h.map{|b,r|r.keys.size}.max.do{|m|m.zero? ? 1 : m}.to_f
|
75
|
+
fn 'histogram',->h{
|
76
|
+
scale = 255 / h.map{|b,r|r.keys.size}.max.do{|m|m.zero? ? 1 : m}.do{|m|m.respond_to?(:to_f) ? m.to_f : 1}
|
70
77
|
bins = h.keys.sort
|
71
78
|
['<table class=histogram><tr>',
|
72
79
|
bins.map{|b|
|
data/infod/html.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
#watch __FILE__
|
2
|
+
|
3
|
+
# Ruby to HTML
|
4
|
+
def H _
|
5
|
+
case _
|
6
|
+
when Hash
|
7
|
+
'<'+(_[:_]||:div).to_s+(_.keys-[:_,:c]).map{|a|
|
8
|
+
' '+a.to_s+'='+"'"+_[a].to_s.chars.map{|c|{"'"=>'%27','>'=>'%3E','<'=>'%3C'}[c]||c}.join+"'"}.join+'>'+
|
9
|
+
(_[:c] ? (H _[:c]) : '')+
|
10
|
+
(_[:_] == :link ? '' : ('</'+(_[:_]||:div).to_s+'>'))
|
11
|
+
when Array
|
12
|
+
_.map{|n|H n}.join
|
13
|
+
else
|
14
|
+
_.to_s if _
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class H
|
19
|
+
|
20
|
+
def H.[] h; H h end
|
21
|
+
|
22
|
+
def H.js a,inline=false
|
23
|
+
p=a+'.js'
|
24
|
+
inline ? {_: :script, c: p.E.r} :
|
25
|
+
{_: :script, type: "text/javascript", src: p}
|
26
|
+
end
|
27
|
+
|
28
|
+
def H.once e,n,*h
|
29
|
+
return if e[n]
|
30
|
+
e[n]=true
|
31
|
+
h
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Array
|
36
|
+
def html v=nil
|
37
|
+
map{|e|e.html v}.join ' '
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Object
|
42
|
+
def html *a
|
43
|
+
name = self.class
|
44
|
+
href = "https://duckduckgo.com/?q=ruby+#{name}"
|
45
|
+
"<a href=#{href}><b>#{name}</b></a>"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class String
|
50
|
+
def br
|
51
|
+
gsub(/\n/,"<br>\n")
|
52
|
+
end
|
53
|
+
def href name=nil
|
54
|
+
'<a href="'+self+'">' + (name||abbrURI) + '</a>'
|
55
|
+
end
|
56
|
+
def abbrURI
|
57
|
+
sub /(?<scheme>[a-z]+:\/\/)?(?<abbr>.*?)(?<frag>[^#\/]*)$/,
|
58
|
+
'<span class="abbr"><span class="scheme">\k<scheme></span>\k<abbr></span><span class="frag">\k<frag></span>'
|
59
|
+
end
|
60
|
+
def html e=nil
|
61
|
+
# CGI.escapeHTML self
|
62
|
+
self
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Fixnum
|
67
|
+
def html e=nil; H({_: :input, type: :number, value: to_s}) end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Float
|
71
|
+
def html e=nil; H({_: :input, type: :number, value: to_s}) end
|
72
|
+
end
|
73
|
+
|
74
|
+
class TrueClass
|
75
|
+
def html e=nil; H({_: :input, type: :checkbox, title: :True, checked: :checked}) end
|
76
|
+
end
|
77
|
+
|
78
|
+
class FalseClass
|
79
|
+
def html e=nil; H({_: :input, type: :checkbox, title: :False}) end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Hash
|
83
|
+
def html e={'SERVER_NAME'=>'localhost'}, key=true
|
84
|
+
(keys.size == 1 && has_key?('uri')) ? url.href :
|
85
|
+
H({_: :table, class: :html, c:
|
86
|
+
map{|k,v|
|
87
|
+
{_: :tr, property: k, c:
|
88
|
+
[({_: :td,
|
89
|
+
c: [{_: :a, name: k, href: (k == 'uri' ? v : k), c: k.to_s.abbrURI}], class: :key} if key),
|
90
|
+
{_: :td,
|
91
|
+
c: (case k
|
92
|
+
when E::Content
|
93
|
+
v
|
94
|
+
when 'uri'
|
95
|
+
u = v.E
|
96
|
+
{_: :a, id: u, href: u.url, c: v}
|
97
|
+
else
|
98
|
+
v.html e
|
99
|
+
end), class: :val}]}}})
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class E
|
104
|
+
|
105
|
+
def html *a
|
106
|
+
url.href
|
107
|
+
end
|
108
|
+
|
109
|
+
fn 'view',->d,e{
|
110
|
+
d.values.sort_by{|r| r[Date].do{|d| d[0].to_s} || ''}.reverse.
|
111
|
+
map{|r| Fn 'view/select',r,e }}
|
112
|
+
|
113
|
+
fn 'view/base',->d,e,k=true{
|
114
|
+
[H.once(e,'base',H.css('/css/html')),
|
115
|
+
d.values.map{|v|v.html e,k}]}
|
116
|
+
|
117
|
+
fn 'view/select',->r,e{
|
118
|
+
graph = {r.uri => r}
|
119
|
+
view = F['view/base']
|
120
|
+
# find types, skipping malformed/missing info
|
121
|
+
if r.class == Hash
|
122
|
+
(r[Type].class==Array ? r[Type] : [r[Type]]).do{|types|
|
123
|
+
views = types.map{|t|
|
124
|
+
# discard non-URIs
|
125
|
+
t.uri if t.respond_to? :uri}.
|
126
|
+
compact.map{|t|
|
127
|
+
subtype = t
|
128
|
+
type = subtype.split(/\//)[-2]
|
129
|
+
[F['view/' + subtype],
|
130
|
+
(F['view/' + type] if type)]}.
|
131
|
+
flatten.compact
|
132
|
+
view = views[0] unless views.empty?}
|
133
|
+
end
|
134
|
+
view[graph,e]}
|
135
|
+
|
136
|
+
# multiple views (comma-separated)
|
137
|
+
fn 'view/multi',->d,e{
|
138
|
+
e.q['views'].do{|vs|
|
139
|
+
vs.split(',').map{|v|
|
140
|
+
F['view/'+v].do{|f|f[d,e]}}}}
|
141
|
+
|
142
|
+
# enumerate available views
|
143
|
+
fn 'view/?',->d,e{
|
144
|
+
F.keys.grep(/^view\/(?!application|text\/x-)/).map{|v|
|
145
|
+
v = v[5..-1] # eat selector
|
146
|
+
[{_: :a, href: e['REQUEST_PATH']+e.q.merge({'view'=>v}).qs, c: v},"<br>\n"]}}
|
147
|
+
|
148
|
+
def triplrBlob
|
149
|
+
glob.select(&:f).do{|f|f.map{|r|
|
150
|
+
yield r.uri,Type,E('blob')
|
151
|
+
yield r.uri,Content,r.r}} end
|
152
|
+
|
153
|
+
def triplrHref enc=nil
|
154
|
+
yield uri,Content,(f && read).do{|r|enc ? r.force_encoding(enc).to_utf8 : r}.hrefs
|
155
|
+
end
|
156
|
+
|
157
|
+
def contentURIresolve *f
|
158
|
+
send(*f){|s,p,o|
|
159
|
+
yield s, p, p == Content ?
|
160
|
+
(Nokogiri::HTML.parse o).do{|o|
|
161
|
+
o.css('a').map{|a|
|
162
|
+
if a.has_attribute? 'href'
|
163
|
+
(a.set_attribute 'href', (URI.join s, (a.attr 'href'))) rescue nil
|
164
|
+
end}
|
165
|
+
o.to_s} : o}
|
166
|
+
end
|
167
|
+
|
168
|
+
fn Render+'text/html',->d,e{ u = d['#']||{}
|
169
|
+
titles = d.map{|u,r| r[Title] if r.class==Hash }.flatten.compact
|
170
|
+
view = F['view/'+e.q['view'].to_s] || F['view']
|
171
|
+
H ['<!DOCTYPE html>',{_: :html,
|
172
|
+
c: [{_: :head, c: ['<meta charset="utf-8" />',
|
173
|
+
{_: :title, c: titles.size==1 ? titles[0] : e.uri},
|
174
|
+
{_: :link, rel: :icon, href:'/css/misc/favicon.ico'},
|
175
|
+
u[Next].do{|n|{_: :link, rel: :next, href: n.uri}},
|
176
|
+
u[Prev].do{|p|{_: :link, rel: :prev, href: p.uri}}]},
|
177
|
+
{_: :body, c: view[d,e]}]}]}
|
178
|
+
|
179
|
+
# property-selector toolbar - utilizes RDFa view
|
180
|
+
fn 'view/p',->d,e{
|
181
|
+
#TODO fragmentURI scheme for selection-state
|
182
|
+
[H.once(e,'property.toolbar',H.once(e,'p',(H.once e,:mu,H.js('/js/mu')),
|
183
|
+
H.js('/js/p'),
|
184
|
+
H.css('/css/table')),
|
185
|
+
{_: :a, href: '#', c: '-', id: :hideP},
|
186
|
+
{_: :a, href: '#', c: '+', id: :showP},
|
187
|
+
{_: :span, id: 'properties',
|
188
|
+
c: E.graphProperties(d).map{|k|
|
189
|
+
{_: :a, class: :n, href: k, c: k.label+' '}}},
|
190
|
+
{_: :style, id: :pS},
|
191
|
+
{_: :style, id: :lS}),
|
192
|
+
(Fn 'view/'+(e.q['pv']||'table'),d,e)]}
|
193
|
+
|
194
|
+
# table-cell placement on sparse matrix of rows/columns
|
195
|
+
# cal.rb contains an example usage
|
196
|
+
fn 'view/t',->d,e,l=nil,a=nil{
|
197
|
+
layout = e.q['table'] || l
|
198
|
+
if layout
|
199
|
+
[H.once(e,'table',H.css('/css/table')),
|
200
|
+
{_: :table, c:
|
201
|
+
{_: :tbody, c: (Fn 'table/'+layout,d).do{|t|
|
202
|
+
rx = t.keys.max
|
203
|
+
rm = t.keys.min
|
204
|
+
c = t.values.map(&:keys)
|
205
|
+
cm = c.map(&:min).min
|
206
|
+
cx = c.map(&:max).max
|
207
|
+
rm && rx && (rm..rx).map{|r|
|
208
|
+
{_: :tr, c:
|
209
|
+
t[r].do{|r|
|
210
|
+
(cm..cx).map{|c|
|
211
|
+
r[c].do{|c|
|
212
|
+
{_: :td, class: :cell, c:(Fn 'view/'+(a||e.q['cellview']||'title'),c,e)}
|
213
|
+
}||{_: :td}}}}} || ''
|
214
|
+
}}}]
|
215
|
+
else
|
216
|
+
"table= layout arg required"
|
217
|
+
end}
|
218
|
+
|
219
|
+
fn 'view/table',->i,e{[H.css('/css/table'),(Fn 'table',i.values,e)]}
|
220
|
+
|
221
|
+
fn 'table',->es,q=nil{ p = {}
|
222
|
+
es.map{|e|e.respond_to?(:keys) &&
|
223
|
+
e.keys.map{|k|p[k]=true}}
|
224
|
+
keys = p.keys
|
225
|
+
keys.empty? ? es.html :
|
226
|
+
H({_: :table,:class => :tab,
|
227
|
+
c: [{_: :tr, c: keys.map{|k|{_: :th, class: :label, property: k, c: k.abbrURI}}},
|
228
|
+
*es.map{|e|
|
229
|
+
{_: :tr, about: e.uri, c: keys.map{|k| {_: :td, property: k, c: k=='uri' ? e.E.html : e[k].html}}}}]})}
|
230
|
+
|
231
|
+
end
|