infod 0.0.3.3 → 0.0.3.4
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.
- checksums.yaml +4 -4
- data/infod.rb +2 -1
- data/infod/404.rb +6 -7
- data/infod/500.rb +4 -40
- data/infod/GET.rb +15 -16
- data/infod/HTTP.rb +5 -1
- data/infod/{mime.rb → MIME.rb} +0 -2
- data/infod/POST.rb +29 -28
- data/infod/SPARQL.rb +6 -0
- data/infod/WebID.rb +3 -0
- data/infod/blog.rb +35 -13
- data/infod/constants.rb +1 -3
- data/infod/edit.rb +10 -8
- data/infod/facets.rb +2 -2
- data/infod/feed.rb +149 -108
- data/infod/fs.rb +33 -6
- data/infod/graph.rb +50 -46
- data/infod/groonga.rb +8 -9
- data/infod/html.rb +34 -67
- data/infod/image.rb +2 -2
- data/infod/infod.rb +2 -1
- data/infod/lambda.rb +2 -6
- data/infod/mail.rb +29 -24
- data/infod/man.rb +1 -1
- data/infod/microblog.rb +10 -1
- data/infod/names.rb +9 -20
- data/infod/rdf.rb +5 -4
- data/infod/schema.rb +4 -16
- data/infod/text.rb +1 -1
- data/infod/time.rb +1 -15
- metadata +35 -5
data/infod/feed.rb
CHANGED
@@ -1,126 +1,169 @@
|
|
1
1
|
#watch __FILE__
|
2
|
-
|
3
|
-
module FeedParse
|
4
|
-
|
5
|
-
def html; CGI.unescapeHTML self end
|
6
|
-
def cdata; sub /^\s*<\!\[CDATA\[(.*?)\]\]>\s*$/m,'\1'end
|
7
|
-
def guess; send (case self
|
8
|
-
when /^\s*<\!/m
|
9
|
-
:cdata
|
10
|
-
when /</m
|
11
|
-
:id
|
12
|
-
else
|
13
|
-
:html
|
14
|
-
end) end
|
15
|
-
|
16
|
-
def parse
|
17
|
-
x={} # populate XMLns prefix table
|
18
|
-
match(/<(rdf|rss|feed)([^>]+)/i)[2].scan(/xmlns:?([a-z]+)?=["']?([^'">\s]+)/){|m|x[m[0]]=m[1]}
|
19
|
-
|
20
|
-
# scan for resources
|
21
|
-
scan(%r{<(?<ns>rss:|atom:)?(?<tag>item|entry)(?<attrs>[\s][^>]*)?>(?<inner>.*?)</\k<ns>?\k<tag>>}mi){|m|
|
22
|
-
# identifier search
|
23
|
-
attrs = m[2]
|
24
|
-
inner = m[3]
|
25
|
-
u = attrs.do{|a| # RDF-style identifier (RSS 1.0)
|
26
|
-
a.match(/about=["']?([^'">\s]+)/).do{|s|
|
27
|
-
s[1] }} ||
|
28
|
-
(inner.match(/<link>([^<]+)/) || # <link> child-node or href attribute
|
29
|
-
inner.match(/<link[^>]+rel=["']?alternate["']?[^>]+href=["']?([^'">\s]+)/) ||
|
30
|
-
inner.match(/<(?:gu)?id[^>]*>([^<]+)/)).do{|s| s[1]} # <id> child
|
31
|
-
|
32
|
-
if u
|
33
|
-
if !u.match /^http/
|
34
|
-
puts "no HTTP URIs found #{u}"
|
35
|
-
u = '/junk/'+u.gsub('/','.')
|
36
|
-
end
|
37
|
-
yield u, R::Type, (R::SIOCt+'BlogPost').R
|
38
|
-
yield u, R::Type, (R::SIOC+'Post').R
|
39
|
-
|
40
|
-
#links
|
41
|
-
inner.scan(%r{<(link|enclosure|media)([^>]+)>}mi){|e|
|
42
|
-
e[1].match(/(href|url|src)=['"]?([^'">\s]+)/).do{|url|
|
43
|
-
yield(u,R::Atom+'/link/'+((r=e[1].match(/rel=['"]?([^'">\s]+)/)) ? r[1] : e[0]), url[2].R)}}
|
44
|
-
|
45
|
-
#elements
|
46
|
-
inner.scan(%r{<([a-z]+:)?([a-z]+)([\s][^>]*)?>(.*?)</\1?\2>}mi){|e|
|
47
|
-
yield u, # s
|
48
|
-
(x[e[0]&&e[0].chop]||R::RSS)+e[1], # p
|
49
|
-
e[3].extend(FeedParse).guess.do{|o|# o
|
50
|
-
o.match(/\A(\/|http)[\S]+\Z/) ? o.R : R::F['cleanHTML'][o]
|
51
|
-
}}
|
52
|
-
else
|
53
|
-
puts "no post-identifiers found #{u}"
|
54
|
-
end
|
55
|
-
}
|
56
|
-
|
57
|
-
nil
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
2
|
class R
|
62
3
|
|
4
|
+
def getFeed h='localhost'; addDocsRDF :format => :feed, :hook => FeedArchiver, :hostname => h end
|
5
|
+
|
63
6
|
Atom = W3+'2005/Atom'
|
64
7
|
RSS = Purl+'rss/1.0/'
|
65
8
|
RSSm = RSS+'modules/'
|
66
|
-
Feed = (R RSS+'channel')
|
67
9
|
|
68
10
|
def listFeeds; (nokogiri.css 'link[rel=alternate]').map{|u|R (URI uri).merge(u.attr :href)} end
|
69
11
|
alias_method :feeds, :listFeeds
|
70
12
|
|
71
|
-
|
72
|
-
|
73
|
-
|
13
|
+
module Feed
|
14
|
+
|
15
|
+
class Format < RDF::Format
|
16
|
+
content_type 'application/atom+xml', :extension => :atom
|
17
|
+
content_encoding 'utf-8'
|
18
|
+
reader { R::Feed::Reader }
|
19
|
+
end
|
20
|
+
|
21
|
+
class Reader < RDF::Reader
|
22
|
+
|
23
|
+
format Format
|
24
|
+
|
25
|
+
def initialize(input = $stdin, options = {}, &block)
|
26
|
+
@doc = (input.respond_to?(:read) ? input : StringIO.new(input.to_s)).read
|
27
|
+
if block_given?
|
28
|
+
case block.arity
|
29
|
+
when 0 then instance_eval(&block)
|
30
|
+
else block.call(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def each_statement &fn
|
37
|
+
dateNormalize(:resolveURIs,:mapPredicates,:rawFeedTriples){|s,p,o|
|
38
|
+
fn.call RDF::Statement.new(s.R, p.R,
|
39
|
+
o.class == R ? o : (l = RDF::Literal o
|
40
|
+
l.datatype=RDF.XMLLiteral if p == Content
|
41
|
+
l),
|
42
|
+
:context => s.R.docBase)} end
|
43
|
+
|
44
|
+
def each_triple &block
|
45
|
+
each_statement{|s| block.call *s.to_triple}
|
46
|
+
end
|
74
47
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
48
|
+
def resolveURIs *f
|
49
|
+
send(*f){|s,p,o|
|
50
|
+
yield s, p, p == Content ?
|
51
|
+
(Nokogiri::HTML.fragment o).do{|o|
|
52
|
+
o.css('a').map{|a|
|
53
|
+
if a.has_attribute? 'href'
|
54
|
+
(a.set_attribute 'href', (URI.join s, (a.attr 'href'))) rescue nil
|
55
|
+
end}
|
56
|
+
o.to_s} : o}
|
57
|
+
end
|
84
58
|
|
85
|
-
|
59
|
+
def mapPredicates *f
|
60
|
+
send(*f){|s,p,o|
|
61
|
+
yield s,
|
62
|
+
{ Purl+'dc/elements/1.1/creator' => Creator,
|
63
|
+
Purl+'dc/elements/1.1/subject' => SIOC+'subject',
|
64
|
+
Atom+'author' => Creator,
|
65
|
+
RSS+'description' => Content,
|
66
|
+
RSS+'encoded' => Content,
|
67
|
+
RSSm+'content/encoded' => Content,
|
68
|
+
Atom+'content' => Content,
|
69
|
+
RSS+'title' => Title,
|
70
|
+
Atom+'title' => Title,
|
71
|
+
}[p]||p,
|
72
|
+
o }
|
73
|
+
end
|
86
74
|
|
87
|
-
|
88
|
-
|
75
|
+
def rawFeedTriples
|
76
|
+
x={} #XMLns prefix table
|
77
|
+
@doc.match(/<(rdf|rss|feed)([^>]+)/i)[2].scan(/xmlns:?([a-z]+)?=["']?([^'">\s]+)/){|m|x[m[0]]=m[1]}
|
78
|
+
|
79
|
+
# resources
|
80
|
+
@doc.scan(%r{<(?<ns>rss:|atom:)?(?<tag>item|entry)(?<attrs>[\s][^>]*)?>(?<inner>.*?)</\k<ns>?\k<tag>>}mi){|m|
|
81
|
+
# identifier search
|
82
|
+
attrs = m[2]
|
83
|
+
inner = m[3]
|
84
|
+
u = attrs.do{|a| # RDF-style identifier (RSS 1.0)
|
85
|
+
a.match(/about=["']?([^'">\s]+)/).do{|s|
|
86
|
+
s[1] }} ||
|
87
|
+
(inner.match(/<link>([^<]+)/) || # <link> child-node or href attribute
|
88
|
+
inner.match(/<link[^>]+rel=["']?alternate["']?[^>]+href=["']?([^'">\s]+)/) ||
|
89
|
+
inner.match(/<(?:gu)?id[^>]*>([^<]+)/)).do{|s| s[1]} # <id> child
|
90
|
+
|
91
|
+
if u
|
92
|
+
if !u.match /^http/
|
93
|
+
puts "no HTTP URIs found #{u}"
|
94
|
+
u = '/junk/'+u.gsub('/','.')
|
95
|
+
end
|
96
|
+
yield u, R::Type, (R::SIOCt+'BlogPost').R
|
97
|
+
yield u, R::Type, (R::SIOC+'Post').R
|
98
|
+
|
99
|
+
#links
|
100
|
+
inner.scan(%r{<(link|enclosure|media)([^>]+)>}mi){|e|
|
101
|
+
e[1].match(/(href|url|src)=['"]?([^'">\s]+)/).do{|url|
|
102
|
+
yield(u,R::Atom+'/link/'+((r=e[1].match(/rel=['"]?([^'">\s]+)/)) ? r[1] : e[0]), url[2].R)}}
|
103
|
+
|
104
|
+
#elements
|
105
|
+
inner.scan(%r{<([a-z]+:)?([a-z]+)([\s][^>]*)?>(.*?)</\1?\2>}mi){|e|
|
106
|
+
yield u, # s
|
107
|
+
(x[e[0]&&e[0].chop]||R::RSS)+e[1], # p
|
108
|
+
e[3].extend(SniffContent).sniff.do{|o|# o
|
109
|
+
o.match(/\A(\/|http)[\S]+\Z/) ? o.R : R::F['cleanHTML'][o]
|
110
|
+
}}
|
111
|
+
else
|
112
|
+
puts "no post-identifiers found #{u}"
|
113
|
+
end
|
114
|
+
}
|
89
115
|
|
90
|
-
|
91
|
-
|
92
|
-
|
116
|
+
end
|
117
|
+
|
118
|
+
def dateNormalize *f
|
119
|
+
send(*f){|s,p,o|
|
120
|
+
yield *({'CreationDate' => true,
|
121
|
+
'Date' => true,
|
122
|
+
RSS+'pubDate' => true,
|
123
|
+
Date => true,
|
124
|
+
Purl+'dc/elements/1.1/date' => true,
|
125
|
+
Atom+'published' => true,
|
126
|
+
Atom+'updated' => true
|
127
|
+
}[p] ?
|
128
|
+
[s,Date,Time.parse(o).utc.iso8601] : [s,p,o])}
|
129
|
+
end
|
93
130
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
131
|
+
end
|
132
|
+
|
133
|
+
module SniffContent
|
134
|
+
|
135
|
+
def sniff
|
136
|
+
send (case self
|
137
|
+
when /^\s*<\!/m
|
138
|
+
:cdata
|
139
|
+
when /</m
|
140
|
+
:id
|
141
|
+
else
|
142
|
+
:html
|
143
|
+
end)
|
144
|
+
end
|
145
|
+
|
146
|
+
def html
|
147
|
+
CGI.unescapeHTML self
|
148
|
+
end
|
149
|
+
|
150
|
+
def cdata
|
151
|
+
sub /^\s*<\!\[CDATA\[(.*?)\]\]>\s*$/m,'\1'
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
103
155
|
|
104
|
-
def triplrFeedRaw &f
|
105
|
-
read.to_utf8.extend(FeedParse).parse &f
|
106
|
-
rescue Exception => e
|
107
|
-
puts [uri,e,e.backtrace[0]].join ' '
|
108
156
|
end
|
109
157
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
Atom+'content' => Content,
|
120
|
-
RSS+'title' => Title,
|
121
|
-
Atom+'title' => Title,
|
122
|
-
}[p]||p,
|
123
|
-
o } end
|
158
|
+
FeedStop = /\b(at|blog|com(ments)?|html|info|org|photo|p|post|r|status|tag|twitter|wordpress|www|1999|2005)\b/
|
159
|
+
FeedArchiver = -> doc, graph, host {
|
160
|
+
doc.roonga host
|
161
|
+
graph.query(RDF::Query::Pattern.new(:s,R[R::Date],:o)).first_value.do{|t|
|
162
|
+
time = t.gsub(/[-T]/,'/').sub /(.00.00|Z)$/, '' # trim normalized timezones
|
163
|
+
base = (graph.name.to_s.sub(/http:\/\//,'.').gsub(/\W/,'..').gsub(FeedStop,'').sub(/\d{12,}/,'')+'.').gsub /\.+/,'.'
|
164
|
+
doc.ln R["http://#{host}/news/#{time}#{base}n3"]}}
|
165
|
+
|
166
|
+
GREP_DIRS.push /^\/news\/\d{4}/
|
124
167
|
|
125
168
|
fn Render+'application/atom+xml',->d,e{
|
126
169
|
id = 'http://' + e['SERVER_NAME'] + (CGI.escapeHTML e['REQUEST_URI'])
|
@@ -140,8 +183,6 @@ class R
|
|
140
183
|
d[Creator].do{|c|{_: :author, c: c[0]}},
|
141
184
|
{_: :content, type: :xhtml,
|
142
185
|
c: {xmlns:"http://www.w3.org/1999/xhtml",
|
143
|
-
c: d[Content]}}].cr
|
144
|
-
}}.cr
|
145
|
-
]}])}
|
186
|
+
c: d[Content]}}].cr}}.cr]}])}
|
146
187
|
|
147
188
|
end
|
data/infod/fs.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
#watch __FILE__
|
2
2
|
class R
|
3
|
+
=begin
|
4
|
+
a RDF::URI has a path defined in names.rb, so do other concepts like a full "triple" - here we've built a RDF store using them
|
5
|
+
since this results in one path per-triple, it's mainly used for current resource-state and "backlink" (reverse order) indexing
|
6
|
+
=end
|
3
7
|
|
4
8
|
def [] p; predicate p end
|
5
9
|
def []= p,o
|
@@ -11,6 +15,14 @@ class R
|
|
11
15
|
end
|
12
16
|
end
|
13
17
|
|
18
|
+
def predicatePath p, s = true
|
19
|
+
container.as s ? p.R.shorten : p
|
20
|
+
end
|
21
|
+
|
22
|
+
def predicates
|
23
|
+
container.c.map{|c|c.base.expand.R}
|
24
|
+
end
|
25
|
+
|
14
26
|
def predicate p, short = true
|
15
27
|
p = predicatePath p, short
|
16
28
|
p.node.take.map{|n|
|
@@ -52,7 +64,9 @@ class R
|
|
52
64
|
end
|
53
65
|
end
|
54
66
|
|
55
|
-
def unsetFs p,o
|
67
|
+
def unsetFs p,o
|
68
|
+
setFs p,o,true
|
69
|
+
end
|
56
70
|
|
57
71
|
def triplrInode
|
58
72
|
if d?
|
@@ -71,17 +85,23 @@ class R
|
|
71
85
|
def ln t, y=:link
|
72
86
|
t = t.R
|
73
87
|
t = t.uri[0..-2].R if t.uri[-1] == '/'
|
74
|
-
if !t.e
|
88
|
+
if !t.e
|
75
89
|
t.dirname.mk
|
76
90
|
FileUtils.send y, node, t.node
|
77
91
|
end
|
78
92
|
end
|
79
93
|
|
94
|
+
def delete; node.deleteNode if e; self end
|
95
|
+
def exist?; node.exist? end
|
96
|
+
def file?; node.file? end
|
80
97
|
def ln_s t; ln t, :symlink end
|
98
|
+
def mk; e || FileUtils.mkdir_p(d); self end
|
99
|
+
def mtime; node.stat.mtime if e end
|
100
|
+
def touch; FileUtils.touch node; self end
|
81
101
|
|
82
|
-
def
|
102
|
+
def read p=false
|
83
103
|
if f
|
84
|
-
p ? (JSON.parse
|
104
|
+
p ? (JSON.parse File.open(d).read) : File.open(d).read
|
85
105
|
else
|
86
106
|
nil
|
87
107
|
end
|
@@ -89,12 +109,19 @@ class R
|
|
89
109
|
puts e
|
90
110
|
end
|
91
111
|
|
92
|
-
def
|
112
|
+
def write o,s=false
|
93
113
|
dirname.mk
|
94
|
-
|
114
|
+
File.open(d,'w'){|f|
|
115
|
+
f << (s ? o.to_json : o)}
|
95
116
|
self
|
96
117
|
end
|
97
118
|
|
119
|
+
alias_method :e, :exist?
|
120
|
+
alias_method :f, :file?
|
121
|
+
alias_method :m, :mtime
|
122
|
+
alias_method :r, :read
|
123
|
+
alias_method :w, :write
|
124
|
+
|
98
125
|
end
|
99
126
|
|
100
127
|
class Pathname
|
data/infod/graph.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
#watch __FILE__
|
2
2
|
class R
|
3
3
|
=begin
|
4
|
-
graph construction
|
4
|
+
two-pass graph construction aka Protograph and Graph
|
5
5
|
|
6
|
-
|
6
|
+
Protograph = ETag. ideal fingerprint sources include filestats, mtime checks (#m), SHA160 hashes of in-RAM entities - <http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-25#section-2.3>
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
triple streams - a source function yields triples up to the caller as it finds them,
|
11
|
-
a function providing a block (consumes yielded values) is a sink, both is a filter
|
12
|
-
these can be stacked into pipelines. see the data-massaging stream-processing in feed.rb
|
8
|
+
a tripleStream function constructing a block (consumes yielded values) is a sink, inverse is a source, both a filter
|
9
|
+
these can be stacked into pipelines, as in feed.rb
|
13
10
|
|
14
11
|
=end
|
15
12
|
|
@@ -21,33 +18,7 @@ class R
|
|
21
18
|
end; m
|
22
19
|
end
|
23
20
|
|
24
|
-
|
25
|
-
* stream triples into graph (memory)
|
26
|
-
* import missing resources to store (fs)
|
27
|
-
* behave as normal triplr to caller, with
|
28
|
-
side-effect of import/indexing to knowledgebase
|
29
|
-
=end
|
30
|
-
def addDocs triplr, host, p=nil, hook=nil, &b
|
31
|
-
graph = fromStream({},triplr)
|
32
|
-
docs = {}
|
33
|
-
graph.map{|u,r|
|
34
|
-
e = u.R # resource
|
35
|
-
doc = e.ef # doc
|
36
|
-
doc.e || # exists - we're nondestructive here
|
37
|
-
(docs[doc.uri] ||= {} # init doc-graph
|
38
|
-
docs[doc.uri][u] = r # add to graph
|
39
|
-
p && p.map{|p| # index predicate
|
40
|
-
r[p].do{|v|v.map{|o| # values exist?
|
41
|
-
e.index p,o}}})} # index triple
|
42
|
-
docs.map{|d,g| # resources in docs
|
43
|
-
d = d.R; puts "<#{d.docBase}>"
|
44
|
-
d.w g,true # write
|
45
|
-
hook[d,g,host] if hook} # insert-hook
|
46
|
-
graph.triples &b if b # emit triples
|
47
|
-
self
|
48
|
-
end
|
49
|
-
|
50
|
-
# default protograph - identity < lazy-expandable resource-thunks
|
21
|
+
# default protograph - identity + lazy-expandable resource-thunks
|
51
22
|
# Resource, Query, Graph -> graphID
|
52
23
|
fn 'protograph/',->e,q,g{
|
53
24
|
g['#'] = {'uri' => '#'}
|
@@ -76,14 +47,9 @@ class R
|
|
76
47
|
s }
|
77
48
|
|
78
49
|
# fs-derived ID for a resource-set
|
79
|
-
fn 'docsID',->g,q{
|
80
|
-
|
81
|
-
|
82
|
-
[u, r.respond_to?(:m) && r.m]}].h }
|
83
|
-
|
84
|
-
# default graph (filesystem store)
|
85
|
-
# to use a different default-graph function (w/o patching here, or querystring param), define a GET handler on / (or a subdir),
|
86
|
-
# update configuration such as q['graph'] = 'hexastore' and return false or call #response..
|
50
|
+
fn 'docsID',->g,q{g.sort.map{|u,r|[u, r.respond_to?(:m) && r.m]}.h }
|
51
|
+
|
52
|
+
# default graph
|
87
53
|
fn 'graph/',->e,q,m{
|
88
54
|
# force thunks
|
89
55
|
m.values.map{|r|(r.env e.env).graphFromFile m if r.class == R }
|
@@ -120,17 +86,56 @@ class R
|
|
120
86
|
].flatten.compact
|
121
87
|
end
|
122
88
|
|
89
|
+
|
90
|
+
# GET Resource -> Graph
|
91
|
+
# missing resources -> local store
|
92
|
+
|
93
|
+
# JSON + Hash variant
|
94
|
+
def addDocs triplr, host, p=nil, hook=nil, &b
|
95
|
+
graph = fromStream({},triplr)
|
96
|
+
docs = {}
|
97
|
+
graph.map{|u,r|
|
98
|
+
e = u.R # resource
|
99
|
+
doc = e.ef # doc
|
100
|
+
doc.e || # exists - we're nondestructive here
|
101
|
+
(docs[doc.uri] ||= {} # init doc-graph
|
102
|
+
docs[doc.uri][u] = r # add to graph
|
103
|
+
p && p.map{|p| # index predicate
|
104
|
+
r[p].do{|v|v.map{|o| # values exist?
|
105
|
+
e.index p,o}}})} # index triple
|
106
|
+
docs.map{|d,g| # resources in docs
|
107
|
+
d = d.R; puts "<#{d.docBase}>"
|
108
|
+
d.w g,true # write
|
109
|
+
hook[d,g,host] if hook} # insert-hook
|
110
|
+
graph.triples &b if b # emit triples
|
111
|
+
self
|
112
|
+
end
|
113
|
+
# RDF::Graph variant
|
114
|
+
def addDocsRDF options = {}
|
115
|
+
g = RDF::Repository.load self, options
|
116
|
+
g.each_graph.map{|graph|
|
117
|
+
if graph.named?
|
118
|
+
doc = graph.name.n3
|
119
|
+
unless doc.e
|
120
|
+
doc.dirname.mk
|
121
|
+
RDF::Writer.open(doc.d){|f|f << graph} ; puts "<#{doc.docBase}> #{graph.count} triples"
|
122
|
+
options[:hook][doc,graph,options[:hostname]] if options[:hook]
|
123
|
+
end
|
124
|
+
end}
|
125
|
+
g
|
126
|
+
end
|
127
|
+
|
123
128
|
def triplrDoc &f; docBase.glob('#*').map{|s| s.triplrResource &f} end
|
124
129
|
|
125
130
|
def triplrResource; predicates.map{|p| self[p].map{|o| yield uri, p.uri, o}} end
|
126
131
|
|
127
132
|
def triplrJSON
|
128
|
-
yield uri, '/application/json', (
|
133
|
+
yield uri, '/application/json', r(true) if e
|
129
134
|
rescue Exception => e
|
130
135
|
end
|
131
136
|
|
132
137
|
def to_json *a
|
133
|
-
|
138
|
+
{'uri' => uri}.to_json *a
|
134
139
|
end
|
135
140
|
|
136
141
|
fn Render+'application/json',->d,_=nil{d.to_json}
|
@@ -160,8 +165,7 @@ class Hash
|
|
160
165
|
def triples &f
|
161
166
|
map{|s,r|
|
162
167
|
r.map{|p,o|
|
163
|
-
o.
|
164
|
-
}
|
168
|
+
o.justArray.map{|o|yield s,p,o} unless p=='uri'} if r.class == Hash}
|
165
169
|
end
|
166
170
|
|
167
171
|
end
|