infod 0.0.3.3 → 0.0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- 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/groonga.rb
CHANGED
@@ -33,7 +33,6 @@ class R
|
|
33
33
|
|
34
34
|
rescue Groonga::SyntaxError => x
|
35
35
|
m['#'] = {Type => R[COGS+'Exception'], Title => "invalid expr", Content => CGI.escapeHTML(x.message)}
|
36
|
-
e['nocache']=true
|
37
36
|
end
|
38
37
|
|
39
38
|
F['docsID'][m,e]}}
|
@@ -66,14 +65,14 @@ class R
|
|
66
65
|
|
67
66
|
# add
|
68
67
|
def roonga graph="global", m = self.graph
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
68
|
+
R.groonga.do{|g|
|
69
|
+
m.map{|u,i|
|
70
|
+
r = g[u] || g.add(u) # create or load entry
|
71
|
+
r.uri = u # update data
|
72
|
+
r.graph = graph.to_s
|
73
|
+
r.content = i.to_s
|
74
|
+
r.time = i[R::Date].do{|t|t[0].to_time}
|
75
|
+
}}
|
77
76
|
self
|
78
77
|
end
|
79
78
|
|
data/infod/html.rb
CHANGED
@@ -40,7 +40,7 @@ end
|
|
40
40
|
class Array
|
41
41
|
def cr; intersperse "\n" end
|
42
42
|
def head; self[0] end
|
43
|
-
def html v=nil
|
43
|
+
def html v=nil; map{|e|e.html v}.join ' ' end
|
44
44
|
def h; join.h end
|
45
45
|
def intersperse i
|
46
46
|
inject([]){|a,b|a << b << i}[0..-2]
|
@@ -63,53 +63,41 @@ class String
|
|
63
63
|
def abbrURI
|
64
64
|
sub /(?<scheme>[a-z]+:\/\/)?(?<abbr>.*?)(?<frag>[^#\/]+)\/?$/,'<span class="abbr"><span class="scheme">\k<scheme></span>\k<abbr></span><span class="frag">\k<frag></span>'
|
65
65
|
end
|
66
|
-
def html e=nil
|
66
|
+
def html e=nil
|
67
67
|
self
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
71
|
class Fixnum
|
72
|
-
def html e=nil
|
72
|
+
def html e=nil; to_s end
|
73
73
|
def max i; i > self ? self : i end
|
74
74
|
def min i; i < self ? self : i end
|
75
75
|
end
|
76
76
|
|
77
77
|
class Float
|
78
|
-
def html e=nil
|
78
|
+
def html e=nil; to_s end
|
79
79
|
def max i; i > self ? self : i end
|
80
80
|
def min i; i < self ? self : i end
|
81
81
|
end
|
82
82
|
|
83
83
|
class TrueClass
|
84
|
-
def html e=nil
|
84
|
+
def html e=nil; H({_: :input, type: :checkbox, title: :True, checked: :checked}) end
|
85
85
|
end
|
86
86
|
|
87
87
|
class FalseClass
|
88
|
-
def html e=nil
|
88
|
+
def html e=nil; H({_: :input, type: :checkbox, title: :False}) end
|
89
89
|
end
|
90
90
|
|
91
|
-
IsBnode = /^_:/
|
92
|
-
|
93
91
|
class Hash
|
94
|
-
def html e={'SERVER_NAME'=>'localhost'}
|
92
|
+
def html e={'SERVER_NAME'=>'localhost'}
|
95
93
|
if keys.size == 1 && has_key?('uri')
|
96
|
-
|
97
|
-
g[uri].do{|r|
|
98
|
-
r.html e,g,key } || uri.href
|
99
|
-
else
|
100
|
-
uri.href
|
101
|
-
end
|
94
|
+
uri.href
|
102
95
|
else
|
103
96
|
H({_: :table, class: :html, c: map{|k,v|
|
104
|
-
|
105
|
-
{_: :
|
106
|
-
|
107
|
-
|
108
|
-
({_: :td, c: [{_: :a, name: k, href: (k == 'uri' ? (v.R.docBase.localURL e)+'?graph=edit' : k), c: k.to_s.abbrURI}], class: :key} if key),
|
109
|
-
{_: :td, c: k == 'uri' ? v.R.do{|u| {_: :a, id: u, href: u.url, c: v}} : v.html(e,g), class: :val},
|
110
|
-
]]}
|
111
|
-
end
|
112
|
-
}})
|
97
|
+
{_: :tr, property: k, c:
|
98
|
+
[k == R::Content ? {_: :td, class: :val, colspan: 2, c: v} :
|
99
|
+
[{_: :td, c: [{_: :a, name: k, href: (k == 'uri' ? (v.R.docBase.localURL e)+'?graph=edit&mono' : k), c: k.to_s.abbrURI}], class: :key},
|
100
|
+
{_: :td, c: k == 'uri' ? v.R.do{|u| {_: :a, id: u, href: u.url, c: v}} : v.html(e), class: :val}]]}}})
|
113
101
|
end
|
114
102
|
end
|
115
103
|
end
|
@@ -121,36 +109,26 @@ class R
|
|
121
109
|
end
|
122
110
|
|
123
111
|
F['view']=->d,e{
|
124
|
-
d.values.
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
[F['view/' + subtype],
|
145
|
-
(F['view/' + type] if type)]}.
|
146
|
-
flatten.compact
|
147
|
-
view = views[0] unless views.empty?}
|
148
|
-
end
|
149
|
-
if !view # default view
|
150
|
-
F['view/base'][graph,e,true,d]
|
151
|
-
else
|
152
|
-
view[graph,e]
|
153
|
-
end}
|
112
|
+
d.values.map{|r|
|
113
|
+
graph = {r.uri => r}
|
114
|
+
view = nil
|
115
|
+
if r.class == Hash
|
116
|
+
r[Type].justArray.do{|types|
|
117
|
+
views = types.map(&:maybeURI).compact.map{|t|
|
118
|
+
subtype = t
|
119
|
+
type = subtype.split(/\//)[-2]
|
120
|
+
[F['view/' + subtype],
|
121
|
+
(F['view/' + type] if type)]}.
|
122
|
+
flatten.compact
|
123
|
+
view = views[0] unless views.empty?}
|
124
|
+
end
|
125
|
+
if !view
|
126
|
+
F['view/base'][graph,e]
|
127
|
+
else
|
128
|
+
view[graph,e]
|
129
|
+
end}}
|
130
|
+
|
131
|
+
F['view/base']=->d,e{[H.once(e,'base',H.css('/css/html')),d.values.map{|v|v.html e}]}
|
154
132
|
|
155
133
|
def triplrBlob
|
156
134
|
glob.select(&:f).do{|f|f.map{|r|
|
@@ -158,10 +136,10 @@ class R
|
|
158
136
|
yield r.uri,Content,r.r}} end
|
159
137
|
|
160
138
|
def triplrHref enc=nil
|
161
|
-
yield uri,Content,(
|
139
|
+
yield uri,Content,open(d).read.do{|r|enc ? r.force_encoding(enc).to_utf8 : r}.hrefs if f
|
162
140
|
end
|
163
141
|
|
164
|
-
def nokogiri; Nokogiri::HTML.parse read end
|
142
|
+
def nokogiri; Nokogiri::HTML.parse (open uri).read end
|
165
143
|
|
166
144
|
F['HTMLbody'] = -> b {
|
167
145
|
b.to_s.split(/<body[^>]*>/)[-1].to_s.split(/<\/body>/)[0] }
|
@@ -182,17 +160,6 @@ class R
|
|
182
160
|
c: {_: :img, src: '/css/misc/cube.png', style: 'height:2em;background-color:white;padding:.54em;border-radius:1em;margin:.2em'}},
|
183
161
|
(H.js '/js/pager'),(H.once e,:mu,(H.js '/js/mu'))]}} # (n)ext (p)rev key binding
|
184
162
|
|
185
|
-
def contentURIresolve *f
|
186
|
-
send(*f){|s,p,o|
|
187
|
-
yield s, p, p == Content ?
|
188
|
-
(Nokogiri::HTML.fragment o).do{|o|
|
189
|
-
o.css('a').map{|a|
|
190
|
-
if a.has_attribute? 'href'
|
191
|
-
(a.set_attribute 'href', (URI.join s, (a.attr 'href'))) rescue nil
|
192
|
-
end}
|
193
|
-
o.to_s} : o}
|
194
|
-
end
|
195
|
-
|
196
163
|
fn Render+'text/html',->d,e{ u = d['#']||{}
|
197
164
|
titles = d.map{|u,r| r[Title] if r.class==Hash }.flatten.compact
|
198
165
|
view = F['view/'+e.q['view'].to_s] || F['view']
|
data/infod/image.rb
CHANGED
@@ -21,14 +21,14 @@ class R
|
|
21
21
|
`gm convert #{i.sh} -thumbnail "#{size}x#{size}" #{path.sh}`
|
22
22
|
end
|
23
23
|
end
|
24
|
-
path.e ? (path.env r).
|
24
|
+
path.e ? (path.env r).fileGET : F[E404][e,r]
|
25
25
|
else
|
26
26
|
F[E404][e,r]
|
27
27
|
end}
|
28
28
|
|
29
29
|
fn 'view/img',->i,_{
|
30
30
|
[i.values.select{|v|v.class==Hash}.map{|i|
|
31
|
-
i[Type] &&
|
31
|
+
i[Type] && i[Type].justArray.map(&:maybeURI).include?(DC+'Image') &&
|
32
32
|
[{_: :a, href: i.url, c: {_: :img, style:'float:left;max-width:61.8%', src: i.url}},
|
33
33
|
i.html]},
|
34
34
|
(H.css '/css/img')]}
|
data/infod/infod.rb
CHANGED
data/infod/lambda.rb
CHANGED
@@ -4,7 +4,7 @@ end
|
|
4
4
|
|
5
5
|
class NilClass
|
6
6
|
def do; nil end
|
7
|
-
def html e=nil
|
7
|
+
def html e=nil; "" end
|
8
8
|
def R; "".R end
|
9
9
|
end
|
10
10
|
|
@@ -21,10 +21,6 @@ def watch f
|
|
21
21
|
|
22
22
|
class R
|
23
23
|
|
24
|
-
def initialize uri
|
25
|
-
@uri = uri.to_s
|
26
|
-
end
|
27
|
-
|
28
24
|
def R uri = nil
|
29
25
|
uri ? R.new(uri) : self
|
30
26
|
end
|
@@ -49,7 +45,7 @@ class R
|
|
49
45
|
i = i.split /:/
|
50
46
|
yield uri, (f + (i[0].match(g)||[0,i[0]])[1]. # s
|
51
47
|
gsub(/\s/,'_').gsub(/\//,'-').gsub(/[\(\)]+/,'')), # p
|
52
|
-
i.tail.join(':').strip.do{|v|v.match(/^[0-9\.]+$/) ? v.to_f : v
|
48
|
+
i.tail.join(':').strip.do{|v|v.match(/^[0-9\.]+$/) ? v.to_f : v}} # o
|
53
49
|
rescue
|
54
50
|
end
|
55
51
|
|
data/infod/mail.rb
CHANGED
@@ -1,18 +1,33 @@
|
|
1
1
|
#watch __FILE__
|
2
2
|
class R
|
3
|
+
=begin usage
|
3
4
|
|
4
|
-
|
5
|
+
messages matching an address
|
6
|
+
msgs = R['/m/semantic-web@w3.org'].take
|
7
|
+
|
8
|
+
mirror message-files backing a resource-set
|
9
|
+
src = R::DC+'source'
|
10
|
+
files = msgs.map{|g| '.' + g.graph.values.find{|r|r.has_key? src}[src].head.R.path}
|
11
|
+
`rsync -avRz #{files.join ' '} h:/www/`
|
12
|
+
|
13
|
+
summary view on directories
|
14
|
+
F['/mail/GET'] = -> e,r {
|
15
|
+
r.q['view'] ||= 'threads' if e.uri[-1] == '/'
|
16
|
+
nil }
|
17
|
+
|
18
|
+
=end
|
5
19
|
|
6
|
-
|
20
|
+
MessagePath = ->id{'/msg/' + id.h[0..2] + '/' + id}
|
21
|
+
GREP_DIRS.push /^\/m\/[^\/]+\// # allow on a single address
|
7
22
|
|
8
23
|
F['/m/GET'] = -> e,r{
|
9
|
-
#
|
24
|
+
# use summary view and start a newest-first tree-range at address
|
10
25
|
if m = e.pathSegment.uri.match(/^\/m\/([^\/]+)$/)
|
11
|
-
r.q['set']
|
26
|
+
r.q['set'] ||= 'depth'
|
12
27
|
r.q['view'] ||= 'threads'
|
13
|
-
|
28
|
+
e.response
|
14
29
|
else
|
15
|
-
false
|
30
|
+
false
|
16
31
|
end}
|
17
32
|
|
18
33
|
IndexMail = ->doc, graph, host {
|
@@ -48,12 +63,16 @@ class R
|
|
48
63
|
creator = '/m/'+f+'#'+f # author URI
|
49
64
|
yield e, Creator, R[creator] # message -> author
|
50
65
|
yield creator, DC+'identifier', R['mailto:'+f] # author ID
|
66
|
+
# reply to
|
67
|
+
r2 = m['List-Post'].do{|lp|lp.decoded[8..-2]} || # List-Post
|
68
|
+
m.reply_to.do{|t|t[0]} || # Reply-To
|
69
|
+
f # From
|
51
70
|
yield e, SIOC+'reply_to', # reply URI
|
52
|
-
R[URI.escape("mailto:#{
|
71
|
+
R[URI.escape("mailto:#{r2}?References=<#{id}>&In-Reply-To=<#{id}>&Subject=#{m.subject}&")+'#reply']}}
|
53
72
|
|
54
73
|
yield e, Date, m.date.iso8601 if m.date # date
|
55
74
|
|
56
|
-
m.subject.do{|s|yield e, Title, s.to_utf8}
|
75
|
+
m.subject.do{|s|yield e, Title, s.to_utf8.hrefs} # subject
|
57
76
|
|
58
77
|
yield e, SIOC+'has_discussion', # thread
|
59
78
|
R[e+'?graph=thread&view=timegraph#discussion'] if m.in_reply_to || m.references
|
@@ -83,7 +102,7 @@ class R
|
|
83
102
|
c: p.decoded.to_utf8.hrefs.gsub(/^\s*(>)(>|\s)*\n/,"").lines.to_a.map{|l| # skip quoted*empty lines
|
84
103
|
l.match(/(^\s*(>|On[^\n]+(said|wrote))[^\n]*)\n/) ? # quoted?
|
85
104
|
{_: :span, class: :q, depth: l.scan(/(>)/).size, c: l} : l # wrap quotes
|
86
|
-
}},(H.css '/css/mail'
|
105
|
+
}},(H.css '/css/mail')])}
|
87
106
|
|
88
107
|
attache = -> { e.R.a('.attache').mk } # filesystem container for attachments & parts
|
89
108
|
|
@@ -106,20 +125,6 @@ class R
|
|
106
125
|
end
|
107
126
|
|
108
127
|
F['view/'+MIMEtype+'message/rfc822'] = NullView # hide container-resource in default view
|
109
|
-
=
|
110
|
-
USAGE(context)
|
111
|
-
|
112
|
-
admin (irb) move messages among filesystems
|
113
|
-
p = R['/m/semantic-web@w3.org'].take.map{|g|'.' + g.graph.values.find{|r|r.has_key?(R::DC+'source')}[R::DC+'source'][0].R.path}
|
114
|
-
`rsync -avRz #{p.join ' '} h:/www/`
|
128
|
+
F['view/'+MIMEtype+'text/n3'] = NullView
|
115
129
|
|
116
|
-
view (rb) default to overview of directory
|
117
|
-
F['/mail/GET'] = -> e,r {
|
118
|
-
r.q['view'] ||= 'threads' if e.uri[-1] == '/'
|
119
|
-
false }
|
120
|
-
|
121
|
-
home (sh) current day-dir in Markdown
|
122
|
-
echo "[today](/?y=day&view=threads)" > TODAY.md
|
123
|
-
|
124
|
-
=end
|
125
130
|
end
|
data/infod/man.rb
CHANGED
data/infod/microblog.rb
CHANGED
@@ -26,9 +26,18 @@ class R
|
|
26
26
|
|
27
27
|
def tw g
|
28
28
|
node.readlines.shuffle.each_slice(22){|s|
|
29
|
-
R['https://twitter.com/search/realtime?q='+s.map{|u|'from:'+u.chomp}.intersperse('+OR+').join].addDocs :triplrTweets, g, nil,
|
29
|
+
R['https://twitter.com/search/realtime?q='+s.map{|u|'from:'+u.chomp}.intersperse('+OR+').join].addDocs :triplrTweets, g, nil, FeedArchiverJSON}
|
30
30
|
end
|
31
31
|
|
32
|
+
FeedArchiverJSON = -> doc, graph, host {
|
33
|
+
doc.roonga host
|
34
|
+
graph.map{|u,r|
|
35
|
+
r[Date].do{|t| # link doc to date-index
|
36
|
+
t = t[0].gsub(/[-T]/,'/').sub /(.00.00|Z)$/, '' # trim normalized timezones and non-unique symbols
|
37
|
+
b = (u.sub(/http:\/\//,'.').gsub(/\W/,'..').gsub(FeedStop,'').sub(/\d{12,}/,'')+'.').gsub /\.+/,'.'
|
38
|
+
doc.ln R["http://#{host}/news/#{t}#{b}e"]}}
|
39
|
+
doc}
|
40
|
+
|
32
41
|
def triplrTweets
|
33
42
|
base = 'http://twitter.com'
|
34
43
|
nokogiri.css('div.tweet').map{|t|
|
data/infod/names.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
class R
|
2
|
+
=begin
|
3
|
+
name-manipulating functions
|
4
|
+
a RDF::URI-identified-resource has an associated filesystem node when using our subclass..
|
5
|
+
=end
|
2
6
|
|
3
7
|
def appendURI u; R uri + u.to_s end
|
4
8
|
def appendSlashURI u; R uri.t + u.to_s end
|
@@ -8,33 +12,22 @@ class R
|
|
8
12
|
def children; node.c.map &:R end
|
9
13
|
def container; @u ||= R[f ? dirname + '/.' + (File.basename path) : path] end
|
10
14
|
def d; node.to_s end
|
11
|
-
def delete; node.deleteNode if e; self end
|
12
15
|
def dirname; node.dirname.do{|d| d.to_s.size <= BaseLen ? '/' : d }.R end
|
13
16
|
def docBase; uri.split(/#/)[0].R.do{|d| d.dirname.as d.bare } end
|
14
17
|
def d?; node.directory? end
|
15
|
-
def env r=nil;r ? (@r = r; self) : @r end
|
16
|
-
def exist?; node.exist? end
|
17
18
|
def expand; uri.expand.R end
|
18
19
|
def ext; File.extname(uri).tail||'' end
|
19
|
-
def file?; node.file? end
|
20
20
|
def frag; uri.frag end
|
21
|
-
def get; open(uri).read end
|
22
21
|
def glob p=""; (Pathname.glob d + p).map &:R end
|
23
22
|
def hostURL e; host='http://'+e['SERVER_NAME']; (uri.index('/') == 0 ? host : '') + uri end
|
24
23
|
def inside; node.expand_path.to_s.index(FSbase) == 0 end
|
25
24
|
def label; uri.label end
|
26
|
-
def mk; e || FileUtils.mkdir_p(d); self end
|
27
|
-
def mtime; node.stat.mtime if e end
|
28
25
|
def node; Pathname.new FSbase + path end
|
29
26
|
def parent; R Pathname.new(uri).parent end
|
30
27
|
def parents; parent.do{|p|p.uri.match(/^[.\/]+$/) ? [p] : [p].concat(p.parents)} end
|
31
28
|
def path; uri.match(/^\//) ? uri : ('/'+uri) end
|
32
29
|
def pathSegment; uri.match(/^([a-z]+:\/\/[^\/]+)?(\/.*)/).do{|p|p[2]&&p[2].R}||nil end
|
33
|
-
def predicatePath p,s=true; container.as s ? p.R.shorten : p end
|
34
|
-
def predicates; container.c.map{|c|c.base.expand.R} end
|
35
30
|
def prependURI u; R u.to_s + uri end
|
36
|
-
def read; f ? readFile : get end
|
37
|
-
def readFile; File.open(d).read end
|
38
31
|
def realpath; node.realpath rescue Errno::ENOENT end
|
39
32
|
def shorten; uri.shorten.R end
|
40
33
|
def siblings; parent.c end
|
@@ -42,11 +35,9 @@ class R
|
|
42
35
|
def == u; to_s == u.to_s end
|
43
36
|
def <=> c; to_s <=> c.to_s end
|
44
37
|
def sh; d.force_encoding('UTF-8').sh end
|
45
|
-
def to_s; uri end
|
46
|
-
def to_h; {'uri' => uri} end
|
47
|
-
def touch; FileUtils.touch node; self end
|
48
|
-
def writeFile c; File.open(d,'w'){|f|f << c} end
|
49
38
|
|
39
|
+
# shortcuts
|
40
|
+
|
50
41
|
alias_method :+, :appendURI
|
51
42
|
alias_method :a, :appendURI
|
52
43
|
alias_method :as, :appendSlashURI
|
@@ -54,11 +45,9 @@ class R
|
|
54
45
|
alias_method :bare, :barename
|
55
46
|
alias_method :c, :children
|
56
47
|
alias_method :dir, :dirname
|
57
|
-
alias_method :
|
58
|
-
alias_method :
|
59
|
-
alias_method :
|
60
|
-
alias_method :maybeURI, :uri
|
61
|
-
alias_method :url, :uri
|
48
|
+
alias_method :maybeURI, :to_s
|
49
|
+
alias_method :url, :to_s
|
50
|
+
alias_method :uri, :to_s
|
62
51
|
|
63
52
|
def localURL e
|
64
53
|
if uri.index('/') == 0
|
data/infod/rdf.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
#watch __FILE__
|
2
2
|
class R
|
3
3
|
|
4
|
-
def nt;
|
5
|
-
def n3;
|
6
|
-
def ttl;
|
4
|
+
def nt; docBase.a '.nt' end
|
5
|
+
def n3; docBase.a '.n3' end
|
6
|
+
def ttl; docBase.a '.ttl' end
|
7
7
|
|
8
8
|
def self.renderRDF d,f,e
|
9
9
|
(RDF::Writer.for f).buffer{|w|
|
@@ -23,7 +23,8 @@ class R
|
|
23
23
|
uri = (local && f) ? d : uri
|
24
24
|
RDF::Reader.open(uri, :format => format){|r|
|
25
25
|
r.each_triple{|s,p,o|
|
26
|
-
yield s.to_s, p.to_s,
|
26
|
+
yield s.to_s, p.to_s,
|
27
|
+
[RDF::Node, RDF::URI].member?(o.class) ? R(o) : o.value.do{|v|v.class == String ? v.to_utf8 : v}}}
|
27
28
|
end
|
28
29
|
|
29
30
|
[['application/ld+json',:jsonld],
|