json-ld 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/lib/json/ld.rb +2 -1
- data/lib/json/ld/format.rb +14 -9
- data/lib/json/ld/reader.rb +382 -0
- metadata +3 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/lib/json/ld.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..')))
|
2
|
-
require 'rdf'
|
2
|
+
require 'rdf' # @see http://rubygems.org/gems/rdf
|
3
3
|
|
4
4
|
module JSON
|
5
5
|
##
|
@@ -20,6 +20,7 @@ module JSON
|
|
20
20
|
#
|
21
21
|
# @author [Gregg Kellogg](http://greggkellogg.net/)
|
22
22
|
module LD
|
23
|
+
require 'json'
|
23
24
|
require 'json/ld/format'
|
24
25
|
autoload :Reader, 'json/ld/reader'
|
25
26
|
autoload :VERSION, 'json/ld/version'
|
data/lib/json/ld/format.rb
CHANGED
@@ -1,26 +1,31 @@
|
|
1
|
-
module
|
1
|
+
module JSON::LD
|
2
2
|
##
|
3
3
|
# RDFa format specification.
|
4
4
|
#
|
5
5
|
# @example Obtaining an Notation3 format class
|
6
|
-
# RDF::Format.for(:
|
7
|
-
# RDF::Format.for(
|
8
|
-
# RDF::Format.for(
|
9
|
-
# RDF::Format.for(
|
6
|
+
# RDF::Format.for(:json) #=> JSON::LD::Format
|
7
|
+
# RDF::Format.for(:ld) #=> JSON::LD::Format
|
8
|
+
# RDF::Format.for("etc/foaf.json")
|
9
|
+
# RDF::Format.for("etc/foaf.ld")
|
10
|
+
# RDF::Format.for(:file_name => "etc/foaf.json")
|
11
|
+
# RDF::Format.for(:file_name => "etc/foaf.ld")
|
12
|
+
# RDF::Format.for(:file_extension => "json")
|
13
|
+
# RDF::Format.for(:file_extension => "ld")
|
10
14
|
# RDF::Format.for(:content_type => "application/json")
|
11
15
|
#
|
12
16
|
# @example Obtaining serialization format MIME types
|
13
17
|
# RDF::Format.content_types #=> {"application/json" => [JSON::LD::Format]}
|
14
18
|
#
|
15
19
|
# @example Obtaining serialization format file extension mappings
|
16
|
-
# RDF::Format.file_extensions #=> {:
|
20
|
+
# RDF::Format.file_extensions #=> {:json => "application/json"}
|
17
21
|
#
|
18
22
|
# @see http://www.w3.org/TR/rdf-testcases/#ntriples
|
19
23
|
class Format < RDF::Format
|
20
|
-
content_type 'application/json', :extension => :
|
24
|
+
content_type 'application/json', :extension => :json
|
25
|
+
content_type 'application/json', :extension => :ld
|
21
26
|
content_encoding 'utf-8'
|
22
27
|
|
23
|
-
reader {
|
24
|
-
writer {
|
28
|
+
reader { JSON::LD::Reader }
|
29
|
+
writer { JSON::LD::Writer }
|
25
30
|
end
|
26
31
|
end
|
data/lib/json/ld/reader.rb
CHANGED
@@ -6,6 +6,388 @@ module JSON::LD
|
|
6
6
|
# @author [Gregg Kellogg](http://greggkellogg.net/)
|
7
7
|
class Reader < RDF::Reader
|
8
8
|
format Format
|
9
|
+
|
10
|
+
# Default context
|
11
|
+
# @see http://json-ld.org/spec/ED/20110507/#the-default-context
|
12
|
+
DEFAULT_CONTEXT = {
|
13
|
+
"rdf" => "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
14
|
+
"rdfs" => "http://www.w3.org/2000/01/rdf-schema#",
|
15
|
+
"owl" => "http://www.w3.org/2002/07/owl#",
|
16
|
+
"xsd" => "http://www.w3.org/2001/XMLSchema#",
|
17
|
+
"dcterms" => "http://purl.org/dc/terms/",
|
18
|
+
"foaf" => "http://xmlns.com/foaf/0.1/",
|
19
|
+
"cal" => "http://www.w3.org/2002/12/cal/ical#",
|
20
|
+
"vcard" => "http://www.w3.org/2006/vcard/ns# ",
|
21
|
+
"geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
|
22
|
+
"cc" => "http://creativecommons.org/ns#",
|
23
|
+
"sioc" => "http://rdfs.org/sioc/ns#",
|
24
|
+
"doap" => "http://usefulinc.com/ns/doap#",
|
25
|
+
"com" => "http://purl.org/commerce#",
|
26
|
+
"ps" => "http://purl.org/payswarm#",
|
27
|
+
"gr" => "http://purl.org/goodrelations/v1#",
|
28
|
+
"sig" => "http://purl.org/signature#",
|
29
|
+
"ccard" => "http://purl.org/commerce/creditcard#",
|
30
|
+
"@coerce" => {
|
31
|
+
# Note: rdf:type is not in the document, but necessary for this implementation
|
32
|
+
"xsd:anyURI" => ["rdf:type", "foaf:homepage", "foaf:member", "rdf:type"],
|
33
|
+
"xsd:integer" => "foaf:age",
|
34
|
+
}
|
35
|
+
}.freeze
|
36
|
+
|
37
|
+
##
|
38
|
+
# The graph constructed when parsing.
|
39
|
+
#
|
40
|
+
# @return [RDF::Graph]
|
41
|
+
attr_reader :graph
|
42
|
+
|
43
|
+
##
|
44
|
+
# Context
|
45
|
+
#
|
46
|
+
# The `@context` keyword is used to change how the JSON-LD processor evaluates key- value pairs. In this
|
47
|
+
# case, it was used to map one string (`'myvocab'`) to another string, which is interpreted as a IRI. In the
|
48
|
+
# example above, the `myvocab` string is replaced with "http://example.org/myvocab#" when it is detected. In
|
49
|
+
# the example above, `"myvocab:personality"` would expand to "http://example.org/myvocab#personality".
|
50
|
+
#
|
51
|
+
# This mechanism is a short-hand for RDF, called a `CURIE`, and provides developers an unambiguous way to
|
52
|
+
# map any JSON value to RDF.
|
53
|
+
#
|
54
|
+
# @private
|
55
|
+
class EvaluationContext # :nodoc:
|
56
|
+
# The base.
|
57
|
+
#
|
58
|
+
# The `@base` string is a special keyword that states that any relative IRI MUST be appended to the string
|
59
|
+
# specified by `@base`.
|
60
|
+
#
|
61
|
+
# @attr [RDF::URI]
|
62
|
+
attr :base, true
|
63
|
+
|
64
|
+
# A list of current, in-scope URI mappings.
|
65
|
+
#
|
66
|
+
# @attr [Hash{Symbol => String}]
|
67
|
+
attr :mappings, true
|
68
|
+
|
69
|
+
# The default vocabulary
|
70
|
+
#
|
71
|
+
# A value to use as the prefix URI when a term is used.
|
72
|
+
# This specification does not define an initial setting for the default vocabulary.
|
73
|
+
# Host Languages may define an initial setting.
|
74
|
+
#
|
75
|
+
# @attr [String]
|
76
|
+
attr :vocab, true
|
77
|
+
|
78
|
+
# Type coersion
|
79
|
+
#
|
80
|
+
# The @coerce keyword is used to specify type coersion rules for the data. For each key in the map, the
|
81
|
+
# key is the type to be coerced to and the value is the vocabulary term to be coerced. Type coersion for
|
82
|
+
# the key `xsd:anyURI` asserts that all vocabulary terms listed should undergo coercion to an IRI,
|
83
|
+
# including `@base` processing for relative IRIs and CURIE processing for compact URI Expressions like
|
84
|
+
# `foaf:homepage`.
|
85
|
+
#
|
86
|
+
# As the value may be an array, this is maintained as a reverse mapping of `property` => `type`.
|
87
|
+
#
|
88
|
+
# @attr [Hash{RDF::URI => RDF::URI}]
|
89
|
+
attr :coerce, true
|
90
|
+
|
91
|
+
##
|
92
|
+
# Create new evaluation context
|
93
|
+
# @yield [ec]
|
94
|
+
# @yieldparam [EvaluationContext]
|
95
|
+
# @return [EvaluationContext]
|
96
|
+
def initialize
|
97
|
+
@base = nil
|
98
|
+
@mappings = {}
|
99
|
+
@vocab = nil
|
100
|
+
@coerce = {}
|
101
|
+
yield(self) if block_given?
|
102
|
+
end
|
103
|
+
|
104
|
+
def inspect
|
105
|
+
v = %w([EvaluationContext) + %w(base vocab).map {|a| "#{a}='#{self.send(a).inspect}'"}
|
106
|
+
v << "mappings[#{mappings.keys.length}]"
|
107
|
+
v << "coerce[#{coerce.keys.length}]"
|
108
|
+
v.join(", ") + "]"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Initializes the RDF/JSON reader instance.
|
114
|
+
#
|
115
|
+
# @param [IO, File, String] input
|
116
|
+
# @param [Hash{Symbol => Object}] options
|
117
|
+
# any additional options (see `RDF::Reader#initialize`)
|
118
|
+
# @yield [reader] `self`
|
119
|
+
# @yieldparam [RDF::Reader] reader
|
120
|
+
# @yieldreturn [void] ignored
|
121
|
+
def initialize(input = $stdin, options = {}, &block)
|
122
|
+
super do
|
123
|
+
@base_uri = uri(options[:base_uri]) if options[:base_uri]
|
124
|
+
@doc = ::JSON.load(input)
|
125
|
+
|
126
|
+
if block_given?
|
127
|
+
case block.arity
|
128
|
+
when 0 then instance_eval(&block)
|
129
|
+
else block.call(self)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
# @private
|
137
|
+
# @see RDF::Reader#each_statement
|
138
|
+
def each_statement(&block)
|
139
|
+
@callback = block
|
140
|
+
|
141
|
+
# initialize the evaluation context with the appropriate base
|
142
|
+
ec = EvaluationContext.new do |ec|
|
143
|
+
ec.base = @base_uri if @base_uri
|
144
|
+
parse_context(ec, DEFAULT_CONTEXT)
|
145
|
+
end
|
146
|
+
|
147
|
+
traverse("", @doc, nil, nil, ec)
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# @private
|
152
|
+
# @see RDF::Reader#each_triple
|
153
|
+
def each_triple(&block)
|
154
|
+
each_statement do |statement|
|
155
|
+
block.call(*statement.to_triple)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
##
|
161
|
+
#
|
162
|
+
# @param [String] path
|
163
|
+
# location within JSON hash
|
164
|
+
# @param [Hash, Array, String] element
|
165
|
+
# The current JSON element being processed
|
166
|
+
# @param [RDF::URI] subject
|
167
|
+
# Inherited subject
|
168
|
+
# @param [RDF::URI] property
|
169
|
+
# Inherited property
|
170
|
+
# @param [EvaluationContext] ec
|
171
|
+
# The active context
|
172
|
+
def traverse(path, element, subject, property, ec)
|
173
|
+
add_debug(path, "traverse: s=#{subject.inspect}, p=#{property.inspect}, e=#{ec.inspect}")
|
174
|
+
object = nil
|
175
|
+
|
176
|
+
case element
|
177
|
+
when Hash
|
178
|
+
# 2) ... For each key-value
|
179
|
+
# pair in the associative array, using the newly created processor state do the
|
180
|
+
# following:
|
181
|
+
|
182
|
+
# 2.1) If a @context keyword is found, the processor merges each key-value pair in
|
183
|
+
# the local context into the active context ...
|
184
|
+
if element["@context"]
|
185
|
+
# Merge context
|
186
|
+
ec = parse_context(ec.dup, element["@context"])
|
187
|
+
prefixes.merge!(ec.mappings) # Update parsed prefixes
|
188
|
+
end
|
189
|
+
|
190
|
+
# Other shortcuts to allow use of this method for terminal associative arrays
|
191
|
+
if element["@iri"].is_a?(String)
|
192
|
+
# Return the IRI found from the value
|
193
|
+
object = expand_term(element["@iri"], ec.base, ec)
|
194
|
+
add_triple(path, subject, property, object) if subject && property
|
195
|
+
elsif element["@literal"]
|
196
|
+
literal_opts = {}
|
197
|
+
literal_opts[:datatype] = expand_term(element["@datatype"], ec.vocab.to_s, ec) if element["@datatype"]
|
198
|
+
literal_opts[:language] = element["@language"].to_sym if element["@language"]
|
199
|
+
object = RDF::Literal.new(element["@literal"], literal_opts)
|
200
|
+
add_triple(path, subject, property, object) if subject && property
|
201
|
+
end
|
202
|
+
|
203
|
+
# 2.2) ... Otherwise, if the local context is known perform the following steps:
|
204
|
+
# 2.2.1) If a @ key is found, the processor sets the active subject to the
|
205
|
+
# value after Object Processing has been performed.
|
206
|
+
if element["@"].is_a?(String)
|
207
|
+
active_subject = expand_term(element["@"], ec.base, ec)
|
208
|
+
|
209
|
+
# 2.2.1.1) If the inherited subject and inherited property values are
|
210
|
+
# specified, generate a triple using the inherited subject for the
|
211
|
+
# subject, the inherited property for the property, and the active
|
212
|
+
# subject for the object.
|
213
|
+
add_triple(path, subject, property, active_subject) if subject && property
|
214
|
+
|
215
|
+
subject = active_subject
|
216
|
+
else
|
217
|
+
# 2.2.7) If the end of the associative array is detected, and a active subject
|
218
|
+
# was not discovered, then:
|
219
|
+
# 2.2.7.1) Generate a blank node identifier and set it as the active subject.
|
220
|
+
subject = RDF::Node.new
|
221
|
+
end
|
222
|
+
|
223
|
+
element.each do |key, value|
|
224
|
+
# 2.2.3) If a key that is not @context, @, or a, set the active property by
|
225
|
+
# performing Property Processing on the key.
|
226
|
+
property = case key
|
227
|
+
when /^@/
|
228
|
+
nil
|
229
|
+
when 'a'
|
230
|
+
RDF.type
|
231
|
+
else
|
232
|
+
expand_term(key, ec.vocab, ec)
|
233
|
+
end
|
234
|
+
|
235
|
+
traverse("#{path}[#{key}]", value, subject, property, ec) if property
|
236
|
+
end
|
237
|
+
when Array
|
238
|
+
# 3) If a regular array is detected, process each value in the array by doing the following:
|
239
|
+
element.each_with_index do |v, i|
|
240
|
+
case v
|
241
|
+
when Hash, String
|
242
|
+
traverse("#{path}[#{i}]", v, subject, property, ec)
|
243
|
+
when Array
|
244
|
+
# 3.3) If the value is a regular array, should we support RDF List/Sequence Processing?
|
245
|
+
last = v.pop
|
246
|
+
first_bnode = last ? RDF::Node.new : RDF.nil
|
247
|
+
add_triple("#{path}[#{i}][]", subject, property, first_bnode)
|
248
|
+
|
249
|
+
v.each do |list_item|
|
250
|
+
traverse("#{path}[#{i}][]", list_item, first_bnode, RDF.first, ec)
|
251
|
+
rest_bnode = RDF::Node.new
|
252
|
+
add_triple("#{path}[#{i}][]", first_bnode, RDF.rest, rest_bnode)
|
253
|
+
first_bnode = rest_bnode
|
254
|
+
end
|
255
|
+
if last
|
256
|
+
traverse("#{path}[#{i}][]", last, first_bnode, RDF.first, ec)
|
257
|
+
add_triple("#{path}[#{i}][]", first_bnode, RDF.rest, RDF.nil)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
when String
|
262
|
+
# Perform coersion of the value, or generate a literal
|
263
|
+
add_debug(path, "traverse(#{element}): coerce?(#{property.inspect}) == #{ec.coerce[property].inspect}, ec=#{ec.coerce.inspect}")
|
264
|
+
object = if ec.coerce[property] == RDF::XSD.anyURI
|
265
|
+
expand_term(element, ec.base, ec)
|
266
|
+
elsif ec.coerce[property]
|
267
|
+
RDF::Literal.new(element, :datatype => ec.coerce[property])
|
268
|
+
else
|
269
|
+
RDF::Literal.new(element)
|
270
|
+
end
|
271
|
+
add_triple(path, subject, property, object) if subject && property
|
272
|
+
when Float
|
273
|
+
object = RDF::Literal::Double.new(element)
|
274
|
+
add_debug(path, "traverse(#{element}): native: #{object.inspect}")
|
275
|
+
add_triple(path, subject, property, object) if subject && property
|
276
|
+
when Fixnum
|
277
|
+
object = RDF::Literal.new(element)
|
278
|
+
add_debug(path, "traverse(#{element}): native: #{object.inspect}")
|
279
|
+
add_triple(path, subject, property, object) if subject && property
|
280
|
+
when TrueClass, FalseClass
|
281
|
+
object = RDF::Literal::Boolean.new(element)
|
282
|
+
add_debug(path, "traverse(#{element}): native: #{object.inspect}")
|
283
|
+
add_triple(path, subject, property, object) if subject && property
|
284
|
+
else
|
285
|
+
raise RDF::ReaderError, "Traverse to unknown element: #{element.inspect} of type #{element.class}"
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
##
|
290
|
+
# add a statement, object can be literal or URI or bnode
|
291
|
+
#
|
292
|
+
# @param [String] path
|
293
|
+
# @param [URI, BNode] subject the subject of the statement
|
294
|
+
# @param [URI] predicate the predicate of the statement
|
295
|
+
# @param [URI, BNode, Literal] object the object of the statement
|
296
|
+
# @return [Statement] Added statement
|
297
|
+
# @raise [ReaderError] Checks parameter types and raises if they are incorrect if parsing mode is _validate_.
|
298
|
+
def add_triple(path, subject, predicate, object)
|
299
|
+
statement = RDF::Statement.new(subject, predicate, object)
|
300
|
+
add_debug(path, "statement: #{statement.to_ntriples}")
|
301
|
+
@callback.call(statement)
|
302
|
+
end
|
303
|
+
|
304
|
+
##
|
305
|
+
# Add debug event to debug array, if specified
|
306
|
+
#
|
307
|
+
# @param [XML Node, any] node:: XML Node or string for showing context
|
308
|
+
# @param [String] message::
|
309
|
+
def add_debug(node, message)
|
310
|
+
puts "#{node}: #{message}" if JSON::LD::debug?
|
311
|
+
@options[:debug] << "#{node}: #{message}" if @options[:debug].is_a?(Array)
|
312
|
+
end
|
313
|
+
|
314
|
+
##
|
315
|
+
# Parse a JSON context, into a new EvaluationContext
|
316
|
+
# @param [Hash{String => String,Hash}] context
|
317
|
+
# JSON representation of @context
|
318
|
+
# @return [EvaluationContext]
|
319
|
+
# @raise [RDF::ReaderError] on a syntax error, or a reference to a term which is not defined.
|
320
|
+
def parse_context(ec, context)
|
321
|
+
context.each do |key, value|
|
322
|
+
#add_debug("parse_context(#{key})", value.inspect)
|
323
|
+
case key
|
324
|
+
when '@vocab' then ec.vocab = value
|
325
|
+
when '@base' then ec.base = uri(value)
|
326
|
+
when '@coerce'
|
327
|
+
# Spec confusion: doc says to merge each key-value mapping to the local context's @coerce mapping,
|
328
|
+
# overwriting duplicate values. In the case where a mapping is indicated to a list of properties
|
329
|
+
# (e.g., { "xsd:anyURI": ["foaf:homepage", "foaf:member"] }, does this overwrite a previous mapping
|
330
|
+
# of { "xsd:anyURI": "foaf:knows" }, or add to it.
|
331
|
+
add_error RDF::ReaderError, "Expected @coerce to reference an associative array" unless value.is_a?(Hash)
|
332
|
+
value.each do |type, property|
|
333
|
+
type_uri = expand_term(type, ec.vocab, ec)
|
334
|
+
[property].flatten.compact.each do |prop|
|
335
|
+
p = expand_term(prop, ec.vocab, ec)
|
336
|
+
ec.coerce[p] = type_uri
|
337
|
+
end
|
338
|
+
end
|
339
|
+
else
|
340
|
+
# Spec confusion: The text indicates to merge each key-value pair into the active context. Is any
|
341
|
+
# processing performed on the values. For instance, could a value be a CURIE, or {"@iri": <value>}?
|
342
|
+
# Examples indicate that there is no such processing, and each value should be an absolute IRI. The
|
343
|
+
# wording makes this unclear.
|
344
|
+
ec.mappings[key.to_sym] = value
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
ec
|
349
|
+
end
|
350
|
+
|
351
|
+
##
|
352
|
+
# Expand a term using the specified context
|
353
|
+
#
|
354
|
+
# @param [String] term
|
355
|
+
# @param [String] base Base to apply to URIs
|
356
|
+
# @param [EvaluationContext] ec
|
357
|
+
#
|
358
|
+
# @return [RDF::URI]
|
359
|
+
# @raise [RDF::ReaderError] if the term cannot be expanded
|
360
|
+
# @see http://json-ld.org/spec/ED/20110507/#markup-of-rdf-concepts
|
361
|
+
def expand_term(term, base, ec)
|
362
|
+
#add_debug("expand_term", "term=#{term.inspect}, base=#{base.inspect}, ec=#{ec.inspect}")
|
363
|
+
prefix, suffix = term.split(":", 2)
|
364
|
+
prefix = prefix.to_sym if prefix
|
365
|
+
return if prefix == '_'
|
366
|
+
if prefix == :_
|
367
|
+
bnode(suffix)
|
368
|
+
elsif ec.mappings.has_key?(prefix)
|
369
|
+
uri(ec.mappings[prefix] + suffix.to_s)
|
370
|
+
elsif base
|
371
|
+
base.respond_to?(:join) ? base.join(term) : uri(base + term)
|
372
|
+
else
|
373
|
+
uri(term)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
def uri(value, append = nil)
|
378
|
+
value = RDF::URI.new(value)
|
379
|
+
value = value.join(append) if append
|
380
|
+
value.validate! if validate?
|
381
|
+
value.canonicalize! if canonicalize?
|
382
|
+
value = RDF::URI.intern(value) if intern?
|
383
|
+
value
|
384
|
+
end
|
385
|
+
|
386
|
+
# Keep track of allocated BNodes
|
387
|
+
def bnode(value = nil)
|
388
|
+
@bnode_cache ||= {}
|
389
|
+
@bnode_cache[value.to_s] ||= RDF::Node.new(value)
|
390
|
+
end
|
9
391
|
end
|
10
392
|
end
|
11
393
|
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: json-ld
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.0.
|
5
|
+
version: 0.0.2
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Gregg Kellogg
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-05-
|
13
|
+
date: 2011-05-09 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -87,7 +87,7 @@ files:
|
|
87
87
|
- lib/json/ld/writer.rb
|
88
88
|
- lib/json/ld.rb
|
89
89
|
has_rdoc: false
|
90
|
-
homepage: http://
|
90
|
+
homepage: http://github.com/gkellogg/json-ld
|
91
91
|
licenses:
|
92
92
|
- Public Domain
|
93
93
|
post_install_message:
|