dcc 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +19 -0
- data/.github/workflows/release.yml +28 -0
- data/.gitignore +4 -0
- data/.rubocop_todo.yml +560 -10
- data/CHANGELOG.adoc +34 -1
- data/CONTRIBUTING.adoc +8 -1
- data/Gemfile +5 -7
- data/README.adoc +162 -7
- data/dcc.gemspec +9 -2
- data/docs/schema-versions.adoc +20 -7
- data/examples/build_minimal_dcc.rb +0 -0
- data/examples/convert_all_formats.rb +2 -1
- data/examples/parse_and_inspect.rb +0 -0
- data/examples/quantity_math_demo.rb +7 -7
- data/examples/validate_full.rb +1 -1
- data/lib/dcc/base/calibration_laboratory.rb +2 -1
- data/lib/dcc/base/condition.rb +5 -2
- data/lib/dcc/base/core_data.rb +8 -3
- data/lib/dcc/base/formula.rb +3 -3
- data/lib/dcc/base/identification.rb +6 -3
- data/lib/dcc/base/mathml.rb +33 -0
- data/lib/dcc/base/quantity.rb +2 -1
- data/lib/dcc/base/resp_person.rb +4 -2
- data/lib/dcc/base/text.rb +1 -1
- data/lib/dcc/base.rb +2 -1
- data/lib/dcc/builder/dsl.rb +1 -1
- data/lib/dcc/builder/session.rb +16 -14
- data/lib/dcc/builder.rb +3 -3
- data/lib/dcc/cli/cli.rb +152 -26
- data/lib/dcc/cli/formatters.rb +35 -7
- data/lib/dcc/cli.rb +1 -1
- data/lib/dcc/context_configuration.rb +2 -1
- data/lib/dcc/context_options.rb +4 -2
- data/lib/dcc/convert/csv.rb +27 -9
- data/lib/dcc/convert/html.rb +26 -110
- data/lib/dcc/convert/json.rb +10 -7
- data/lib/dcc/convert/liquid_filters.rb +58 -0
- data/lib/dcc/convert/result.rb +1 -1
- data/lib/dcc/convert/templates/report.liquid +250 -0
- data/lib/dcc/convert/yaml.rb +1 -1
- data/lib/dcc/convert.rb +1 -0
- data/lib/dcc/diff/change.rb +1 -1
- data/lib/dcc/diff/result.rb +8 -4
- data/lib/dcc/diff.rb +43 -11
- data/lib/dcc/error.rb +7 -0
- data/lib/dcc/extract/file.rb +3 -4
- data/lib/dcc/extract/formula/ast.rb +137 -0
- data/lib/dcc/extract/formula/evaluator.rb +357 -0
- data/lib/dcc/extract/formula/parser.rb +305 -0
- data/lib/dcc/extract/formula/quantity.rb +89 -0
- data/lib/dcc/extract/formula.rb +110 -0
- data/lib/dcc/extract/ring.rb +1 -1
- data/lib/dcc/extract.rb +1 -1
- data/lib/dcc/i18n/text_lookup.rb +8 -4
- data/lib/dcc/i18n.rb +1 -1
- data/lib/dcc/inspect/report.rb +51 -14
- data/lib/dcc/inspect.rb +1 -1
- data/lib/dcc/migrate/route.rb +44 -0
- data/lib/dcc/migrate/v2_to_v3.rb +445 -0
- data/lib/dcc/migrate.rb +130 -26
- data/lib/dcc/plugin/base.rb +64 -0
- data/lib/dcc/plugin/registry.rb +7 -2
- data/lib/dcc/plugin.rb +6 -2
- data/lib/dcc/quantity_format/formatter.rb +16 -10
- data/lib/dcc/quantity_format.rb +1 -1
- data/lib/dcc/quantity_math/complex.rb +252 -0
- data/lib/dcc/quantity_math/matrix.rb +217 -0
- data/lib/dcc/quantity_math/quantity.rb +1 -1
- data/lib/dcc/quantity_math/real.rb +6 -6
- data/lib/dcc/quantity_math.rb +31 -3
- data/lib/dcc/schema/version.rb +2 -2
- data/lib/dcc/server/app.html +282 -0
- data/lib/dcc/server/app.rb +171 -87
- data/lib/dcc/server/demo_cert.xml +244 -0
- data/lib/dcc/server/storage/entry.rb +1 -1
- data/lib/dcc/server/storage/memory.rb +3 -2
- data/lib/dcc/server/storage.rb +1 -1
- data/lib/dcc/server.rb +1 -1
- data/lib/dcc/si/base/complex.rb +1 -1
- data/lib/dcc/si/base/complex_list_xml_list.rb +1 -1
- data/lib/dcc/si/base/constant.rb +1 -1
- data/lib/dcc/si/base/coverage_interval.rb +36 -0
- data/lib/dcc/si/base/coverage_interval_mu.rb +1 -1
- data/lib/dcc/si/base/expanded_mu.rb +1 -1
- data/lib/dcc/si/base/expanded_unc.rb +34 -0
- data/lib/dcc/si/base/expanded_unc_xml_list.rb +3 -2
- data/lib/dcc/si/base/hybrid.rb +3 -2
- data/lib/dcc/si/base/measurement_uncertainty_univariate.rb +1 -1
- data/lib/dcc/si/base/real.rb +13 -3
- data/lib/dcc/si/base/real_list_xml_list.rb +5 -3
- data/lib/dcc/si/base/si_list.rb +3 -2
- data/lib/dcc/si/base/standard_mu.rb +1 -1
- data/lib/dcc/si/base.rb +5 -2
- data/lib/dcc/si/v1/coverage_interval.rb +8 -0
- data/lib/dcc/si/v1/expanded_unc.rb +1 -16
- data/lib/dcc/si/v1/measurement_uncertainty_univariate.rb +2 -1
- data/lib/dcc/si/v1.rb +5 -2
- data/lib/dcc/si/v2/coverage_interval.rb +8 -0
- data/lib/dcc/si/v2/coverage_interval_mu_xml_list.rb +4 -2
- data/lib/dcc/si/v2/expanded_mu_xml_list.rb +2 -1
- data/lib/dcc/si/v2/expanded_unc.rb +8 -0
- data/lib/dcc/si/v2/measurement_uncertainty_univariate.rb +2 -1
- data/lib/dcc/si/v2.rb +8 -3
- data/lib/dcc/signature/result.rb +2 -2
- data/lib/dcc/signature/signer.rb +31 -18
- data/lib/dcc/signature/verifier.rb +23 -21
- data/lib/dcc/signature.rb +1 -1
- data/lib/dcc/streaming/reader.rb +246 -0
- data/lib/dcc/streaming.rb +79 -0
- data/lib/dcc/transform/result.rb +1 -1
- data/lib/dcc/transform/xslt.rb +22 -12
- data/lib/dcc/transform.rb +5 -4
- data/lib/dcc/type/base64_binary.rb +5 -3
- data/lib/dcc/type/decimal_xml_list.rb +99 -13
- data/lib/dcc/type/iso_country_code.rb +3 -2
- data/lib/dcc/type/iso_language_code.rb +3 -2
- data/lib/dcc/type/schema_version.rb +3 -2
- data/lib/dcc/type/si_unit.rb +3 -2
- data/lib/dcc/type_guards.rb +3 -3
- data/lib/dcc/v2/calibration_laboratory.rb +2 -1
- data/lib/dcc/v2/condition.rb +22 -1
- data/lib/dcc/v2/configuration.rb +1 -1
- data/lib/dcc/v2/digital_calibration_certificate.rb +2 -1
- data/lib/dcc/v2/identification.rb +14 -1
- data/lib/dcc/v2/mathml.rb +8 -0
- data/lib/dcc/v2/measurement_meta_data_list.rb +2 -1
- data/lib/dcc/v2/quantity.rb +4 -2
- data/lib/dcc/v2.rb +4 -2
- data/lib/dcc/v3/calibration_laboratory.rb +2 -1
- data/lib/dcc/v3/configuration.rb +1 -1
- data/lib/dcc/v3/digital_calibration_certificate.rb +2 -1
- data/lib/dcc/v3/mathml.rb +8 -0
- data/lib/dcc/v3/measurement_meta_data_list.rb +2 -1
- data/lib/dcc/v3/quantity.rb +4 -2
- data/lib/dcc/v3.rb +12 -2
- data/lib/dcc/validate/business_rules.rb +23 -9
- data/lib/dcc/validate/issue.rb +2 -1
- data/lib/dcc/validate/result.rb +6 -4
- data/lib/dcc/validate/schematron/profile.rb +73 -18
- data/lib/dcc/validate/schematron/rule.rb +13 -3
- data/lib/dcc/validate/schematron/rules/administrative_data_completeness.rb +11 -4
- data/lib/dcc/validate/schematron/rules/base.rb +1 -2
- data/lib/dcc/validate/schematron/rules/date_range_check.rb +11 -5
- data/lib/dcc/validate/schematron/rules/id_refid_linking.rb +0 -3
- data/lib/dcc/validate/schematron/rules/influence_conditions_placement.rb +22 -12
- data/lib/dcc/validate/schematron/rules/iso_code_validation.rb +31 -14
- data/lib/dcc/validate/schematron/rules/language_code_dedup.rb +12 -5
- data/lib/dcc/validate/schematron/rules/non_si_declaration.rb +7 -4
- data/lib/dcc/validate/schematron/rules/release_format_check.rb +7 -4
- data/lib/dcc/validate/schematron/rules/schema_version_check.rb +13 -9
- data/lib/dcc/validate/schematron/rules/uncertainty_consistency.rb +39 -10
- data/lib/dcc/validate/schematron/rules/unit_format_check.rb +2 -3
- data/lib/dcc/validate/schematron/rules/used_methods_placement.rb +15 -10
- data/lib/dcc/validate/schematron/rules/used_software_placement.rb +15 -10
- data/lib/dcc/validate/schematron/rules/xml_list_spacing.rb +4 -3
- data/lib/dcc/validate/schematron/rules.rb +28 -14
- data/lib/dcc/validate/schematron.rb +1 -1
- data/lib/dcc/validate/xsd.rb +9 -3
- data/lib/dcc/validate.rb +1 -1
- data/lib/dcc/version.rb +1 -1
- data/lib/dcc/versioned_parser.rb +1 -1
- data/lib/dcc.rb +68 -10
- data/sample_dcc.xml +140 -0
- data/web/index.html +12 -0
- data/web/package-lock.json +1599 -0
- data/web/package.json +22 -0
- data/web/src/App.vue +349 -0
- data/web/src/lib/builder.ts +171 -0
- data/web/src/lib/dcc.ts +338 -0
- data/web/src/lib/units.json +5581 -0
- data/web/src/main.ts +5 -0
- data/web/src/style.css +27 -0
- data/web/tsconfig.json +15 -0
- data/web/vite.config.ts +14 -0
- metadata +148 -9
data/lib/dcc/builder/session.rb
CHANGED
|
@@ -25,32 +25,32 @@ module Dcc
|
|
|
25
25
|
|
|
26
26
|
# Top-level DSL methods. Each opens a nested container that captures
|
|
27
27
|
# subsequent setter calls via instance_eval.
|
|
28
|
-
def administrative_data(**attrs, &
|
|
28
|
+
def administrative_data(**attrs, &)
|
|
29
29
|
apply_attrs(@administrative_in_progress, attrs)
|
|
30
|
-
instance_eval(&
|
|
30
|
+
instance_eval(&) if block_given?
|
|
31
31
|
@administrative_in_progress
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def core_data(**attrs, &
|
|
34
|
+
def core_data(**attrs, &)
|
|
35
35
|
cd = new_core_data
|
|
36
36
|
apply_attrs(cd, attrs)
|
|
37
37
|
@administrative_in_progress.core_data = cd
|
|
38
|
-
instance_eval(&
|
|
38
|
+
instance_eval(&) if block_given?
|
|
39
39
|
cd
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
def items(**attrs, &
|
|
42
|
+
def items(**attrs, &)
|
|
43
43
|
items_obj = new_items
|
|
44
44
|
apply_attrs(items_obj, attrs)
|
|
45
45
|
@administrative_in_progress.items = items_obj
|
|
46
|
-
instance_eval(&
|
|
46
|
+
instance_eval(&) if block_given?
|
|
47
47
|
items_obj
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
-
def item(**attrs, &
|
|
50
|
+
def item(**attrs, &)
|
|
51
51
|
it = new_item
|
|
52
52
|
apply_attrs(it, attrs)
|
|
53
|
-
instance_eval(&
|
|
53
|
+
instance_eval(&) if block_given?
|
|
54
54
|
(@items_in_progress ||= []) << it
|
|
55
55
|
# Wire into the open items container if present.
|
|
56
56
|
if @administrative_in_progress&.items
|
|
@@ -60,14 +60,14 @@ module Dcc
|
|
|
60
60
|
it
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
def measurement_results(**_attrs, &
|
|
64
|
-
instance_eval(&
|
|
63
|
+
def measurement_results(**_attrs, &)
|
|
64
|
+
instance_eval(&) if block_given?
|
|
65
65
|
end
|
|
66
66
|
|
|
67
|
-
def measurement_result(**attrs, &
|
|
67
|
+
def measurement_result(**attrs, &)
|
|
68
68
|
mr = new_measurement_result
|
|
69
69
|
apply_attrs(mr, attrs)
|
|
70
|
-
instance_eval(&
|
|
70
|
+
instance_eval(&) if block_given?
|
|
71
71
|
@results_in_progress << mr
|
|
72
72
|
mr
|
|
73
73
|
end
|
|
@@ -130,7 +130,9 @@ module Dcc
|
|
|
130
130
|
def apply_attrs(target, attrs)
|
|
131
131
|
attrs.each do |k, v|
|
|
132
132
|
setter = :"#{k}="
|
|
133
|
-
target.public_send(setter, v) if Dcc::TypeGuards.has_attribute?(
|
|
133
|
+
target.public_send(setter, v) if Dcc::TypeGuards.has_attribute?(
|
|
134
|
+
target, setter
|
|
135
|
+
)
|
|
134
136
|
end
|
|
135
137
|
end
|
|
136
138
|
|
|
@@ -167,4 +169,4 @@ module Dcc
|
|
|
167
169
|
end
|
|
168
170
|
end
|
|
169
171
|
end
|
|
170
|
-
end
|
|
172
|
+
end
|
data/lib/dcc/builder.rb
CHANGED
|
@@ -23,12 +23,12 @@ module Dcc
|
|
|
23
23
|
# @param version [Integer, nil] major DCC version (default: 3).
|
|
24
24
|
# @yield [Dcc::Builder::Session] builder session.
|
|
25
25
|
# @return [Dcc::V2::DigitalCalibrationCertificate, Dcc::V3::DigitalCalibrationCertificate]
|
|
26
|
-
def call(version: 3, &
|
|
26
|
+
def call(version: 3, &)
|
|
27
27
|
::Dcc.load_all!
|
|
28
28
|
session = Session.new(version: version)
|
|
29
|
-
session.instance_eval(&
|
|
29
|
+
session.instance_eval(&) if block_given?
|
|
30
30
|
session.build
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
|
-
end
|
|
34
|
+
end
|
data/lib/dcc/cli/cli.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
|
-
require "tty-table"
|
|
5
4
|
|
|
6
5
|
# `Dcc::Cli::Cli` is the Thor-based command-line interface. Each subcommand
|
|
7
6
|
# (validate, convert, extract, signature, transform, inspect, diff, issue)
|
|
@@ -27,7 +26,8 @@ module Dcc
|
|
|
27
26
|
true
|
|
28
27
|
end
|
|
29
28
|
|
|
30
|
-
desc "validate SCHEMA FILE",
|
|
29
|
+
desc "validate SCHEMA FILE",
|
|
30
|
+
"Validate a DCC XML against XSD, Schematron, or both"
|
|
31
31
|
long_desc <<~DESC
|
|
32
32
|
Validates the given DCC XML file.
|
|
33
33
|
|
|
@@ -37,20 +37,21 @@ module Dcc
|
|
|
37
37
|
$ dcc validate all cert.xml
|
|
38
38
|
DESC
|
|
39
39
|
method_option :version, type: :string, default: "auto",
|
|
40
|
-
|
|
40
|
+
desc: "Schema version (e.g. '3.3.0', 'auto')"
|
|
41
41
|
method_option :format, type: :string, default: "text",
|
|
42
|
-
|
|
42
|
+
enum: %w[text json yaml]
|
|
43
43
|
def validate(schema, file)
|
|
44
44
|
Cli.ensure_loaded!
|
|
45
45
|
xml = File.read(file)
|
|
46
46
|
version = options[:version]
|
|
47
47
|
result =
|
|
48
48
|
case schema
|
|
49
|
-
when "xsd" then ::Dcc::Validate::Xsd.call(xml,
|
|
49
|
+
when "xsd" then ::Dcc::Validate::Xsd.call(xml,
|
|
50
|
+
version: version)
|
|
50
51
|
when "schematron" then ::Dcc::Validate::Schematron.call(Dcc.parse(xml))
|
|
51
52
|
when "all"
|
|
52
53
|
::Dcc::Validate::Xsd.call(xml, version: version)
|
|
53
|
-
|
|
54
|
+
.merge(::Dcc::Validate::Schematron.call(Dcc.parse(xml)))
|
|
54
55
|
else
|
|
55
56
|
abort "Unknown validation target: #{schema} (expected xsd|schematron|all)"
|
|
56
57
|
end
|
|
@@ -58,17 +59,18 @@ module Dcc
|
|
|
58
59
|
exit(1) unless result.ok?
|
|
59
60
|
end
|
|
60
61
|
|
|
61
|
-
desc "convert FORMAT FILE",
|
|
62
|
+
desc "convert FORMAT FILE",
|
|
63
|
+
"Convert a DCC XML file to json / yaml / csv / html"
|
|
62
64
|
method_option :output, type: :string, banner: "PATH",
|
|
63
|
-
|
|
65
|
+
desc: "Write output to a file instead of stdout"
|
|
64
66
|
def convert(format, file)
|
|
65
67
|
Cli.ensure_loaded!
|
|
66
68
|
xml = File.read(file)
|
|
67
|
-
dcc = Dcc.parse(xml)
|
|
68
69
|
result =
|
|
69
70
|
case format
|
|
70
|
-
when "json" then ::Dcc::Convert::Json.call(
|
|
71
|
-
when "yaml" then ::Dcc::Convert::Yaml.call(
|
|
71
|
+
when "json" then ::Dcc::Convert::Json.call(Dcc.parse(xml))
|
|
72
|
+
when "yaml" then ::Dcc::Convert::Yaml.call(Dcc.parse(xml))
|
|
73
|
+
when "html" then ::Dcc::Convert::Html.call(xml)
|
|
72
74
|
else
|
|
73
75
|
abort "Format not yet implemented: #{format}"
|
|
74
76
|
end
|
|
@@ -80,25 +82,25 @@ module Dcc
|
|
|
80
82
|
end
|
|
81
83
|
end
|
|
82
84
|
|
|
83
|
-
desc "extract TARGET FILE",
|
|
85
|
+
desc "extract TARGET FILE",
|
|
86
|
+
"Extract embedded files or formulae from a DCC"
|
|
84
87
|
method_option :index, type: :numeric, desc: "Index of the file to extract"
|
|
85
88
|
method_option :output, type: :string, banner: "PATH",
|
|
86
|
-
|
|
89
|
+
desc: "Write extracted payload to PATH"
|
|
87
90
|
method_option :ring, type: :string,
|
|
88
|
-
|
|
91
|
+
enum: %w[administrativeData measurementResults comment document]
|
|
92
|
+
# `repeatable` and not `type: :array`: with an array type Thor lets a
|
|
93
|
+
# second `-v` overwrite the first, so `-v T=42 -v R0=1` would silently
|
|
94
|
+
# drop T. PTB's CLI accumulates, and so must this.
|
|
95
|
+
method_option :variable, type: :string, aliases: "-v", repeatable: true,
|
|
96
|
+
banner: "NAME=VALUE",
|
|
97
|
+
desc: "Override a formula variable (repeatable; NAME=V1,V2 for a list)"
|
|
89
98
|
def extract(target, file)
|
|
90
99
|
Cli.ensure_loaded!
|
|
91
100
|
dcc = Dcc.parse(File.read(file))
|
|
92
101
|
case target
|
|
93
|
-
when "files"
|
|
94
|
-
|
|
95
|
-
files = files.select { |f| f.ring.to_s == options[:ring] } if options[:ring]
|
|
96
|
-
if options[:index]
|
|
97
|
-
f = files[options[:index].to_i] || abort("No file at index #{options[:index]}")
|
|
98
|
-
output_to(f.data, options[:output], f.file_name)
|
|
99
|
-
else
|
|
100
|
-
::Dcc::Cli::Formatters.print_files(files)
|
|
101
|
-
end
|
|
102
|
+
when "files" then extract_files(dcc)
|
|
103
|
+
when "formulae" then extract_formulae(dcc)
|
|
102
104
|
else
|
|
103
105
|
abort "Unknown extract target: #{target}"
|
|
104
106
|
end
|
|
@@ -106,7 +108,7 @@ module Dcc
|
|
|
106
108
|
|
|
107
109
|
desc "inspect FILE", "Show a human-readable summary of a DCC"
|
|
108
110
|
method_option :format, type: :string, default: "text",
|
|
109
|
-
|
|
111
|
+
enum: %w[text json yaml]
|
|
110
112
|
def inspect(file)
|
|
111
113
|
Cli.ensure_loaded!
|
|
112
114
|
dcc = Dcc.parse(File.read(file))
|
|
@@ -116,7 +118,7 @@ module Dcc
|
|
|
116
118
|
|
|
117
119
|
desc "diff FILE1 FILE2", "Compare two DCC files structurally"
|
|
118
120
|
method_option :format, type: :string, default: "text",
|
|
119
|
-
|
|
121
|
+
enum: %w[text json]
|
|
120
122
|
def diff(file1, file2)
|
|
121
123
|
Cli.ensure_loaded!
|
|
122
124
|
dcc1 = Dcc.parse(File.read(file1))
|
|
@@ -125,6 +127,17 @@ module Dcc
|
|
|
125
127
|
::Dcc::Cli::Formatters.print(diff, format: options[:format])
|
|
126
128
|
end
|
|
127
129
|
|
|
130
|
+
desc "serve", "Start the DCC web validator (http://localhost:4567)"
|
|
131
|
+
method_option :port, type: :numeric, default: 4567, desc: "Port"
|
|
132
|
+
method_option :bind, type: :string, default: "0.0.0.0",
|
|
133
|
+
desc: "Bind address"
|
|
134
|
+
def serve
|
|
135
|
+
require "dcc/server"
|
|
136
|
+
::Dcc.load_all!
|
|
137
|
+
puts "Starting DCC Validator on http://localhost:#{options[:port]}"
|
|
138
|
+
::Dcc::Server::App.run!(port: options[:port], bind: options[:bind])
|
|
139
|
+
end
|
|
140
|
+
|
|
128
141
|
desc "version", "Print the dcc gem version"
|
|
129
142
|
def version
|
|
130
143
|
puts "dcc #{::Dcc::VERSION}"
|
|
@@ -143,7 +156,120 @@ module Dcc
|
|
|
143
156
|
$stdout.write(payload)
|
|
144
157
|
end
|
|
145
158
|
end
|
|
159
|
+
|
|
160
|
+
def extract_files(dcc)
|
|
161
|
+
files = ::Dcc::Extract::File.each(dcc)
|
|
162
|
+
if options[:ring]
|
|
163
|
+
files = files.select { |f| f.ring.to_s == options[:ring] }
|
|
164
|
+
end
|
|
165
|
+
return ::Dcc::Cli::Formatters.print_files(files) unless options[:index]
|
|
166
|
+
|
|
167
|
+
index = options[:index].to_i
|
|
168
|
+
file = files[index] || abort("No file at index #{index}")
|
|
169
|
+
output_to(file.data, options[:output], file.file_name)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
no_commands do
|
|
174
|
+
def extract_formulae(dcc)
|
|
175
|
+
variables = parse_variables(options[:variable])
|
|
176
|
+
formulae = ::Dcc::Extract::Formula.call(dcc)
|
|
177
|
+
check_overrides(formulae, variables)
|
|
178
|
+
print_formulae(formulae, variables)
|
|
179
|
+
rescue ::Dcc::ExtractionError => e
|
|
180
|
+
abort e.message
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# The evaluator slices bindings down to the names a formula
|
|
184
|
+
# actually references, so a mistyped -v reaches nothing and the
|
|
185
|
+
# run still reports success with plausible default numbers.
|
|
186
|
+
# `decimal_or_abort` already aborts on a malformed -v; an
|
|
187
|
+
# override that lands nowhere is the same class of mistake.
|
|
188
|
+
def check_overrides(formulae, variables)
|
|
189
|
+
unknown = variables.keys - formulae.flat_map(&:variables)
|
|
190
|
+
return if unknown.empty?
|
|
191
|
+
|
|
192
|
+
abort "No formula references #{unknown.join(', ')}"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
no_commands do
|
|
197
|
+
def parse_variables(assignments)
|
|
198
|
+
Array(assignments).each_with_object({}) do |assignment, variables|
|
|
199
|
+
name, values = split_assignment(assignment)
|
|
200
|
+
variables[name] = override_value(values)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# `-v T=42` is a scalar and broadcasts across the document's lists;
|
|
205
|
+
# only `-v T=1,2` is a list, and a list has to match every other
|
|
206
|
+
# list's length. Wrapping a lone value in an array made `-v R0=1`
|
|
207
|
+
# a one-element list that collided with the document's own.
|
|
208
|
+
def override_value(values)
|
|
209
|
+
decimals = values.map { |v| decimal_or_abort(v) }
|
|
210
|
+
decimals.size == 1 ? decimals.first : decimals
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
no_commands do
|
|
215
|
+
# `split(",", -1)` keeps trailing empty fields, so `T=42,` reaches
|
|
216
|
+
# validation instead of silently losing the empty entry.
|
|
217
|
+
def split_assignment(assignment)
|
|
218
|
+
name, values = assignment.split("=", 2)
|
|
219
|
+
if name.nil? || name.empty? || values.nil? || values.empty?
|
|
220
|
+
abort "#{assignment} is not a valid NAME=value"
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
[name, values.split(",", -1)]
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Validated here, at the command line, not where the value is used.
|
|
227
|
+
# The evaluator only coerces variables the formula references, so a
|
|
228
|
+
# bad `-v` for an unreferenced variable — or any `-v` against a
|
|
229
|
+
# document with no formulae — would otherwise pass in silence.
|
|
230
|
+
#
|
|
231
|
+
# Delegates rather than calling BigDecimal itself: a second
|
|
232
|
+
# coercion site is exactly the drift the single boundary exists to
|
|
233
|
+
# prevent, even when it starts out agreeing.
|
|
234
|
+
def decimal_or_abort(value)
|
|
235
|
+
::Dcc::Extract::Formula::Quantity.decimal(value)
|
|
236
|
+
rescue ::Dcc::ExtractionError
|
|
237
|
+
abort "#{value} is not a valid decimal value"
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
no_commands do
|
|
242
|
+
def print_formulae(formulae, variables)
|
|
243
|
+
return puts("No formulae found.") if formulae.empty?
|
|
244
|
+
|
|
245
|
+
# `map`, deliberately, not `all?` with a block: `all?`
|
|
246
|
+
# short-circuits, and stopping at the first failure is exactly
|
|
247
|
+
# the bug this is fixing.
|
|
248
|
+
printed = formulae.map { |formula| print_formula(formula, variables) }
|
|
249
|
+
exit(1) unless printed.all?
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
no_commands do
|
|
254
|
+
# Evaluates before printing the header. A header printed ahead
|
|
255
|
+
# of a failure leaves a truncated record on stdout that reads
|
|
256
|
+
# like a formula with no values. One formula that cannot be
|
|
257
|
+
# evaluated is reported on stderr and the rest still print.
|
|
258
|
+
def print_formula(formula, variables)
|
|
259
|
+
values = formula.evaluate(variables)
|
|
260
|
+
puts formula
|
|
261
|
+
values.each { |value| puts " #{value.to_s('F')}" }
|
|
262
|
+
true
|
|
263
|
+
rescue ::Dcc::ExtractionError => e
|
|
264
|
+
# `$stderr.puts`, not `warn`: Kernel#warn prints nothing when
|
|
265
|
+
# warnings are off, so `RUBYOPT=-W0` would leave the user with
|
|
266
|
+
# exit 1 and no explanation at all. The cop's own rationale —
|
|
267
|
+
# "to allow such output to be disabled" — is the thing this
|
|
268
|
+
# message must not permit.
|
|
269
|
+
$stderr.puts "#{formula}: #{e.message}" # rubocop:disable Style/StderrPuts
|
|
270
|
+
false
|
|
271
|
+
end
|
|
146
272
|
end
|
|
147
273
|
end
|
|
148
274
|
end
|
|
149
|
-
end
|
|
275
|
+
end
|
data/lib/dcc/cli/formatters.rb
CHANGED
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "tty-table"
|
|
4
|
-
|
|
5
3
|
module Dcc
|
|
6
4
|
module Cli
|
|
7
5
|
# `Dcc::Cli::Formatters` picks the right text/JSON/YAML representation
|
|
8
6
|
# of a result model and prints it.
|
|
9
7
|
module Formatters
|
|
8
|
+
# Goes to stderr, so a piped `extract files` still yields clean data.
|
|
9
|
+
TTY_TABLE_HINT = "note: install tty-table for aligned output " \
|
|
10
|
+
"(gem install tty-table)"
|
|
11
|
+
|
|
12
|
+
# `tty-table` is optional. It pulls in eight further gems to prettify
|
|
13
|
+
# one command, which every library user would otherwise install just
|
|
14
|
+
# to parse XML. Loaded on first use, and its absence falls back to the
|
|
15
|
+
# plain renderer rather than raising the way `Dcc::Server` does.
|
|
16
|
+
def self.table_available?
|
|
17
|
+
return @table_available unless @table_available.nil?
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
require "tty-table"
|
|
21
|
+
@table_available = true
|
|
22
|
+
rescue ::LoadError
|
|
23
|
+
@table_available = false
|
|
24
|
+
end
|
|
25
|
+
@table_available
|
|
26
|
+
end
|
|
27
|
+
|
|
10
28
|
module_function
|
|
11
29
|
|
|
12
30
|
def print(object, format: "text")
|
|
@@ -14,7 +32,7 @@ module Dcc
|
|
|
14
32
|
when "json" then puts object.to_json
|
|
15
33
|
when "yaml" then puts object.to_yaml
|
|
16
34
|
else
|
|
17
|
-
puts object
|
|
35
|
+
puts object
|
|
18
36
|
end
|
|
19
37
|
end
|
|
20
38
|
|
|
@@ -24,6 +42,14 @@ module Dcc
|
|
|
24
42
|
return
|
|
25
43
|
end
|
|
26
44
|
|
|
45
|
+
unless table_available?
|
|
46
|
+
# Hint first: a stderr that raises is caught by the rescue below,
|
|
47
|
+
# which re-runs the renderer. Printing first would duplicate it.
|
|
48
|
+
warn TTY_TABLE_HINT
|
|
49
|
+
print_files_plain(files)
|
|
50
|
+
return
|
|
51
|
+
end
|
|
52
|
+
|
|
27
53
|
# TTY::Table calls ioctl which fails on StringIO / pipes. Fall back
|
|
28
54
|
# to a plain-text rendering when stdout isn't a real terminal.
|
|
29
55
|
if $stdout.is_a?(::IO) && !$stdout.tty?
|
|
@@ -34,7 +60,8 @@ module Dcc
|
|
|
34
60
|
table = ::TTY::Table.new(
|
|
35
61
|
["Index", "Name", "File Name", "MIME", "Ring", "Size"],
|
|
36
62
|
files.each_with_index.map do |f, i|
|
|
37
|
-
[i, f.name.to_s, f.file_name.to_s, f.mime_type.to_s, f.ring,
|
|
63
|
+
[i, f.name.to_s, f.file_name.to_s, f.mime_type.to_s, f.ring,
|
|
64
|
+
f.data.bytesize]
|
|
38
65
|
end,
|
|
39
66
|
)
|
|
40
67
|
puts table.render
|
|
@@ -45,8 +72,9 @@ module Dcc
|
|
|
45
72
|
|
|
46
73
|
def print_files_plain(files)
|
|
47
74
|
# Widths
|
|
48
|
-
widths = ["Index".size, "Name".size, "File Name".size, "MIME".size,
|
|
49
|
-
|
|
75
|
+
widths = ["Index".size, "Name".size, "File Name".size, "MIME".size,
|
|
76
|
+
"Ring".size, "Size".size]
|
|
77
|
+
files.each_with_index.map do |f, i|
|
|
50
78
|
[
|
|
51
79
|
i.to_s,
|
|
52
80
|
f.name.to_s,
|
|
@@ -74,4 +102,4 @@ module Dcc
|
|
|
74
102
|
end
|
|
75
103
|
end
|
|
76
104
|
end
|
|
77
|
-
end
|
|
105
|
+
end
|
data/lib/dcc/cli.rb
CHANGED
|
@@ -34,7 +34,8 @@ module Dcc
|
|
|
34
34
|
# @param registry [Lutaml::Model::TypeRegistry, nil] custom registry.
|
|
35
35
|
# @param fallback_to [Array<Symbol>] fallback chain for type lookup.
|
|
36
36
|
# @param substitutions [Array<Hash{from_type:, to_type:}>] type substitutions.
|
|
37
|
-
def create_context(id:, registry: nil, fallback_to: [context_id],
|
|
37
|
+
def create_context(id:, registry: nil, fallback_to: [context_id],
|
|
38
|
+
substitutions: [])
|
|
38
39
|
normalized_id = ::Dcc::ContextOptions.normalize_context_reference(id)
|
|
39
40
|
return populate_context! if normalized_id == context_id
|
|
40
41
|
|
data/lib/dcc/context_options.rb
CHANGED
|
@@ -13,7 +13,8 @@ module Dcc
|
|
|
13
13
|
# @param register [Symbol, String, nil] deprecated alias for `context:`.
|
|
14
14
|
# @param default_context [Symbol, nil] returned when neither is given.
|
|
15
15
|
# @param warning_source [String] caller name for deprecation warnings.
|
|
16
|
-
def normalize_context_option(context:, register:, default_context:,
|
|
16
|
+
def normalize_context_option(context:, register:, default_context:,
|
|
17
|
+
warning_source:)
|
|
17
18
|
raise_if_conflicting!(context, register)
|
|
18
19
|
return normalize_context_reference(register) || default_context if register
|
|
19
20
|
return default_context unless context_specified?(context)
|
|
@@ -29,7 +30,8 @@ module Dcc
|
|
|
29
30
|
return reference.to_sym if reference.is_a?(String)
|
|
30
31
|
return reference.id.to_sym if reference.is_a?(::Lutaml::Xml::Namespace)
|
|
31
32
|
|
|
32
|
-
raise ArgumentError,
|
|
33
|
+
raise ArgumentError,
|
|
34
|
+
"Unsupported context/register reference: #{reference.inspect}"
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def context_specified?(context)
|
data/lib/dcc/convert/csv.rb
CHANGED
|
@@ -7,7 +7,8 @@ module Dcc
|
|
|
7
7
|
# `Dcc::Convert::Csv` flattens a parsed DCC into a tabular CSV with
|
|
8
8
|
# one row per measurement quantity.
|
|
9
9
|
module Csv
|
|
10
|
-
HEADERS = %w[result_name quantity_name quantity_type value unit
|
|
10
|
+
HEADERS = %w[result_name quantity_name quantity_type value unit
|
|
11
|
+
uncertainty coverage_factor].freeze
|
|
11
12
|
|
|
12
13
|
class << self
|
|
13
14
|
# @param dcc [Lutaml::Model::Serializable]
|
|
@@ -31,7 +32,8 @@ module Dcc
|
|
|
31
32
|
|
|
32
33
|
def collect_quantities(dcc)
|
|
33
34
|
rows = []
|
|
34
|
-
return rows unless Dcc::TypeGuards.has_attribute?(dcc,
|
|
35
|
+
return rows unless Dcc::TypeGuards.has_attribute?(dcc,
|
|
36
|
+
:measurement_results)
|
|
35
37
|
|
|
36
38
|
Array(dcc.measurement_results.measurement_result).each do |mr|
|
|
37
39
|
mr_name = name_of(mr)
|
|
@@ -53,7 +55,9 @@ module Dcc
|
|
|
53
55
|
walk_quantities(Array(data.quantity), mr_name, result_name, rows)
|
|
54
56
|
Array(data.list).each do |list|
|
|
55
57
|
walk_quantities(Array(list.quantity), mr_name, result_name, rows)
|
|
56
|
-
Array(list.list).each
|
|
58
|
+
Array(list.list).each do |sub|
|
|
59
|
+
walk_data(sub, mr_name, result_name, rows)
|
|
60
|
+
end
|
|
57
61
|
end
|
|
58
62
|
end
|
|
59
63
|
|
|
@@ -63,10 +67,10 @@ module Dcc
|
|
|
63
67
|
end
|
|
64
68
|
end
|
|
65
69
|
|
|
66
|
-
def push_quantity(q, mr_name,
|
|
70
|
+
def push_quantity(q, mr_name, _result_name, kind, rows)
|
|
67
71
|
rows << [
|
|
68
72
|
mr_name, name_of(q), kind,
|
|
69
|
-
value_of(q), unit_of(q), uncertainty_of(q), k_factor_of(q)
|
|
73
|
+
value_of(q), unit_of(q), uncertainty_of(q), k_factor_of(q)
|
|
70
74
|
]
|
|
71
75
|
end
|
|
72
76
|
|
|
@@ -74,11 +78,21 @@ module Dcc
|
|
|
74
78
|
return "" unless node && Dcc::TypeGuards.has_attribute?(node, :name)
|
|
75
79
|
return "" unless node.name
|
|
76
80
|
|
|
77
|
-
content = Dcc::TypeGuards.has_attribute?(node.name,
|
|
81
|
+
content = if Dcc::TypeGuards.has_attribute?(node.name,
|
|
82
|
+
:content)
|
|
83
|
+
node.name.content
|
|
84
|
+
else
|
|
85
|
+
[]
|
|
86
|
+
end
|
|
78
87
|
first = Array(content).first
|
|
79
88
|
return "" unless first
|
|
80
89
|
|
|
81
|
-
vals = Dcc::TypeGuards.has_attribute?(first,
|
|
90
|
+
vals = if Dcc::TypeGuards.has_attribute?(first,
|
|
91
|
+
:value)
|
|
92
|
+
first.value
|
|
93
|
+
else
|
|
94
|
+
[]
|
|
95
|
+
end
|
|
82
96
|
Array(vals).first.to_s
|
|
83
97
|
end
|
|
84
98
|
|
|
@@ -87,7 +101,11 @@ module Dcc
|
|
|
87
101
|
return reals.first.value.to_s if reals.any?
|
|
88
102
|
|
|
89
103
|
lists = Array(q.real_list_xml_list)
|
|
90
|
-
|
|
104
|
+
# Serialized rather than mapped over: BigDecimal#to_s renders 0.072
|
|
105
|
+
# as "0.72e-1", which is not what the document said.
|
|
106
|
+
if lists.any?
|
|
107
|
+
return ::Dcc::Type::DecimalXmlList.serialize(lists.first.value_xml_list)
|
|
108
|
+
end
|
|
91
109
|
|
|
92
110
|
""
|
|
93
111
|
end
|
|
@@ -126,4 +144,4 @@ module Dcc
|
|
|
126
144
|
end
|
|
127
145
|
end
|
|
128
146
|
end
|
|
129
|
-
end
|
|
147
|
+
end
|