diff --git a/.yardopts b/.yardopts index 2dd92d7..267c455 100644 --- a/.yardopts +++ b/.yardopts @@ -5,6 +5,7 @@ --hide-void-return --markup markdown --readme README.md +--exclude lib/psych/amazing_print.rb - AUTHORS VERSION diff --git a/VERSION b/VERSION index 8acdd82..4e379d2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.1 +0.0.2 diff --git a/examples/i18n.yamlld b/examples/i18n.yamlld new file mode 100644 index 0000000..7e4351a --- /dev/null +++ b/examples/i18n.yamlld @@ -0,0 +1,14 @@ +%YAML 1.2 +%TAG !i18n! https://www.w3.org/ns/i18n%23 +--- +"@context": + "@vocab": http://example.com/ + name: http://xmlns.com/foaf/0.1/name + homepage: http://xmlns.com/foaf/0.1/homepage + depiction: http://xmlns.com/foaf/0.1/depiction +name: Gregg Kellogg +homepage: https://greggkellogg.net/ +depiction: http://www.gravatar.com/avatar/42f948adff3afaa52249d963117af7c8 +lang: !i18n!en-US EN-US +dir: !i18n!_ltr "Left to Right" +langdir: !i18n!en-US_ltr "Left to Right US English" diff --git a/examples/scalars.yamlld b/examples/scalars.yamlld new file mode 100644 index 0000000..9580205 --- /dev/null +++ b/examples/scalars.yamlld @@ -0,0 +1,26 @@ +%YAML 1.2 +%TAG !xsd! http://www.w3.org/2001/XMLSchema%23 +--- +"@context": + "@vocab": http://example.com/ + name: http://xmlns.com/foaf/0.1/name + homepage: http://xmlns.com/foaf/0.1/homepage + depiction: http://xmlns.com/foaf/0.1/depiction +name: Gregg Kellogg +homepage: https://greggkellogg.net/ +depiction: http://www.gravatar.com/avatar/42f948adff3afaa52249d963117af7c8 +yint: 123 +yfloat: 123.45 +ystring: string +ynull: null +ybool: true +ydate: 2022-08-08 +ytime: 12:00:00 +ydateTime: 2022-08-08T12:00:00.000 +integer: !xsd!integer 123 +decimal: !xsd!decimal 123.456 +double: !xsd!double 123.456e78 +bool: !xsd!boolean true +date: !xsd!date "2022-08-08" +time: !xsd!time "12:00:00.000" +dateTime: !xsd!dateTime "2022-08-08T12:00:00.000" diff --git a/examples/xsd.yamlld b/examples/xsd.yamlld new file mode 100644 index 0000000..9ab84a0 --- /dev/null +++ b/examples/xsd.yamlld @@ -0,0 +1,9 @@ +%YAML 1.2 +%TAG ! http://www.w3.org/2001/XMLSchema%23 +--- +"@context": + "@vocab": http://xmlns.com/foaf/0.1/ +name: !string Gregg Kellogg +homepage: https://greggkellogg.net/ +depiction: http://www.gravatar.com/avatar/42f948adff3afaa52249d963117af7c8 +date: !date "2022-08-08" \ No newline at end of file diff --git a/lib/psych/amazing_print.rb b/lib/psych/amazing_print.rb new file mode 100644 index 0000000..a2bee1d --- /dev/null +++ b/lib/psych/amazing_print.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +# Amazing Print extensions for Psych +#------------------------------------------------------------------------------ +module AmazingPrint + module Psych + def self.included(base) + base.send :alias_method, :cast_without_psych, :cast + base.send :alias_method, :cast, :cast_with_psych + end + + # Add Psych Node names to the dispatcher pipeline. + #------------------------------------------------------------------------------ + def cast_with_psych(object, type) + cast = cast_without_psych(object, type) + if (defined?(::Psych::Nodes::Node) && object.is_a?(::Psych::Nodes::Node)) + cast = :psych_node + end + cast + end + +STYLES = %w(ANY BLOCK FLOW) #------------------------------------------------------------------------------ + def awesome_psych_node(object) + contents = [] + contents << colorize(object.class.name.split('::').last, :class) + contents << colorize("!<#{object.tag}>", :args) if object.tag + contents << colorize("&#{object.anchor}", :args) if object.respond_to?(:anchor) && object.anchor + contents << colorize("(implicit)", :args) if object.respond_to?(:implicit) && object.implicit + contents << colorize("(implicit end)", :args) if object.respond_to?(:implicit_end) && object.implicit_end + case object + when ::Psych::Nodes::Stream + contents << awesome_array(object.children) + when ::Psych::Nodes::Document + contents << colorize('%TAG(' + object.tag_directives.flatten.join(' ') + ')', :args) unless Array(object.tag_directives).empty? + contents << colorize("version #{object.version.join('.')}", :args) if object.version + contents << awesome_array(object.children) + when ::Psych::Nodes::Sequence + style = %w(ANY BLOCK FLOW)[object.style.to_i] + contents << awesome_array(object.children) + when ::Psych::Nodes::Mapping + style = %w(ANY BLOCK FLOW)[object.style.to_i] + contents << colorize(style, :args) if object.style + contents << awesome_hash(object.children.each_slice(2).to_h) + when ::Psych::Nodes::Scalar + style = %w(ANY PLAIN SINGLE_QUOTED DOUBLE_QUOTED LITERAL FOLDED)[object.style.to_i] + contents << colorize(style, :args) if object.style + contents << colorize("(plain)", :args) if object.plain + contents << colorize("(quoted)", :args) if object.quoted + contents << awesome_simple(object.value.inspect, :variable) + when ::Psych::Nodes::Alias + # No children + else + "Unknown node type: #{object.inspect}" + end + + contents.join(' ') + end + end +end + +AmazingPrint::Formatter.include AmazingPrint::Psych \ No newline at end of file diff --git a/lib/yaml_ld.rb b/lib/yaml_ld.rb index 85648db..d6c15e0 100644 --- a/lib/yaml_ld.rb +++ b/lib/yaml_ld.rb @@ -28,6 +28,7 @@ module YAML_LD autoload :API, 'yaml_ld/api' autoload :Reader, 'yaml_ld/reader' + autoload :Representation, 'yaml_ld/representation' autoload :VERSION, 'yaml_ld/version' autoload :Writer, 'yaml_ld/writer' diff --git a/lib/yaml_ld/api.rb b/lib/yaml_ld/api.rb index 48c98f9..1e66e0c 100644 --- a/lib/yaml_ld/api.rb +++ b/lib/yaml_ld/api.rb @@ -9,7 +9,6 @@ module YAML_LD # @see https://www.w3.org/TR/json-ld11-api/#the-application-programming-interface # @author [Gregg Kellogg](http://greggkellogg.net/) class API < ::JSON::LD::API - # The following constants are used to reduce object allocations LINK_REL_CONTEXT = %w(rel http://www.w3.org/ns/yaml-ld#context).freeze LINK_REL_ALTERNATE = %w(rel alternate).freeze @@ -30,6 +29,35 @@ class API < ::JSON::LD::API # A Serializer method used for generating the YAML serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to YAML externally via `#to_yaml`. # See {YAML_LD::API.serializer}. # @param [Hash{Symbol => Object}] options + # @option options [RDF::URI, String, #to_s] :base + # The Base IRI to use when expanding the document. This overrides the value of `input` if it is a _IRI_. If not specified and `input` is not an _IRI_, the base IRI defaults to the current document IRI if in a browser context, or the empty string if there is no document context. If not specified, and a base IRI is found from `input`, options[:base] will be modified with this value. + # @option options [Boolean] :compactArrays (true) + # If set to `true`, the JSON-LD processor replaces arrays with just one element with that element during compaction. If set to `false`, all arrays will remain arrays even if they have just one element. + # @option options [Boolean] :compactToRelative (true) + # Creates document relative IRIs when compacting, if `true`, otherwise leaves expanded. + # @option options [Proc] :documentLoader + # The callback of the loader to be used to retrieve remote documents and contexts. If specified, it must be used to retrieve remote documents and contexts; otherwise, if not specified, the processor's built-in loader must be used. See {documentLoader} for the method signature. + # @option options [String, #read, Hash, Array, JSON::LD::Context] :expandContext + # A context that is used to initialize the active context when expanding a document. + # @option options [Boolean] :extendedYAML (false) + # Use the expanded internal representation. + # @option options [Boolean] :extractAllScripts + # If set, when given an HTML input without a fragment identifier, extracts all `script` elements with type `application/ld+json` into an array during expansion. + # @option options [Boolean, String, RDF::URI] :flatten + # If set to a value that is not `false`, the JSON-LD processor must modify the output of the Compaction Algorithm or the Expansion Algorithm by coalescing all properties associated with each subject via the Flattening Algorithm. The value of `flatten must` be either an _IRI_ value representing the name of the graph to flatten, or `true`. If the value is `true`, then the first graph encountered in the input document is selected and flattened. + # @option options [String] :language + # When set, this has the effect of inserting a context definition with `@language` set to the associated value, creating a default language for interpreting string values. + # @option options [Boolean] :lowercaseLanguage + # By default, language tags are left as is. To normalize to lowercase, set this option to `true`. + # @option options [Boolean] :ordered (true) + # Order traversal of dictionary members by key when performing algorithms. + # @option options [Boolean] :rdfstar (false) + # support parsing JSON-LD-star statement resources. + # @option options [Boolean] :rename_bnodes (true) + # Rename bnodes as part of expansion, or keep them the same. + # @option options [Boolean] :unique_bnodes (false) + # Use unique bnode identifiers, defaults to using the identifier which the node was originally initialized with (if any). + # @option options [Boolean] :validate Validate input, if a string or readable object. # @raise [JsonLdError] # @yield YAML_LD, base_iri # @yieldparam [Array] yamlld @@ -46,7 +74,10 @@ def self.expand(input, **options, &block) JSON::LD::API.expand(input, + allowed_content_types: %r(application/(.+\+)?yaml), documentLoader: documentLoader, + extendedRepresentation: options[:extendedYAML], + processingMode: 'json-ld-1.1', serializer: serializer, **options, &block) @@ -68,7 +99,7 @@ def self.expand(input, # See {YAML_LD::API.serializer}. # @param [Boolean] expanded (false) Input is already expanded # @param [Hash{Symbol => Object}] options - # @option options (see #initialize) + # @option options (see expand) # @yield YAML_LD # @yieldparam [Array] yamlld # The expanded YAML-LD document @@ -83,7 +114,10 @@ def self.compact(input, context, expanded: false, **options, &block) JSON::LD::API.compact(input, context, expanded: expanded, + allowed_content_types: %r(application/(.+\+)?yaml), documentLoader: documentLoader, + extendedRepresentation: options[:extendedYAML], + processingMode: 'json-ld-1.1', serializer: serializer, **options, &block) @@ -103,9 +137,11 @@ def self.compact(input, context, expanded: false, # A Serializer method used for generating the YAML serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to YAML externally via `#to_yaml`. # See {YAML_LD::API.serializer}. # @param [Hash{Symbol => Object}] options - # @option options (see #initialize) + # @option options (see expand) # @option options [Boolean] :createAnnotations # Unfold embedded nodes which can be represented using `@annotation`. + # @option options [Boolean] :extendedYAML (false) + # Use the expanded internal representation. # @yield YAML_LD # @yieldparam [Array] yamlld # The expanded YAML-LD document @@ -119,7 +155,10 @@ def self.flatten(input, context, expanded: false, **options, &block) JSON::LD::API.flatten(input, context, expanded: expanded, + allowed_content_types: %r(application/(.+\+)?yaml), documentLoader: documentLoader, + extendedRepresentation: options[:extendedYAML], + processingMode: 'json-ld-1.1', serializer: serializer, **options, &block) @@ -135,11 +174,13 @@ def self.flatten(input, context, expanded: false, # @param [String, #read, Hash, Array] frame # The frame to use when re-arranging the data. # @param [Boolean] expanded (false) Input is already expanded - # @option options (see #initialize) + # @option options (see expand) # @option options ['@always', '@link', '@once', '@never'] :embed ('@once') # a flag specifying that objects should be directly embedded in the output, instead of being referred to by their IRI. # @option options [Boolean] :explicit (false) # a flag specifying that for properties to be included in the output, they must be explicitly declared in the framing context. + # @option options [Boolean] :extendedYAML (false) + # Use the expanded internal representation. # @option options [Boolean] :requireAll (false) # A flag specifying that all properties present in the input frame must either have a default value or be present in the JSON-LD input for the frame to match. # @option options [Boolean] :omitDefault (false) @@ -160,7 +201,10 @@ def self.frame(input, frame, expanded: false, **options, &block) JSON::LD::API.frame(input, frame, expanded: expanded, + allowed_content_types: %r(application/(.+\+)?yaml), documentLoader: documentLoader, + extendedRepresentation: options[:extendedYAML], + processingMode: 'json-ld-1.1', serializer: serializer, **options, &block) @@ -172,7 +216,9 @@ def self.frame(input, frame, expanded: false, # @param [String, #read, Hash, Array] input # The YAML-LD object to process when outputting statements. # @param [Boolean] expanded (false) Input is already expanded - # @option options (see #initialize) + # @option options (see expand) + # @option options [Boolean] :extendedYAML (false) + # Use the expanded internal representation. # @option options [Boolean] :produceGeneralizedRdf (false) # If true, output will include statements having blank node predicates, otherwise they are dropped. # @raise [JsonLdError] @@ -184,7 +230,10 @@ def self.toRdf(input, expanded: false, **options, &block) JSON::LD::API.toRdf(input, expanded: expanded, + allowed_content_types: %r(application/(.+\+)?yaml), documentLoader: documentLoader, + extendedRepresentation: options[:extendedYAML], + processingMode: 'json-ld-1.1', **options, &block) end @@ -202,7 +251,9 @@ def self.toRdf(input, expanded: false, # A Serializer method used for generating the YAML serialization of the result. If absent, the internal Ruby objects are returned, which can be transformed to YAML externally via `#to_yaml`. # See {YAML_LD::API.serializer}. # @param [Hash{Symbol => Object}] options - # @option options (see #initialize) + # @option options (see expand) + # @option options [Boolean] :extendedYAML (false) + # Use the expanded internal representation. # @yield jsonld # @yieldparam [Hash] jsonld # The JSON-LD document in expanded form @@ -215,10 +266,13 @@ def self.fromRdf(input, useRdfType: false, useNativeTypes: false, **options, &block) JSON::LD::API.fromRdf(input, - useRdfType: useRdfType, - useNativeTypes: useNativeTypes, + allowed_content_types: %r(application/(.+\+)?yaml), documentLoader: documentLoader, + extendedRepresentation: options[:extendedYAML], + processingMode: 'json-ld-1.1', serializer: serializer, + useRdfType: useRdfType, + useNativeTypes: useNativeTypes, **options, &block) end @@ -257,7 +311,7 @@ def self.documentLoader(url, extractAllScripts: false, profile: nil, requestProf content = case content_type when nil, %r(application/(\w+\+)*yaml) # Parse YAML - Psych.safe_load(url.read, aliases: true) + Representation.load(url.read, filename: url.to_s, **options) else url.read end @@ -267,7 +321,11 @@ def self.documentLoader(url, extractAllScripts: false, profile: nil, requestProf contextUrl: context_url)) elsif url.to_s.match?(/\.yaml\w*$/) || content_type.to_s.match?(%r(application/(\w+\+)*yaml)) # Parse YAML - block.call(RemoteDocument.new(Psych.load_file(url.to_s, aliases: true), + content = Representation.load(RDF::Util::File.open_file(url.to_s).read, + filename: url.to_s, + **options) + + block.call(RemoteDocument.new(content, documentUrl: base_uri, contentType: content_type, contextUrl: context_url)) @@ -288,7 +346,7 @@ def self.documentLoader(url, extractAllScripts: false, profile: nil, requestProf # options passed from the invoking context. def self.serializer(object, *args, **options) # de-alias any objects to avoid the use of aliases and anchors - "%YAML 1.2\n" + Psych.dump(object, **options) + "%YAML 1.2\n" + Representation.dump(object, **options) end end end diff --git a/lib/yaml_ld/format.rb b/lib/yaml_ld/format.rb index 43f6290..c2fd478 100644 --- a/lib/yaml_ld/format.rb +++ b/lib/yaml_ld/format.rb @@ -1,5 +1,7 @@ # -*- encoding: utf-8 -*- # frozen_string_literal: true +require 'json/ld/format' + module YAML_LD ## # YAML-LD format specification. @@ -28,6 +30,25 @@ class Format < RDF::Format reader { YAML_LD::Reader } writer { YAML_LD::Writer } + # Specify how to execute CLI commands for each supported format. These are added to the `LD_FORMATS` defined for `JSON::LD::Format` + # Derived formats (e.g., YAML-LD) define their own entrypoints. + LD_FORMATS = { + yamlld: { + expand: ->(input, **options) { + YAML_LD::API.expand(input, validate: false, **options) + }, + compact: ->(input, **options) { + YAML_LD::API.compact(input, options[:context], **options) + }, + flatten: ->(input, **options) { + YAML_LD::API.flatten(input, options[:context], **options) + }, + frame: ->(input, **options) { + YAML_LD::API.frame(input, options[:frame], **options) + }, + } + } + ## # Sample detection to see if it matches YAML-LD # @@ -54,3 +75,7 @@ def self.name end end end + + +# Load into JSON::LD::Format::LD_FORMATS +::JSON::LD::Format.const_get(:LD_FORMATS).merge!(::YAML_LD::Format::LD_FORMATS) diff --git a/lib/yaml_ld/reader.rb b/lib/yaml_ld/reader.rb index 0ac842f..d110730 100644 --- a/lib/yaml_ld/reader.rb +++ b/lib/yaml_ld/reader.rb @@ -24,7 +24,9 @@ class Reader < JSON::LD::Reader def initialize(input = $stdin, documentLoader: YAML_LD::API.method(:documentLoader), **options, &block) - input = StringIO.new(input) if input.is_a?(String) + input = StringIO.new(input).tap do |d| + d.define_singleton_method(:content_type) {'application/ld+yaml'} + end if input.is_a?(String) super(input, documentLoader: documentLoader, **options, &block) end diff --git a/lib/yaml_ld/representation.rb b/lib/yaml_ld/representation.rb new file mode 100644 index 0000000..570ddfc --- /dev/null +++ b/lib/yaml_ld/representation.rb @@ -0,0 +1,256 @@ +# -*- encoding: utf-8 -*- +# frozen_string_literal: true +require 'psych' +require 'rdf/xsd' + +module YAML_LD + ## + # Transforms a Psych AST to the JSON-LD (extended) Internal Representation build using `Psych.parse`, `.parse_stream`, or `parse_file`. + # + # FIXME: Aliases + module Representation + ### + # Load multiple documents given in +yaml+. Returns the parsed documents + # as a list. If a block is given, each document will be converted to Ruby + # and passed to the block during parsing + # + # @example + # + # load_stream("--- foo\n...\n--- bar\n...") # => ['foo', 'bar'] + # + # list = [] + # load_stream("--- foo\n...\n--- bar\n...") do |ruby| + # list << ruby + # end + # list # => ['foo', 'bar'] + # + # @param [String] yaml + # @param [String] filename + # @param [Object] fallback + # @param [Hash{Symbol => Object}] options + # @return [Array] + def load_stream(yaml, filename: nil, fallback: [], **options) + result = if block_given? + Psych.parse_stream(yaml, filename: filename) do |node| + yield as_jsonld_ir(node, **options) + end + else + as_jsonld_ir(Psych.parse_stream(yaml, filename: filename), **options) + end + + result.is_a?(Array) && result.empty? ? fallback : result + end + module_function :load_stream + + ## + # Load a single document from +yaml+. + # @param (see load_stream) + # @return [Object] + def load(yaml, filename: nil, fallback: nil, **options) + result = if block_given? + load_stream(yaml, filename: filename, **options) do |node| + yield node.first + end + else + load_stream(yaml, filename: filename, **options).first + end + + result || fallback + end + module_function :load + + ## + # Dump internal representaiton to YAML + # + # @option options[Boolean] :extendedYAML + # Allows the use of aliases and encodes RDF::Literals + def dump(ir, **options) + if options[:extendedYAML] + options = { + aliases: true, + permitted_classes: [RDF::Literal] + }.merge(options) + else + # Deep duplicate representation to avoid alias nodes + ir = deep_dup(ir) + end + visitor = Representation::IRTree.create(options) + visitor << ir + visitor.tree.yaml + end + module_function :dump + + def deep_dup(obj) + case obj + when Array then obj.map {|e| deep_dup(e)} + when Hash then obj.inject({}) {|memo, (k,v)| memo.merge(k => deep_dup(v))} + else obj # No need to de-dup + end + end + module_function :deep_dup + + ## + # Transform a Psych::Nodes::Node to the JSON-LD Internal Representation + # + # @param [Psych::Nodes::Node] node + # @param [Hash{Symbol => Object}] options + # @option options [Boolean] :extendedYAML (false) + # Use the expanded internal representation. + # @return [Object] + def as_jsonld_ir(node, **options) + # Scans scalars for built-in classes + @ss ||= Psych::ScalarScanner.new(Psych::ClassLoader::Restricted.new([], %i())) + case node + when Psych::Nodes::Stream + node.children.map {|n| as_jsonld_ir(n, **options)} + when Psych::Nodes::Document then as_jsonld_ir(node.children.first, **options) + when Psych::Nodes::Sequence then node.children.map {|n| as_jsonld_ir(n, **options)} + when Psych::Nodes::Mapping + node.children.each_slice(2).inject({}) do |memo, (k,v)| + memo.merge(as_jsonld_ir(k) => as_jsonld_ir(v, **options)) + end + when ::Psych::Nodes::Scalar then scan_scalar(node, **options) + when ::Psych::Nodes::Alias + # FIXME + end + end + module_function :as_jsonld_ir + + ## + # Scans a scalar value to a JSON-LD IR scalar value. + # Quoted scalar values are not interpreted. + # + # @param [Psych::Nodes::Scalar] node + # @param [Hash{Symbol => Object}] options + # @option options [Boolean] :extendedYAML (false) + # Use the expanded internal representation. + # @return [Object] + def scan_scalar(node, **options) + return node.value if node.quoted # No interpretation + case node.tag + when "", NilClass + # Tokenize, but prohibit certain types + case node.value + # Don't scan some scalar values to types other than string + when Psych::ScalarScanner::TIME, + /^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/, # Date + /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}$/, # Time to seconds + /^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}\.[0-9_]*$/ # Time to seconds + node.value + else + @ss.tokenize(node.value) + end + when '!str', 'tag:yaml.org,2002:str' + node.value + when '!int', 'tag:yaml.org,2002:int' + Integer(node.value) + when "!float", "tag:yaml.org,2002:float" + Float(@ss.tokenize(node.value)) + when "!null", "tag:yaml.org,2002:null" + nil + when "!bool", "tag:yaml.org,2002:bool" + node.value.downcase == 'true' + when %r(^https://www.w3.org/ns/i18n) + l_d = node.tag[26..-1] + l_d = l_d[1..-1] if l_d.start_with?('#') + l, d = l_d.split('_') + if !options[:extendedYAML] + (node.style== Psych::Nodes::Scalar::PLAIN ? @ss.tokenize(node.value) : node.value) + elsif d.nil? + # Just use language component + RDF::Literal(node.value, language: l) + else + # Language and direction + RDF::Literal(node.value, datatype: RDF::URI("https://www.w3.org/ns/i18n##{l_d}")) + end + else + tag = node.tag + options[:extendedYAML] ? + RDF::Literal(node.value, datatype: RDF::URI(tag), validate: true) : + (node.style== Psych::Nodes::Scalar::PLAIN ? @ss.tokenize(node.value) : node.value) + end + end + module_function :scan_scalar + + ## + # Build a YAML AST with an RDF::Literal visitor + # + # + # builder = Psych::Visitors::YAMLTree.new + # builder << { :foo => 'bar' } + # builder.tree # => # "..." + # + # @note Rails 6.0 not compatible with Psych 4.0, which defines `RestrictedYamlTree`. + class IRTree < (Psych::Visitors.const_defined?(:RestrictedYamlTree) ? Psych::Visitors::RestrictedYAMLTree : Psych::Visitors::YAMLTree) + ## + # Retrive the literals from an object + def datatypes(object) + case object + when Array then object.inject([]) {|memo, o| memo += datatypes(o)} + when Hash then object.values.inject([]) {|memo, o| memo += datatypes(o)} + when RDF::Literal then object.datatype? ? [object.datatype] : [] + else [] + end + end + + ## + # Add the object to be emitted. If the `:extended` option is set when the visitor is created, tags are added to the document. + def push object + start unless started? + version = [] + version = [1,1] if @options[:header] + + case @options[:version] + when Array + version = @options[:version] + when String + version = @options[:version].split('.').map { |x| x.to_i } + else + version = [1,1] + end if @options.key? :version + + dts = datatypes(object).uniq + tags = dts.inject({}) do |memo, dt| + # Find the most suitable voabulary, if any + if memo.keys.any? {|u| dt.to_s.start_with?(u)} + memo + # Already have a prefix for this + elsif vocab = RDF::Vocabulary.each.to_a.detect {|v| dt.to_s.index(v.to_uri.to_s) == 0} + # Index by vocabulary URI + memo.merge(vocab.to_uri.to_s => "!#{vocab.__prefix__}!") + else + memo + end + end + + @emitter.start_document version, tags.invert.to_a, false + accept object + @emitter.end_document !@emitter.streaming? + end + alias :<< :push + + ## + # Emit an RDF Literal. If `extended` is set, use the datatype as an tag, + # otherwise, emit in expanded form. + def visit_RDF_Literal o + tag = case o.datatype + when nil then nil + when RDF::XSD.string then nil + when RDF.langString + "https://www.w3.org/ns/i18n##{o.language}" if @options[:extendedYAML] + else + if o.datatype.to_s.start_with?('https://www.w3.org/ns/i18n#') + o.datatype.to_s if @options[:extendedYAML] + elsif @options[:extendedYAML] + o.datatype.to_s + else + nil + end + end + formatted = o.value + register o, @emitter.scalar(formatted, nil, tag, false, false, Psych::Nodes::Scalar::PLAIN) + end + end + end +end \ No newline at end of file diff --git a/lib/yaml_ld/version.rb b/lib/yaml_ld/version.rb index a23b982..4d7414c 100644 --- a/lib/yaml_ld/version.rb +++ b/lib/yaml_ld/version.rb @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- # frozen_string_literal: true module YAML_LD::VERSION - VERSION_FILE = File.join(File.expand_path(File.dirname(__FILE__)), "..", "..", "..", "VERSION") + VERSION_FILE = File.expand_path("../../../VERSION", __FILE__) MAJOR, MINOR, TINY, EXTRA = File.read(VERSION_FILE).chomp.split(".") STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.') diff --git a/script/parse b/script/parse new file mode 100755 index 0000000..933e9aa --- /dev/null +++ b/script/parse @@ -0,0 +1,189 @@ +#!/usr/bin/env ruby +require 'rubygems' +require "bundler/setup" +$:.unshift(File.expand_path("../../lib", __FILE__)) +require 'rdf/turtle' +begin + require 'linkeddata' +rescue LoadError +end +require 'logger' +require 'yaml_ld' +require 'getoptlong' +require 'open-uri' +require 'ruby-prof' +require 'amazing_print' +require 'psych/amazing_print' + +def run(input, options) + if options[:profile] + output_dir = File.expand_path("../../doc/profiles/#{File.basename __FILE__, ".rb"}", __FILE__) + FileUtils.mkdir_p(output_dir) + profile = RubyProf::Profile.new + profile.exclude_methods!(Array, :each, :map) + profile.exclude_method!(Hash, :each) + profile.exclude_method!(Kernel, :require) + profile.exclude_method!(Object, :run) + profile.exclude_common_methods! + profile.start + run(input, **options.merge(profile: false)) + result = profile.stop + + # Print a graph profile to text + printer = RubyProf::MultiPrinter.new(result) + printer.print(path: output_dir, profile: "profile") + puts "output saved in #{output_dir}" + return + end + reader_class = RDF::Reader.for(options[:input_format].to_sym) + raise "Reader not found for #{options[:input_format]}" unless reader_class + + start = Time.new + if options[:flatten] + output = YAML_LD::API.flatten(input, options.delete(:context), **options) + secs = Time.new - start + options[:output].puts output + puts "Flattened in #{secs} seconds." + elsif options[:expand] + options = options.merge(expandContext: options.delete(:context)) if options.key?(:context) + output = YAML_LD::API.expand(input, **options) + secs = Time.new - start + options[:output].puts output + puts "Expanded in #{secs} seconds." + elsif options[:compact] + output = YAML_LD::API.compact(input, options[:context], **options) + secs = Time.new - start + options[:output].puts output + puts "Compacted in #{secs} seconds." + elsif options[:frame] + output = YAML_LD::API.frame(input, options[:frame], **options) + secs = Time.new - start + options[:output].puts output + puts "Framed in #{secs} seconds." + elsif options[:translate] && %i(jsonld yamlld).include?(options[:output_format]) + # Translate between formats + ir = YAML_LD::Representation.load(input, **options[:parser_options]) + options[:output].puts( + options[:output_format] == :jsonld ? + ir.to_json(JSON::LD::JSON_STATE) : + YAML_LD::Representation.dump(ir, version: [1,2], **options[:parser_options]) + ) + else + r = reader_class.new(input, **options[:parser_options]) + if options[:output_format] == :none + num = 0 + r.each_statement { num += 1 } # Eat statements + secs = Time.new - start + else + g = RDF::Repository.new << r + secs = Time.new - start + num = g.count + parser_options = options[:parser_options].merge(prefixes: r.prefixes, standard_prefixes: true) + options[:output].puts g.dump(options[:output_format], **parser_options) + puts + end + puts "Parsed #{num} statements in #{secs} seconds @ #{num/secs} statements/second." + end +rescue + fname = input.respond_to?(:path) ? input.path : "-stdin-" + STDERR.puts("Error in #{fname}") + raise +end + +logger = Logger.new(STDERR) +logger.level = Logger::WARN +logger.formatter = lambda {|severity, datetime, progname, msg| "#{severity}: #{msg}\n"} + +parser_options = { + base: nil, + progress: false, + profile: false, + validate: false, + logger: logger, +} + +options = { + parser_options: parser_options, + output: STDOUT, + output_format: :turtle, + input_format: :yamlld, + logger: logger, +} +input = nil + +OPT_ARGS = [ + ["--debug", GetoptLong::NO_ARGUMENT, "Debug output"], + ["--compact", GetoptLong::NO_ARGUMENT, "Compact input, using context"], + ["--context", GetoptLong::REQUIRED_ARGUMENT, "Context used for compaction"], + ["--execute", "-e", GetoptLong::REQUIRED_ARGUMENT, "Use option argument as the patch input"], + ["--expand", GetoptLong::NO_ARGUMENT, "Expand input"], + ["--expanded", GetoptLong::NO_ARGUMENT, "Input is already expanded"], + ["--extended", GetoptLong::NO_ARGUMENT, "Parse/emit extended YAML-LD"], + ["--flatten", GetoptLong::NO_ARGUMENT, "Flatten input"], + ["--format", GetoptLong::REQUIRED_ARGUMENT, "Output format, for RDF output"], + ["--frame", GetoptLong::REQUIRED_ARGUMENT, "Frame input, option value is frame to use"], + ["--help", "-?", GetoptLong::NO_ARGUMENT, "This message"], + ["--input-format", GetoptLong::REQUIRED_ARGUMENT, "Format of input, if not YAML-LD"], + ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT, "Where to store output (default STDOUT)"], + ["--profile", GetoptLong::NO_ARGUMENT, "Run profiler with output to doc/profiles/"], + ["--quiet", GetoptLong::NO_ARGUMENT, "Reduce output"], + ["--rdfstar", GetoptLong::NO_ARGUMENT, "RDF-star mode"], + ["--translate", GetoptLong::NO_ARGUMENT, "Translate between JSON and YAML formats"], + ["--uri", GetoptLong::REQUIRED_ARGUMENT, "Run with argument value as base"], + ["--validate", GetoptLong::NO_ARGUMENT, "Validate input"], +] + +opts = GetoptLong.new(*OPT_ARGS.map {|o| o[0..-2]}) + +def usage + STDERR.puts %{Usage: #{$0} [options] file ...} + width = OPT_ARGS.map do |o| + l = o.first.length + l += o[1].length + 2 if o[1].is_a?(String) + l + end.max + OPT_ARGS.each do |o| + s = " %-*s " % [width, (o[1].is_a?(String) ? "#{o[0,2].join(', ')}" : o[0])] + s += o.last + STDERR.puts s + end + exit(1) +end + +opts.each do |opt, arg| + case opt + when '--debug' then logger.level = Logger::DEBUG + when '--compact' then options[:compact] = true + when '--context' then options[:context] = arg + when '--execute' then input = arg + when '--expand' then options[:expand] = true + when '--expanded' then options[:expanded] = true + when '--extended' then parser_options.merge!(extendedYAML: true) + when '--flatten' then options[:flatten] = true + when '--format' then options[:output_format] = arg.to_sym + when '--frame' then options[:frame] = arg + when "--help" then usage + when '--input-format' then options[:input_format] = arg.to_sym + when '--output' then options[:output] = File.open(arg, "w") + when '--profile' then options[:profile] = true + when '--quiet' + options[:quiet] = true + logger.level = Logger::FATAL + when '--rdfstar' then parser_options[:rdfstar] = true + when '--translate' then options[:translate] = true + when '--uri' then parser_options[:base] = arg + when '--validate' then parser_options[:validate] = true + end +end + +if ARGV.empty? + s = input ? input : $stdin.read + run(StringIO.new(s), options) +else + ARGV.each do |test_file| + io = Kernel.open(test_file) + io.define_singleton_method(:content_type) {"text/html"} if test_file.end_with?('.html') + run(io, options) + end +end +puts diff --git a/spec/api_spec.rb b/spec/api_spec.rb index bbada1a..ee600d5 100644 --- a/spec/api_spec.rb +++ b/spec/api_spec.rb @@ -10,12 +10,11 @@ %i(psych).each do |adapter| Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), 'test-files/*-input.*'))) do |filename| test = File.basename(filename).sub(/-input\..*$/, '') - frame = filename.sub(/-input\..*$/, '-frame.jsonld') - framed = filename.sub(/-input\..*$/, '-framed.jsonld') - compacted = filename.sub(/-input\..*$/, '-compacted.jsonld') - context = filename.sub(/-input\..*$/, '-context.jsonld') - expanded = filename.sub(/-input\..*$/, '-expanded.jsonld') - expanded_yaml = filename.sub(/-input\..*$/, '-expanded.yamlld') + frame = filename.sub(/-input\..*$/, '-frame.yamlld') + framed = filename.sub(/-input\..*$/, '-framed.yamlld') + compacted = filename.sub(/-input\..*$/, '-compacted.yamlld') + context = filename.sub(/-input\..*$/, '-context.yamlld') + expanded = filename.sub(/-input\..*$/, '-expanded.yamlld') ttl = filename.sub(/-input\..*$/, '-rdf.ttl') context test do @@ -27,7 +26,7 @@ when /.jsonld$/ @file.define_singleton_method(:content_type) {'application/ld+json'} end - if context + if File.exist?(context) @ctx_io = File.open(context) case context when /\.yamlld$/ @@ -36,9 +35,19 @@ @ctx_io.define_singleton_method(:content_type) {'application/ld+json'} end end + if File.exist?(frame) + @frame_io = File.open(frame) + case frame + when /\.yamlld$/ + @frame_io.define_singleton_method(:content_type) {'application/ld+yaml'} + when /.jsonld$/ + @frame_io.define_singleton_method(:content_type) {'application/ld+json'} + end + end example.run @file.close @ctx_io.close if @ctx_io + @frame_io.close if @frame_io end if File.exist?(expanded) @@ -47,18 +56,7 @@ options[:expandContext] = @ctx_io if context yaml = described_class.expand(@file, **options) expect(yaml).to be_a(String) - parsed_json = JSON.parse(File.read(expanded)) - expect(yaml).to produce_yamlld(parsed_json, logger) - end - end - - if File.exist?(expanded_yaml) - it "expands to YAML" do - options = {logger: logger, adapter: adapter} - options[:expandContext] = @ctx_io if context - yaml = described_class.expand(@file, **options) - expect(yaml).to be_a(String) - expect(yaml).to produce_yamlld(YAML.load_file(expanded_yaml), logger) + expect(yaml).to produce_yamlld(File.read(expanded), logger) end end @@ -66,19 +64,15 @@ it "compacts" do yaml = described_class.compact(@file, @ctx_io, adapter: adapter, logger: logger) expect(yaml).to be_a(String) - parsed_json = JSON.parse(File.read(compacted)) - expect(yaml).to produce_yamlld(parsed_json, logger) + expect(yaml).to produce_yamlld(File.read(compacted), logger) end end if File.exist?(framed) && File.exist?(frame) it "frames" do - File.open(frame) do |frame_io| - yaml = described_class.frame(@file, frame_io, adapter: adapter, logger: logger) - expect(yaml).to be_a(String) - parsed_json = JSON.parse(File.read(framed)) - expect(yaml).to produce_yamlld(parsed_json, logger) - end + yaml = described_class.frame(@file, @frame_io, adapter: adapter, logger: logger) + expect(yaml).to be_a(String) + expect(yaml).to produce_yamlld(File.read(framed), logger) end end diff --git a/spec/compact_spec.rb b/spec/compact_spec.rb index 806c5f3..ec073db 100644 --- a/spec/compact_spec.rb +++ b/spec/compact_spec.rb @@ -328,7 +328,7 @@ ) }, }.each do |title, params| - it(title) {run_compact(processingMode: 'json-ld-1.1', **params)} + it(title) {run_compact(**params)} end end end diff --git a/spec/expand_spec.rb b/spec/expand_spec.rb index 2dffe16..b214744 100644 --- a/spec/expand_spec.rb +++ b/spec/expand_spec.rb @@ -118,7 +118,7 @@ output: %( - http://example.com/p: - "@id": http://example.com/Sub1 - }]) + ) }, }.each_pair do |title, params| it(title) {run_expand params} diff --git a/spec/format_spec.rb b/spec/format_spec.rb index 3937edd..c5d2287 100644 --- a/spec/format_spec.rb +++ b/spec/format_spec.rb @@ -51,4 +51,60 @@ describe "#to_uri" do specify {expect(described_class.to_uri).to eq RDF::URI('http://www.w3.org/ns/formats/YAML-LD')} end + + describe ".cli_commands", skip: Gem.win_platform? do + require 'rdf/cli' + let(:ttl) {File.expand_path("../test-files/test-1-rdf.ttl", __FILE__)} + let(:yaml) {File.expand_path("../test-files/test-1-input.yamlld", __FILE__)} + let(:json) {File.expand_path("../test-files/test-1-compacted.jsonld", __FILE__)} + let(:context) {File.expand_path("../test-files/test-1-context.jsonld", __FILE__)} + + describe "#expand" do + it "expands RDF" do + expect {RDF::CLI.exec(["expand", ttl], format: :ttl, output_format: :yamlld)}.to write.to(:output) + end + it "expands JSON" do + expect {RDF::CLI.exec(["expand", json], format: :jsonld, output_format: :yamlld, validate: false)}.to write.to(:output) + end + it "expands YAML" do + expect {RDF::CLI.exec(["expand", yaml], format: :yamlld, output_format: :yamlld, validate: false)}.to write.to(:output) + end + end + + describe "#compact" do + it "compacts RDF" do + expect {RDF::CLI.exec(["compact", ttl], context: context, format: :ttl, output_format: :yamlld, validate: false)}.to write.to(:output) + end + it "compacts JSON" do + expect {RDF::CLI.exec(["compact", json], context: context, format: :jsonld, output_format: :yamlld, validate: false)}.to write.to(:output) + end + it "compacts YAML" do + expect {RDF::CLI.exec(["compact", yaml], context: context, format: :yamlld, output_format: :yamlld, validate: false)}.to write.to(:output) + end + end + + describe "#flatten" do + it "flattens RDF" do + expect {RDF::CLI.exec(["flatten", ttl], context: context, format: :ttl, output_format: :yamlld, validate: false)}.to write.to(:output) + end + it "flattens JSON" do + expect {RDF::CLI.exec(["flatten", json], context: context, format: :jsonld, output_format: :yamlld, validate: false)}.to write.to(:output) + end + it "flattens YAML" do + expect {RDF::CLI.exec(["flatten", yaml], context: context, format: :yamlld, output_format: :yamlld, validate: false)}.to write.to(:output) + end + end + + describe "#frame" do + it "frames RDF" do + expect {RDF::CLI.exec(["frame", ttl], frame: context, format: :ttl, output_format: :yamlld)}.to write.to(:output) + end + it "frames JSON" do + expect {RDF::CLI.exec(["frame", json], frame: context, format: :jsonld, output_format: :yamlld, validate: false)}.to write.to(:output) + end + it "frames YAML" do + expect {RDF::CLI.exec(["frame", yaml], frame: context, format: :yamlld, output_format: :yamlld, validate: false)}.to write.to(:output) + end + end + end end diff --git a/spec/frame_spec.rb b/spec/frame_spec.rb index d471243..30d3f54 100644 --- a/spec/frame_spec.rb +++ b/spec/frame_spec.rb @@ -22,12 +22,11 @@ "@id": ex:Sub2 "@type": ex:Type2 ), - output: %(--- + output: %( "@context": ex: http://example.org/ - "@graph": - - "@id": ex:Sub1 - "@type": ex:Type1 + "@id": ex:Sub1 + "@type": ex:Type1 ) }, "wildcard @type match": { @@ -76,9 +75,8 @@ output: %( "@context": ex: http://example.org/ - "@graph": - - "@id": ex:Sub2 - ex:p: Bar + "@id": ex:Sub2 + ex:p: Bar ) }, "multiple matches on @type": { @@ -136,9 +134,8 @@ output: %( "@context": ex: http://example.org/ - "@graph": - - "@id": ex:Sub1 - "@type": ex:Type1 + "@id": ex:Sub1 + "@type": ex:Type1 ) }, "multiple @id match": { @@ -194,10 +191,9 @@ output: %( "@context": ex: http://example.org/ - "@graph": - - "@id": ex:Sub1 - ex:p: - ex:q: bar + "@id": ex:Sub1 + ex:p: + ex:q: bar ) }, "match on any property if @requireAll is false": { @@ -308,8 +304,7 @@ familyName: Doe givenName: John name: John Doe - ), - processingMode: 'json-ld-1.1' + ) }, "mixed content": { frame: %( @@ -329,11 +324,10 @@ output: %( "@context": ex: http://example.org/ - "@graph": - - "@id": ex:Sub1 - ex:mixed: - - "@id": ex:Sub2 - - literal1 + "@id": ex:Sub1 + ex:mixed: + - "@id": ex:Sub2 + - literal1 ) }, "framed list": { @@ -365,13 +359,12 @@ list: "@id": ex:list "@container": "@list" - "@graph": - - "@id": ex:Sub1 - "@type": ex:Type1 - list: - - "@id": ex:Sub2 - "@type": ex:Element - - literal1 + "@id": ex:Sub1 + "@type": ex:Type1 + list: + - "@id": ex:Sub2 + "@type": ex:Element + - literal1 ) }, "presentation example": { @@ -410,12 +403,11 @@ sameAs: "@id": http://www.w3.org/2002/07/owl#sameAs "@type": "@id" - "@graph": - - "@id": http://en.wikipedia.org/wiki/Linked_Data - primaryTopic: - "@id": http://dbpedia.org/resource/Linked_Data - "@type": http://dbpedia.org/class/yago/Buzzwords - sameAs: http://rdf.freebase.com/ns/m/02r2kb1 + "@id": http://en.wikipedia.org/wiki/Linked_Data + primaryTopic: + "@id": http://dbpedia.org/resource/Linked_Data + "@type": http://dbpedia.org/class/yago/Buzzwords + sameAs: http://rdf.freebase.com/ns/m/02r2kb1 ) }, "library": { @@ -455,20 +447,19 @@ xsd: http://www.w3.org/2001/XMLSchema# ex:contains: "@type": "@id" - "@graph": - - "@id": http://example.org/library - "@type": ex:Library - dc:name: Library + "@id": http://example.org/library + "@type": ex:Library + dc:name: Library + ex:contains: + "@id": http://example.org/library/the-republic + "@type": ex:Book + dc:creator: Plato + dc:title: The Republic ex:contains: - "@id": http://example.org/library/the-republic - "@type": ex:Book - dc:creator: Plato - dc:title: The Republic - ex:contains: - "@id": http://example.org/library/the-republic#introduction - "@type": ex:Chapter - dc:description: An introductory chapter on The Republic. - dc:title: The Introduction + "@id": http://example.org/library/the-republic#introduction + "@type": ex:Chapter + dc:description: An introductory chapter on The Republic. + dc:title: The Introduction ) } }.each do |title, params| @@ -502,15 +493,14 @@ output: %( "@context": ex: http://example.org/ - "@graph": - - "@id": ex:Sub1 - "@type": ex:Type1 - "@reverse": + "@id": ex:Sub1 + "@type": ex:Type1 + "@reverse": + ex:includes: + "@id": ex:Sub2 + "@type": ex:Type2 ex:includes: - "@id": ex:Sub2 - "@type": ex:Type2 - ex:includes: - "@id": ex:Sub1 + "@id": ex:Sub1 ) }, "embed matched frames with reversed property": { @@ -539,14 +529,13 @@ ex: http://example.org/ excludes: "@reverse": ex:includes - "@graph": - - "@id": ex:Sub1 - "@type": ex:Type1 - excludes: - "@id": ex:Sub2 - "@type": ex:Type2 - ex:includes: - "@id": ex:Sub1 + "@id": ex:Sub1 + "@type": ex:Type1 + excludes: + "@id": ex:Sub2 + "@type": ex:Type2 + ex:includes: + "@id": ex:Sub1 ) }, }.each do |title, params| @@ -558,27 +547,7 @@ context "omitGraph option" do { - "Defaults to false in 1.0": { - input: %( - - http://example.org/prop: - - "@value": value - http://example.org/foo: - - "@value": bar - ), - frame: %( - "@context": - "@vocab": http://example.org/ - ), - output: %( - "@context": - "@vocab": http://example.org/ - "@graph": - - foo: bar - prop: value - ), - processingMode: "json-ld-1.0" - }, - "Set with option in 1.0": { + "Defaults to true": { input: %( - http://example.org/prop: - "@value": value @@ -594,30 +563,9 @@ "@vocab": http://example.org/ foo: bar prop: value - ), - processingMode: "json-ld-1.0", - omitGraph: true - }, - "Defaults to true in 1.1": { - input: %( - - http://example.org/prop: - - "@value": value - http://example.org/foo: - - "@value": bar - ), - frame: %( - "@context": - "@vocab": http://example.org/ - ), - output: %( - "@context": - "@vocab": http://example.org/ - foo: bar - prop: value - ), - processingMode: "json-ld-1.1" + ) }, - "Set with option in 1.1": { + "Set with option": { input: %( - http://example.org/prop: - "@value": value @@ -635,7 +583,6 @@ - foo: bar prop: value ), - processingMode: "json-ld-1.1", omitGraph: false }, }.each do |title, params| @@ -643,11 +590,9 @@ end end end - def do_frame(params) begin input, frame, output = params[:input], params[:frame], params[:output] - params = {processingMode: 'json-ld-1.0'}.merge(params) input = StringIO.new(input) if input.is_a?(String) frame = StringIO.new(frame) if frame.is_a?(String) yld = nil diff --git a/spec/from_rdf_spec.rb b/spec/from_rdf_spec.rb index 851c133..4931b51 100644 --- a/spec/from_rdf_spec.rb +++ b/spec/from_rdf_spec.rb @@ -296,7 +296,7 @@ }, }.each do |title, params| params[:input] = RDF::Graph.new << RDF::Turtle::Reader.new(params[:input]) - it(title) {do_fromRdf(processingMode: "json-ld-1.1", **params)} + it(title) {do_fromRdf(**params)} end end end @@ -442,7 +442,7 @@ } }.each_pair do |name, params| it name do - do_fromRdf(params.merge(reader: RDF::Turtle::Reader, rdfDirection: 'i18n-datatype', processingMode: 'json-ld-1.1')) + do_fromRdf(params.merge(reader: RDF::Turtle::Reader, rdfDirection: 'i18n-datatype')) end end end @@ -505,7 +505,7 @@ } }.each_pair do |name, params| it name do - do_fromRdf(params.merge(reader: RDF::Turtle::Reader, rdfDirection: 'compound-literal', processingMode: 'json-ld-1.1')) + do_fromRdf(params.merge(reader: RDF::Turtle::Reader, rdfDirection: 'compound-literal')) end end end diff --git a/spec/matchers.rb b/spec/matchers.rb index ad18603..79b2617 100644 --- a/spec/matchers.rb +++ b/spec/matchers.rb @@ -3,8 +3,8 @@ RSpec::Matchers.define :produce_yamlld do |expected, logger| match do |actual| - actual = Psych.load(actual, aliases: true) if actual.is_a?(String) - expected = Psych.load(expected, aliases: true) if expected.is_a?(String) + actual = YAML_LD::Representation.load(actual, aliases: true) if actual.is_a?(String) + expected = YAML_LD::Representation.load(expected, aliases: true) if expected.is_a?(String) expect(actual).to be_equivalent_structure expected end diff --git a/spec/reader_spec.rb b/spec/reader_spec.rb index 849f5fa..28d6fa4 100644 --- a/spec/reader_spec.rb +++ b/spec/reader_spec.rb @@ -58,7 +58,7 @@ http://example.com/bob/ "foaf:name": Bob - }), + ), leading_comment: %q(--- # A comment before content "@context": diff --git a/spec/representation_spec.rb b/spec/representation_spec.rb new file mode 100644 index 0000000..f854e30 --- /dev/null +++ b/spec/representation_spec.rb @@ -0,0 +1,170 @@ +# coding: utf-8 +require_relative 'spec_helper' + +describe YAML_LD::Representation do + describe "load_stream" do + { + "Stream": { + input: %( + --- + a + ... + --- + b + ... + ), + expected: %w(a b) + }, + "String": { + input: %(a), + expected: %w(a) + }, + }.each do |name, params| + it name do + input = params[:input] + ir = YAML_LD::Representation.load_stream(input.unindent.strip) + expected = params[:expected] + expect(ir).to be_equivalent_structure expected + end + end + end + + describe "load" do + { + "Stream": { + input: %( + --- + a + ... + --- + b + ... + ), + expected: "a" + }, + "Null": { + input: %(null), + expected: nil + }, + "!!null null": { + input: %(!!null null), + expected: nil + }, + "! null": { + input: %(! null), + expected: nil + }, + "Boolean": { + input: %(true), + expected: true + }, + "!!bool true": { + input: %(!!bool true), + expected: true + }, + "! true": { + input: %(! true), + expected: true + }, + "String": { + input: %(a), + expected: "a" + }, + "Tagged !!str String": { + input: %(!!str string), + expected: "string" + }, + "Tagged ! String": { + input: %(! string), + expected: "string" + }, + "Integer": { + input: %(1), + expected: 1 + }, + "Tagged !!int 1": { + input: %(!!int 1), + expected: 1 + }, + "Tagged ! 1": { + input: %(! 1), + expected: 1 + }, + "Float": { + input: %(1.0), + expected: Float(1.0) + }, + "Tagged !!float -1": { + input: %(!!float -1), + expected: Float(-1) + }, + "Tagged ! 2.3e4": { + input: %(! 2.3e4), + expected: Float(2.3e4) + }, + "Tagged ! .inf": { + input: %(! .inf), + expected: Float::INFINITY + }, + }.each do |name, params| + it name do + input = params[:input] + ir = YAML_LD::Representation.load(input.unindent.strip) + expected = params[:expected] + expect(ir).to be_equivalent_structure expected + end + end + + { + "! 123": { + input: %(! 123), + xsd: RDF::Literal("123", datatype: "http://www.w3.org/2001/XMLSchema#integer"), + plain: 123 + }, + "! 123.456": { + input: %(! 123.456), + xsd: RDF::Literal("123.456", datatype: "http://www.w3.org/2001/XMLSchema#decimal"), + plain: 123.456 + }, + "! 123.456e78": { + input: %(! 123.456e+78), + xsd: RDF::Literal("123.456e+78", datatype: "http://www.w3.org/2001/XMLSchema#double"), + plain: 123.456e+78 + }, + "! true": { + input: %(! true), + xsd: RDF::Literal("true", datatype: "http://www.w3.org/2001/XMLSchema#boolean"), + plain: true + }, + "! 2022-08-17": { + input: %(! "2022-08-17"), + xsd: RDF::Literal("2022-08-17", datatype: "http://www.w3.org/2001/XMLSchema#date"), + plain: "2022-08-17" + }, + "! 12:00:00.000": { + input: %(! "12:00:00.000"), + xsd: RDF::Literal("12:00:00.000", datatype: "http://www.w3.org/2001/XMLSchema#time"), + plain: "12:00:00.000" + }, + "! 2022-08-17T12:00:00.000": { + input: %(! "2022-08-17T12:00:00.000"), + xsd: RDF::Literal("2022-08-17T12:00:00.000", datatype: "http://www.w3.org/2001/XMLSchema#dateTime"), + plain: "2022-08-17T12:00:00.000" + }, + }.each do |name, params| + it "#{name} with xsd" do + input = params[:input] + ir = YAML_LD::Representation.load(input.unindent.strip, extendedYAML: true) + expected = params[:xsd] + expect(ir).to be_equivalent_structure expected + end + + it "#{name} without xsd" do + input = params[:input] + ir = YAML_LD::Representation.load(input.unindent.strip, extendedYAML: false) + expected = params[:plain] + expect(ir).to be_equivalent_structure expected + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8e2eb09..1783579 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,6 +12,7 @@ require 'rdf/spec' require 'rdf/spec/matchers' require_relative 'matchers' +require_relative '../lib/psych/amazing_print' require 'yaml' begin require 'simplecov' diff --git a/spec/test-files/test-1-compacted.yamlld b/spec/test-files/test-1-compacted.yamlld new file mode 100644 index 0000000..cd27a19 --- /dev/null +++ b/spec/test-files/test-1-compacted.yamlld @@ -0,0 +1,9 @@ +%YAML 1.2 +--- +"@context": + avatar: http://xmlns.com/foaf/0.1/avatar + homepage: http://xmlns.com/foaf/0.1/homepage + name: http://xmlns.com/foaf/0.1/name +avatar: http://twitter.com/account/profile_image/manusporny +homepage: http://manu.sporny.org/ +name: Manu Sporny diff --git a/spec/test-files/test-1-context.yamlld b/spec/test-files/test-1-context.yamlld new file mode 100644 index 0000000..7492c03 --- /dev/null +++ b/spec/test-files/test-1-context.yamlld @@ -0,0 +1,6 @@ +%YAML 1.2 +--- +"@context": + name: http://xmlns.com/foaf/0.1/name + homepage: http://xmlns.com/foaf/0.1/homepage + avatar: http://xmlns.com/foaf/0.1/avatar diff --git a/spec/test-files/test-1-expanded.jsonld b/spec/test-files/test-1-expanded.jsonld deleted file mode 100644 index 94074de..0000000 --- a/spec/test-files/test-1-expanded.jsonld +++ /dev/null @@ -1,5 +0,0 @@ -[{ - "http://xmlns.com/foaf/0.1/name": [{"@value": "Manu Sporny"}], - "http://xmlns.com/foaf/0.1/homepage": [{"@value": "http://manu.sporny.org/"}], - "http://xmlns.com/foaf/0.1/avatar": [{"@value": "http://twitter.com/account/profile_image/manusporny"}] -}] \ No newline at end of file diff --git a/spec/test-files/test-1-expanded.yamlld b/spec/test-files/test-1-expanded.yamlld new file mode 100644 index 0000000..7329764 --- /dev/null +++ b/spec/test-files/test-1-expanded.yamlld @@ -0,0 +1,8 @@ +%YAML 1.2 +--- +- http://xmlns.com/foaf/0.1/name: + - "@value": Manu Sporny + http://xmlns.com/foaf/0.1/homepage: + - "@value": http://manu.sporny.org/ + http://xmlns.com/foaf/0.1/avatar: + - "@value": http://twitter.com/account/profile_image/manusporny diff --git a/spec/test-files/test-1-input.yamlld b/spec/test-files/test-1-input.yamlld index eb2d8f9..d4a05cb 100644 --- a/spec/test-files/test-1-input.yamlld +++ b/spec/test-files/test-1-input.yamlld @@ -1,3 +1,4 @@ +%YAML 1.2 --- "@context": name: http://xmlns.com/foaf/0.1/name diff --git a/spec/test-files/test-2-compacted.jsonld b/spec/test-files/test-2-compacted.jsonld deleted file mode 100644 index 6d8375f..0000000 --- a/spec/test-files/test-2-compacted.jsonld +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - }, - "@id": "http://example.org/library", - "@type": "ex:Library", - "ex:contains": { - "@id": "http://example.org/library/the-republic", - "@type": "ex:Book", - "dc:creator": "Plato", - "dc:title": "The Republic", - "ex:contains": { - "@id": "http://example.org/library/the-republic#introduction", - "@type": "ex:Chapter", - "dc:description": "An introductory chapter on The Republic.", - "dc:title": "The Introduction" - } - } -} diff --git a/spec/test-files/test-2-compacted.yamlld b/spec/test-files/test-2-compacted.yamlld new file mode 100644 index 0000000..12cb958 --- /dev/null +++ b/spec/test-files/test-2-compacted.yamlld @@ -0,0 +1,17 @@ +%YAML 1.2 +--- +"@context": + dc: http://purl.org/dc/elements/1.1/ + ex: http://example.org/vocab# +"@id": http://example.org/library +"@type": ex:Library +ex:contains: + "@id": http://example.org/library/the-republic + "@type": ex:Book + dc:creator: Plato + dc:title: The Republic + ex:contains: + "@id": http://example.org/library/the-republic#introduction + "@type": ex:Chapter + dc:description: An introductory chapter on The Republic. + dc:title: The Introduction diff --git a/spec/test-files/test-2-context.jsonld b/spec/test-files/test-2-context.jsonld deleted file mode 100644 index a5a152d..0000000 --- a/spec/test-files/test-2-context.jsonld +++ /dev/null @@ -1,7 +0,0 @@ -{ - "@context": { - "dc": "http://purl.org/dc/elements/1.1/", - "ex": "http://example.org/vocab#" - } - -} diff --git a/spec/test-files/test-2-context.yamlld b/spec/test-files/test-2-context.yamlld new file mode 100644 index 0000000..4574905 --- /dev/null +++ b/spec/test-files/test-2-context.yamlld @@ -0,0 +1,5 @@ +%YAML 1.2 +--- +"@context": + dc: http://purl.org/dc/elements/1.1/ + ex: http://example.org/vocab# diff --git a/spec/test-files/test-2-expanded.jsonld b/spec/test-files/test-2-expanded.jsonld deleted file mode 100644 index eddcb8f..0000000 --- a/spec/test-files/test-2-expanded.jsonld +++ /dev/null @@ -1,16 +0,0 @@ -[{ - "@id": "http://example.org/library", - "@type": ["http://example.org/vocab#Library"], - "http://example.org/vocab#contains": [{ - "@id": "http://example.org/library/the-republic", - "@type": ["http://example.org/vocab#Book"], - "http://purl.org/dc/elements/1.1/creator": [{"@value": "Plato"}], - "http://purl.org/dc/elements/1.1/title": [{"@value": "The Republic"}], - "http://example.org/vocab#contains": [{ - "@id": "http://example.org/library/the-republic#introduction", - "@type": ["http://example.org/vocab#Chapter"], - "http://purl.org/dc/elements/1.1/description": [{"@value": "An introductory chapter on The Republic."}], - "http://purl.org/dc/elements/1.1/title": [{"@value": "The Introduction"}] - }] - }] -}] \ No newline at end of file diff --git a/spec/test-files/test-2-expanded.yamlld b/spec/test-files/test-2-expanded.yamlld new file mode 100644 index 0000000..32f1570 --- /dev/null +++ b/spec/test-files/test-2-expanded.yamlld @@ -0,0 +1,21 @@ +%YAML 1.2 +--- +- "@id": http://example.org/library + "@type": + - http://example.org/vocab#Library + http://example.org/vocab#contains: + - "@id": http://example.org/library/the-republic + "@type": + - http://example.org/vocab#Book + http://purl.org/dc/elements/1.1/creator: + - "@value": Plato + http://purl.org/dc/elements/1.1/title: + - "@value": The Republic + http://example.org/vocab#contains: + - "@id": http://example.org/library/the-republic#introduction + "@type": + - http://example.org/vocab#Chapter + http://purl.org/dc/elements/1.1/description: + - "@value": An introductory chapter on The Republic. + http://purl.org/dc/elements/1.1/title: + - "@value": The Introduction diff --git a/spec/test-files/test-3-compacted.jsonld b/spec/test-files/test-3-compacted.jsonld deleted file mode 100644 index b0e56cb..0000000 --- a/spec/test-files/test-3-compacted.jsonld +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@context": { - "xsd": "http://www.w3.org/2001/XMLSchema#", - "name": "http://xmlns.com/foaf/0.1/name", - "age": {"@id": "http://xmlns.com/foaf/0.1/age", "@type": "xsd:integer"}, - "homepage": {"@id": "http://xmlns.com/foaf/0.1/homepage", "@type": "@id"} - }, - "name": "Manu Sporny", - "age": "41", - "homepage": "http://manu.sporny.org/" -} diff --git a/spec/test-files/test-3-compacted.yamlld b/spec/test-files/test-3-compacted.yamlld new file mode 100644 index 0000000..4a6f09d --- /dev/null +++ b/spec/test-files/test-3-compacted.yamlld @@ -0,0 +1,14 @@ +%YAML 1.2 +--- +"@context": + xsd: http://www.w3.org/2001/XMLSchema# + name: http://xmlns.com/foaf/0.1/name + age: + "@id": http://xmlns.com/foaf/0.1/age + "@type": xsd:integer + homepage: + "@id": http://xmlns.com/foaf/0.1/homepage + "@type": "@id" +name: Manu Sporny +age: '41' +homepage: http://manu.sporny.org/ diff --git a/spec/test-files/test-3-context.jsonld b/spec/test-files/test-3-context.jsonld deleted file mode 100644 index ddefcb4..0000000 --- a/spec/test-files/test-3-context.jsonld +++ /dev/null @@ -1,8 +0,0 @@ -{ - "@context": { - "xsd": "http://www.w3.org/2001/XMLSchema#", - "name": "http://xmlns.com/foaf/0.1/name", - "age": {"@id": "http://xmlns.com/foaf/0.1/age", "@type": "xsd:integer"}, - "homepage": {"@id": "http://xmlns.com/foaf/0.1/homepage", "@type": "@id"} - } -} diff --git a/spec/test-files/test-3-context.yamlld b/spec/test-files/test-3-context.yamlld new file mode 100644 index 0000000..68fc162 --- /dev/null +++ b/spec/test-files/test-3-context.yamlld @@ -0,0 +1,11 @@ +%YAML 1.2 +--- +"@context": + xsd: http://www.w3.org/2001/XMLSchema# + name: http://xmlns.com/foaf/0.1/name + age: + "@id": http://xmlns.com/foaf/0.1/age + "@type": xsd:integer + homepage: + "@id": http://xmlns.com/foaf/0.1/homepage + "@type": "@id" diff --git a/spec/test-files/test-3-expanded.jsonld b/spec/test-files/test-3-expanded.jsonld deleted file mode 100644 index 7efecd2..0000000 --- a/spec/test-files/test-3-expanded.jsonld +++ /dev/null @@ -1,10 +0,0 @@ -[{ - "http://xmlns.com/foaf/0.1/name": [{"@value": "Manu Sporny"}], - "http://xmlns.com/foaf/0.1/age": [{ - "@type": "http://www.w3.org/2001/XMLSchema#integer", - "@value": "41" - }], - "http://xmlns.com/foaf/0.1/homepage": [{ - "@id": "http://manu.sporny.org/" - }] -}] diff --git a/spec/test-files/test-3-expanded.yamlld b/spec/test-files/test-3-expanded.yamlld new file mode 100644 index 0000000..cde6e04 --- /dev/null +++ b/spec/test-files/test-3-expanded.yamlld @@ -0,0 +1,9 @@ +%YAML 1.2 +--- +- http://xmlns.com/foaf/0.1/name: + - "@value": Manu Sporny + http://xmlns.com/foaf/0.1/age: + - "@type": http://www.w3.org/2001/XMLSchema#integer + "@value": '41' + http://xmlns.com/foaf/0.1/homepage: + - "@id": http://manu.sporny.org/ diff --git a/spec/test-files/test-4-compacted.jsonld b/spec/test-files/test-4-compacted.jsonld deleted file mode 100644 index 6a2a054..0000000 --- a/spec/test-files/test-4-compacted.jsonld +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@context": { - "": "http://manu.sporny.org/", - "foaf": "http://xmlns.com/foaf/0.1/" - }, - "@id": ":#me", - "@type": "foaf:Person", - "foaf:name": "Manu Sporny", - "foaf:homepage": { "@id": "http://manu.sporny.org/" } -} diff --git a/spec/test-files/test-4-compacted.yamlld b/spec/test-files/test-4-compacted.yamlld new file mode 100644 index 0000000..a6aebd9 --- /dev/null +++ b/spec/test-files/test-4-compacted.yamlld @@ -0,0 +1,10 @@ +%YAML 1.2 +--- +"@context": + '': http://manu.sporny.org/ + foaf: http://xmlns.com/foaf/0.1/ +"@id": ":#me" +"@type": foaf:Person +foaf:name: Manu Sporny +foaf:homepage: + "@id": http://manu.sporny.org/ diff --git a/spec/test-files/test-4-context.jsonld b/spec/test-files/test-4-context.jsonld deleted file mode 100644 index fced953..0000000 --- a/spec/test-files/test-4-context.jsonld +++ /dev/null @@ -1,7 +0,0 @@ -{ - "@context": { - "": "http://manu.sporny.org/", - "foaf": "http://xmlns.com/foaf/0.1/" - } -} - diff --git a/spec/test-files/test-4-context.yamlld b/spec/test-files/test-4-context.yamlld new file mode 100644 index 0000000..5a2999d --- /dev/null +++ b/spec/test-files/test-4-context.yamlld @@ -0,0 +1,5 @@ +%YAML 1.2 +--- +"@context": + '': http://manu.sporny.org/ + foaf: http://xmlns.com/foaf/0.1/ diff --git a/spec/test-files/test-4-expanded.jsonld b/spec/test-files/test-4-expanded.jsonld deleted file mode 100644 index 119ca5d..0000000 --- a/spec/test-files/test-4-expanded.jsonld +++ /dev/null @@ -1,6 +0,0 @@ -[{ - "@id": "http://manu.sporny.org/#me", - "@type": ["http://xmlns.com/foaf/0.1/Person"], - "http://xmlns.com/foaf/0.1/name": [{"@value": "Manu Sporny"}], - "http://xmlns.com/foaf/0.1/homepage": [{ "@id": "http://manu.sporny.org/" }] -}] \ No newline at end of file diff --git a/spec/test-files/test-4-expanded.yamlld b/spec/test-files/test-4-expanded.yamlld new file mode 100644 index 0000000..7c4241c --- /dev/null +++ b/spec/test-files/test-4-expanded.yamlld @@ -0,0 +1,9 @@ +%YAML 1.2 +--- +- "@id": http://manu.sporny.org/#me + "@type": + - http://xmlns.com/foaf/0.1/Person + http://xmlns.com/foaf/0.1/name: + - "@value": Manu Sporny + http://xmlns.com/foaf/0.1/homepage: + - "@id": http://manu.sporny.org/ diff --git a/spec/test-files/test-5-compacted.jsonld b/spec/test-files/test-5-compacted.jsonld deleted file mode 100644 index 4f84d11..0000000 --- a/spec/test-files/test-5-compacted.jsonld +++ /dev/null @@ -1,13 +0,0 @@ -{ - "@context": { - "": "http://manu.sporny.org/", - "foaf": "http://xmlns.com/foaf/0.1/" - }, - "@id": ":#me", - "@type": "foaf:Person", - "foaf:name": "Manu Sporny", - "foaf:knows": { - "@type": "foaf:Person", - "foaf:name": "Gregg Kellogg" - } -} diff --git a/spec/test-files/test-5-compacted.yamlld b/spec/test-files/test-5-compacted.yamlld new file mode 100644 index 0000000..d95a04f --- /dev/null +++ b/spec/test-files/test-5-compacted.yamlld @@ -0,0 +1,11 @@ +%YAML 1.2 +--- +"@context": + '': http://manu.sporny.org/ + foaf: http://xmlns.com/foaf/0.1/ +"@id": ":#me" +"@type": foaf:Person +foaf:name: Manu Sporny +foaf:knows: + "@type": foaf:Person + foaf:name: Gregg Kellogg diff --git a/spec/test-files/test-5-context.jsonld b/spec/test-files/test-5-context.jsonld deleted file mode 100644 index fced953..0000000 --- a/spec/test-files/test-5-context.jsonld +++ /dev/null @@ -1,7 +0,0 @@ -{ - "@context": { - "": "http://manu.sporny.org/", - "foaf": "http://xmlns.com/foaf/0.1/" - } -} - diff --git a/spec/test-files/test-5-context.yamlld b/spec/test-files/test-5-context.yamlld new file mode 100644 index 0000000..5a2999d --- /dev/null +++ b/spec/test-files/test-5-context.yamlld @@ -0,0 +1,5 @@ +%YAML 1.2 +--- +"@context": + '': http://manu.sporny.org/ + foaf: http://xmlns.com/foaf/0.1/ diff --git a/spec/test-files/test-5-expanded.jsonld b/spec/test-files/test-5-expanded.jsonld deleted file mode 100644 index dcb5a31..0000000 --- a/spec/test-files/test-5-expanded.jsonld +++ /dev/null @@ -1,9 +0,0 @@ -[{ - "@id": "http://manu.sporny.org/#me", - "@type": ["http://xmlns.com/foaf/0.1/Person"], - "http://xmlns.com/foaf/0.1/name": [{"@value": "Manu Sporny"}], - "http://xmlns.com/foaf/0.1/knows": [{ - "@type": ["http://xmlns.com/foaf/0.1/Person"], - "http://xmlns.com/foaf/0.1/name": [{"@value": "Gregg Kellogg"}] - }] -}] diff --git a/spec/test-files/test-5-expanded.yamlld b/spec/test-files/test-5-expanded.yamlld new file mode 100644 index 0000000..5f636e9 --- /dev/null +++ b/spec/test-files/test-5-expanded.yamlld @@ -0,0 +1,12 @@ +%YAML 1.2 +--- +- "@id": http://manu.sporny.org/#me + "@type": + - http://xmlns.com/foaf/0.1/Person + http://xmlns.com/foaf/0.1/name: + - "@value": Manu Sporny + http://xmlns.com/foaf/0.1/knows: + - "@type": + - http://xmlns.com/foaf/0.1/Person + http://xmlns.com/foaf/0.1/name: + - "@value": Gregg Kellogg diff --git a/spec/test-files/test-6-compacted.jsonld b/spec/test-files/test-6-compacted.jsonld deleted file mode 100644 index 63bec62..0000000 --- a/spec/test-files/test-6-compacted.jsonld +++ /dev/null @@ -1,10 +0,0 @@ -{ - "@context": { - "": "http://manu.sporny.org/", - "foaf": "http://xmlns.com/foaf/0.1/" - }, - "@id": "http://example.org/people#joebob", - "@type": "foaf:Person", - "foaf:name": "Joe Bob", - "foaf:nick": { "@list": [ "joe", "bob", "jaybe" ] } -} diff --git a/spec/test-files/test-6-compacted.yamlld b/spec/test-files/test-6-compacted.yamlld new file mode 100644 index 0000000..707fb7e --- /dev/null +++ b/spec/test-files/test-6-compacted.yamlld @@ -0,0 +1,13 @@ +%YAML 1.2 +--- +"@context": + '': http://manu.sporny.org/ + foaf: http://xmlns.com/foaf/0.1/ +"@id": http://example.org/people#joebob +"@type": foaf:Person +foaf:name: Joe Bob +foaf:nick: + "@list": + - joe + - bob + - jaybe diff --git a/spec/test-files/test-6-context.jsonld b/spec/test-files/test-6-context.jsonld deleted file mode 100644 index 085bc57..0000000 --- a/spec/test-files/test-6-context.jsonld +++ /dev/null @@ -1,7 +0,0 @@ -{ - "@context": { - "": "http://manu.sporny.org/", - "foaf": "http://xmlns.com/foaf/0.1/" - } - -} diff --git a/spec/test-files/test-6-context.yamlld b/spec/test-files/test-6-context.yamlld new file mode 100644 index 0000000..5a2999d --- /dev/null +++ b/spec/test-files/test-6-context.yamlld @@ -0,0 +1,5 @@ +%YAML 1.2 +--- +"@context": + '': http://manu.sporny.org/ + foaf: http://xmlns.com/foaf/0.1/ diff --git a/spec/test-files/test-6-expanded.jsonld b/spec/test-files/test-6-expanded.jsonld deleted file mode 100644 index dcf0659..0000000 --- a/spec/test-files/test-6-expanded.jsonld +++ /dev/null @@ -1,10 +0,0 @@ -[{ - "@id": "http://example.org/people#joebob", - "@type": ["http://xmlns.com/foaf/0.1/Person"], - "http://xmlns.com/foaf/0.1/name": [{"@value": "Joe Bob"}], - "http://xmlns.com/foaf/0.1/nick": [{ "@list": [ - {"@value": "joe"}, - {"@value": "bob"}, - {"@value": "jaybe"} - ]}] -}] diff --git a/spec/test-files/test-6-expanded.yamlld b/spec/test-files/test-6-expanded.yamlld new file mode 100644 index 0000000..63a215e --- /dev/null +++ b/spec/test-files/test-6-expanded.yamlld @@ -0,0 +1,12 @@ +%YAML 1.2 +--- +- "@id": http://example.org/people#joebob + "@type": + - http://xmlns.com/foaf/0.1/Person + http://xmlns.com/foaf/0.1/name: + - "@value": Joe Bob + http://xmlns.com/foaf/0.1/nick: + - "@list": + - "@value": joe + - "@value": bob + - "@value": jaybe diff --git a/spec/test-files/test-7-compacted.jsonld b/spec/test-files/test-7-compacted.jsonld deleted file mode 100644 index 497c13b..0000000 --- a/spec/test-files/test-7-compacted.jsonld +++ /dev/null @@ -1,23 +0,0 @@ -{ - "@context": { "foaf": "http://xmlns.com/foaf/0.1/" }, - "@graph": [ - { - "@id": "_:bnode1", - "@type": "foaf:Person", - "foaf:homepage": "http://example.com/bob/", - "foaf:name": "Bob" - }, - { - "@id": "_:bnode2", - "@type": "foaf:Person", - "foaf:homepage": "http://example.com/eve/", - "foaf:name": "Eve" - }, - { - "@id": "_:bnode3", - "@type": "foaf:Person", - "foaf:homepage": "http://example.com/manu/", - "foaf:name": "Manu" - } - ] -} diff --git a/spec/test-files/test-7-compacted.yamlld b/spec/test-files/test-7-compacted.yamlld new file mode 100644 index 0000000..04a0044 --- /dev/null +++ b/spec/test-files/test-7-compacted.yamlld @@ -0,0 +1,17 @@ +%YAML 1.2 +--- +"@context": + foaf: http://xmlns.com/foaf/0.1/ +"@graph": +- "@id": _:bnode1 + "@type": foaf:Person + foaf:homepage: http://example.com/bob/ + foaf:name: Bob +- "@id": _:bnode2 + "@type": foaf:Person + foaf:homepage: http://example.com/eve/ + foaf:name: Eve +- "@id": _:bnode3 + "@type": foaf:Person + foaf:homepage: http://example.com/manu/ + foaf:name: Manu diff --git a/spec/test-files/test-7-context.jsonld b/spec/test-files/test-7-context.jsonld deleted file mode 100644 index 7ca7f1b..0000000 --- a/spec/test-files/test-7-context.jsonld +++ /dev/null @@ -1,4 +0,0 @@ -{ - "@context": { "foaf": "http://xmlns.com/foaf/0.1/" } -} - diff --git a/spec/test-files/test-7-context.yamlld b/spec/test-files/test-7-context.yamlld new file mode 100644 index 0000000..a0351c7 --- /dev/null +++ b/spec/test-files/test-7-context.yamlld @@ -0,0 +1,4 @@ +%YAML 1.2 +--- +"@context": + foaf: http://xmlns.com/foaf/0.1/ diff --git a/spec/test-files/test-7-expanded.jsonld b/spec/test-files/test-7-expanded.jsonld deleted file mode 100644 index 7d04b36..0000000 --- a/spec/test-files/test-7-expanded.jsonld +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "@id": "_:bnode1", - "@type": ["http://xmlns.com/foaf/0.1/Person"], - "http://xmlns.com/foaf/0.1/homepage": [{"@value": "http://example.com/bob/"}], - "http://xmlns.com/foaf/0.1/name": [{"@value": "Bob"}] - }, - { - "@id": "_:bnode2", - "@type": ["http://xmlns.com/foaf/0.1/Person"], - "http://xmlns.com/foaf/0.1/homepage": [{"@value": "http://example.com/eve/"}], - "http://xmlns.com/foaf/0.1/name": [{"@value": "Eve"}] - }, - { - "@id": "_:bnode3", - "@type": ["http://xmlns.com/foaf/0.1/Person"], - "http://xmlns.com/foaf/0.1/homepage": [{"@value": "http://example.com/manu/"}], - "http://xmlns.com/foaf/0.1/name": [{"@value": "Manu"}] - } -] \ No newline at end of file diff --git a/spec/test-files/test-7-expanded.yamlld b/spec/test-files/test-7-expanded.yamlld new file mode 100644 index 0000000..659fe50 --- /dev/null +++ b/spec/test-files/test-7-expanded.yamlld @@ -0,0 +1,23 @@ +%YAML 1.2 +--- +- "@id": _:bnode1 + "@type": + - http://xmlns.com/foaf/0.1/Person + http://xmlns.com/foaf/0.1/homepage: + - "@value": http://example.com/bob/ + http://xmlns.com/foaf/0.1/name: + - "@value": Bob +- "@id": _:bnode2 + "@type": + - http://xmlns.com/foaf/0.1/Person + http://xmlns.com/foaf/0.1/homepage: + - "@value": http://example.com/eve/ + http://xmlns.com/foaf/0.1/name: + - "@value": Eve +- "@id": _:bnode3 + "@type": + - http://xmlns.com/foaf/0.1/Person + http://xmlns.com/foaf/0.1/homepage: + - "@value": http://example.com/manu/ + http://xmlns.com/foaf/0.1/name: + - "@value": Manu diff --git a/spec/test-files/test-8-compacted.jsonld b/spec/test-files/test-8-compacted.jsonld deleted file mode 100644 index 1128b75..0000000 --- a/spec/test-files/test-8-compacted.jsonld +++ /dev/null @@ -1,34 +0,0 @@ -{ - "@context": { - "Book": "http://example.org/vocab#Book", - "Chapter": "http://example.org/vocab#Chapter", - "contains": { - "@id": "http://example.org/vocab#contains", - "@type": "@id" - }, - "creator": "http://purl.org/dc/terms/creator", - "description": "http://purl.org/dc/terms/description", - "Library": "http://example.org/vocab#Library", - "title": "http://purl.org/dc/terms/title" - }, - "@graph": [ - { - "@id": "http://example.com/library", - "@type": "Library", - "contains": "http://example.org/library/the-republic" - }, - { - "@id": "http://example.org/library/the-republic", - "@type": "Book", - "creator": "Plato", - "title": "The Republic", - "contains": "http://example.org/library/the-republic#introduction" - }, - { - "@id": "http://example.org/library/the-republic#introduction", - "@type": "Chapter", - "description": "An introductory chapter on The Republic.", - "title": "The Introduction" - } - ] -} \ No newline at end of file diff --git a/spec/test-files/test-8-compacted.yamlld b/spec/test-files/test-8-compacted.yamlld new file mode 100644 index 0000000..d1022f6 --- /dev/null +++ b/spec/test-files/test-8-compacted.yamlld @@ -0,0 +1,25 @@ +%YAML 1.2 +--- +"@context": + Book: http://example.org/vocab#Book + Chapter: http://example.org/vocab#Chapter + contains: + "@id": http://example.org/vocab#contains + "@type": "@id" + creator: http://purl.org/dc/terms/creator + description: http://purl.org/dc/terms/description + Library: http://example.org/vocab#Library + title: http://purl.org/dc/terms/title +"@graph": +- "@id": http://example.com/library + "@type": Library + contains: http://example.org/library/the-republic +- "@id": http://example.org/library/the-republic + "@type": Book + creator: Plato + title: The Republic + contains: http://example.org/library/the-republic#introduction +- "@id": http://example.org/library/the-republic#introduction + "@type": Chapter + description: An introductory chapter on The Republic. + title: The Introduction diff --git a/spec/test-files/test-8-context.jsonld b/spec/test-files/test-8-context.jsonld deleted file mode 100644 index e78a617..0000000 --- a/spec/test-files/test-8-context.jsonld +++ /dev/null @@ -1,11 +0,0 @@ -{ - "@context": { - "Book": "http://example.org/vocab#Book", - "Chapter": "http://example.org/vocab#Chapter", - "contains": {"@id": "http://example.org/vocab#contains", "@type": "@id"}, - "creator": "http://purl.org/dc/terms/creator", - "description": "http://purl.org/dc/terms/description", - "Library": "http://example.org/vocab#Library", - "title": "http://purl.org/dc/terms/title" - } -} \ No newline at end of file diff --git a/spec/test-files/test-8-context.yamlld b/spec/test-files/test-8-context.yamlld new file mode 100644 index 0000000..cf36626 --- /dev/null +++ b/spec/test-files/test-8-context.yamlld @@ -0,0 +1,12 @@ +%YAML 1.2 +--- +"@context": + Book: http://example.org/vocab#Book + Chapter: http://example.org/vocab#Chapter + contains: + "@id": http://example.org/vocab#contains + "@type": "@id" + creator: http://purl.org/dc/terms/creator + description: http://purl.org/dc/terms/description + Library: http://example.org/vocab#Library + title: http://purl.org/dc/terms/title diff --git a/spec/test-files/test-8-expanded.jsonld b/spec/test-files/test-8-expanded.jsonld deleted file mode 100644 index eeb5fa5..0000000 --- a/spec/test-files/test-8-expanded.jsonld +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "@id": "http://example.com/library", - "@type": ["http://example.org/vocab#Library"], - "http://example.org/vocab#contains": [{ - "@id": "http://example.org/library/the-republic" - }] - }, - { - "@id": "http://example.org/library/the-republic", - "@type": ["http://example.org/vocab#Book"], - "http://purl.org/dc/terms/creator": [{"@value": "Plato"}], - "http://purl.org/dc/terms/title": [{"@value": "The Republic"}], - "http://example.org/vocab#contains": [{ - "@id": "http://example.org/library/the-republic#introduction" - }] - }, - { - "@id": "http://example.org/library/the-republic#introduction", - "@type": ["http://example.org/vocab#Chapter"], - "http://purl.org/dc/terms/description": [{"@value": "An introductory chapter on The Republic."}], - "http://purl.org/dc/terms/title": [{"@value": "The Introduction"}] - } -] \ No newline at end of file diff --git a/spec/test-files/test-8-expanded.yamlld b/spec/test-files/test-8-expanded.yamlld new file mode 100644 index 0000000..a9d13d6 --- /dev/null +++ b/spec/test-files/test-8-expanded.yamlld @@ -0,0 +1,23 @@ +%YAML 1.2 +--- +- "@id": http://example.com/library + "@type": + - http://example.org/vocab#Library + http://example.org/vocab#contains: + - "@id": http://example.org/library/the-republic +- "@id": http://example.org/library/the-republic + "@type": + - http://example.org/vocab#Book + http://purl.org/dc/terms/creator: + - "@value": Plato + http://purl.org/dc/terms/title: + - "@value": The Republic + http://example.org/vocab#contains: + - "@id": http://example.org/library/the-republic#introduction +- "@id": http://example.org/library/the-republic#introduction + "@type": + - http://example.org/vocab#Chapter + http://purl.org/dc/terms/description: + - "@value": An introductory chapter on The Republic. + http://purl.org/dc/terms/title: + - "@value": The Introduction diff --git a/spec/test-files/test-8-frame.jsonld b/spec/test-files/test-8-frame.jsonld deleted file mode 100644 index f028bff..0000000 --- a/spec/test-files/test-8-frame.jsonld +++ /dev/null @@ -1,18 +0,0 @@ -{ - "@context": { - "Book": "http://example.org/vocab#Book", - "Chapter": "http://example.org/vocab#Chapter", - "contains": "http://example.org/vocab#contains", - "creator": "http://purl.org/dc/terms/creator", - "description": "http://purl.org/dc/terms/description", - "Library": "http://example.org/vocab#Library", - "title": "http://purl.org/dc/terms/title" - }, - "@type": "Library", - "contains": { - "@type": "Book", - "contains": { - "@type": "Chapter" - } - } -} diff --git a/spec/test-files/test-8-frame.yamlld b/spec/test-files/test-8-frame.yamlld new file mode 100644 index 0000000..0608459 --- /dev/null +++ b/spec/test-files/test-8-frame.yamlld @@ -0,0 +1,15 @@ +%YAML 1.2 +--- +"@context": + Book: http://example.org/vocab#Book + Chapter: http://example.org/vocab#Chapter + contains: http://example.org/vocab#contains + creator: http://purl.org/dc/terms/creator + description: http://purl.org/dc/terms/description + Library: http://example.org/vocab#Library + title: http://purl.org/dc/terms/title +"@type": Library +contains: + "@type": Book + contains: + "@type": Chapter diff --git a/spec/test-files/test-8-framed.jsonld b/spec/test-files/test-8-framed.jsonld deleted file mode 100644 index 1f366b6..0000000 --- a/spec/test-files/test-8-framed.jsonld +++ /dev/null @@ -1,25 +0,0 @@ -{ - "@context": { - "Book": "http://example.org/vocab#Book", - "Chapter": "http://example.org/vocab#Chapter", - "contains": "http://example.org/vocab#contains", - "creator": "http://purl.org/dc/terms/creator", - "description": "http://purl.org/dc/terms/description", - "Library": "http://example.org/vocab#Library", - "title": "http://purl.org/dc/terms/title" - }, - "@id": "http://example.com/library", - "@type": "Library", - "contains": { - "@id": "http://example.org/library/the-republic", - "@type": "Book", - "contains": { - "@id": "http://example.org/library/the-republic#introduction", - "@type": "Chapter", - "description": "An introductory chapter on The Republic.", - "title": "The Introduction" - }, - "creator": "Plato", - "title": "The Republic" - } -} diff --git a/spec/test-files/test-8-framed.yamlld b/spec/test-files/test-8-framed.yamlld new file mode 100644 index 0000000..854a557 --- /dev/null +++ b/spec/test-files/test-8-framed.yamlld @@ -0,0 +1,22 @@ +%YAML 1.2 +--- +"@context": + Book: http://example.org/vocab#Book + Chapter: http://example.org/vocab#Chapter + contains: http://example.org/vocab#contains + creator: http://purl.org/dc/terms/creator + description: http://purl.org/dc/terms/description + Library: http://example.org/vocab#Library + title: http://purl.org/dc/terms/title +"@id": http://example.com/library +"@type": Library +contains: + "@id": http://example.org/library/the-republic + "@type": Book + contains: + "@id": http://example.org/library/the-republic#introduction + "@type": Chapter + description: An introductory chapter on The Republic. + title: The Introduction + creator: Plato + title: The Republic diff --git a/spec/test-files/test-9-compacted.jsonld b/spec/test-files/test-9-compacted.jsonld deleted file mode 100644 index d4db790..0000000 --- a/spec/test-files/test-9-compacted.jsonld +++ /dev/null @@ -1,20 +0,0 @@ -{ - "@context": { - "id1": "http://example.com/id1", - "t1": "http://example.com/t1", - "t2": "http://example.com/t2", - "term1": "http://example.com/term", - "term2": {"@id": "http://example.com/term", "@type": "t2"}, - "term3": {"@id": "http://example.com/term", "@language": "en"}, - "term4": {"@id": "http://example.com/term", "@container": "@list"}, - "term5": {"@id": "http://example.com/term", "@language": null}, - "@language": "de" - }, - "@id": "http://example.com/id1", - "@type": "t1", - "term1": "v1", - "term2": "v2", - "term3": "v3", - "term4": [ 1, 2 ], - "term5": [ "v5", "plain literal" ] -} diff --git a/spec/test-files/test-9-compacted.yamlld b/spec/test-files/test-9-compacted.yamlld new file mode 100644 index 0000000..b7d872b --- /dev/null +++ b/spec/test-files/test-9-compacted.yamlld @@ -0,0 +1,31 @@ +%YAML 1.2 +--- +"@context": + id1: http://example.com/id1 + t1: http://example.com/t1 + t2: http://example.com/t2 + term1: http://example.com/term + term2: + "@id": http://example.com/term + "@type": t2 + term3: + "@id": http://example.com/term + "@language": en + term4: + "@id": http://example.com/term + "@container": "@list" + term5: + "@id": http://example.com/term + "@language": + "@language": de +"@id": http://example.com/id1 +"@type": t1 +term1: v1 +term2: v2 +term3: v3 +term4: +- 1 +- 2 +term5: +- v5 +- plain literal diff --git a/spec/test-files/test-9-context.jsonld b/spec/test-files/test-9-context.jsonld deleted file mode 100644 index 3cb7c10..0000000 --- a/spec/test-files/test-9-context.jsonld +++ /dev/null @@ -1,13 +0,0 @@ -{ - "@context": { - "id1": "http://example.com/id1", - "t1": "http://example.com/t1", - "t2": "http://example.com/t2", - "term1": "http://example.com/term", - "term2": {"@id": "http://example.com/term", "@type": "t2"}, - "term3": {"@id": "http://example.com/term", "@language": "en"}, - "term4": {"@id": "http://example.com/term", "@container": "@list"}, - "term5": {"@id": "http://example.com/term", "@language": null}, - "@language": "de" - } -} diff --git a/spec/test-files/test-9-context.yamlld b/spec/test-files/test-9-context.yamlld new file mode 100644 index 0000000..caf962d --- /dev/null +++ b/spec/test-files/test-9-context.yamlld @@ -0,0 +1,20 @@ +%YAML 1.2 +--- +"@context": + id1: http://example.com/id1 + t1: http://example.com/t1 + t2: http://example.com/t2 + term1: http://example.com/term + term2: + "@id": http://example.com/term + "@type": t2 + term3: + "@id": http://example.com/term + "@language": en + term4: + "@id": http://example.com/term + "@container": "@list" + term5: + "@id": http://example.com/term + "@language": + "@language": de diff --git a/spec/test-files/test-9-expanded.jsonld b/spec/test-files/test-9-expanded.jsonld deleted file mode 100644 index d18dc41..0000000 --- a/spec/test-files/test-9-expanded.jsonld +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "@id": "http://example.com/id1", - "@type": ["http://example.com/t1"], - "http://example.com/term": [ - {"@value": "v1","@language": "de"}, - {"@value": "v2","@type": "http://example.com/t2"}, - {"@value": "v3","@language": "en"}, - {"@list": [{"@value": 1},{"@value": 2}]}, - {"@value": "v5"}, - {"@value": "plain literal"} - ] - } -] diff --git a/spec/test-files/test-9-expanded.yamlld b/spec/test-files/test-9-expanded.yamlld new file mode 100644 index 0000000..ef174fa --- /dev/null +++ b/spec/test-files/test-9-expanded.yamlld @@ -0,0 +1,17 @@ +%YAML 1.2 +--- +- "@id": http://example.com/id1 + "@type": + - http://example.com/t1 + http://example.com/term: + - "@value": v1 + "@language": de + - "@value": v2 + "@type": http://example.com/t2 + - "@value": v3 + "@language": en + - "@list": + - "@value": 1 + - "@value": 2 + - "@value": v5 + - "@value": plain literal diff --git a/spec/to_rdf_spec.rb b/spec/to_rdf_spec.rb index 9c15ffd..ab7d27d 100644 --- a/spec/to_rdf_spec.rb +++ b/spec/to_rdf_spec.rb @@ -131,14 +131,14 @@ http://example.com/foo: - bar - baz - }), + ), %q([ "bar"^^xsd:string, "baz"^^xsd:string ] .) ], "IRI" => [ %q( http://example.com/foo: "@id": http://example.com/bar - }), + ), %q([ ] .) ], "IRIs" => [ @@ -146,13 +146,13 @@ http://example.com/foo: - "@id": http://example.com/bar - "@id": http://example.com/baz - }), + ), %q([ , ] .) ], - }.each do |title, (js, ttl)| + }.each do |title, (yaml, ttl)| it title do ttl = "@prefix xsd: . #{ttl}" - expect(parse(js)).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + expect(parse(yaml)).to be_equivalent_graph(ttl, logger: logger, inputDocument: yaml) end end end @@ -212,10 +212,10 @@ "1957-02-27"^^ . ) ], - }.each do |title, (js, ttl)| + }.each do |title, (yaml, ttl)| it title do ttl = "@prefix xsd: . #{ttl}" - expect(parse(js)).to be_equivalent_graph(ttl, logger: logger, inputDocument: js) + expect(parse(yaml)).to be_equivalent_graph(ttl, logger: logger, inputDocument: yaml) end end @@ -540,7 +540,9 @@ def parse(input, **options) def run_to_rdf(params) input, output = params[:input], params[:output] graph = params[:graph] || RDF::Graph.new - input = StringIO.new(input) if input.is_a?(String) + input = StringIO.new(input).tap do |d| + d.define_singleton_method(:content_type) {'application/ld+yaml'} + end if input.is_a?(String) pending params.fetch(:pending, "test implementation") unless input if params[:exception] expect {YAML_LD::API.toRdf(input, **params)}.to raise_error(params[:exception]) diff --git a/yaml-ld.gemspec b/yaml-ld.gemspec index 26c9cfb..9bd966d 100755 --- a/yaml-ld.gemspec +++ b/yaml-ld.gemspec @@ -29,9 +29,10 @@ Gem::Specification.new do |gem| gem.required_ruby_version = '>= 2.6' gem.requirements = [] - gem.add_runtime_dependency 'json-ld', '~> 3.2', '>= 3.2.2' - gem.add_runtime_dependency 'psych', '~> 4.0' - gem.add_runtime_dependency 'rdf', '~> 3.2' + gem.add_runtime_dependency 'json-ld', '~> 3.2', '>= 3.2.3' + gem.add_runtime_dependency 'psych', '>= 3.3' # Rails 6.0 cannot use psych 4.0 + gem.add_runtime_dependency 'rdf', '~> 3.2', '>= 3.2.9' + gem.add_runtime_dependency 'rdf-xsd', '~> 3.2' gem.add_development_dependency 'rdf-isomorphic', '~> 3.2' gem.add_development_dependency 'rdf-spec', '~> 3.2' gem.add_development_dependency 'rdf-trig', '~> 3.2' @@ -41,6 +42,7 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'rspec', '~> 3.10' gem.add_development_dependency 'rspec-its', '~> 1.3' gem.add_development_dependency 'yard' , '~> 0.9' + gem.post_install_message = nil end