diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..37cba644 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +jobs: + include: + - stage: common + script: + - git remote add -f b https://github.com/w3c/json-ld-wg.git + - git remote update + - git diff --exit-code remotes/b/master -- common + - stage: examples + language: ruby + rvm: 2.5 + bundler_args: --without debug + script: "rake test" diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..7ba5edbd --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem 'nokogiri' +gem 'linkeddata' +gem 'colorize' +gem 'rake' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..ac08f941 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,161 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + bcp47 (0.3.3) + i18n + builder (3.2.3) + colorize (0.8.1) + concurrent-ruby (1.0.5) + connection_pool (2.2.2) + ebnf (1.1.2) + rdf (>= 2.2, < 4.0) + sxp (~> 1.0) + equivalent-xml (0.6.0) + nokogiri (>= 1.4.3) + haml (5.0.4) + temple (>= 0.8.0) + tilt + hamster (3.0.0) + concurrent-ruby (~> 1.0) + htmlentities (4.3.4) + i18n (1.0.1) + concurrent-ruby (~> 1.0) + json-ld (2.2.1) + multi_json (~> 1.12) + rdf (>= 2.2.8, < 4.0) + json-ld-preloaded (2.2.3) + json-ld (>= 2.2, < 4.0) + multi_json (~> 1.12) + rdf (>= 2.2, < 4.0) + ld-patch (0.3.3) + ebnf (~> 1.1) + rdf (>= 2.2, < 4.0) + rdf-xsd (>= 2.2, < 4.0) + sparql (>= 2.2, < 4.0) + sxp (~> 1.0) + link_header (0.0.8) + linkeddata (3.0.1) + equivalent-xml (~> 0.6) + json-ld (>= 2.2.1, < 4.0) + ld-patch (~> 0.3, >= 0.3.3) + nokogiri (~> 1.8) + nokogumbo (~> 1.5) + rdf (~> 3.0) + rdf-aggregate-repo (>= 2.2.1, < 4.0) + rdf-isomorphic (~> 3.0) + rdf-json (>= 2.2, < 4.0) + rdf-microdata (>= 2.2.3, < 4.0) + rdf-n3 (~> 3.0) + rdf-normalize (~> 0.3, >= 0.3.3) + rdf-rdfa (~> 3.0) + rdf-rdfxml (>= 2.2.1, < 4.0) + rdf-reasoner (~> 0.5.0) + rdf-tabular (>= 2.2.1, < 4.0) + rdf-trig (>= 2.2, < 4.0) + rdf-trix (>= 2.2.1, < 4.0) + rdf-turtle (~> 3.0, >= 3.0.1) + rdf-vocab (~> 3.0) + rdf-xsd (~> 3.0) + shex (~> 0.5, >= 0.5.2) + sparql (~> 3.0) + sparql-client (~> 3.0) + mini_portile2 (2.3.0) + multi_json (1.13.1) + net-http-persistent (3.0.0) + connection_pool (~> 2.2) + nokogiri (1.8.2) + mini_portile2 (~> 2.3.0) + nokogumbo (1.5.0) + nokogiri + public_suffix (3.0.2) + rake (12.3.1) + rdf (3.0.2) + hamster (~> 3.0) + link_header (~> 0.0, >= 0.0.8) + rdf-aggregate-repo (2.2.1) + rdf (>= 2.2, < 4.0) + rdf-isomorphic (3.0.0) + rdf (~> 3.0) + rdf-json (2.2.0) + rdf (>= 2.2, < 4.0) + rdf-microdata (2.2.3) + htmlentities (~> 4.3) + nokogiri (~> 1.8) + rdf (>= 2.2.8, < 4.0) + rdf-xsd (>= 2.2, < 4.0) + rdf-n3 (3.0.0) + rdf (~> 3.0) + rdf-normalize (0.3.3) + rdf (>= 2.2, < 4.0) + rdf-rdfa (3.0.1) + haml (~> 5.0) + htmlentities (~> 4.3) + rdf (~> 3.0) + rdf-aggregate-repo (>= 2.2, < 4.0) + rdf-xsd (~> 3.0) + rdf-rdfxml (2.2.1) + htmlentities (~> 4.3) + rdf (>= 2.2, < 4.0) + rdf-rdfa (>= 2.2, < 4.0) + rdf-xsd (>= 2.2, < 4.0) + rdf-reasoner (0.5.1) + rdf (~> 3.0) + rdf-vocab (~> 3.0) + rdf-xsd (~> 3.0) + rdf-tabular (2.2.1) + addressable (~> 2.3) + bcp47 (~> 0.3, >= 0.3.3) + json-ld (>= 2.1, < 4.0) + rdf (>= 2.2, < 4.0) + rdf-vocab (>= 2.2, < 4.0) + rdf-xsd (>= 2.2, < 4.0) + rdf-trig (2.2.0) + ebnf (~> 1.0, >= 1.0.1) + rdf (>= 2.2, < 4.0) + rdf-turtle (>= 2.2, < 4.0) + rdf-trix (2.2.1) + rdf (>= 2.2, < 4.0) + rdf-turtle (3.0.1) + ebnf (~> 1.1) + rdf (~> 3.0) + rdf-vocab (3.0.2) + rdf (~> 3.0) + rdf-xsd (3.0.0) + rdf (~> 3.0) + shex (0.5.2) + ebnf (~> 1.1) + json-ld (>= 2.2, < 4.0) + json-ld-preloaded (>= 2.2, < 4.0) + rdf (>= 2.2, < 4.0) + rdf-xsd (>= 2.2, < 4.0) + sparql (>= 2.2, < 4.0) + sxp (~> 1.0) + sparql (3.0.1) + builder (~> 3.2) + ebnf (~> 1.1) + rdf (~> 3.0) + rdf-aggregate-repo (>= 2.2, < 4.0) + rdf-xsd (~> 3.0) + sparql-client (~> 3.0) + sxp (~> 1.0) + sparql-client (3.0.0) + net-http-persistent (>= 2.9, < 4) + rdf (~> 3.0) + sxp (1.0.1) + rdf (>= 2.2, < 4.0) + temple (0.8.0) + tilt (2.0.8) + +PLATFORMS + ruby + +DEPENDENCIES + colorize + linkeddata + nokogiri + rake + +BUNDLED WITH + 1.16.1 diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..ec17e916 --- /dev/null +++ b/Rakefile @@ -0,0 +1,11 @@ +task default: :test + +desc "Test examples in spec files" +task :test do + sh %(bundle exec common/extract-examples.rb index.html) +end + +desc "Extract Examples" +task :examples do + sh %(bundle exec common/extract-examples.rb --example-dir examples index.html) +end diff --git a/common/README.rb b/common/README.rb new file mode 100644 index 00000000..e69de29b diff --git a/common/extract-examples.rb b/common/extract-examples.rb new file mode 100755 index 00000000..47dbc745 --- /dev/null +++ b/common/extract-examples.rb @@ -0,0 +1,160 @@ +#!/usr/bin/env ruby +# Extracts examples from a ReSpec document, verifies that example titles are unique. Numbering attempts to replicate that used by ReSpec. Examples in script elements, which are not visibile, may be used for describing the results of related examples +require 'getoptlong' +require 'json' +require 'nokogiri' +require 'linkeddata' +require 'fileutils' +require 'colorize' + +example_dir = nil +opts = GetoptLong.new( + ["--example-dir", GetoptLong::REQUIRED_ARGUMENT] +) +opts.each do |opt, arg| + case opt + when '--example-dir' then example_dir = arg + end +end + +num_errors = 0 + +ARGV.each do |input| + $stdout.puts "\ninput: #{input}" + example_number = 0 + examples = {} + errors = [] + warnings = [] + + File.open(input, "r") do |f| + doc = Nokogiri::HTML.parse(f.read) + doc.css(".example").each do |element| + warn = false + example_number += 1 if element.name == "pre" + + if element.attr('data-ignore') || element.name == "table" + $stdout.write "i".colorize(:yellow) + next + end + + if (title = element.attr('title')).to_s.empty? + errors << "Example #{example_number} at line #{element.line} has no title" + $stdout.write "F".colorize(:red) + next + end + + if examples.any? {|n, ex| ex[:title] == title} + warnings << "Example #{example_number} at line #{element.line} uses duplicate title: #{title}" + warn = true + end + + content = element.inner_html. + sub(/^\s*\s*$/m, ''). + gsub('****', ''). + gsub(/####([^#]*)####/, '') + + ext = case element.attr('data-content-type') + when nil, '' then "json" + when 'application/n-quads', 'nq' then 'nq' + when 'text/html', 'html' then 'html' + when 'text/turtle', 'ttl' then 'ttl' + else 'txt' + end + + # Perform example syntactic validation based on extension + case ext + when 'json' + begin + ::JSON.parse(content) + rescue JSON::ParserError => e + errors << "Example #{example_number} at line #{element.line} parse error: #{e.message}" + $stdout.write "F".colorize(:red) + next + end + when 'html' + begin + doc = Nokogiri::HTML.parse(content) {|c| c.strict} + doc.errors.each do |e| + errors << "Example #{example_number} at line #{element.line} parse error: #{e}" + end + unless doc.errors.empty? + $stdout.write "F".colorize(:red) + next + end + rescue Nokogiri::XML::SyntaxError => e + errors << "Example #{example_number} at line #{element.line} parse error: #{e.message}" + $stdout.write "F".colorize(:red) + next + end + when 'ttl' + begin + reader_errors = [] + RDF::Turtle::Reader.new(content, logger: reader_errors) {|r| r.validate!} + rescue + reader_errors.each do |e| + errors << "Example #{example_number} at line #{element.line} parse error: #{e}" + end + $stdout.write "F".colorize(:red) + next + end + when 'nq' + begin + reader_errors = [] + RDF::NQuads::Reader.new(content, logger: reader_errors) {|r| r.validate!} + rescue + reader_errors.each do |e| + errors << "Example #{example_number} at line #{element.line} parse error: #{e}" + end + $stdout.write "F".colorize(:red) + next + end + end + + case element.name + when "pre" + fn = "example-#{"%03d" % example_number}-#{title.gsub(/[^\w]+/, '-')}.#{ext}" + examples[example_number] = { + title: title, + filename: fn, + content: content + } + #puts "example #{example_number}: #{content}" + when "script" + # Validate the previous example appropriately + else + errors << "Example #{example_number} at line #{element.line} has unknown element type #{element.name}" + $stdout.write "F".colorize(:red) + next + end + + if warn + $stdout.write "w".colorize(:yellow) + else + $stdout.write ".".colorize(:green) + end + end + end + + $stdout.puts "\nWarnings:" unless warnings.empty? + warnings.each {|e| $stdout.puts " #{e}".colorize(:yellow)} + $stdout.puts "\nErrors:" unless errors.empty? + errors.each {|e| $stdout.puts " #{e}".colorize(:red)} + num_errors += errors.length + + if example_dir + # Make examples directory + FileUtils::mkdir_p(example_dir) + examples.each do |num, ex| + File.open(File.join(example_dir, ex[:filename]), 'w') {|f| f.write(ex[:content])} + end + end +end + +if num_errors == 0 + $stdout.puts "\nok".colorize(:green) +else + exit(1) +end + +exit(0) \ No newline at end of file