earl-report 0.5.0 → 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49a823f243638c7162903264e52f226f6f02e8e85943f1a4cd97a12594b080ab
4
- data.tar.gz: b4855bcd3dbddc0e3b8eff04dbfd1ff8e8ed062827878471268e11b0fd509dbb
3
+ metadata.gz: a5d9b1de6d1ff74153dad8f2ff10691a43066da67b118c2c3cac375d030963e9
4
+ data.tar.gz: 2717c41b45c6f878157d070e34ca459530eda435b668e83141e6acb730f0be7b
5
5
  SHA512:
6
- metadata.gz: c971edc43f724554263423eb192fde98aa810cf2eaed21a5349f3199e765550087e8ce34ee0e8191c073b075415cf49f40ae5905b41bcc5954b0221fb7667c36
7
- data.tar.gz: bf787b883a24228aab04490fb93b32e26b9763dd67e00565f61040b0a1bfd0e1bef4a41e91353182c685c5bf7913e8c5d17a141feeac691abfb7d3b36695bc91
6
+ metadata.gz: f32d6ee582e6fb93c7a87f43a88c8146ec2ca952cb6000e8459213d65260cb454a253947adefe013d8cd0026aa847fde157a0eda0949077cb72e265f06ed61b3
7
+ data.tar.gz: 6ade127c2bb38bb1bec57106a71672d091a107799e6b2e1f37e65ecc66e4608a2573455a50470be918636aa230669b88fbde660e65c9172d660619982539dd73
data/README.md CHANGED
@@ -2,8 +2,8 @@
2
2
  Ruby gem to consolidate multiple EARL report and generate a rollup conformance report.
3
3
 
4
4
  [![Gem Version](https://badge.fury.io/rb/earl-report.png)](http://badge.fury.io/rb/earl-report)
5
- [![Build Status](https://travis-ci.org/gkellogg/earl-report.png?branch=master)](http://travis-ci.org/gkellogg/earl-report)
6
- [![Coverage Status](https://coveralls.io/repos/gkellogg/earl-report/badge.svg)](https://coveralls.io/r/gkellogg/earl-report)
5
+ [![Build Status](https://github.com/gkellogg/earl-report/workflows/CI/badge.svg?branch=develop)](https://github.com/gkellogg/earl-report/actions?query=workflow%3ACI)
6
+ [![Coverage Status](https://coveralls.io/repos/gkellogg/earl-report/badge.svg?branch=develop)](https://coveralls.io/r/gkellogg/earl-report?branch=develop)
7
7
 
8
8
  ## Description
9
9
  Reads a test manifest in the
@@ -17,8 +17,8 @@ may be specified in an any compatible RDF serialization. The report is composed
17
17
  in the following form:
18
18
 
19
19
  [ a earl:Assertion;
20
- earl:assertedBy <http://greggkellogg.net/foaf#me>;
21
- earl:subject <http://rubygems.org/gems/rdf-turtle>;
20
+ earl:assertedBy <https://greggkellogg.net/foaf#me>;
21
+ earl:subject <https://rubygems.org/gems/rdf-turtle>;
22
22
  earl:test <http://dvcs.w3.org/hg/rdf/raw-file/default/rdf-turtle/tests-ttl/manifest.ttl#turtle-syntax-file-01>;
23
23
  earl:result [
24
24
  a earl:TestResult;
@@ -29,9 +29,9 @@ in the following form:
29
29
  Additionally, `earl:subject` is expected to reference a [DOAP]() description
30
30
  of the reported software, in the following form:
31
31
 
32
- <http://rubygems.org/gems/rdf-turtle> a doap:Project, earl:TestSubject, earl:Software ;
32
+ <https://rubygems.org/gems/rdf-turtle> a doap:Project, earl:TestSubject, earl:Software ;
33
33
  doap:name "RDF::Turtle" ;
34
- doap:developer <http://greggkellogg.net/foaf#me> ;
34
+ doap:developer <https://greggkellogg.net/foaf#me> ;
35
35
  doap:homepage <http://ruby-rdf.github.com/rdf-turtle> ;
36
36
  doap:description "RDF::Turtle is an Turtle reader/writer for the RDF.rb library suite."@en ;
37
37
  doap:release [
@@ -49,7 +49,7 @@ The `doap:developer` is expected to reference a [FOAF]() profile for the agent
49
49
  (user or organization) responsible for the test subject. It is expected to be
50
50
  of the following form:
51
51
 
52
- <http://greggkellogg.net/foaf#me> foaf:name "Gregg Kellogg" .
52
+ <https://greggkellogg.net/foaf#me> foaf:name "Gregg Kellogg" .
53
53
 
54
54
  If not found, the IRI identified by `doap:developer`
55
55
  will be dereferenced and is presumed to provide a [FOAF]() profile of the developer.
@@ -96,14 +96,14 @@ Version 0.3 and prior re-constructed the test manifest used to create the body o
96
96
  mf:result <http://example/test-00.out>;
97
97
  earl:assertions ([
98
98
  a earl:Assertion;
99
- earl:assertedBy <http://greggkellogg.net/foaf#me>;
99
+ earl:assertedBy <https://greggkellogg.net/foaf#me>;
100
100
  earl:mode earl:automatic;
101
101
  earl:result [
102
102
  a earl:TestResult;
103
103
  dc:date "2012-11-06T19:23:29-08:00"^^xsd:dateTime;
104
104
  earl:outcome earl:passed
105
105
  ];
106
- earl:subject <http://rubygems.org/gems/rdf-turtle>;
106
+ earl:subject <https://rubygems.org/gems/rdf-turtle>;
107
107
  earl:test <http://example/manifest.ttl#testeval00>
108
108
  ]) .
109
109
 
@@ -132,7 +132,7 @@ Generally, creating a `json` format first is more efficient. Subsequent invocati
132
132
  When run, `earl-report` attempts to open the file `.earl` in the current directory. This file is in [YAML][] format with entries for each option. Use the `--rc` option to automatically generate it.
133
133
 
134
134
  ## Author
135
- * [Gregg Kellogg](http://github.com/gkellogg) - <http://greggkellogg.net/>
135
+ * [Gregg Kellogg](https://github.com/gkellogg) - <https://greggkellogg.net/>
136
136
 
137
137
  ## License
138
138
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.6.2
data/bin/earl-report CHANGED
@@ -15,6 +15,7 @@ OPT_ARGS = [
15
15
  ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT,"Output report to file"],
16
16
  ["--query", GetoptLong::REQUIRED_ARGUMENT,"Query, or file containing query for extracting information from Test manifest"],
17
17
  ["--rc", GetoptLong::NO_ARGUMENT, "Write options to run-control file"],
18
+ ["--strict", GetoptLong::NO_ARGUMENT, "Fails if any skipped result is found"],
18
19
  ["--template", GetoptLong::OPTIONAL_ARGUMENT,"Specify or return default report template"],
19
20
  ["--verbose", GetoptLong::NO_ARGUMENT, "Detail on execution"],
20
21
  ["--help", "-?", GetoptLong::NO_ARGUMENT, "This message"]
@@ -65,6 +66,7 @@ opts.each do |opt, arg|
65
66
  when '--name' then options[:name] = arg
66
67
  when '--output' then options[:io] = File.open(arg, "w")
67
68
  when '--rc' then options[:rc] = true
69
+ when '--strict' then options[:strict] = true
68
70
  when '--template' then options[:template] = (File.open(arg, "r") unless arg.empty?)
69
71
  when '--verbose' then options[:verbose] = true
70
72
  when '--help' then usage
@@ -94,6 +96,6 @@ if options.has_key?(:template) && options[:template].nil?
94
96
  elsif ARGV.empty?
95
97
  usage
96
98
  else
97
- earl = EarlReport.new(*ARGV, options)
98
- earl.generate(options)
99
+ earl = EarlReport.new(*ARGV, **options)
100
+ earl.generate(**options)
99
101
  end
data/lib/earl_report.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # EARL reporting
2
- require 'linkeddata'
2
+ require 'json/ld'
3
+ require 'rdf/turtle'
4
+ require 'rdf/vocab'
3
5
  require 'sparql'
4
6
  require 'haml'
5
7
  require 'open-uri'
@@ -11,6 +13,7 @@ class EarlReport
11
13
  autoload :VERSION, 'earl_report/version'
12
14
 
13
15
  attr_reader :graph
16
+ attr_reader :verbose
14
17
 
15
18
  # Return information about each test.
16
19
  # Tests all have an mf:action property.
@@ -152,35 +155,71 @@ class EarlReport
152
155
  }]
153
156
  }.freeze
154
157
 
158
+ TURTLE_PREFIXES = %(@prefix dc: <http://purl.org/dc/terms/> .
159
+ @prefix doap: <http://usefulinc.com/ns/doap#> .
160
+ @prefix earl: <http://www.w3.org/ns/earl#> .
161
+ @prefix mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#> .
162
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
163
+ @prefix foaf: <http://xmlns.com/foaf/0.1/> .
164
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
165
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
166
+ ).gsub(/^ /, '')
167
+
168
+ TURTLE_SOFTWARE = %(
169
+ # Report Generation Software
170
+ <https://rubygems.org/gems/earl-report> a earl:Software, doap:Project;
171
+ doap:name "earl-report";
172
+ doap:shortdesc "Earl Report summary generator"@en;
173
+ doap:description "EarlReport generates HTML+RDFa rollups of multiple EARL reports"@en;
174
+ doap:homepage <https://github.com/gkellogg/earl-report>;
175
+ doap:programming-language "Ruby";
176
+ doap:license <http://unlicense.org>;
177
+ doap:release <https://github.com/gkellogg/earl-report/tree/#{VERSION}>;
178
+ doap:developer <https://greggkellogg.net/foaf#me> .
179
+
180
+ <https://github.com/gkellogg/earl-report/tree/#{VERSION}> a doap:Version;
181
+ doap:name "earl-report-#{VERSION}";
182
+ doap:created "#{File.mtime(File.expand_path('../../VERSION', __FILE__)).strftime('%Y-%m-%d')}"^^xsd:date;
183
+ doap:revision "#{VERSION}" .
184
+ ).gsub(/^ /, '')
185
+
155
186
  # Convenience vocabularies
156
187
  class EARL < RDF::Vocabulary("http://www.w3.org/ns/earl#"); end
157
188
  class MF < RDF::Vocabulary("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#"); end
158
189
 
159
190
  ##
160
191
  # Load test assertions and look for referenced software and developer information
161
- # @overload initialize(*files)
162
- # @param [Array<String>] files Assertions
163
- # @overload initialize(*files, options = {})
164
- # @param [Hash{Symbol => Object}] options
165
- # @option options [Boolean] :verbose (true)
166
- # @option options [String] :base Base IRI for loading Manifest
167
- # @option options [String] :bibRef
168
- # ReSpec bibliography reference for specification being tested
169
- # @option options [String] :json Result of previous JSON-LD generation
170
- # @option options [String, Array<String>] :manifest Test manifest
171
- # @option options [String] :name Name of specification
172
- # @option options [String] :query
173
- # Query, or file containing query for extracting information from Test manifests
174
- def initialize(*files)
175
- @options = files.last.is_a?(Hash) ? files.pop.dup : {}
176
- @options[:query] ||= MANIFEST_QUERY
177
- raise "Test Manifests must be specified with :manifest option" unless @options[:manifest] || @options[:json]
192
+ #
193
+ # @param [Array<String>] files Assertions
194
+ # @param [String] base (nil) Base IRI for loading Manifest
195
+ # @param [String] bibRef ('Unknown reference')
196
+ # ReSpec bibliography reference for specification being tested
197
+ # @param [Boolean] json (false) File is in the JSON format of a report.
198
+ # @param [String, Array<String>] manifest (nil) Test manifest(s)
199
+ # @param [String] name ('Unknown') Name of specification
200
+ # @param [String] query (MANIFEST_QUERY)
201
+ # Query, or file containing query for extracting information from Test manifests
202
+ # @param [Boolean] strict (false) Abort on any warning
203
+ # @param [Boolean] verbose (false)
204
+ def initialize(*files,
205
+ base: nil,
206
+ bibRef: 'Unknown reference',
207
+ json: false,
208
+ manifest: nil,
209
+ name: 'Unknown',
210
+ query: MANIFEST_QUERY,
211
+ strict: false,
212
+ verbose: false,
213
+ **options)
214
+ @verbose = verbose
215
+ raise "Test Manifests must be specified with :manifest option" unless manifest || json
178
216
  raise "Require at least one input file" if files.empty?
179
217
  @files = files
180
218
  @prefixes = {}
219
+ @warnings = 0
181
220
 
182
- # If provided :json, it is used for generating all other output forms
183
- if @options[:json]
221
+ # If provided json, it is used for generating all other output forms
222
+ if json
184
223
  @json_hash = ::JSON.parse(File.read(files.first))
185
224
  # Add a base_uri so relative subjects aren't dropped
186
225
  JSON::LD::Reader.open(files.first, base_uri: "http://example.org/report") do |r|
@@ -195,25 +234,25 @@ class EarlReport
195
234
  end
196
235
 
197
236
  # Load manifests, possibly with base URI
198
- status "read #{@options[:manifest].inspect}"
237
+ status "read #{manifest.inspect}"
199
238
  man_opts = {}
200
- man_opts[:base_uri] = RDF::URI(@options[:base]) if @options[:base]
239
+ man_opts[:base_uri] = RDF::URI(base) if base
201
240
  @graph = RDF::Graph.new
202
- Array(@options[:manifest]).each do |man|
241
+ Array(manifest).each do |man|
203
242
  g = RDF::Graph.load(man, unique_bnodes: true, **man_opts)
204
243
  status " loaded #{g.count} triples from #{man}"
205
244
  graph << g
206
245
  end
207
246
 
208
247
  # Hash test cases by URI
209
- tests = SPARQL.execute(@options[:query], graph)
248
+ tests = SPARQL.execute(query, graph)
210
249
  .to_a
211
250
  .inject({}) {|memo, soln| memo[soln[:uri]] = soln; memo}
212
251
 
213
252
  if tests.empty?
214
253
  raise "no tests found querying manifest.\n" +
215
254
  "Results are found using the following query, this can be overridden using the --query option:\n" +
216
- "#{@options[:query]}"
255
+ "#{query}"
217
256
  end
218
257
 
219
258
  # Manifests in graph
@@ -234,7 +273,7 @@ class EarlReport
234
273
  status "read #{file}"
235
274
  file_graph = RDF::Graph.load(file)
236
275
  if file_graph.first_object(predicate: RDF::URI('http://www.w3.org/ns/earl#testSubjects'))
237
- status " skip #{file}, which seems to be a previous rollup earl report"
276
+ warn " skip #{file}, which seems to be a previous rollup earl report"
238
277
  @files -= [file]
239
278
  else
240
279
  status " loaded #{file_graph.count} triples"
@@ -286,7 +325,7 @@ class EarlReport
286
325
  subjects[solution[:uri]] = RDF::URI(file)
287
326
 
288
327
  # Add TestSubject information to main graph
289
- name = solution[:name].to_s if solution[:name]
328
+ doapName = solution[:name].to_s if solution[:name]
290
329
  language = solution[:language].to_s if solution[:language]
291
330
  doapDesc = solution[:doapDesc] if solution[:doapDesc]
292
331
  doapDesc.language ||= :en if doapDesc
@@ -294,7 +333,7 @@ class EarlReport
294
333
  graph << RDF::Statement(solution[:uri], RDF.type, RDF::Vocab::DOAP.Project)
295
334
  graph << RDF::Statement(solution[:uri], RDF.type, EARL.TestSubject)
296
335
  graph << RDF::Statement(solution[:uri], RDF.type, EARL.Software)
297
- graph << RDF::Statement(solution[:uri], RDF::Vocab::DOAP.name, name)
336
+ graph << RDF::Statement(solution[:uri], RDF::Vocab::DOAP.name, doapName)
298
337
  graph << RDF::Statement(solution[:uri], RDF::Vocab::DOAP.developer, solution[:developer])
299
338
  graph << RDF::Statement(solution[:uri], RDF::Vocab::DOAP.homepage, solution[:homepage]) if solution[:homepage]
300
339
  graph << RDF::Statement(solution[:uri], RDF::Vocab::DOAP.description, doapDesc) if doapDesc
@@ -303,7 +342,12 @@ class EarlReport
303
342
  graph << RDF::Statement(solution[:developer], RDF::Vocab::FOAF.name, devName) if devName
304
343
  graph << RDF::Statement(solution[:developer], RDF::Vocab::FOAF.homepage, solution[:devHomepage]) if solution[:devHomepage]
305
344
 
306
- release ||= solution[:release] || RDF::Node.new
345
+ # Make sure BNode identifiers don't leak
346
+ release ||= if !solution[:release] || solution[:release].node?
347
+ RDF::Node.new
348
+ else
349
+ solution[:release]
350
+ end
307
351
  graph << RDF::Statement(solution[:uri], RDF::Vocab::DOAP.release, release)
308
352
  graph << RDF::Statement(release, RDF::Vocab::DOAP.revision, (solution[:revision] || "unknown"))
309
353
  end
@@ -317,12 +361,12 @@ class EarlReport
317
361
  subject = solution[:subject]
318
362
  unless tests[solution[:test]]
319
363
  assertion_stats["Skipped"] = assertion_stats["Skipped"].to_i + 1
320
- $stderr.puts "Skipping result for #{solution[:test]} for #{subject}, which is not defined in manifests"
364
+ warn "Skipping result for #{solution[:test]} for #{subject}, which is not defined in manifests"
321
365
  next
322
366
  end
323
367
  unless subjects[subject]
324
368
  assertion_stats["Missing Subject"] = assertion_stats["Missing Subject"].to_i + 1
325
- $stderr.puts "No test result subject found for #{subject}: in #{subjects.keys.join(', ')}"
369
+ warn "No test result subject found for #{subject}: in #{subjects.keys.join(', ')}"
326
370
  next
327
371
  end
328
372
  found_solutions ||= true
@@ -345,7 +389,7 @@ class EarlReport
345
389
  end
346
390
 
347
391
  # See if subject did not report results, which may indicate a formatting error in the EARL source
348
- $stderr.puts "No results found for #{subject} using #{ASSERTION_QUERY}" unless found_solutions
392
+ warn "No results found for #{subject} using #{ASSERTION_QUERY}" unless found_solutions
349
393
  end
350
394
  end
351
395
 
@@ -374,36 +418,16 @@ class EarlReport
374
418
  assertion_stats.each {|stat, count| status("Assertions #{stat}: #{count}")}
375
419
 
376
420
  # Add report wrapper to graph
377
- ttl = %(
378
- @prefix dc: <http://purl.org/dc/terms/> .
379
- @prefix doap: <http://usefulinc.com/ns/doap#> .
380
- @prefix earl: <http://www.w3.org/ns/earl#> .
381
- @prefix mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#> .
382
- @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
383
-
384
- <> a earl:Software, doap:Project;
385
- doap:name #{quoted(@options.fetch(:name, 'Unknown'))};
386
- dc:bibliographicCitation "#{@options.fetch(:bibRef, 'Unknown reference')}";
387
- earl:generatedBy <http://rubygems.org/gems/earl-report>;
388
- earl:assertions #{subjects.values.map {|f| f.to_ntriples}.join(",\n ")};
389
- earl:testSubjects #{subjects.keys.map {|f| f.to_ntriples}.join(",\n ")};
390
- mf:entries (#{man_uris.map {|f| f.to_ntriples}.join("\n ")}) .
391
-
392
- <http://rubygems.org/gems/earl-report> a earl:Software, doap:Project;
393
- doap:name "earl-report";
394
- doap:shortdesc "Earl Report summary generator"@en;
395
- doap:description "EarlReport generates HTML+RDFa rollups of multiple EARL reports"@en;
396
- doap:homepage <https://github.com/gkellogg/earl-report>;
397
- doap:programming-language "Ruby";
398
- doap:license <http://unlicense.org>;
399
- doap:release <https://github.com/gkellogg/earl-report/tree/#{VERSION}>;
400
- doap:developer <http://greggkellogg.net/foaf#me> .
401
-
402
- <https://github.com/gkellogg/earl-report/tree/#{VERSION}> a doap:Version;
403
- doap:name "earl-report-#{VERSION}";
404
- doap:created "#{File.mtime(File.expand_path('../../VERSION', __FILE__)).strftime('%Y-%m-%d')}"^^xsd:date;
405
- doap:revision "#{VERSION}" .
406
- ).gsub(/^ /, '')
421
+ ttl = TURTLE_PREFIXES + %(
422
+ <> a earl:Software, doap:Project;
423
+ doap:name #{quoted(name)};
424
+ dc:bibliographicCitation "#{bibRef}";
425
+ earl:generatedBy <https://rubygems.org/gems/earl-report>;
426
+ earl:assertions #{subjects.values.map {|f| f.to_ntriples}.join(",\n ")};
427
+ earl:testSubjects #{subjects.keys.map {|f| f.to_ntriples}.join(",\n ")};
428
+ mf:entries (#{man_uris.map {|f| f.to_ntriples}.join("\n ")}) .
429
+ ).gsub(/^ /, '') +
430
+ TURTLE_SOFTWARE
407
431
  RDF::Turtle::Reader.new(ttl) {|r| graph << r}
408
432
 
409
433
  # Each manifest is an earl:Report
@@ -421,6 +445,8 @@ class EarlReport
421
445
  graph << RDF::Statement.new(u, RDF.type, EARL.TestCriterion)
422
446
  graph << RDF::Statement.new(u, RDF.type, EARL.TestCase)
423
447
  end
448
+
449
+ raise "Warnings issued in strict mode" if strict && @warnings > 0
424
450
  end
425
451
 
426
452
  ##
@@ -428,48 +454,47 @@ class EarlReport
428
454
  #
429
455
  # If no `io` option is provided, the output is returned as a string
430
456
  #
457
+ # @param [Symbol] format (:html)
458
+ # @param [IO] io (nil)
459
+ # `IO` to output results
431
460
  # @param [Hash{Symbol => Object}] options
432
- # @option options [Symbol] format (:html)
433
- # @option options[IO] :io
434
- # Optional `IO` to output results
461
+ # @param [String] template
462
+ # HAML template for generating report
435
463
  # @return [String] serialized graph, if `io` is nil
436
- def generate(options = {})
437
- options = {format: :html}.merge(options)
438
-
439
- io = options[:io]
464
+ def generate(format: :html, io: nil, template: nil, **options)
440
465
 
441
- status("generate: #{options[:format]}")
466
+ status("generate: #{format}")
442
467
  ##
443
468
  # Retrieve Hashed information in JSON-LD format
444
- case options[:format]
469
+ case format
445
470
  when :jsonld, :json
446
471
  json = json_hash.to_json(JSON::LD::JSON_STATE)
447
472
  io.write(json) if io
448
473
  json
449
474
  when :turtle, :ttl
450
475
  if io
451
- earl_turtle(options)
476
+ earl_turtle(io: io)
452
477
  else
453
478
  io = StringIO.new
454
- earl_turtle(options.merge(io: io))
479
+ earl_turtle(io: io)
455
480
  io.rewind
456
481
  io.read
457
482
  end
458
483
  when :html
459
- template = case options[:template]
460
- when String then options[:tempate]
461
- when IO, StringIO then options[:template].read
484
+ haml = case template
485
+ when String then template
486
+ when IO, StringIO then template.read
462
487
  else
463
488
  File.read(File.expand_path('../earl_report/views/earl_report.html.haml', __FILE__))
464
489
  end
465
490
 
466
491
  # Generate HTML report
467
- html = Haml::Engine.new(template, format: :xhtml).render(self, tests: json_hash)
492
+ html = Haml::Engine.new(haml, format: :xhtml).render(self, tests: json_hash)
468
493
  io.write(html) if io
469
494
  html
470
495
  else
471
- writer = RDF::Writer.for(options[:format])
472
- writer.dump(@graph, io, options.merge(standard_prefixes: true))
496
+ writer = RDF::Writer.for(format)
497
+ writer.dump(@graph, io, standard_prefixes: true, **options)
473
498
  end
474
499
  end
475
500
 
@@ -487,10 +512,10 @@ class EarlReport
487
512
  embed: '@never',
488
513
  pruneBlankNodeIdentifiers: false)
489
514
  # Reorder test subjects by @id
490
- framed['testSubjects'] = framed['testSubjects'].sort_by {|t| t['@id']}
515
+ framed['testSubjects'] = Array(framed['testSubjects']).sort_by {|t| t['@id']}
491
516
 
492
517
  # Reorder test assertions to make them consistent with subject order
493
- framed['entries'].each do |manifest|
518
+ Array(framed['entries']).each do |manifest|
494
519
  manifest['entries'].each do |test|
495
520
  test['assertions'] = test['assertions'].sort_by {|a| a['subject']}
496
521
  end
@@ -506,61 +531,119 @@ class EarlReport
506
531
 
507
532
  ##
508
533
  # Output consoloated EARL report as Turtle
509
- # @param [Hash{Symbol => Object}] options
510
- # @option options [IO, StringIO] :io
534
+ # @param [IO] io ($stdout)
535
+ # `IO` to output results
511
536
  # @return [String]
512
- def earl_turtle(options)
513
- io = options[:io]
514
-
515
- top_level = graph.first_subject(predicate: EARL.generatedBy)
537
+ def earl_turtle(io: $stdout)
538
+ context = JSON::LD::Context.parse(json_hash['@context'])
539
+ io.write(TURTLE_PREFIXES + "\n")
516
540
 
517
- # Write starting with the entire graph to get preamble
518
- writer = RDF::Turtle::Writer.new(io, standard_prefixes: true)
519
- writer << graph
541
+ # Write project header
542
+ ttl_entity(io, json_hash, context)
520
543
 
521
- writer.send(:preprocess)
522
- writer.send(:start_document)
544
+ # Write out each manifest entry
545
+ io.puts("# Manifests")
546
+ json_hash['entries'].each do |man|
547
+ ttl_entity(io, man, context)
523
548
 
524
- # Write top-level object referencing manifests and subjects
525
- writer.send(:statement, top_level)
549
+ # Output each test entry with assertions
550
+ man['entries'].each do |entry|
551
+ ttl_entity(io, entry, context)
552
+ end
553
+ end
526
554
 
527
- # Write each manifest
528
- io.puts "\n# Manifests"
529
- RDF::List.new(subject: graph.first_object(subject: top_level, predicate: MF[:entries]), graph: graph).each do |manifest|
530
- writer.send(:statement, manifest)
555
+ # Output each DOAP
556
+ json_hash['testSubjects'].each do |doap|
557
+ ttl_entity(io, doap, context)
531
558
 
532
- # Write each test case
533
- RDF::List.new(subject: graph.first_object(subject: manifest, predicate: MF[:entries]), graph: graph).each do |tc|
534
- writer.send(:statement, tc)
559
+ # FOAF
560
+ dev = doap['developer']
561
+ dev = [dev] unless dev.is_a?(Array)
562
+ dev.each do |foaf|
563
+ ttl_entity(io, foaf, context)
535
564
  end
536
565
  end
566
+
567
+ io.write(TURTLE_SOFTWARE)
568
+ end
537
569
 
538
- # Write test subjects
539
- io.puts "\n# Test Subjects"
540
- graph.query(subject: top_level, predicate: EARL.testSubjects).each do |s|
541
- writer.send(:statement, s.object)
570
+ def ttl_entity(io, entity, context)
571
+ io.write(ttl_value(entity) + " " + entity.map do |dk, dv|
572
+ case dk
573
+ when '@context', '@id'
574
+ nil
575
+ when '@type'
576
+ "a " + ttl_value(dv)
577
+ when 'assertions'
578
+ "earl:assertions #{dv.map {|a| ttl_assertion(a)}.join(", ")}"
579
+ when 'entries'
580
+ "mf:entries #{ttl_value({'@list' => dv}, whitespace: "\n ")}"
581
+ when 'release'
582
+ "doap:release [doap:revision #{quoted(dv['revision'])}]"
583
+ else
584
+ dv = [dv] unless dv.is_a?(Array)
585
+ dv = dv.map {|v| v.is_a?(Hash) ? v : context.expand_value(dk, v)}
586
+ "#{ttl_value(dk)} #{ttl_value(dv, whitespace: "\n ")}"
587
+ end
588
+ end.compact.join(" ;\n ") + " .\n\n")
589
+ end
542
590
 
543
- # Write each developer
544
- graph.query(subject: s.object, predicate: RDF::Vocab::DOAP.developer).each do |d|
545
- writer.send(:statement, d.object)
591
+ def ttl_value(value, whitespace: " ")
592
+ if value.is_a?(Array)
593
+ value.map {|v| ttl_value(v)}.join(",#{whitespace}")
594
+ elsif value.is_a?(Hash)
595
+ if value.key?('@list')
596
+ "(#{value['@list'].map {|vv| ttl_value(vv)}.join(whitespace)})"
597
+ elsif value.key?('@value')
598
+ quoted(value['@value'], language: value['@language'], datatype: value['@type'])
599
+ elsif value.key?('@id')
600
+ ttl_value(value['@id'])
601
+ else
602
+ "[]"
546
603
  end
604
+ elsif value.start_with?(/https?/) || value.start_with?('/')
605
+ "<#{value}>"
606
+ elsif value.include?(':')
607
+ value
608
+ elsif json_hash['@context'][value].is_a?(Hash)
609
+ json_hash['@context'][value].fetch('@id', "earl:#{value}")
610
+ elsif value.empty?
611
+ "<>"
612
+ else
613
+ "earl:#{value}"
547
614
  end
615
+ end
548
616
 
549
- # Write generator
550
- io.puts "\n# Report Generation Software"
551
- writer.send(:statement, RDF::URI("http://rubygems.org/gems/earl-report"))
552
- writer.send(:statement, RDF::URI("https://github.com/gkellogg/earl-report/tree/#{VERSION}"))
617
+ def ttl_assertion(value)
618
+ return ttl_value(value) if value.is_a?(String)
619
+ block = [
620
+ "[",
621
+ " a earl:Assertion ;",
622
+ " earl:test #{ttl_value(value['test'])} ;",
623
+ " earl:subject #{ttl_value(value['subject'])} ;",
624
+ " earl:result [",
625
+ " a earl:TestResult ;",
626
+ " earl:outcome #{ttl_value(value['result']['outcome'])}",
627
+ " ] ;",
628
+ ]
629
+ block << " earl:assertedBy #{ttl_value(value['assertedBy'])} ;" if value['assertedBy']
630
+
631
+ block.join("\n") + "\n ]"
553
632
  end
554
633
 
555
- def quoted(string)
556
- (@turtle_writer ||= RDF::Turtle::Writer.new).send(:quoted, string)
634
+ def quoted(string, language: nil, datatype: nil)
635
+ str = (@turtle_writer ||= RDF::Turtle::Writer.new).send(:quoted, string)
636
+ str += "@#{language}" if language
637
+ str += "^^#{ttl_value(datatype)}" if datatype
638
+ str
557
639
  end
558
640
 
559
641
  def warn(message)
642
+ @warnings += 1
560
643
  $stderr.puts message
561
644
  end
562
645
 
563
646
  def status(message)
564
- $stderr.puts message if @options[:verbose]
647
+ $stderr.puts message if verbose
565
648
  end
566
649
  end