asciichem 0.26.0 → 0.28.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54b5e13e60ca4236628c8b8a145add5991c347747b7d7e2aa24f6acda6d89ad1
4
- data.tar.gz: 7b8b2380ea9e5030065958cf35148da3addf2c6dcfb3dd1428066fee088dc410
3
+ metadata.gz: 8fb92ff1cd35eeb546372b2ac9c3cf97b908971409e9a1d473b575479464e356
4
+ data.tar.gz: 2cc3be9f85b7ca2d7e3fe773e981073b6a755344cc6b593b1940666653fb52e7
5
5
  SHA512:
6
- metadata.gz: 0f3f2ae54757260f1ad644d77415a557b16ccdcb15d80829775362fbd2194de3351327babf11ed662676fcfefcd6898a3e9d942928e4a831ac770bb8339846fe
7
- data.tar.gz: 360208b6be6fc9666505610618e7a029711595c9ff353e3ef391e09f80619e47cafbe1304c15e2dffac7c15d436f016094e039ce6a02480393f967bbc9ab1edc
6
+ metadata.gz: 6d2f065122017d83a4e0fdb5a9da9db50a9dbd321e335a9f49bce5c3d253e78ae11f7ae33899ad4ce60fa28b9053a085f4cac7acefbd22999c88287fc0d38d2d
7
+ data.tar.gz: 369b73e26be9c6234fd36c6a2e5d14b53a8746a5fa44c7bc5949297cc17978e6a135e4f003a90641ad7e63a2ce4dd400e05107bb79157628028f14bc68104ac2
data/CHANGELOG.md CHANGED
@@ -3,6 +3,41 @@
3
3
  All notable changes to AsciiChem are documented here.
4
4
  This project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.28.0] - 2026-09-14
7
+
8
+ ### Changed
9
+ - Embedded-math MathML now crosses through the mml gem
10
+ (plurimath/mml) in both directions: Plurimath output is parsed
11
+ into a typed Mml::V3 graph and re-serialized by the framework
12
+ before grafting - replacing xpath surgery on the raw string.
13
+ mml (~> 2.3) is now a direct dependency (previously transitive
14
+ via plurimath).
15
+ - New spec contract: every emitted MathML (and every shared-corpus
16
+ golden) parses back through Mml.parse - output is valid per the
17
+ typed MathML contract model, not just well-formed XML.
18
+
19
+ ## [0.27.0] - 2026-09-14
20
+
21
+ ### Added
22
+ - Local InChI identity engine (TODO.impl 48 / TODO.v2 10): opt-in
23
+ `AsciiChem::Inchi` abstraction over the IUPAC `inchi-1` binary
24
+ (never reimplemented). `EngineMissingError` carries install
25
+ guidance. `IdentityCrossCheck` linter compares annotated
26
+ `@inchi`/`@inchikey` against the drawn structure (error with
27
+ engine, one guidance warning without). CLI: `asciichem identity`.
28
+ - L2 MathML golden conformance: corpus suite pinned against the
29
+ reference goldens; report claims the level.
30
+
31
+ ### Fixed
32
+ - Electron-configuration MathML separator is a real U+00A0 (was a
33
+ double-escaped entity artifact).
34
+ - Reaction conditions render in-place through the same MathML
35
+ formatter (was serialize → reparse → graft, which leaked xmlns and
36
+ reset indentation).
37
+ - Molecule stereo markers (`(R)-`, `(alpha)-`, …) survive the v1
38
+ wire form (additive optional `stereo` field; paired with
39
+ asciichem-model#13).
40
+
6
41
  ## [0.26.0] - 2026-09-14
7
42
 
8
43
  ### Added
@@ -483,3 +518,13 @@ This project follows [Semantic Versioning](https://semver.org/).
483
518
  [0.3.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.2.0...v0.3.0
484
519
  [0.2.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.1.0...v0.2.0
485
520
  [0.1.0]: https://github.com/asciichem/asciichem-ruby/releases/tag/v0.1.0
521
+ ### Changed
522
+ - Embedded-math MathML now crosses through the mml gem (plurimath/mml)
523
+ in both directions: Plurimath output is parsed into a typed
524
+ Mml::V3 graph and re-serialized by the framework before grafting —
525
+ replacing xpath surgery on the raw string. mml (~> 2.3) is now a
526
+ direct dependency (previously transitive via plurimath).
527
+ - New spec contract: every emitted MathML (and every shared-corpus
528
+ golden) parses back through Mml.parse — output is valid per the
529
+ typed MathML contract model, not just well-formed XML.
530
+
data/asciichem.gemspec CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.add_dependency "chemicalml", "~> 0.3.0"
37
37
  spec.add_dependency "elkrb", "~> 1.0"
38
38
  spec.add_dependency "lutaml-model", ">= 0.8", "< 2"
39
+ spec.add_dependency "mml", "~> 2.3"
39
40
  spec.add_dependency "nokogiri", "~> 1.16"
40
41
  spec.add_dependency "parslet", "~> 2.0"
41
42
  spec.add_dependency "relaton-bib", ">= 0.1", "< 2"
data/lib/asciichem/cli.rb CHANGED
@@ -168,6 +168,31 @@ module AsciiChem
168
168
  exit 1
169
169
  end
170
170
 
171
+ desc "identity -i INPUT", "Derive InChI/InChIKey locally from the structure (offline; requires an InChI engine)"
172
+ method_option :input, aliases: "-i", type: :string,
173
+ desc: "Source text (or '-' for stdin)"
174
+ method_option :file, aliases: "-f", type: :string,
175
+ desc: "Read source from a file"
176
+ method_option :from, type: :string, default: "asciichem",
177
+ desc: "Input grammar: asciichem|smiles|molfile"
178
+ method_option :engine_bin, type: :string,
179
+ desc: "Path to the inchi-1 binary (overrides the configured engine)"
180
+ def identity
181
+ formula = ingest(read_source, options[:from])
182
+ molecules = molecules_in(formula)
183
+ unless molecules.length == 1
184
+ raise AsciiChem::Error, "identity derivation needs exactly one molecule, got #{molecules.length}"
185
+ end
186
+
187
+ engine = options[:engine_bin] ? AsciiChem::Inchi::BinaryEngine.new(bin: options[:engine_bin]) : nil
188
+ derived = AsciiChem::Inchi.identity_for(molecules.first, engine: engine)
189
+ puts derived.inchi
190
+ puts derived.inchikey if derived.inchikey
191
+ rescue AsciiChem::EngineMissingError, AsciiChem::Error => e
192
+ warn "Identity error: #{e.message}"
193
+ exit 5
194
+ end
195
+
171
196
  private
172
197
 
173
198
  def read_source
@@ -206,6 +231,19 @@ module AsciiChem
206
231
  AsciiChem::Formatter.render(format.to_sym, formula)
207
232
  end
208
233
 
234
+ # Molecules anywhere at the top level: direct formula nodes plus
235
+ # reaction terms. Identity derivation needs exactly one molecule.
236
+ def molecules_in(formula)
237
+ formula.nodes.flat_map do |node|
238
+ case node
239
+ when AsciiChem::Model::Reaction then node.reactants + node.products
240
+ when AsciiChem::Model::ReactionCascade
241
+ node.steps.flat_map { |step| step.reactants + step.products }
242
+ else [node]
243
+ end
244
+ end.grep(AsciiChem::Model::Molecule)
245
+ end
246
+
209
247
  def output_lint(diagnostics, format)
210
248
  case format.to_s
211
249
  when "json" then output_lint_json(diagnostics)
@@ -6,4 +6,9 @@ module AsciiChem
6
6
  class ParseError < Error; end
7
7
 
8
8
  class FormatError < Error; end
9
+
10
+ # No InChI engine is configured (or the engine binary is absent).
11
+ # Identity derivation is opt-in; the message always carries install
12
+ # guidance (TODO.v2 10: never a silent fallback or homemade InChI).
13
+ class EngineMissingError < Error; end
9
14
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "nokogiri"
4
+ require "mml"
4
5
 
5
6
  module AsciiChem
6
7
  module Formatter
@@ -186,7 +187,7 @@ module AsciiChem
186
187
  def visit_electron_configuration(ec)
187
188
  mrow = el("mrow")
188
189
  ec.orbitals.each_with_index do |(orbital, occupancy), index|
189
- mrow.add_child(mo("&#xA0;")) if index.positive?
190
+ mrow.add_child(mo(" ")) if index.positive?
190
191
  msup = el("msup")
191
192
  msup.add_child(mi(orbital))
192
193
  msup.add_child(mn(occupancy))
@@ -196,17 +197,27 @@ module AsciiChem
196
197
  end
197
198
 
198
199
  def visit_embedded_math(em)
199
- # Strip the outer <math> wrapper so the embedded fragment slots
200
- # into our surrounding <mrow>.
201
- fragment = em.formula.to_mathml
202
- parsed = Nokogiri::XML(fragment)
203
- math = parsed.at_xpath("//m:math", m: MATHML_NS)
204
- return el("mrow") unless math
205
-
206
- # Detach children into a fresh <mrow>.
200
+ # Embedded math crosses through the mml gem in both directions:
201
+ # Plurimath's MathML string is parsed into a typed Mml::V3
202
+ # graph (the MathML contract model), and each top-level child
203
+ # is re-serialized by the framework before grafting — never
204
+ # ad-hoc xpath surgery on the string.
205
+ math = Mml.parse(em.formula.to_mathml, version: 3)
207
206
  mrow = el("mrow")
208
- math.children.each { |c| mrow.add_child(c.dup) }
207
+ math.element_order.each do |entry|
208
+ next unless entry.respond_to?(:name)
209
+
210
+ attr_name = xml_element_attribute(math.class, entry.name)
211
+ next unless attr_name
212
+
213
+ Array(math.public_send(attr_name)).each do |child|
214
+ fragment = Nokogiri::XML::DocumentFragment.parse(child.to_xml)
215
+ fragment.children.each { |node| mrow.add_child(node) }
216
+ end
217
+ end
209
218
  mrow
219
+ rescue Mml::Error, Lutaml::Model::Error, Nokogiri::XML::SyntaxError
220
+ el("mrow")
210
221
  end
211
222
 
212
223
  def visit_text(text)
@@ -309,6 +320,14 @@ module AsciiChem
309
320
 
310
321
  private
311
322
 
323
+ # Symbol of the model attribute an XML child element name maps
324
+ # to on a Mml class (e.g. "mstyle" => :mstyle_value), via the
325
+ # lutaml-model XML mapping — no name-munging.
326
+ def xml_element_attribute(klass, element_name)
327
+ rule = klass.mappings_for(:xml).elements.find { |r| r.name == element_name }
328
+ rule&.to
329
+ end
330
+
312
331
  def render_node(node)
313
332
  node.accept(self)
314
333
  end
@@ -338,25 +357,21 @@ module AsciiChem
338
357
  # Render a reaction-condition string. The condition is captured
339
358
  # as raw text by the grammar, but chemists expect `_N` and `^N`
340
359
  # patterns to render as proper sub/superscripts. We parse the
341
- # condition as AsciiChem and use its MathML output. If the parse
342
- # fails (e.g. the condition is free-form prose), fall back to
343
- # plain <mtext>.
360
+ # condition as AsciiChem and render it in-place with this same
361
+ # formatter (no serialize/reparse round-trip, so no stray xmlns
362
+ # or reset indentation). If the parse fails (e.g. the condition
363
+ # is free-form prose), fall back to plain <mtext>.
344
364
  def render_condition(text)
345
365
  return mtext("") if text.nil? || text.empty?
346
366
 
347
367
  begin
348
- inner = AsciiChem.parse(text).to_mathml
349
- parsed = Nokogiri::XML(inner)
350
- math = parsed.at_xpath("//m:math", m: MATHML_NS)
351
- if math
352
- mrow = el("mrow")
353
- math.children.each { |c| mrow.add_child(c.dup) }
354
- return mrow
355
- end
368
+ formula = AsciiChem.parse(text)
369
+ mrow = el("mrow")
370
+ formula.nodes.each { |n| mrow.add_child(render_node(n)) }
371
+ mrow
356
372
  rescue AsciiChem::ParseError, AsciiChem::Error
357
- # fall through to plain text
373
+ mtext(text)
358
374
  end
359
- mtext(text)
360
375
  end
361
376
 
362
377
  def wrap_in_sub(base, sub)
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'open3'
4
+
5
+ module AsciiChem
6
+ module Inchi
7
+ # Engine that shells out to the standalone IUPAC `inchi-1`
8
+ # binary. The binary is a documented external dependency — nothing
9
+ # is vendored (vendoring the InChI library requires the licence
10
+ # review recorded in TODO.v2 10, a maintainer decision).
11
+ #
12
+ # AsciiChem::Inchi.engine = AsciiChem::Inchi::BinaryEngine.new
13
+ # AsciiChem::Inchi.engine = AsciiChem::Inchi::BinaryEngine.new(bin: "/usr/local/bin/inchi-1")
14
+ class BinaryEngine < Engine
15
+ DEFAULT_FLAGS = %w[-STDIO -AuxNone -NoLabels -Key].freeze
16
+
17
+ attr_reader :bin, :flags
18
+
19
+ def initialize(bin: 'inchi-1', flags: DEFAULT_FLAGS)
20
+ super()
21
+ @bin = bin
22
+ @flags = flags.freeze
23
+ end
24
+
25
+ # model → molfile → inchi-1 → Identity. The molfile emission is
26
+ # the pipe: atom coordinates, charges and isotopes cross into
27
+ # the engine through the V2000 block.
28
+ def identity(molecule)
29
+ molfile = AsciiChem::Molfile.write(molecule)
30
+ out = execute(molfile)
31
+ inchi = out[/^InChI=\S+/]
32
+ raise AsciiChem::Error, "#{bin} produced no InChI:\n#{out}" unless inchi
33
+
34
+ inchikey = out[/^InChIKey=(\S+)/, 1]
35
+ raise AsciiChem::Error, "#{bin} produced no InChIKey (pass -Key):\n#{out}" unless inchikey
36
+
37
+ Identity.new(inchi: inchi, inchikey: inchikey)
38
+ end
39
+
40
+ private
41
+
42
+ def execute(input)
43
+ out, err, status = Open3.capture3(bin, *flags, stdin_data: input)
44
+ unless status.success?
45
+ raise AsciiChem::Error, "#{bin} failed (exit #{status.exitstatus}): #{(err + out).strip[0, 500]}"
46
+ end
47
+
48
+ out
49
+ rescue Errno::ENOENT
50
+ raise EngineMissingError, "InChI engine binary #{bin.inspect} not found — #{AsciiChem::Inchi::INSTALL_GUIDE}"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiChem
4
+ module Inchi
5
+ # Abstract identity engine. Subclasses wrap an InChI
6
+ # implementation (IUPAC software, RDKit, WASM build) behind one
7
+ # contract. Registering an engine is how a user opts in to local
8
+ # identity derivation — the core gem stays dependency-free.
9
+ class Engine
10
+ # The single engine obligation: return the Identity of a
11
+ # Model::Molecule.
12
+ def identity(_molecule)
13
+ raise NotImplementedError, "#{self.class} must implement #identity"
14
+ end
15
+
16
+ # Standard InChI string of the molecule (TODO.v2 10 interface).
17
+ def to_inchi(molecule, standard: true)
18
+ identity = identity(molecule)
19
+ return identity.inchi if standard
20
+
21
+ raise ArgumentError, "#{self.class} derives standard InChI only"
22
+ end
23
+
24
+ # InChIKey of the molecule.
25
+ def to_inchikey(molecule)
26
+ identity(molecule).inchikey
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiChem
4
+ module Inchi
5
+ # Derived identity of a structure: the standard InChI string and
6
+ # its InChIKey. Value object returned by engines.
7
+ Identity = Struct.new(:inchi, :inchikey, keyword_init: true)
8
+ end
9
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiChem
4
+ # Local identity derivation (TODO.v2 10; TODO.impl 48).
5
+ #
6
+ # The InChI algorithm is never reimplemented: engines wrap the IUPAC
7
+ # reference software (the standalone `inchi-1` binary here). The pipe
8
+ # is Model::Molecule → molfile → engine → InChI/InChIKey.
9
+ #
10
+ # Engines are opt-in: set `AsciiChem::Inchi.engine` (or pass one per
11
+ # call). Without an engine every derivation raises EngineMissingError
12
+ # with install guidance — no silent fallback.
13
+ module Inchi
14
+ autoload :BinaryEngine, 'asciichem/inchi/binary_engine'
15
+ autoload :Engine, 'asciichem/inchi/engine'
16
+ autoload :Identity, 'asciichem/inchi/identity'
17
+
18
+ INSTALL_GUIDE = 'install the IUPAC InChI software ' \
19
+ '(https://www.inchi-trust.org/downloads/) and set ' \
20
+ 'AsciiChem::Inchi.engine = AsciiChem::Inchi::BinaryEngine.new ' \
21
+ '(or pass bin: "path/to/inchi-1")'
22
+
23
+ class << self
24
+ attr_accessor :engine
25
+
26
+ # Derives the identity of one molecule. Falls back to the
27
+ # configured engine when none is passed; raises
28
+ # EngineMissingError when neither exists.
29
+ def identity_for(molecule, engine: nil)
30
+ selected = engine || self.engine
31
+ raise EngineMissingError, "no InChI engine configured — #{INSTALL_GUIDE}" if selected.nil?
32
+
33
+ selected.identity(molecule)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AsciiChem
4
+ module Linter
5
+ # Structural cross-check of identity annotations (TODO.v2 10).
6
+ # Where IdentifierConsistencyCheck compares offline-analysable
7
+ # layers (formula composition), this check derives the InChI of
8
+ # the drawn structure via the configured engine and compares it
9
+ # to the annotated `@inchi`/`@inchikey` — catching transcription
10
+ # errors that composition checks cannot (wrong connectivity,
11
+ # stereo, isotopes).
12
+ #
13
+ # Behaviour is engine-scaled, mirroring the resolver's opt-in
14
+ # sources: with no engine configured the check emits one warning
15
+ # explaining how to enable it (protection off is worth saying);
16
+ # with an engine, mismatches are errors. Molecules without
17
+ # identity annotations are never checked.
18
+ class IdentityCrossCheck < Base
19
+ register :identity_cross_check
20
+
21
+ def run(formula)
22
+ annotated = []
23
+ walk(formula) do |node|
24
+ next unless node.is_a?(AsciiChem::Model::Molecule)
25
+
26
+ annotated << node if identity_annotations?(node)
27
+ end
28
+ return [] if annotated.empty?
29
+
30
+ engine = AsciiChem::Inchi.engine
31
+ return [warning(skip_message(annotated.length))] if engine.nil?
32
+
33
+ annotated.flat_map { |molecule| cross_check(molecule, engine) }
34
+ rescue AsciiChem::EngineMissingError => e
35
+ [warning(e.message)]
36
+ end
37
+
38
+ private
39
+
40
+ def identity_annotations?(molecule)
41
+ molecule.identifiers.any? { |i| %w[inchi inchikey].include?(i.convention.to_s) }
42
+ end
43
+
44
+ def cross_check(molecule, engine)
45
+ identity = engine.identity(molecule)
46
+ molecule.identifiers.filter_map do |i|
47
+ diagnostic_for(i, identity, molecule)
48
+ end
49
+ rescue AsciiChem::Error => e
50
+ # Not derivable (e.g. a formula is not a structure: no bonds)
51
+ # or the engine rejected it — report, never raise: the linter
52
+ # stays total.
53
+ [warning("cannot derive InChI for cross-check: #{e.message}", node: molecule)]
54
+ end
55
+
56
+ def diagnostic_for(identifier, identity, molecule)
57
+ case identifier.convention.to_s
58
+ when 'inchi'
59
+ return nil if identifier.value == identity.inchi
60
+
61
+ error('InChI annotation does not match the drawn structure: ' \
62
+ "annotated #{identifier.value}, computed #{identity.inchi}",
63
+ node: molecule)
64
+ when 'inchikey'
65
+ return nil if identifier.value == identity.inchikey
66
+
67
+ error('InChIKey annotation does not match the drawn structure: ' \
68
+ "annotated #{identifier.value}, computed #{identity.inchikey}",
69
+ node: molecule)
70
+ end
71
+ end
72
+
73
+ def skip_message(count)
74
+ "structural identity cross-check skipped for #{count} annotated molecule(s): " \
75
+ "no InChI engine configured — #{AsciiChem::Inchi::INSTALL_GUIDE}"
76
+ end
77
+ end
78
+ end
79
+ end
@@ -18,6 +18,7 @@ module AsciiChem
18
18
  autoload :ElementValidationCheck, "asciichem/linter/element_validation_check"
19
19
  autoload :IdentifierConsistencyCheck, "asciichem/linter/identifier_consistency_check"
20
20
  autoload :IdentifierFormatCheck, "asciichem/linter/identifier_format_check"
21
+ autoload :IdentityCrossCheck, "asciichem/linter/identity_cross_check"
21
22
  autoload :Diagnostic, "asciichem/linter/diagnostic"
22
23
  autoload :IsotopeSanityCheck, "asciichem/linter/isotope_sanity_check"
23
24
  autoload :Registry, "asciichem/linter/registry"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AsciiChem
4
- VERSION = "0.26.0"
4
+ VERSION = "0.28.0"
5
5
  end
@@ -63,10 +63,12 @@ module AsciiChem
63
63
 
64
64
  attribute :nodes, Base, collection: true
65
65
  attribute :coefficient, :string
66
+ attribute :stereo, :string
66
67
  attribute :identifiers, Identifier, collection: true
67
68
  json do
68
69
  map "nodes", to: :nodes
69
70
  map "coefficient", to: :coefficient
71
+ map "stereo", to: :stereo
70
72
  map "identifiers", to: :identifiers
71
73
  end
72
74
  end
@@ -136,10 +136,25 @@ module AsciiChem
136
136
  type: "molecule",
137
137
  nodes: node.nodes.map { |n| to_wire(n) },
138
138
  coefficient: node.coefficient,
139
+ stereo: stereo_to_wire(node.stereo),
139
140
  identifiers: node.identifiers.map { |i| identifier_to_wire(i) }
140
141
  )
141
142
  end
142
143
 
144
+ # Stereo markers cross the wire as the v1 enum strings
145
+ # ("R", "alpha", ...); the model carries symbols.
146
+ def stereo_to_wire(stereo)
147
+ return nil unless stereo
148
+
149
+ AsciiChem::Model::Molecule::STEREO_TO_LETTER.fetch(stereo)
150
+ end
151
+
152
+ def stereo_from_wire(stereo)
153
+ return nil unless stereo
154
+
155
+ AsciiChem::Model::Molecule::STEREO_LETTERS.fetch(stereo.to_s)
156
+ end
157
+
143
158
  def group_to_wire(node)
144
159
  AsciiChem::Wire::Group.new(
145
160
  type: "group",
@@ -295,6 +310,7 @@ module AsciiChem
295
310
  AsciiChem::Model::Molecule.new(
296
311
  nodes: Array(wire.nodes).map { |n| from_wire(n) },
297
312
  coefficient: wire.coefficient,
313
+ stereo: stereo_from_wire(wire.stereo),
298
314
  identifiers: Array(wire.identifiers).map { |i| identifier_from_wire(i) }
299
315
  )
300
316
  end
data/lib/asciichem.rb CHANGED
@@ -15,10 +15,12 @@ module AsciiChem
15
15
  autoload :Error, "asciichem/errors"
16
16
  autoload :ParseError, "asciichem/errors"
17
17
  autoload :FormatError, "asciichem/errors"
18
+ autoload :EngineMissingError, "asciichem/errors"
18
19
  autoload :Formatter, "asciichem/formatter"
19
20
  autoload :Grammar, "asciichem/grammar"
20
21
  autoload :Greek, "asciichem/greek"
21
22
  autoload :Identifiers, "asciichem/identifiers"
23
+ autoload :Inchi, "asciichem/inchi"
22
24
  autoload :Layout, "asciichem/layout"
23
25
  autoload :Linter, "asciichem/linter"
24
26
  autoload :Model, "asciichem/model"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciichem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.26.0
4
+ version: 0.28.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -57,6 +57,20 @@ dependencies:
57
57
  - - "<"
58
58
  - !ruby/object:Gem::Version
59
59
  version: '2'
60
+ - !ruby/object:Gem::Dependency
61
+ name: mml
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '2.3'
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '2.3'
60
74
  - !ruby/object:Gem::Dependency
61
75
  name: nokogiri
62
76
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +220,10 @@ files:
206
220
  - lib/asciichem/identifiers/inchi.rb
207
221
  - lib/asciichem/identifiers/inchikey.rb
208
222
  - lib/asciichem/identifiers/smiles.rb
223
+ - lib/asciichem/inchi.rb
224
+ - lib/asciichem/inchi/binary_engine.rb
225
+ - lib/asciichem/inchi/engine.rb
226
+ - lib/asciichem/inchi/identity.rb
209
227
  - lib/asciichem/layout.rb
210
228
  - lib/asciichem/linter.rb
211
229
  - lib/asciichem/linter/balance_check.rb
@@ -217,6 +235,7 @@ files:
217
235
  - lib/asciichem/linter/element_validation_check.rb
218
236
  - lib/asciichem/linter/identifier_consistency_check.rb
219
237
  - lib/asciichem/linter/identifier_format_check.rb
238
+ - lib/asciichem/linter/identity_cross_check.rb
220
239
  - lib/asciichem/linter/isotope_sanity_check.rb
221
240
  - lib/asciichem/linter/registry.rb
222
241
  - lib/asciichem/linter/spectrum_peak_check.rb