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.
Files changed (248) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rubocop.yml +20 -0
  4. data/CHANGELOG.md +21 -0
  5. data/CLAUDE.md +198 -0
  6. data/Gemfile +14 -0
  7. data/LICENSE +24 -0
  8. data/NOTICES.adoc +115 -0
  9. data/README.adoc +87 -0
  10. data/Rakefile +8 -0
  11. data/TODO.align/01-cml-base-mixins.md +122 -0
  12. data/TODO.align/02-real-schema-hierarchies.md +88 -0
  13. data/TODO.align/03-configuration.md +50 -0
  14. data/TODO.align/04-parse-entry-points.md +59 -0
  15. data/TODO.align/05-schema-aware-translator.md +33 -0
  16. data/TODO.align/06-no-respond-to.md +44 -0
  17. data/TODO.align/07-canon-xml-comparison.md +43 -0
  18. data/TODO.align/08-expand-fixtures.md +37 -0
  19. data/TODO.align/09-final-spec-lint.md +29 -0
  20. data/TODO.align/10-role-marker-modules.md +54 -0
  21. data/TODO.align/11-dry-configuration.md +63 -0
  22. data/TODO.align/12-wire-class-macro.md +51 -0
  23. data/TODO.align/13-wire-namespace.md +44 -0
  24. data/TODO.align/14-schema24-correctness.md +37 -0
  25. data/TODO.align/15-canon-fixture-roundtrip.md +38 -0
  26. data/TODO.align/16-schema24-convention-coverage.md +33 -0
  27. data/TODO.align/17-final-spec-lint.md +28 -0
  28. data/TODO.align/README.md +27 -0
  29. data/TODO.align/README.round2.md +24 -0
  30. data/TODO.cml-full/01-reference-docs.md +43 -0
  31. data/TODO.cml-full/02-schema-versioned-model.md +70 -0
  32. data/TODO.cml-full/03-fixtures-scrape.md +52 -0
  33. data/TODO.cml-full/04-convention-framework.md +69 -0
  34. data/TODO.cml-full/05-dictionary-layer.md +77 -0
  35. data/TODO.cml-full/06-extend-canonical-translator.md +60 -0
  36. data/TODO.cml-full/07-attribution-notices.md +42 -0
  37. data/TODO.cml-full/08-autoload-only.md +48 -0
  38. data/TODO.cml-full/09-spec-coverage.md +38 -0
  39. data/TODO.cml-full/README.md +23 -0
  40. data/chemicalml.gemspec +35 -0
  41. data/data/dictionaries/_index.yaml +14 -0
  42. data/data/dictionaries/compchem.yaml +481 -0
  43. data/data/dictionaries/unit_non_si.yaml +53 -0
  44. data/data/dictionaries/unit_si.yaml +87 -0
  45. data/data/dictionaries/unit_type.yaml +113 -0
  46. data/lib/chemicalml/cml/array.rb +10 -0
  47. data/lib/chemicalml/cml/atom.rb +10 -0
  48. data/lib/chemicalml/cml/atom_array.rb +10 -0
  49. data/lib/chemicalml/cml/atom_parity.rb +10 -0
  50. data/lib/chemicalml/cml/base/array.rb +36 -0
  51. data/lib/chemicalml/cml/base/atom.rb +38 -0
  52. data/lib/chemicalml/cml/base/atom_array.rb +22 -0
  53. data/lib/chemicalml/cml/base/atom_parity.rb +24 -0
  54. data/lib/chemicalml/cml/base/bond.rb +28 -0
  55. data/lib/chemicalml/cml/base/bond_array.rb +22 -0
  56. data/lib/chemicalml/cml/base/bond_stereo.rb +28 -0
  57. data/lib/chemicalml/cml/base/cml_module.rb +40 -0
  58. data/lib/chemicalml/cml/base/dictionary.rb +32 -0
  59. data/lib/chemicalml/cml/base/dictionary_entry.rb +34 -0
  60. data/lib/chemicalml/cml/base/document.rb +26 -0
  61. data/lib/chemicalml/cml/base/formula.rb +38 -0
  62. data/lib/chemicalml/cml/base/identifier.rb +26 -0
  63. data/lib/chemicalml/cml/base/label.rb +28 -0
  64. data/lib/chemicalml/cml/base/list.rb +36 -0
  65. data/lib/chemicalml/cml/base/matrix.rb +38 -0
  66. data/lib/chemicalml/cml/base/metadata.rb +30 -0
  67. data/lib/chemicalml/cml/base/metadata_list.rb +28 -0
  68. data/lib/chemicalml/cml/base/molecule.rb +40 -0
  69. data/lib/chemicalml/cml/base/name.rb +26 -0
  70. data/lib/chemicalml/cml/base/parameter.rb +34 -0
  71. data/lib/chemicalml/cml/base/parameter_list.rb +28 -0
  72. data/lib/chemicalml/cml/base/product.rb +22 -0
  73. data/lib/chemicalml/cml/base/product_list.rb +22 -0
  74. data/lib/chemicalml/cml/base/property.rb +34 -0
  75. data/lib/chemicalml/cml/base/property_list.rb +28 -0
  76. data/lib/chemicalml/cml/base/reactant.rb +22 -0
  77. data/lib/chemicalml/cml/base/reactant_list.rb +22 -0
  78. data/lib/chemicalml/cml/base/reaction.rb +32 -0
  79. data/lib/chemicalml/cml/base/reaction_list.rb +22 -0
  80. data/lib/chemicalml/cml/base/scalar.rb +32 -0
  81. data/lib/chemicalml/cml/base/substance.rb +30 -0
  82. data/lib/chemicalml/cml/base/unit.rb +38 -0
  83. data/lib/chemicalml/cml/base/unit_list.rb +30 -0
  84. data/lib/chemicalml/cml/base/unit_type.rb +28 -0
  85. data/lib/chemicalml/cml/base/unit_type_list.rb +30 -0
  86. data/lib/chemicalml/cml/base.rb +51 -0
  87. data/lib/chemicalml/cml/bond.rb +10 -0
  88. data/lib/chemicalml/cml/bond_array.rb +10 -0
  89. data/lib/chemicalml/cml/bond_stereo.rb +10 -0
  90. data/lib/chemicalml/cml/cml_module.rb +10 -0
  91. data/lib/chemicalml/cml/dictionary.rb +10 -0
  92. data/lib/chemicalml/cml/dictionary_entry.rb +10 -0
  93. data/lib/chemicalml/cml/document.rb +10 -0
  94. data/lib/chemicalml/cml/elements.rb +58 -0
  95. data/lib/chemicalml/cml/formula.rb +10 -0
  96. data/lib/chemicalml/cml/identifier.rb +10 -0
  97. data/lib/chemicalml/cml/label.rb +10 -0
  98. data/lib/chemicalml/cml/list.rb +10 -0
  99. data/lib/chemicalml/cml/matrix.rb +10 -0
  100. data/lib/chemicalml/cml/metadata.rb +10 -0
  101. data/lib/chemicalml/cml/metadata_list.rb +10 -0
  102. data/lib/chemicalml/cml/molecule.rb +10 -0
  103. data/lib/chemicalml/cml/name.rb +10 -0
  104. data/lib/chemicalml/cml/namespace.rb +17 -0
  105. data/lib/chemicalml/cml/parameter.rb +10 -0
  106. data/lib/chemicalml/cml/parameter_list.rb +10 -0
  107. data/lib/chemicalml/cml/product.rb +10 -0
  108. data/lib/chemicalml/cml/product_list.rb +10 -0
  109. data/lib/chemicalml/cml/property.rb +10 -0
  110. data/lib/chemicalml/cml/property_list.rb +10 -0
  111. data/lib/chemicalml/cml/reactant.rb +10 -0
  112. data/lib/chemicalml/cml/reactant_list.rb +10 -0
  113. data/lib/chemicalml/cml/reaction.rb +10 -0
  114. data/lib/chemicalml/cml/reaction_list.rb +10 -0
  115. data/lib/chemicalml/cml/role/array.rb +13 -0
  116. data/lib/chemicalml/cml/role/atom.rb +13 -0
  117. data/lib/chemicalml/cml/role/atom_array.rb +13 -0
  118. data/lib/chemicalml/cml/role/atom_parity.rb +13 -0
  119. data/lib/chemicalml/cml/role/bond.rb +13 -0
  120. data/lib/chemicalml/cml/role/bond_array.rb +13 -0
  121. data/lib/chemicalml/cml/role/bond_stereo.rb +13 -0
  122. data/lib/chemicalml/cml/role/cml_module.rb +14 -0
  123. data/lib/chemicalml/cml/role/dictionary.rb +13 -0
  124. data/lib/chemicalml/cml/role/dictionary_entry.rb +13 -0
  125. data/lib/chemicalml/cml/role/document.rb +13 -0
  126. data/lib/chemicalml/cml/role/formula.rb +13 -0
  127. data/lib/chemicalml/cml/role/identifier.rb +13 -0
  128. data/lib/chemicalml/cml/role/label.rb +13 -0
  129. data/lib/chemicalml/cml/role/list.rb +13 -0
  130. data/lib/chemicalml/cml/role/matrix.rb +13 -0
  131. data/lib/chemicalml/cml/role/metadata.rb +13 -0
  132. data/lib/chemicalml/cml/role/metadata_list.rb +13 -0
  133. data/lib/chemicalml/cml/role/molecule.rb +13 -0
  134. data/lib/chemicalml/cml/role/name.rb +13 -0
  135. data/lib/chemicalml/cml/role/parameter.rb +13 -0
  136. data/lib/chemicalml/cml/role/parameter_list.rb +13 -0
  137. data/lib/chemicalml/cml/role/product.rb +13 -0
  138. data/lib/chemicalml/cml/role/product_list.rb +13 -0
  139. data/lib/chemicalml/cml/role/property.rb +13 -0
  140. data/lib/chemicalml/cml/role/property_list.rb +13 -0
  141. data/lib/chemicalml/cml/role/reactant.rb +13 -0
  142. data/lib/chemicalml/cml/role/reactant_list.rb +13 -0
  143. data/lib/chemicalml/cml/role/reaction.rb +13 -0
  144. data/lib/chemicalml/cml/role/reaction_list.rb +13 -0
  145. data/lib/chemicalml/cml/role/scalar.rb +13 -0
  146. data/lib/chemicalml/cml/role/substance.rb +13 -0
  147. data/lib/chemicalml/cml/role/unit.rb +13 -0
  148. data/lib/chemicalml/cml/role/unit_list.rb +13 -0
  149. data/lib/chemicalml/cml/role/unit_type.rb +13 -0
  150. data/lib/chemicalml/cml/role/unit_type_list.rb +13 -0
  151. data/lib/chemicalml/cml/role.rb +54 -0
  152. data/lib/chemicalml/cml/scalar.rb +10 -0
  153. data/lib/chemicalml/cml/schema24/configuration.rb +19 -0
  154. data/lib/chemicalml/cml/schema24.rb +40 -0
  155. data/lib/chemicalml/cml/schema3/configuration.rb +17 -0
  156. data/lib/chemicalml/cml/schema3.rb +40 -0
  157. data/lib/chemicalml/cml/substance.rb +10 -0
  158. data/lib/chemicalml/cml/translator/value_translations.rb +265 -0
  159. data/lib/chemicalml/cml/translator.rb +233 -0
  160. data/lib/chemicalml/cml/unit.rb +10 -0
  161. data/lib/chemicalml/cml/unit_list.rb +10 -0
  162. data/lib/chemicalml/cml/unit_type.rb +10 -0
  163. data/lib/chemicalml/cml/unit_type_list.rb +10 -0
  164. data/lib/chemicalml/cml/visitable.rb +65 -0
  165. data/lib/chemicalml/cml/wire_class_macro.rb +45 -0
  166. data/lib/chemicalml/cml.rb +55 -0
  167. data/lib/chemicalml/context_configuration.rb +127 -0
  168. data/lib/chemicalml/convention/base.rb +48 -0
  169. data/lib/chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list.rb +33 -0
  170. data/lib/chemicalml/convention/compchem/constraints/job_must_contain_initialization.rb +38 -0
  171. data/lib/chemicalml/convention/compchem/constraints.rb +14 -0
  172. data/lib/chemicalml/convention/compchem.rb +28 -0
  173. data/lib/chemicalml/convention/constraint.rb +79 -0
  174. data/lib/chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary.rb +33 -0
  175. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term.rb +34 -0
  176. data/lib/chemicalml/convention/dictionary/constraints.rb +14 -0
  177. data/lib/chemicalml/convention/dictionary.rb +28 -0
  178. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms.rb +30 -0
  179. data/lib/chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule.rb +42 -0
  180. data/lib/chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule.rb +54 -0
  181. data/lib/chemicalml/convention/molecular/constraints.rb +19 -0
  182. data/lib/chemicalml/convention/molecular.rb +29 -0
  183. data/lib/chemicalml/convention/registry.rb +50 -0
  184. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb +46 -0
  185. data/lib/chemicalml/convention/unit_dictionary/constraints.rb +12 -0
  186. data/lib/chemicalml/convention/unit_dictionary.rb +27 -0
  187. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb +34 -0
  188. data/lib/chemicalml/convention/unit_type_dictionary/constraints.rb +12 -0
  189. data/lib/chemicalml/convention/unit_type_dictionary.rb +27 -0
  190. data/lib/chemicalml/convention/violation.rb +36 -0
  191. data/lib/chemicalml/convention.rb +39 -0
  192. data/lib/chemicalml/dictionary/entry.rb +59 -0
  193. data/lib/chemicalml/dictionary/enum.rb +51 -0
  194. data/lib/chemicalml/dictionary/link.rb +32 -0
  195. data/lib/chemicalml/dictionary/loader.rb +60 -0
  196. data/lib/chemicalml/dictionary/model.rb +64 -0
  197. data/lib/chemicalml/dictionary/registry.rb +75 -0
  198. data/lib/chemicalml/dictionary.rb +26 -0
  199. data/lib/chemicalml/errors.rb +7 -0
  200. data/lib/chemicalml/model/atom.rb +40 -0
  201. data/lib/chemicalml/model/bond.rb +33 -0
  202. data/lib/chemicalml/model/cml_array.rb +27 -0
  203. data/lib/chemicalml/model/cml_module.rb +45 -0
  204. data/lib/chemicalml/model/document.rb +29 -0
  205. data/lib/chemicalml/model/formula.rb +34 -0
  206. data/lib/chemicalml/model/identifier.rb +21 -0
  207. data/lib/chemicalml/model/label.rb +21 -0
  208. data/lib/chemicalml/model/matrix.rb +29 -0
  209. data/lib/chemicalml/model/metadata.rb +23 -0
  210. data/lib/chemicalml/model/metadata_list.rb +25 -0
  211. data/lib/chemicalml/model/molecule.rb +38 -0
  212. data/lib/chemicalml/model/name.rb +21 -0
  213. data/lib/chemicalml/model/node.rb +45 -0
  214. data/lib/chemicalml/model/parameter.rb +27 -0
  215. data/lib/chemicalml/model/parameter_list.rb +25 -0
  216. data/lib/chemicalml/model/product.rb +21 -0
  217. data/lib/chemicalml/model/product_list.rb +21 -0
  218. data/lib/chemicalml/model/property.rb +28 -0
  219. data/lib/chemicalml/model/property_list.rb +25 -0
  220. data/lib/chemicalml/model/reactant.rb +21 -0
  221. data/lib/chemicalml/model/reactant_list.rb +21 -0
  222. data/lib/chemicalml/model/reaction.rb +41 -0
  223. data/lib/chemicalml/model/reaction_list.rb +23 -0
  224. data/lib/chemicalml/model/scalar.rb +25 -0
  225. data/lib/chemicalml/model/substance.rb +25 -0
  226. data/lib/chemicalml/model.rb +38 -0
  227. data/lib/chemicalml/schema/definition.rb +41 -0
  228. data/lib/chemicalml/schema/registry.rb +30 -0
  229. data/lib/chemicalml/schema.rb +35 -0
  230. data/lib/chemicalml/version.rb +5 -0
  231. data/lib/chemicalml/versioned_parser.rb +43 -0
  232. data/lib/chemicalml.rb +47 -0
  233. data/lib/tasks/reference_fetch.rake +105 -0
  234. data/lib/tasks/scrape_fixtures.rake +133 -0
  235. data/reference-docs/conventions/compchem.md +141 -0
  236. data/reference-docs/conventions/dictionary.md +119 -0
  237. data/reference-docs/conventions/index.md +56 -0
  238. data/reference-docs/conventions/molecular.md +115 -0
  239. data/reference-docs/conventions/unit-dictionary.md +92 -0
  240. data/reference-docs/conventions/unitType-dictionary.md +51 -0
  241. data/reference-docs/dictionaries/compchem.md +335 -0
  242. data/reference-docs/dictionaries/index.md +52 -0
  243. data/reference-docs/examples/schema24-index.md +50 -0
  244. data/reference-docs/examples/schema3-compchem-index.md +14 -0
  245. data/reference-docs/examples/schema3-molecular-index.md +28 -0
  246. data/reference-docs/schemas/schema24/schema.xsd +10624 -0
  247. data/reference-docs/schemas/schema3/schema.xsd +11459 -0
  248. 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
@@ -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
@@ -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