json-ld 0.0.1 → 0.0.2
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.
- 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:
|