asciichem 0.26.0 → 0.27.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 +4 -4
- data/CHANGELOG.md +22 -0
- data/lib/asciichem/cli.rb +38 -0
- data/lib/asciichem/errors.rb +5 -0
- data/lib/asciichem/formatter/mathml.rb +10 -14
- data/lib/asciichem/inchi/binary_engine.rb +54 -0
- data/lib/asciichem/inchi/engine.rb +30 -0
- data/lib/asciichem/inchi/identity.rb +9 -0
- data/lib/asciichem/inchi.rb +37 -0
- data/lib/asciichem/linter/identity_cross_check.rb +79 -0
- data/lib/asciichem/linter.rb +1 -0
- data/lib/asciichem/version.rb +1 -1
- data/lib/asciichem/wire/core.rb +2 -0
- data/lib/asciichem/wire_adapter.rb +16 -0
- data/lib/asciichem.rb +2 -0
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4602bf349585d9d81483e3561d970efd6bb06f3196d30a0a3017df316699664f
|
|
4
|
+
data.tar.gz: ea4c38d2af66b75ce2024c5cb1cc866af19584af2edba92b09cc264a7e48037e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4dad38021607a88a0f37b90a2320b228d47b6e6197b7102eb48616ea94486ccfc16f953c2c233446ca216eefa1d76aebd0c88b1d40edcb2f582abb56c062e79f
|
|
7
|
+
data.tar.gz: 4fc1f074a3b182e2d22c263f09044d873a9d96ba1e627e89780e42977a00aedd0e7142590df6aa645e405e7dcbca89076e505abccc3e1e37be99dddfca527372
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
All notable changes to AsciiChem are documented here.
|
|
4
4
|
This project follows [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [0.27.0] - 2026-09-14
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- Local InChI identity engine (TODO.impl 48 / TODO.v2 10): opt-in
|
|
10
|
+
`AsciiChem::Inchi` abstraction over the IUPAC `inchi-1` binary
|
|
11
|
+
(never reimplemented). `EngineMissingError` carries install
|
|
12
|
+
guidance. `IdentityCrossCheck` linter compares annotated
|
|
13
|
+
`@inchi`/`@inchikey` against the drawn structure (error with
|
|
14
|
+
engine, one guidance warning without). CLI: `asciichem identity`.
|
|
15
|
+
- L2 MathML golden conformance: corpus suite pinned against the
|
|
16
|
+
reference goldens; report claims the level.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Electron-configuration MathML separator is a real U+00A0 (was a
|
|
20
|
+
double-escaped entity artifact).
|
|
21
|
+
- Reaction conditions render in-place through the same MathML
|
|
22
|
+
formatter (was serialize → reparse → graft, which leaked xmlns and
|
|
23
|
+
reset indentation).
|
|
24
|
+
- Molecule stereo markers (`(R)-`, `(alpha)-`, …) survive the v1
|
|
25
|
+
wire form (additive optional `stereo` field; paired with
|
|
26
|
+
asciichem-model#13).
|
|
27
|
+
|
|
6
28
|
## [0.26.0] - 2026-09-14
|
|
7
29
|
|
|
8
30
|
### Added
|
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)
|
data/lib/asciichem/errors.rb
CHANGED
|
@@ -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
|
|
@@ -186,7 +186,7 @@ module AsciiChem
|
|
|
186
186
|
def visit_electron_configuration(ec)
|
|
187
187
|
mrow = el("mrow")
|
|
188
188
|
ec.orbitals.each_with_index do |(orbital, occupancy), index|
|
|
189
|
-
mrow.add_child(mo("
|
|
189
|
+
mrow.add_child(mo(" ")) if index.positive?
|
|
190
190
|
msup = el("msup")
|
|
191
191
|
msup.add_child(mi(orbital))
|
|
192
192
|
msup.add_child(mn(occupancy))
|
|
@@ -338,25 +338,21 @@ module AsciiChem
|
|
|
338
338
|
# Render a reaction-condition string. The condition is captured
|
|
339
339
|
# as raw text by the grammar, but chemists expect `_N` and `^N`
|
|
340
340
|
# patterns to render as proper sub/superscripts. We parse the
|
|
341
|
-
# condition as AsciiChem and
|
|
342
|
-
#
|
|
343
|
-
#
|
|
341
|
+
# condition as AsciiChem and render it in-place with this same
|
|
342
|
+
# formatter (no serialize/reparse round-trip, so no stray xmlns
|
|
343
|
+
# or reset indentation). If the parse fails (e.g. the condition
|
|
344
|
+
# is free-form prose), fall back to plain <mtext>.
|
|
344
345
|
def render_condition(text)
|
|
345
346
|
return mtext("") if text.nil? || text.empty?
|
|
346
347
|
|
|
347
348
|
begin
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
mrow = el("mrow")
|
|
353
|
-
math.children.each { |c| mrow.add_child(c.dup) }
|
|
354
|
-
return mrow
|
|
355
|
-
end
|
|
349
|
+
formula = AsciiChem.parse(text)
|
|
350
|
+
mrow = el("mrow")
|
|
351
|
+
formula.nodes.each { |n| mrow.add_child(render_node(n)) }
|
|
352
|
+
mrow
|
|
356
353
|
rescue AsciiChem::ParseError, AsciiChem::Error
|
|
357
|
-
|
|
354
|
+
mtext(text)
|
|
358
355
|
end
|
|
359
|
-
mtext(text)
|
|
360
356
|
end
|
|
361
357
|
|
|
362
358
|
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,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
|
data/lib/asciichem/linter.rb
CHANGED
|
@@ -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"
|
data/lib/asciichem/version.rb
CHANGED
data/lib/asciichem/wire/core.rb
CHANGED
|
@@ -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.
|
|
4
|
+
version: 0.27.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -206,6 +206,10 @@ files:
|
|
|
206
206
|
- lib/asciichem/identifiers/inchi.rb
|
|
207
207
|
- lib/asciichem/identifiers/inchikey.rb
|
|
208
208
|
- lib/asciichem/identifiers/smiles.rb
|
|
209
|
+
- lib/asciichem/inchi.rb
|
|
210
|
+
- lib/asciichem/inchi/binary_engine.rb
|
|
211
|
+
- lib/asciichem/inchi/engine.rb
|
|
212
|
+
- lib/asciichem/inchi/identity.rb
|
|
209
213
|
- lib/asciichem/layout.rb
|
|
210
214
|
- lib/asciichem/linter.rb
|
|
211
215
|
- lib/asciichem/linter/balance_check.rb
|
|
@@ -217,6 +221,7 @@ files:
|
|
|
217
221
|
- lib/asciichem/linter/element_validation_check.rb
|
|
218
222
|
- lib/asciichem/linter/identifier_consistency_check.rb
|
|
219
223
|
- lib/asciichem/linter/identifier_format_check.rb
|
|
224
|
+
- lib/asciichem/linter/identity_cross_check.rb
|
|
220
225
|
- lib/asciichem/linter/isotope_sanity_check.rb
|
|
221
226
|
- lib/asciichem/linter/registry.rb
|
|
222
227
|
- lib/asciichem/linter/spectrum_peak_check.rb
|