chemicalml 0.1.0
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 +7 -0
- data/.gitignore +11 -0
- data/.rubocop.yml +20 -0
- data/CHANGELOG.md +21 -0
- data/CLAUDE.md +198 -0
- data/Gemfile +14 -0
- data/LICENSE +24 -0
- data/NOTICES.adoc +115 -0
- data/README.adoc +87 -0
- data/Rakefile +8 -0
- data/TODO.align/01-cml-base-mixins.md +122 -0
- data/TODO.align/02-real-schema-hierarchies.md +88 -0
- data/TODO.align/03-configuration.md +50 -0
- data/TODO.align/04-parse-entry-points.md +59 -0
- data/TODO.align/05-schema-aware-translator.md +33 -0
- data/TODO.align/06-no-respond-to.md +44 -0
- data/TODO.align/07-canon-xml-comparison.md +43 -0
- data/TODO.align/08-expand-fixtures.md +37 -0
- data/TODO.align/09-final-spec-lint.md +29 -0
- data/TODO.align/10-role-marker-modules.md +54 -0
- data/TODO.align/11-dry-configuration.md +63 -0
- data/TODO.align/12-wire-class-macro.md +51 -0
- data/TODO.align/13-wire-namespace.md +44 -0
- data/TODO.align/14-schema24-correctness.md +37 -0
- data/TODO.align/15-canon-fixture-roundtrip.md +38 -0
- data/TODO.align/16-schema24-convention-coverage.md +33 -0
- data/TODO.align/17-final-spec-lint.md +28 -0
- data/TODO.align/README.md +27 -0
- data/TODO.align/README.round2.md +24 -0
- data/TODO.cml-full/01-reference-docs.md +43 -0
- data/TODO.cml-full/02-schema-versioned-model.md +70 -0
- data/TODO.cml-full/03-fixtures-scrape.md +52 -0
- data/TODO.cml-full/04-convention-framework.md +69 -0
- data/TODO.cml-full/05-dictionary-layer.md +77 -0
- data/TODO.cml-full/06-extend-canonical-translator.md +60 -0
- data/TODO.cml-full/07-attribution-notices.md +42 -0
- data/TODO.cml-full/08-autoload-only.md +48 -0
- data/TODO.cml-full/09-spec-coverage.md +38 -0
- data/TODO.cml-full/README.md +23 -0
- data/chemicalml.gemspec +35 -0
- data/data/dictionaries/_index.yaml +14 -0
- data/data/dictionaries/compchem.yaml +481 -0
- data/data/dictionaries/unit_non_si.yaml +53 -0
- data/data/dictionaries/unit_si.yaml +87 -0
- data/data/dictionaries/unit_type.yaml +113 -0
- data/lib/chemicalml/cml/array.rb +10 -0
- data/lib/chemicalml/cml/atom.rb +10 -0
- data/lib/chemicalml/cml/atom_array.rb +10 -0
- data/lib/chemicalml/cml/atom_parity.rb +10 -0
- data/lib/chemicalml/cml/base/array.rb +36 -0
- data/lib/chemicalml/cml/base/atom.rb +38 -0
- data/lib/chemicalml/cml/base/atom_array.rb +22 -0
- data/lib/chemicalml/cml/base/atom_parity.rb +24 -0
- data/lib/chemicalml/cml/base/bond.rb +28 -0
- data/lib/chemicalml/cml/base/bond_array.rb +22 -0
- data/lib/chemicalml/cml/base/bond_stereo.rb +28 -0
- data/lib/chemicalml/cml/base/cml_module.rb +40 -0
- data/lib/chemicalml/cml/base/dictionary.rb +32 -0
- data/lib/chemicalml/cml/base/dictionary_entry.rb +34 -0
- data/lib/chemicalml/cml/base/document.rb +26 -0
- data/lib/chemicalml/cml/base/formula.rb +38 -0
- data/lib/chemicalml/cml/base/identifier.rb +26 -0
- data/lib/chemicalml/cml/base/label.rb +28 -0
- data/lib/chemicalml/cml/base/list.rb +36 -0
- data/lib/chemicalml/cml/base/matrix.rb +38 -0
- data/lib/chemicalml/cml/base/metadata.rb +30 -0
- data/lib/chemicalml/cml/base/metadata_list.rb +28 -0
- data/lib/chemicalml/cml/base/molecule.rb +40 -0
- data/lib/chemicalml/cml/base/name.rb +26 -0
- data/lib/chemicalml/cml/base/parameter.rb +34 -0
- data/lib/chemicalml/cml/base/parameter_list.rb +28 -0
- data/lib/chemicalml/cml/base/product.rb +22 -0
- data/lib/chemicalml/cml/base/product_list.rb +22 -0
- data/lib/chemicalml/cml/base/property.rb +34 -0
- data/lib/chemicalml/cml/base/property_list.rb +28 -0
- data/lib/chemicalml/cml/base/reactant.rb +22 -0
- data/lib/chemicalml/cml/base/reactant_list.rb +22 -0
- data/lib/chemicalml/cml/base/reaction.rb +32 -0
- data/lib/chemicalml/cml/base/reaction_list.rb +22 -0
- data/lib/chemicalml/cml/base/scalar.rb +32 -0
- data/lib/chemicalml/cml/base/substance.rb +30 -0
- data/lib/chemicalml/cml/base/unit.rb +38 -0
- data/lib/chemicalml/cml/base/unit_list.rb +30 -0
- data/lib/chemicalml/cml/base/unit_type.rb +28 -0
- data/lib/chemicalml/cml/base/unit_type_list.rb +30 -0
- data/lib/chemicalml/cml/base.rb +51 -0
- data/lib/chemicalml/cml/bond.rb +10 -0
- data/lib/chemicalml/cml/bond_array.rb +10 -0
- data/lib/chemicalml/cml/bond_stereo.rb +10 -0
- data/lib/chemicalml/cml/cml_module.rb +10 -0
- data/lib/chemicalml/cml/dictionary.rb +10 -0
- data/lib/chemicalml/cml/dictionary_entry.rb +10 -0
- data/lib/chemicalml/cml/document.rb +10 -0
- data/lib/chemicalml/cml/elements.rb +58 -0
- data/lib/chemicalml/cml/formula.rb +10 -0
- data/lib/chemicalml/cml/identifier.rb +10 -0
- data/lib/chemicalml/cml/label.rb +10 -0
- data/lib/chemicalml/cml/list.rb +10 -0
- data/lib/chemicalml/cml/matrix.rb +10 -0
- data/lib/chemicalml/cml/metadata.rb +10 -0
- data/lib/chemicalml/cml/metadata_list.rb +10 -0
- data/lib/chemicalml/cml/molecule.rb +10 -0
- data/lib/chemicalml/cml/name.rb +10 -0
- data/lib/chemicalml/cml/namespace.rb +17 -0
- data/lib/chemicalml/cml/parameter.rb +10 -0
- data/lib/chemicalml/cml/parameter_list.rb +10 -0
- data/lib/chemicalml/cml/product.rb +10 -0
- data/lib/chemicalml/cml/product_list.rb +10 -0
- data/lib/chemicalml/cml/property.rb +10 -0
- data/lib/chemicalml/cml/property_list.rb +10 -0
- data/lib/chemicalml/cml/reactant.rb +10 -0
- data/lib/chemicalml/cml/reactant_list.rb +10 -0
- data/lib/chemicalml/cml/reaction.rb +10 -0
- data/lib/chemicalml/cml/reaction_list.rb +10 -0
- data/lib/chemicalml/cml/role/array.rb +13 -0
- data/lib/chemicalml/cml/role/atom.rb +13 -0
- data/lib/chemicalml/cml/role/atom_array.rb +13 -0
- data/lib/chemicalml/cml/role/atom_parity.rb +13 -0
- data/lib/chemicalml/cml/role/bond.rb +13 -0
- data/lib/chemicalml/cml/role/bond_array.rb +13 -0
- data/lib/chemicalml/cml/role/bond_stereo.rb +13 -0
- data/lib/chemicalml/cml/role/cml_module.rb +14 -0
- data/lib/chemicalml/cml/role/dictionary.rb +13 -0
- data/lib/chemicalml/cml/role/dictionary_entry.rb +13 -0
- data/lib/chemicalml/cml/role/document.rb +13 -0
- data/lib/chemicalml/cml/role/formula.rb +13 -0
- data/lib/chemicalml/cml/role/identifier.rb +13 -0
- data/lib/chemicalml/cml/role/label.rb +13 -0
- data/lib/chemicalml/cml/role/list.rb +13 -0
- data/lib/chemicalml/cml/role/matrix.rb +13 -0
- data/lib/chemicalml/cml/role/metadata.rb +13 -0
- data/lib/chemicalml/cml/role/metadata_list.rb +13 -0
- data/lib/chemicalml/cml/role/molecule.rb +13 -0
- data/lib/chemicalml/cml/role/name.rb +13 -0
- data/lib/chemicalml/cml/role/parameter.rb +13 -0
- data/lib/chemicalml/cml/role/parameter_list.rb +13 -0
- data/lib/chemicalml/cml/role/product.rb +13 -0
- data/lib/chemicalml/cml/role/product_list.rb +13 -0
- data/lib/chemicalml/cml/role/property.rb +13 -0
- data/lib/chemicalml/cml/role/property_list.rb +13 -0
- data/lib/chemicalml/cml/role/reactant.rb +13 -0
- data/lib/chemicalml/cml/role/reactant_list.rb +13 -0
- data/lib/chemicalml/cml/role/reaction.rb +13 -0
- data/lib/chemicalml/cml/role/reaction_list.rb +13 -0
- data/lib/chemicalml/cml/role/scalar.rb +13 -0
- data/lib/chemicalml/cml/role/substance.rb +13 -0
- data/lib/chemicalml/cml/role/unit.rb +13 -0
- data/lib/chemicalml/cml/role/unit_list.rb +13 -0
- data/lib/chemicalml/cml/role/unit_type.rb +13 -0
- data/lib/chemicalml/cml/role/unit_type_list.rb +13 -0
- data/lib/chemicalml/cml/role.rb +54 -0
- data/lib/chemicalml/cml/scalar.rb +10 -0
- data/lib/chemicalml/cml/schema24/configuration.rb +19 -0
- data/lib/chemicalml/cml/schema24.rb +40 -0
- data/lib/chemicalml/cml/schema3/configuration.rb +17 -0
- data/lib/chemicalml/cml/schema3.rb +40 -0
- data/lib/chemicalml/cml/substance.rb +10 -0
- data/lib/chemicalml/cml/translator/value_translations.rb +265 -0
- data/lib/chemicalml/cml/translator.rb +233 -0
- data/lib/chemicalml/cml/unit.rb +10 -0
- data/lib/chemicalml/cml/unit_list.rb +10 -0
- data/lib/chemicalml/cml/unit_type.rb +10 -0
- data/lib/chemicalml/cml/unit_type_list.rb +10 -0
- data/lib/chemicalml/cml/visitable.rb +65 -0
- data/lib/chemicalml/cml/wire_class_macro.rb +45 -0
- data/lib/chemicalml/cml.rb +55 -0
- data/lib/chemicalml/context_configuration.rb +127 -0
- data/lib/chemicalml/convention/base.rb +48 -0
- data/lib/chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list.rb +33 -0
- data/lib/chemicalml/convention/compchem/constraints/job_must_contain_initialization.rb +38 -0
- data/lib/chemicalml/convention/compchem/constraints.rb +14 -0
- data/lib/chemicalml/convention/compchem.rb +28 -0
- data/lib/chemicalml/convention/constraint.rb +79 -0
- data/lib/chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary.rb +33 -0
- data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term.rb +34 -0
- data/lib/chemicalml/convention/dictionary/constraints.rb +14 -0
- data/lib/chemicalml/convention/dictionary.rb +28 -0
- data/lib/chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms.rb +30 -0
- data/lib/chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule.rb +42 -0
- data/lib/chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule.rb +54 -0
- data/lib/chemicalml/convention/molecular/constraints.rb +19 -0
- data/lib/chemicalml/convention/molecular.rb +29 -0
- data/lib/chemicalml/convention/registry.rb +50 -0
- data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb +46 -0
- data/lib/chemicalml/convention/unit_dictionary/constraints.rb +12 -0
- data/lib/chemicalml/convention/unit_dictionary.rb +27 -0
- data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb +34 -0
- data/lib/chemicalml/convention/unit_type_dictionary/constraints.rb +12 -0
- data/lib/chemicalml/convention/unit_type_dictionary.rb +27 -0
- data/lib/chemicalml/convention/violation.rb +36 -0
- data/lib/chemicalml/convention.rb +39 -0
- data/lib/chemicalml/dictionary/entry.rb +59 -0
- data/lib/chemicalml/dictionary/enum.rb +51 -0
- data/lib/chemicalml/dictionary/link.rb +32 -0
- data/lib/chemicalml/dictionary/loader.rb +60 -0
- data/lib/chemicalml/dictionary/model.rb +64 -0
- data/lib/chemicalml/dictionary/registry.rb +75 -0
- data/lib/chemicalml/dictionary.rb +26 -0
- data/lib/chemicalml/errors.rb +7 -0
- data/lib/chemicalml/model/atom.rb +40 -0
- data/lib/chemicalml/model/bond.rb +33 -0
- data/lib/chemicalml/model/cml_array.rb +27 -0
- data/lib/chemicalml/model/cml_module.rb +45 -0
- data/lib/chemicalml/model/document.rb +29 -0
- data/lib/chemicalml/model/formula.rb +34 -0
- data/lib/chemicalml/model/identifier.rb +21 -0
- data/lib/chemicalml/model/label.rb +21 -0
- data/lib/chemicalml/model/matrix.rb +29 -0
- data/lib/chemicalml/model/metadata.rb +23 -0
- data/lib/chemicalml/model/metadata_list.rb +25 -0
- data/lib/chemicalml/model/molecule.rb +38 -0
- data/lib/chemicalml/model/name.rb +21 -0
- data/lib/chemicalml/model/node.rb +45 -0
- data/lib/chemicalml/model/parameter.rb +27 -0
- data/lib/chemicalml/model/parameter_list.rb +25 -0
- data/lib/chemicalml/model/product.rb +21 -0
- data/lib/chemicalml/model/product_list.rb +21 -0
- data/lib/chemicalml/model/property.rb +28 -0
- data/lib/chemicalml/model/property_list.rb +25 -0
- data/lib/chemicalml/model/reactant.rb +21 -0
- data/lib/chemicalml/model/reactant_list.rb +21 -0
- data/lib/chemicalml/model/reaction.rb +41 -0
- data/lib/chemicalml/model/reaction_list.rb +23 -0
- data/lib/chemicalml/model/scalar.rb +25 -0
- data/lib/chemicalml/model/substance.rb +25 -0
- data/lib/chemicalml/model.rb +38 -0
- data/lib/chemicalml/schema/definition.rb +41 -0
- data/lib/chemicalml/schema/registry.rb +30 -0
- data/lib/chemicalml/schema.rb +35 -0
- data/lib/chemicalml/version.rb +5 -0
- data/lib/chemicalml/versioned_parser.rb +43 -0
- data/lib/chemicalml.rb +47 -0
- data/lib/tasks/reference_fetch.rake +105 -0
- data/lib/tasks/scrape_fixtures.rake +133 -0
- data/reference-docs/conventions/compchem.md +141 -0
- data/reference-docs/conventions/dictionary.md +119 -0
- data/reference-docs/conventions/index.md +56 -0
- data/reference-docs/conventions/molecular.md +115 -0
- data/reference-docs/conventions/unit-dictionary.md +92 -0
- data/reference-docs/conventions/unitType-dictionary.md +51 -0
- data/reference-docs/dictionaries/compchem.md +335 -0
- data/reference-docs/dictionaries/index.md +52 -0
- data/reference-docs/examples/schema24-index.md +50 -0
- data/reference-docs/examples/schema3-compchem-index.md +14 -0
- data/reference-docs/examples/schema3-molecular-index.md +28 -0
- data/reference-docs/schemas/schema24/schema.xsd +10624 -0
- data/reference-docs/schemas/schema3/schema.xsd +11459 -0
- metadata +306 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module Molecular
|
|
6
|
+
# Namespace container for all molecular-convention constraint
|
|
7
|
+
# classes. One class per file, registered from the parent
|
|
8
|
+
# `molecular.rb`.
|
|
9
|
+
module Constraints
|
|
10
|
+
autoload :AtomArrayMustContainAtoms,
|
|
11
|
+
"chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms"
|
|
12
|
+
autoload :AtomIdsUniqueWithinMolecule,
|
|
13
|
+
"chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule"
|
|
14
|
+
autoload :BondMustReferenceAtomsInSameMolecule,
|
|
15
|
+
"chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
# The molecular convention. Constraints are based on the rules at
|
|
6
|
+
# http://www.xml-cml.org/convention/molecular (archived in
|
|
7
|
+
# `reference-docs/conventions/molecular.md`).
|
|
8
|
+
module Molecular
|
|
9
|
+
extend Base
|
|
10
|
+
|
|
11
|
+
autoload :Constraints, "chemicalml/convention/molecular/constraints"
|
|
12
|
+
|
|
13
|
+
QNAME = "convention:molecular".freeze
|
|
14
|
+
NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}molecular".freeze
|
|
15
|
+
|
|
16
|
+
def self.qname
|
|
17
|
+
QNAME
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.namespace_uri
|
|
21
|
+
NAMESPACE_URI
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
register Constraints::AtomArrayMustContainAtoms
|
|
25
|
+
register Constraints::AtomIdsUniqueWithinMolecule
|
|
26
|
+
register Constraints::BondMustReferenceAtomsInSameMolecule
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
# Lookup table for conventions. Built-in conventions are
|
|
6
|
+
# registered lazily on first access to avoid load-order surprises.
|
|
7
|
+
#
|
|
8
|
+
# Adding a new convention = creating a subclass of `Base` that
|
|
9
|
+
# returns its QName and namespace_uri, then registering it here.
|
|
10
|
+
# No switch statements, no if/elsif chains in framework code.
|
|
11
|
+
module Registry
|
|
12
|
+
@mutex = Mutex.new
|
|
13
|
+
@cache = nil
|
|
14
|
+
|
|
15
|
+
def self.lookup(qname)
|
|
16
|
+
load_cache[qname.to_s]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.validate(document, qname:)
|
|
20
|
+
convention = lookup(qname)
|
|
21
|
+
raise ArgumentError, "unknown convention: #{qname.inspect}" unless convention
|
|
22
|
+
|
|
23
|
+
convention.validate(document)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.builtin_qnames
|
|
27
|
+
load_cache.keys.sort
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.reset!
|
|
31
|
+
@mutex.synchronize { @cache = nil }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.load_cache
|
|
35
|
+
@mutex.synchronize do
|
|
36
|
+
@cache ||= begin
|
|
37
|
+
{
|
|
38
|
+
Molecular.qname => Molecular,
|
|
39
|
+
Compchem.qname => Compchem,
|
|
40
|
+
Dictionary.qname => Dictionary,
|
|
41
|
+
UnitDictionary.qname => UnitDictionary,
|
|
42
|
+
UnitTypeDictionary.qname => UnitTypeDictionary
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
private_class_method :load_cache
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module UnitDictionary
|
|
6
|
+
module Constraints
|
|
7
|
+
# Every unit MUST have `id`, `title`, `symbol`, `parentSI`,
|
|
8
|
+
# at least one of `multiplierToSI`/`constantToSI`, and
|
|
9
|
+
# `unitType` per the unit-dictionary convention.
|
|
10
|
+
class UnitMustHaveSymbolAndUnitType < Chemicalml::Convention::Constraint::NodeConstraint
|
|
11
|
+
REQUIRED = %i[title symbol parent_si unit_type].freeze
|
|
12
|
+
|
|
13
|
+
def check_node(node, path)
|
|
14
|
+
return [] unless unit?(node)
|
|
15
|
+
|
|
16
|
+
violations = []
|
|
17
|
+
REQUIRED.each do |attr|
|
|
18
|
+
val = node.public_send(attr)
|
|
19
|
+
next unless val.to_s.empty?
|
|
20
|
+
|
|
21
|
+
violations << violation(path: path.join("/"),
|
|
22
|
+
message: "unit must have #{attr} (id=#{node.id.inspect})")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
unless has_si_conversion?(node)
|
|
26
|
+
violations << violation(path: path.join("/"),
|
|
27
|
+
message: "unit must have multiplierToSI or constantToSI (id=#{node.id.inspect})")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
violations
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def unit?(node)
|
|
36
|
+
node.is_a?(Chemicalml::Cml::Role::Unit)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def has_si_conversion?(node)
|
|
40
|
+
!node.multiplier_to_si.to_s.empty? || !node.constant_to_si.to_s.empty?
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module UnitDictionary
|
|
6
|
+
module Constraints
|
|
7
|
+
autoload :UnitMustHaveSymbolAndUnitType,
|
|
8
|
+
"chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
# The unit-dictionary convention. Constraints based on
|
|
6
|
+
# http://www.xml-cml.org/convention/unit-dictionary (archived in
|
|
7
|
+
# `reference-docs/conventions/unit-dictionary.md`).
|
|
8
|
+
module UnitDictionary
|
|
9
|
+
extend Base
|
|
10
|
+
|
|
11
|
+
autoload :Constraints, "chemicalml/convention/unit_dictionary/constraints"
|
|
12
|
+
|
|
13
|
+
QNAME = "convention:unit-dictionary".freeze
|
|
14
|
+
NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}unit-dictionary".freeze
|
|
15
|
+
|
|
16
|
+
def self.qname
|
|
17
|
+
QNAME
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.namespace_uri
|
|
21
|
+
NAMESPACE_URI
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
register Constraints::UnitMustHaveSymbolAndUnitType
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module UnitTypeDictionary
|
|
6
|
+
module Constraints
|
|
7
|
+
# Every unitType MUST have an `id` and a `name` attribute per
|
|
8
|
+
# the unitType-dictionary convention.
|
|
9
|
+
class UnitTypeMustHaveIdAndName < Chemicalml::Convention::Constraint::NodeConstraint
|
|
10
|
+
def check_node(node, path)
|
|
11
|
+
return [] unless unit_type?(node)
|
|
12
|
+
|
|
13
|
+
violations = []
|
|
14
|
+
if node.id.to_s.empty?
|
|
15
|
+
violations << violation(path: path.join("/"),
|
|
16
|
+
message: "unitType must have an id")
|
|
17
|
+
end
|
|
18
|
+
if node.name.to_s.empty?
|
|
19
|
+
violations << violation(path: path.join("/"),
|
|
20
|
+
message: "unitType must have a name")
|
|
21
|
+
end
|
|
22
|
+
violations
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def unit_type?(node)
|
|
28
|
+
node.is_a?(Chemicalml::Cml::Role::UnitType)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module UnitTypeDictionary
|
|
6
|
+
module Constraints
|
|
7
|
+
autoload :UnitTypeMustHaveIdAndName,
|
|
8
|
+
"chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
# The unitType-dictionary convention. Constraints based on
|
|
6
|
+
# http://www.xml-cml.org/convention/unitType-dictionary (archived
|
|
7
|
+
# in `reference-docs/conventions/unitType-dictionary.md`).
|
|
8
|
+
module UnitTypeDictionary
|
|
9
|
+
extend Base
|
|
10
|
+
|
|
11
|
+
autoload :Constraints, "chemicalml/convention/unit_type_dictionary/constraints"
|
|
12
|
+
|
|
13
|
+
QNAME = "convention:unitType-dictionary".freeze
|
|
14
|
+
NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}unitType-dictionary".freeze
|
|
15
|
+
|
|
16
|
+
def self.qname
|
|
17
|
+
QNAME
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.namespace_uri
|
|
21
|
+
NAMESPACE_URI
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
register Constraints::UnitTypeMustHaveIdAndName
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
# Value object describing one constraint violation. Severity is
|
|
6
|
+
# either `:error` (document is invalid under the convention) or
|
|
7
|
+
# `:warning` (document is valid but problematic).
|
|
8
|
+
class Violation
|
|
9
|
+
SEVERITIES = %i[error warning].freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :path, :message, :severity, :constraint
|
|
12
|
+
|
|
13
|
+
def initialize(path:, message:, severity: :error, constraint: nil)
|
|
14
|
+
raise ArgumentError, "unknown severity: #{severity.inspect}" unless SEVERITIES.include?(severity.to_sym)
|
|
15
|
+
|
|
16
|
+
@path = path
|
|
17
|
+
@message = message
|
|
18
|
+
@severity = severity.to_sym
|
|
19
|
+
@constraint = constraint
|
|
20
|
+
freeze
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def error?
|
|
24
|
+
severity == :error
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def warning?
|
|
28
|
+
severity == :warning
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_s
|
|
32
|
+
"#{severity.upcase} #{path}: #{message}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
# Convention framework. A convention is a named, namespaced set of
|
|
5
|
+
# constraints that CML documents conform to in a particular domain.
|
|
6
|
+
#
|
|
7
|
+
# Conventions are *registered*, not switch-statement'd. Each
|
|
8
|
+
# convention owns its constraint classes; the framework walks
|
|
9
|
+
# whatever was registered.
|
|
10
|
+
#
|
|
11
|
+
# The five built-in conventions:
|
|
12
|
+
#
|
|
13
|
+
# molecular http://www.xml-cml.org/convention/molecular
|
|
14
|
+
# compchem http://www.xml-cml.org/convention/compchem
|
|
15
|
+
# dictionary http://www.xml-cml.org/convention/dictionary
|
|
16
|
+
# unit-dictionary http://www.xml-cml.org/convention/unit-dictionary
|
|
17
|
+
# unitType-dictionary http://www.xml-cml.org/convention/unitType-dictionary
|
|
18
|
+
module Convention
|
|
19
|
+
autoload :Base, "chemicalml/convention/base"
|
|
20
|
+
autoload :Compchem, "chemicalml/convention/compchem"
|
|
21
|
+
autoload :Constraint, "chemicalml/convention/constraint"
|
|
22
|
+
autoload :Dictionary, "chemicalml/convention/dictionary"
|
|
23
|
+
autoload :Molecular, "chemicalml/convention/molecular"
|
|
24
|
+
autoload :Registry, "chemicalml/convention/registry"
|
|
25
|
+
autoload :UnitDictionary, "chemicalml/convention/unit_dictionary"
|
|
26
|
+
autoload :UnitTypeDictionary, "chemicalml/convention/unit_type_dictionary"
|
|
27
|
+
autoload :Violation, "chemicalml/convention/violation"
|
|
28
|
+
|
|
29
|
+
CONVENTION_NAMESPACE = "http://www.xml-cml.org/convention/".freeze
|
|
30
|
+
|
|
31
|
+
def self.lookup(qname)
|
|
32
|
+
Registry.lookup(qname)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.validate(document, qname:)
|
|
36
|
+
Registry.validate(document, qname: qname)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Dictionary
|
|
5
|
+
# One dictionary entry. Plain Ruby value object — format-agnostic.
|
|
6
|
+
# The YAML shape mirrors the structure documented in
|
|
7
|
+
# `TODO.cml-full/05-dictionary-layer.md`.
|
|
8
|
+
class Entry
|
|
9
|
+
ID_PATTERN = /\A[A-Za-z][A-Za-z0-9._-]*\z/.freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :id, :term, :definition, :description,
|
|
12
|
+
:data_type, :unit_type, :units,
|
|
13
|
+
:enum, :links, :source_code
|
|
14
|
+
|
|
15
|
+
def initialize(id:, term:, definition:, description: nil,
|
|
16
|
+
data_type: nil, unit_type: nil, units: nil,
|
|
17
|
+
enum: nil, links: [], source_code: nil)
|
|
18
|
+
raise ArgumentError, "invalid entry id: #{id.inspect}" unless id.to_s.match?(ID_PATTERN)
|
|
19
|
+
|
|
20
|
+
@id = id.to_s
|
|
21
|
+
@term = term
|
|
22
|
+
@definition = definition
|
|
23
|
+
@description = description
|
|
24
|
+
@data_type = data_type
|
|
25
|
+
@unit_type = unit_type
|
|
26
|
+
@units = units
|
|
27
|
+
@enum = enum
|
|
28
|
+
@links = links.to_a
|
|
29
|
+
@source_code = source_code
|
|
30
|
+
freeze
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_h
|
|
34
|
+
h = {
|
|
35
|
+
id: id,
|
|
36
|
+
term: term,
|
|
37
|
+
definition: definition
|
|
38
|
+
}
|
|
39
|
+
h[:description] = description if description
|
|
40
|
+
h[:data_type] = data_type if data_type
|
|
41
|
+
h[:unit_type] = unit_type if unit_type
|
|
42
|
+
h[:units] = units if units
|
|
43
|
+
h[:enum] = enum.to_h if enum
|
|
44
|
+
h[:links] = links.map(&:to_h) unless links.empty?
|
|
45
|
+
h[:source_code] = source_code if source_code
|
|
46
|
+
h
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def eql?(other)
|
|
50
|
+
other.is_a?(Entry) && id == other.id && to_h == other.to_h
|
|
51
|
+
end
|
|
52
|
+
alias == eql?
|
|
53
|
+
|
|
54
|
+
def hash
|
|
55
|
+
[id, to_h].hash
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Dictionary
|
|
5
|
+
# An entry's enum constraint. CML dictionary entries can be tagged
|
|
6
|
+
# as either an *open* set (the listed values are recommended but
|
|
7
|
+
# others are allowed) or a *closed* set (only the listed values are
|
|
8
|
+
# valid).
|
|
9
|
+
class Enum
|
|
10
|
+
KINDS = %i[open closed].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :kind, :values
|
|
13
|
+
|
|
14
|
+
def initialize(kind: :open, values: [])
|
|
15
|
+
raise ArgumentError, "unknown enum kind: #{kind.inspect}" unless KINDS.include?(kind.to_sym)
|
|
16
|
+
|
|
17
|
+
@kind = kind.to_sym
|
|
18
|
+
@values = values.to_a
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def open?
|
|
23
|
+
kind == :open
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def closed?
|
|
27
|
+
kind == :closed
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def allows?(value)
|
|
31
|
+
return true if open?
|
|
32
|
+
return true if values.empty?
|
|
33
|
+
|
|
34
|
+
values.include?(value)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_h
|
|
38
|
+
{ kind: kind, values: values }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def eql?(other)
|
|
42
|
+
other.is_a?(Enum) && kind == other.kind && values == other.values
|
|
43
|
+
end
|
|
44
|
+
alias == eql?
|
|
45
|
+
|
|
46
|
+
def hash
|
|
47
|
+
[kind, values].hash
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Dictionary
|
|
5
|
+
# One external link from an entry's description or definition.
|
|
6
|
+
# Maps to the `xhtml:a` element in the original CML dictionary
|
|
7
|
+
# entries.
|
|
8
|
+
class Link
|
|
9
|
+
attr_reader :rel, :href, :title
|
|
10
|
+
|
|
11
|
+
def initialize(rel:, href:, title: nil)
|
|
12
|
+
@rel = rel
|
|
13
|
+
@href = href
|
|
14
|
+
@title = title
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_h
|
|
19
|
+
{ rel: rel, href: href, title: title }.compact
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def eql?(other)
|
|
23
|
+
other.is_a?(Link) && to_h == other.to_h
|
|
24
|
+
end
|
|
25
|
+
alias == eql?
|
|
26
|
+
|
|
27
|
+
def hash
|
|
28
|
+
to_h.hash
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Chemicalml
|
|
6
|
+
module Dictionary
|
|
7
|
+
# Reads a YAML file conforming to the dictionary schema (see
|
|
8
|
+
# `TODO.cml-full/05-dictionary-layer.md`) and constructs a
|
|
9
|
+
# `Chemicalml::Dictionary::Model`.
|
|
10
|
+
#
|
|
11
|
+
# This is the *only* place that knows about the YAML wire shape —
|
|
12
|
+
# everything else works in terms of the canonical Model.
|
|
13
|
+
module Loader
|
|
14
|
+
def self.from_file(path)
|
|
15
|
+
from_hash(YAML.load_file(path))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.from_hash(hash)
|
|
19
|
+
Model.new(
|
|
20
|
+
namespace: hash.fetch("namespace"),
|
|
21
|
+
prefix: hash.fetch("prefix"),
|
|
22
|
+
title: hash.fetch("title"),
|
|
23
|
+
description: hash["description"],
|
|
24
|
+
entries: (hash["entries"] || []).map { |e| build_entry(e) }
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.build_entry(h)
|
|
29
|
+
Entry.new(
|
|
30
|
+
id: h.fetch("id"),
|
|
31
|
+
term: h.fetch("term"),
|
|
32
|
+
definition: h.fetch("definition"),
|
|
33
|
+
description: h["description"],
|
|
34
|
+
data_type: h["data_type"],
|
|
35
|
+
unit_type: h["unit_type"],
|
|
36
|
+
units: h["units"],
|
|
37
|
+
enum: build_enum(h["enum"]),
|
|
38
|
+
links: (h["links"] || []).map { |l| build_link(l) },
|
|
39
|
+
source_code: h["source_code"]
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
private_class_method :build_entry
|
|
43
|
+
|
|
44
|
+
def self.build_enum(h)
|
|
45
|
+
return nil unless h
|
|
46
|
+
|
|
47
|
+
Enum.new(
|
|
48
|
+
kind: h["kind"] || :open,
|
|
49
|
+
values: h["values"] || []
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
private_class_method :build_enum
|
|
53
|
+
|
|
54
|
+
def self.build_link(h)
|
|
55
|
+
Link.new(rel: h.fetch("rel"), href: h.fetch("href"), title: h["title"])
|
|
56
|
+
end
|
|
57
|
+
private_class_method :build_link
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Dictionary
|
|
5
|
+
# The canonical dictionary model: namespace, prefix, title,
|
|
6
|
+
# description, and an ordered collection of `Entry` instances.
|
|
7
|
+
#
|
|
8
|
+
# Plain Ruby — no XML, no YAML. Loading from YAML goes through
|
|
9
|
+
# `Loader`; the wire (XML) shape is `Chemicalml::Cml::Dictionary`.
|
|
10
|
+
class Model
|
|
11
|
+
attr_reader :namespace, :prefix, :title, :description, :entries
|
|
12
|
+
|
|
13
|
+
def initialize(namespace:, prefix:, title:, description: nil, entries: [])
|
|
14
|
+
@namespace = namespace
|
|
15
|
+
@prefix = prefix
|
|
16
|
+
@title = title
|
|
17
|
+
@description = description
|
|
18
|
+
@entries = entries.to_a
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def lookup(qualified_name)
|
|
23
|
+
_, id = split_qname(qualified_name)
|
|
24
|
+
return nil unless id
|
|
25
|
+
|
|
26
|
+
entries.find { |e| e.id == id }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def lookup!(qualified_name)
|
|
30
|
+
lookup(qualified_name) ||
|
|
31
|
+
raise(KeyError, "no entry #{qualified_name.inspect} in #{namespace}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def entry_ids
|
|
35
|
+
entries.map(&:id)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def to_h
|
|
39
|
+
{
|
|
40
|
+
namespace: namespace,
|
|
41
|
+
prefix: prefix,
|
|
42
|
+
title: title,
|
|
43
|
+
description: description,
|
|
44
|
+
entries: entries.map(&:to_h)
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def eql?(other)
|
|
49
|
+
other.is_a?(Model) && namespace == other.namespace && to_h == other.to_h
|
|
50
|
+
end
|
|
51
|
+
alias == eql?
|
|
52
|
+
|
|
53
|
+
def hash
|
|
54
|
+
[namespace, to_h].hash
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def split_qname(qname)
|
|
60
|
+
qname.to_s.split(":", 2)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Dictionary
|
|
5
|
+
# Registry of built-in dictionaries. Lazily loads YAML from
|
|
6
|
+
# `data/dictionaries/` on first access; subsequent lookups hit the
|
|
7
|
+
# in-memory cache.
|
|
8
|
+
#
|
|
9
|
+
# Adding a new built-in dictionary = dropping a new `.yaml` file
|
|
10
|
+
# under `data/dictionaries/`. No code change required.
|
|
11
|
+
module Registry
|
|
12
|
+
@cache = {}
|
|
13
|
+
@mutex = Mutex.new
|
|
14
|
+
|
|
15
|
+
def self.lookup(qname)
|
|
16
|
+
prefix, id = qname.to_s.split(":", 2)
|
|
17
|
+
return nil unless id
|
|
18
|
+
|
|
19
|
+
dict = load_by_prefix(prefix)
|
|
20
|
+
return nil unless dict
|
|
21
|
+
|
|
22
|
+
dict.lookup(qname)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.lookup!(qname)
|
|
26
|
+
lookup(qname) || raise(KeyError, "no dictionary entry #{qname.inspect}")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.load_builtin(name)
|
|
30
|
+
@mutex.synchronize do
|
|
31
|
+
@cache[name.to_s] ||= begin
|
|
32
|
+
path = File.join(Chemicalml::Dictionary::BUILTIN_DIR, "#{name}.yaml")
|
|
33
|
+
raise KeyError, "no built-in dictionary #{name.inspect}" unless File.exist?(path)
|
|
34
|
+
|
|
35
|
+
Loader.from_file(path)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.load_by_prefix(prefix)
|
|
41
|
+
manifest.each do |name, info|
|
|
42
|
+
return load_builtin(name) if info["prefix"] == prefix
|
|
43
|
+
end
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.reset!
|
|
48
|
+
@mutex.synchronize { @cache.clear }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.builtin_names
|
|
52
|
+
Dir.glob(File.join(BUILTIN_DIR, "*.yaml"))
|
|
53
|
+
.reject { |p| File.basename(p).start_with?("_") }
|
|
54
|
+
.map { |p| File.basename(p, ".yaml").to_sym }
|
|
55
|
+
.sort
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.manifest
|
|
59
|
+
@manifest ||= begin
|
|
60
|
+
path = File.join(BUILTIN_DIR, "_index.yaml")
|
|
61
|
+
if File.exist?(path)
|
|
62
|
+
YAML.load_file(path) || {}
|
|
63
|
+
else
|
|
64
|
+
builtin_names.each_with_object({}) do |name, h|
|
|
65
|
+
h[name.to_s] = { "prefix" => name.to_s }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
private_class_method :manifest
|
|
71
|
+
|
|
72
|
+
require "yaml"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|