earl-report 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # earl-report
2
+ ============
3
+
4
+ Ruby gem to consolidate multiple EARL report and generate a rollup conformance report.
5
+
6
+ ## Description
7
+
8
+ Reads a test manifest in the
9
+ [standard RDF WG format](http://www.w3.org/2011/rdf-wg/wiki/Turtle_Test_Suite)
10
+ and generates a rollup report in RDFa+HTML.
11
+
12
+ ## Test Specifications
13
+ The test manifest is presumed to be of the following form:
14
+
15
+ ### Manifest Header
16
+
17
+ The manifest header looks like:
18
+
19
+ <> rdf:type mf:Manifest ;
20
+ rdfs:comment "Turtle tests" ;
21
+ mf:entries
22
+ (
23
+ ....
24
+ ) .
25
+
26
+ where .... is a list of links to test descriptions, one per line.
27
+
28
+ ### Test description
29
+
30
+ This is an example of a synatx test:
31
+
32
+ <#turtle-syntax-file-01> rdf:type rdft:TestTurtlePositiveSyntax ;
33
+ mf:name "turtle-syntax-file-01" ;
34
+ rdfs:comment "Further description of the test" ;
35
+ mf:action <turtle-syntax-file-01.ttl> ;
36
+ mf:result <turtle-eval-struct-01.nt> .
37
+
38
+ ## Individual EARL reports
39
+
40
+ Results for individual implementations should be specified in Turtle form, but
41
+ may be specified in an any compatible RDF serialization (JSON-LD is presumed to
42
+ be a cached rollup report). The report is composed of `Assertion` declarations
43
+ in the following form:
44
+
45
+ [ a earl:Assertion;
46
+ earl:assertedBy <http://greggkellogg.net/foaf#me>;
47
+ earl:subject <http://rubygems.org/gems/rdf-turtle>;
48
+ earl:test <http://dvcs.w3.org/hg/rdf/raw-file/e80b58a1a711/rdf-turtle/tests-ttl/manifest.ttl#turtle-syntax-file-01>;
49
+ earl:result [
50
+ a earl:TestResult;
51
+ earl:outcome earl:passed;
52
+ dc:date "2012-11-17T15:19:11-05:00"^^xsd:dateTime];
53
+ earl:mode earl:automatic ] .
54
+
55
+ Additionally, `earl:subject` is expected to reference a [DOAP]() description
56
+ of the reported software, in the following form:
57
+
58
+ <http://rubygems.org/gems/rdf-turtle> a doap:Project, earl:TestSubject, earl:Software ;
59
+ doap:name "RDF::Turtle" ;
60
+ doap:developer <http://greggkellogg.net/foaf#me> ;
61
+ doap:homepage <http://ruby-rdf.github.com/rdf-turtle> ;
62
+ doap:description "RDF::Turtle is an Turtle reader/writer for the RDF.rb library suite."@en ;
63
+ doap:programming-language "Ruby" .
64
+
65
+ The [DOAP]() description may be included in the [EARL]() report. If not found,
66
+ the IRI identified by `earl:subject` will be dereferenced and is presumed to
67
+ provide a [DOAP]() specification of the test subject.
68
+
69
+ The `doap:developer` is expected to reference a [FOAF]() profile for the agent
70
+ (user or organization) responsible for the test subject. It is expected to be
71
+ of the following form:
72
+
73
+ <http://greggkellogg.net/foaf#me> foaf:name "Gregg Kellogg" .
74
+
75
+ If not found, the IRI identified by `doap:developer`
76
+ will be dereferenced and is presumed to provide a [FOAF]() profile of the developer.
77
+
78
+ ## Usage
79
+
80
+ The `earl` command may be used to directly create a report from zero or more input files, which are themselves [EARL][] report.
81
+
82
+ gem install earl-report
83
+
84
+ earl \
85
+ --output FILE # Location for generated report
86
+ --tempate [FILE] # Location of report template file; returns default if not specified
87
+ --bibRef # The default ReSpec-formatted bibliographic reference for the report
88
+ --name # The name of the software being reported upon
89
+ manifest # one or more test manifests used to define test descriptions
90
+ report* # one or more EARL report in most RDF formats
91
+
92
+ ## Report generation template
93
+
94
+ The report template is in ReSpec form using [Haml]() to generate individual elements.
95
+
96
+ [DOAP]: https://github.com/edumbill/doap/wiki
97
+ [EARL]: http://www.w3.org/TR/EARL10-Schema/
98
+ [FOAF]: http://xmlns.com/foaf/spec/
99
+ [Haml]: http://haml.info/
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/earl-report ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", 'lib')))
4
+
5
+ require 'earl_report'
6
+ require 'getoptlong'
7
+
8
+ def run(input, options)
9
+ end
10
+
11
+ OPT_ARGS = [
12
+ ["--bibRef", GetoptLong::REQUIRED_ARGUMENT,"ReSpec BibRef of specification being reported upon"],
13
+ ["--format", "-f", GetoptLong::REQUIRED_ARGUMENT,"Format of output, one of 'ttl', 'json', or 'html'"],
14
+ ["--json", GetoptLong::NO_ARGUMENT, "Input is a JSON-LD formatted result"],
15
+ ["--name", GetoptLong::REQUIRED_ARGUMENT,"Name of specification"],
16
+ ["--output", "-o", GetoptLong::REQUIRED_ARGUMENT,"Output report to file"],
17
+ ["--template", GetoptLong::OPTIONAL_ARGUMENT,"Specify or return default report template"],
18
+ ["--verbose", GetoptLong::NO_ARGUMENT, "Detail on execution"],
19
+ ["--help", "-?", GetoptLong::NO_ARGUMENT, "This message"]
20
+ ]
21
+ def usage
22
+ STDERR.puts %{
23
+ Generate EARL report for mutliple test results against a test manifest.
24
+
25
+ Usage: #{$0} [options] test-manifest test-result ...
26
+ }.gsub(/^ /, '')
27
+ width = OPT_ARGS.map do |o|
28
+ l = o.first.length
29
+ l += o[1].length + 2 if o[1].is_a?(String)
30
+ l
31
+ end.max
32
+ OPT_ARGS.each do |o|
33
+ s = " %-*s " % [width, (o[1].is_a?(String) ? "#{o[0,2].join(', ')}" : o[0])]
34
+ s += o.last
35
+ STDERR.puts s
36
+ end
37
+ exit(1)
38
+ end
39
+
40
+ opts = GetoptLong.new(*OPT_ARGS.map {|o| o[0..-2]})
41
+
42
+ options = {
43
+ :format => :html,
44
+ :io => STDOUT}
45
+
46
+ opts.each do |opt, arg|
47
+ case opt
48
+ when '--bibRef' then options[:bibRef] = arg
49
+ when '--format' then options[:format] = arg.to_sym
50
+ when '--name' then options[:name] = arg
51
+ when '--output' then options[:io] = File.open(arg, "w")
52
+ when '--template' then options[:template] = arg
53
+ when '--verbose' then options[:verbose] = true
54
+ when '--help' then usage
55
+ else
56
+ options[opt.to_sym] = arg
57
+ end
58
+ end
59
+
60
+ # If requesting template, just return it
61
+ if options.has_key?(:template) && options[:template].empty?
62
+ File.open(File.expand_path("../../lib/earl_report/views/earl_report.html.haml", __FILE__)) do |f|
63
+ options[:io].write(f.read)
64
+ end
65
+ else
66
+ earl = EarlReport.new(*ARGV, options)
67
+ earl.generate(options.merge(:source_files => ARGV))
68
+ end
@@ -0,0 +1,424 @@
1
+ # EARL reporting
2
+ require 'linkeddata'
3
+ require 'sparql'
4
+ require 'haml'
5
+
6
+ ##
7
+ # EARL reporting class.
8
+ # Instantiate a new class using one or more input graphs
9
+ class EarlReport
10
+ attr_reader :graph
11
+ TEST_SUBJECT_QUERY = %(
12
+ PREFIX doap: <http://usefulinc.com/ns/doap#>
13
+ PREFIX foaf: <http://xmlns.com/foaf/0.1/>
14
+
15
+ SELECT DISTINCT ?uri ?name ?developer ?dev_name ?dev_type ?doap_desc ?homepage ?language
16
+ WHERE {
17
+ ?uri a doap:Project; doap:name ?name .
18
+ OPTIONAL { ?uri doap:developer ?developer .}
19
+ OPTIONAL { ?uri doap:homepage ?homepage . }
20
+ OPTIONAL { ?uri doap:description ?doap_desc . }
21
+ OPTIONAL { ?uri doap:programming-language ?language . }
22
+ OPTIONAL { ?developer foaf:name ?dev_name .}
23
+ OPTIONAL { ?developer a ?dev_type . }
24
+ }
25
+ ).freeze
26
+
27
+ DOAP_QUERY = %(
28
+ PREFIX earl: <http://www.w3.org/ns/earl#>
29
+ PREFIX doap: <http://usefulinc.com/ns/doap#>
30
+
31
+ SELECT DISTINCT ?subject ?name
32
+ WHERE {
33
+ [ a earl:Assertion; earl:subject ?subject ] .
34
+ OPTIONAL {
35
+ ?subject a doap:Project; doap:name ?name
36
+ }
37
+ }
38
+ )
39
+
40
+ ASSERTION_QUERY = %(
41
+ PREFIX earl: <http://www.w3.org/ns/earl#>
42
+
43
+ SELECT ?by ?mode ?outcome ?subject ?test
44
+ WHERE {
45
+ [ a earl:Assertion;
46
+ earl:assertedBy ?by;
47
+ earl:mode ?mode;
48
+ earl:result [earl:outcome ?outcome];
49
+ earl:subject ?subject;
50
+ earl:test ?test ] .
51
+ }
52
+ ).freeze
53
+
54
+ # Convenience vocabularies
55
+ class EARL < RDF::Vocabulary("http://www.w3.org/ns/earl#"); end
56
+ class MF < RDF::Vocabulary("http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#"); end
57
+
58
+ ##
59
+ # Load test assertions and look for referenced software and developer information
60
+ # @param [Array<String>] *files Assertions
61
+ # @param [Hash{Symbol => Object}] options
62
+ # @option options [Boolean] :verbose (true)
63
+ def initialize(*files)
64
+ @options = files.last.is_a?(Hash) ? files.pop.dup : {}
65
+ @graph = RDF::Graph.new
66
+ @prefixes = {}
67
+ files.flatten.each do |file|
68
+ status "read #{file}"
69
+ file_graph = case file
70
+ when /\.jsonld/
71
+ @json_hash = ::JSON.parse(File.read(file))
72
+ return
73
+ else RDF::Graph.load(file)
74
+ end
75
+ status " loaded #{file_graph.count} triples"
76
+ @graph << file_graph
77
+ end
78
+
79
+ # Find or load DOAP descriptions for all subjects
80
+ SPARQL.execute(DOAP_QUERY, @graph).each do |solution|
81
+ subject = solution[:subject]
82
+
83
+ # Load DOAP definitions
84
+ unless solution[:name] # not loaded
85
+ status "read doap description for #{subject}"
86
+ begin
87
+ doap_graph = RDF::Graph.load(subject)
88
+ status " loaded #{doap_graph.count} triples"
89
+ @graph << doap_graph.to_a
90
+ rescue
91
+ status " failed"
92
+ end
93
+ end
94
+ end
95
+
96
+ # Load developers referenced from Test Subjects
97
+ SPARQL.execute(TEST_SUBJECT_QUERY, @graph).each do |solution|
98
+ # Load DOAP definitions
99
+ if solution[:developer] && !solution[:dev_name] # not loaded
100
+ status "read description for #{solution[:developer].inspect}"
101
+ begin
102
+ foaf_graph = RDF::Graph.load(solution[:developer])
103
+ status " loaded #{foaf_graph.count} triples"
104
+ @graph << foaf_graph.to_a
105
+ rescue
106
+ status " failed"
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ ##
113
+ # Dump the collesced output graph
114
+ #
115
+ # If no `io` option is provided, the output is returned as a string
116
+ #
117
+ # @param [Hash{Symbol => Object}] options
118
+ # @option options [Symbol] format (:html)
119
+ # @option options [String] :bibRef
120
+ # ReSpec bibliography reference for specification being tested
121
+ # @option options [Array<String>] :source_files
122
+ # Used for referencing the files used to generate this report
123
+ # @option options[IO] :io
124
+ # Optional `IO` to output results
125
+ # @return [String] serialized graph, if `io` is nil
126
+ def generate(options = {})
127
+ options = {
128
+ format: :html,
129
+ bibRef: "[[TURTLE]]",
130
+ name: "Turtle Test Results",
131
+ }.merge(options)
132
+
133
+ io = options[:io]
134
+
135
+ status("generate: #{options[:format]}")
136
+ ##
137
+ # Retrieve Hashed information in JSON-LD format
138
+ hash = json_hash(options)
139
+ case options[:format]
140
+ when :jsonld, :json
141
+ json = hash.to_json(JSON::LD::JSON_STATE)
142
+ io.write(json) if io
143
+ json
144
+ when :turtle, :ttl
145
+ if io
146
+ earl_turtle(options.merge(:json_hash => hash))
147
+ else
148
+ io = StringIO.new
149
+ earl_turtle(:json_hash => hash, :io => io)
150
+ io.rewind
151
+ io.read
152
+ end
153
+ when :html
154
+ template = options[:template] ||
155
+ File.read(File.expand_path('../earl_report/views/earl_report.html.haml', __FILE__))
156
+
157
+ # Generate HTML report
158
+ html = Haml::Engine.new(template, :format => :xhtml)
159
+ .render(self, :tests => hash, :source_files => options.fetch(:source_files, []))
160
+ io.write(html) if io
161
+ html
162
+ else
163
+ if io
164
+ RDF::Writer.for(options[:format]).new(io) {|w| w << graph}
165
+ else
166
+ graph.dump(options[:format])
167
+ end
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ ##
174
+ # Return hashed EARL report in JSON-LD form
175
+ # @return [Hash]
176
+ def json_hash(options)
177
+ @json_hash ||= begin
178
+ # Customized JSON-LD output
179
+ {
180
+ "@context" => {
181
+ dc: "http://purl.org/dc/terms/",
182
+ doap: "http://usefulinc.com/ns/doap#",
183
+ earl: "http://www.w3.org/ns/earl#",
184
+ mf: "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#",
185
+ foaf: "http://xmlns.com/foaf/0.1/",
186
+ rdfs: "http://www.w3.org/2000/01/rdf-schema#",
187
+ assertedBy: {"@id" => "earl:assertedBy", "@type" => "@id"},
188
+ bibRef: {"@id" => "dc: bibliographicCitation"},
189
+ description: {"@id" => "dc:description"},
190
+ developer: {"@id" => "doap:developer", "@type" => "@id"},
191
+ homepage: {"@id" => "doap:homepage", "@type" => "@id"},
192
+ doap_desc: {"@id" => "doap:description"},
193
+ language: {"@id" => "doap:programming-language"},
194
+ testAction: {"@id" => "mf:action", "@type" => "@id"},
195
+ testResult: {"@id" => "mf:result", "@type" => "@id"},
196
+ label: {"@id" => "rdfs:label"},
197
+ mode: {"@id" => "earl:mode", "@type" => "@id"},
198
+ name: {"@id" => "doap:name"},
199
+ outcome: {"@id" => "earl:outcome", "@type" => "@id"},
200
+ result: {"@id" => "earl:result"},
201
+ subject: {"@id" => "earl:subject", "@type" => "@id"},
202
+ test: {"@id" => "earl:test", "@type" => "@id"},
203
+ title: {"@id" => "dc:title"}
204
+ },
205
+ "@id" => "",
206
+ "@type" => %w(earl:Software doap:Project),
207
+ 'name' => options[:name],
208
+ 'bibRef' => options[:bibRef],
209
+ 'testSubjects' => json_test_subject_info,
210
+ 'tests' => json_result_info
211
+ }
212
+ end
213
+ end
214
+
215
+ ##
216
+ # Return array of test subject information
217
+ # @return [Array]
218
+ def json_test_subject_info
219
+ # Get the set of subjects
220
+ ts_info = {}
221
+ SPARQL.execute(TEST_SUBJECT_QUERY, @graph).each do |solution|
222
+ status "solution #{solution.to_hash.inspect}"
223
+ info = ts_info[solution[:uri].to_s] ||= {}
224
+ %w(name doap_desc homepage language).each do |prop|
225
+ info[prop] = solution[prop.to_sym].to_s if solution[prop.to_sym]
226
+ end
227
+ if solution[:dev_name]
228
+ dev_type = solution[:dev_type].to_s =~ /Organization/ ? "foaf:Organization" : "foaf:Person"
229
+ info['developer'] = Hash.ordered
230
+ info['developer']['@id'] = solution[:developer].to_s if solution[:developer].uri?
231
+ info['developer']['@type'] = dev_type
232
+ info['developer']['foaf:name'] = solution[:dev_name].to_s if solution[:dev_name]
233
+ end
234
+ end
235
+
236
+ # Map ids and values to array entries
237
+ ts_info.keys.map do |id|
238
+ info = ts_info[id]
239
+ subject = Hash.ordered
240
+ subject["@id"] = id
241
+ subject["@type"] = %w(earl:TestSubject doap:Project)
242
+ %w(name developer doap_desc homepage language).each do |prop|
243
+ subject[prop] = info[prop] if info[prop]
244
+ end
245
+ subject
246
+ end
247
+ end
248
+
249
+ ##
250
+ # Return result information for each test
251
+ #
252
+ # @return [Array]
253
+ def json_result_info
254
+ test_cases = {}
255
+
256
+ @graph.query(:predicate => MF['entries']) do |stmt|
257
+ # Iterate through the test manifest and write out a TestCase
258
+ # for each test
259
+ RDF::List.new(stmt.object, @graph).map do |tc|
260
+ tc_hash = {}
261
+ tc_hash['@id'] = tc.to_s
262
+ tc_hash['@type'] = %w(earl:TestCriterion earl:TestCase)
263
+
264
+ # Extract important properties
265
+ @graph.query(:subject => tc).each do |tc_stmt|
266
+ case tc_stmt.predicate.to_s
267
+ when MF['name'].to_s
268
+ tc_hash['title'] = tc_stmt.object.to_s
269
+ when RDF::RDFS.comment.to_s
270
+ tc_hash['description'] = tc_stmt.object.to_s
271
+ when MF.action.to_s
272
+ tc_hash['testAction'] = tc_stmt.object.to_s
273
+ when MF.result.to_s
274
+ tc_hash['testResult'] = tc_stmt.object.to_s
275
+ else
276
+ #STDERR.puts "TC soln: #{tc_stmt.inspect}"
277
+ end
278
+ end
279
+
280
+ test_cases[tc.to_s] = tc_hash
281
+ end
282
+ end
283
+
284
+ raise "No test cases found" if test_cases.empty?
285
+
286
+ status "Test cases:\n #{test_cases.keys.join("\n ")}"
287
+ # Iterate through assertions and add to appropriate test case
288
+ SPARQL.execute(ASSERTION_QUERY, @graph).each do |solution|
289
+ tc = test_cases[solution[:test].to_s]
290
+ STDERR.puts "No test case found for #{solution[:test]}" unless tc
291
+ tc ||= {}
292
+ subject = solution[:subject].to_s
293
+ ta_hash = {}
294
+ ta_hash['@type'] = 'earl:Assertion'
295
+ ta_hash['assertedBy'] = solution[:by].to_s
296
+ ta_hash['test'] = solution[:test].to_s
297
+ ta_hash['mode'] = "earl:#{solution[:mode].to_s.split('#').last || 'automatic'}"
298
+ ta_hash['subject'] = subject
299
+ ta_hash['result'] = {
300
+ '@type' => 'earl:TestResult',
301
+ "outcome" => (solution[:outcome] == EARL.passed ? 'earl:passed' : 'earl:failed')
302
+ }
303
+ tc[subject] = ta_hash
304
+ end
305
+
306
+ test_cases.values
307
+ end
308
+
309
+ ##
310
+ # Output consoloated EARL report as Turtle
311
+ # @param [IO, StringIO] io
312
+ # @return [String]
313
+ def earl_turtle(options)
314
+ io = options[:io]
315
+ json_hash = options[:json_hash]
316
+ # Write preamble
317
+ {
318
+ :dc => RDF::DC,
319
+ :doap => RDF::DOAP,
320
+ :earl => EARL,
321
+ :foaf => RDF::FOAF,
322
+ :mf => MF,
323
+ :owl => RDF::OWL,
324
+ :rdf => RDF,
325
+ :rdfs => RDF::RDFS,
326
+ :xhv => RDF::XHV,
327
+ :xsd => RDF::XSD
328
+ }.each do |prefix, vocab|
329
+ io.puts("@prefix #{prefix}: <#{vocab.to_uri}> .")
330
+ end
331
+ io.puts
332
+
333
+ # Write earl:Software for the report
334
+ io.puts %(<#{json_hash['@id']}> a earl:Software, doap:Project;)
335
+ io.puts %( doap:homepage <#{json_hash['homepage']}>;)
336
+ io.puts %( doap:name "#{json_hash['name']}".)
337
+
338
+ # Test Cases
339
+ # also collect each assertion definition
340
+ test_cases = {}
341
+ assertions = []
342
+
343
+ # Tests
344
+ json_hash['tests'].each do |test_case|
345
+ tc_desc = test_cases[test_case['test']] ||= test_case.dup
346
+ test_case.keys.select {|k| k =~ /^http:/}.each do |ts_uri|
347
+ tc_desc[ts_uri] = test_case[ts_uri]['@id']
348
+ assertions << test_case[ts_uri]
349
+ end
350
+ end
351
+
352
+ # Write out each earl:TestSubject
353
+ io.puts %(#\n# Subject Definitions\n#)
354
+ json_hash['testSubjects'].each do |ts_desc|
355
+ io.write(test_subject_turtle(ts_desc))
356
+ end
357
+
358
+ # Write out each earl:TestCase
359
+ io.puts %(#\n# Test Case Definitions\n#)
360
+ test_cases.keys.sort.each do |num|
361
+ io.write(tc_turtle(test_cases[num]))
362
+ end
363
+
364
+ # Write out each earl:Assertion
365
+ io.puts %(#\n# Assertions\n#)
366
+ assertions.sort_by {|a| a['@id']}.each do |as_desc|
367
+ io.write(as_turtle(as_desc))
368
+ end
369
+ end
370
+
371
+ ##
372
+ # Write out Test Subject definition for each earl:TestSubject
373
+ # @param [Hash] desc
374
+ # @return [String]
375
+ def test_subject_turtle(desc)
376
+ developer = desc['developer']
377
+ res = %(<#{desc['@id']}> a #{desc['@type'].join(', ')};\n)
378
+ res += %( doap:name "#{desc['name']}";\n)
379
+ res += %( doap:description """#{desc['doap_desc']}""";\n) if desc['doap_desc']
380
+ res += %( doap:programming-language "#{desc['language']}";\n) if desc['language']
381
+ if developer && developer['@id']
382
+ res += %( doap:developer <#{developer['@id']}> .\n\n)
383
+ res += %(<#{developer['@id']}> a #{[developer['@type']].flatten.join(', ')};\n)
384
+ res += %( foaf:name "#{developer['foaf:name']}" .\n)
385
+ elsif developer
386
+ res += %( doap:developer [ a #{developer['@type'] || "foaf:Person"}; foaf:name "#{developer['foaf:name']}"] .\n)
387
+ else
388
+ res += %( .\n)
389
+ end
390
+ res + "\n"
391
+ end
392
+
393
+ ##
394
+ # Write out each Test Case definition
395
+ # @prarm[Hash] desc
396
+ # @return [String]
397
+ def tc_turtle(desc)
398
+ res = %(<#{desc['@id']}> a #{[desc['@type']].flatten.join(', ')};\n)
399
+ res += %( dc:title "#{desc['title']}";\n)
400
+ res += %( dc:description """#{desc['description']}""";\n)
401
+ res += %( mf:action <#{desc['testAction']}>;\n)
402
+ res += %( mf:result <#{desc['testResult']}>;\n)
403
+ res + "\n"
404
+ end
405
+
406
+ ##
407
+ # Write out each Assertion definition
408
+ # @prarm[Hash] desc
409
+ # @return [String]
410
+ def as_turtle(desc)
411
+ res = %([ a earl:Assertion;\n)
412
+ res += %( earl:assertedBy <#{desc['assertedBy']}>;\n)
413
+ res += %( earl:test <#{desc['test']}>;\n)
414
+ res += %( earl:subject <#{desc['subject']}>;\n)
415
+ res += %( earl:mode #{desc['mode']};\n)
416
+ res += %( earl:result [ a earl:TestResult; earl:outcome #{desc['result']['outcome']}] ] .\n)
417
+ res += %(\n)
418
+ res
419
+ end
420
+
421
+ def status(message)
422
+ puts message if @options[:verbose]
423
+ end
424
+ end