asciichem 0.31.12 → 0.31.14

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: 02ee049d5ed6b808972e18f4ecf7bbf6723b39b8ba7a6342c38a06cd24b3673a
4
- data.tar.gz: d455c3156215f27e7509fa610769b573a6bb0e6592097f7c4c67357738095e1d
3
+ metadata.gz: 380b64ab12e3ccc18a554a5d1dc7793046bfa7ea5ba80d51330bb5d713ea618f
4
+ data.tar.gz: 4b3ec1535d5854626b5e2976adb060b4432bfb7f9d20331e79717ae0db4cdf73
5
5
  SHA512:
6
- metadata.gz: e83e9ce16a0ed8a1b22491c1964d15fc906467c152a5afec383ce4774d073beb591d8011a481216033855abc76e7f3febd377437f8808a6c1620448e17c60539
7
- data.tar.gz: caeddc2cb2db07543e3ca9b49932a9ed2a0f68461f2f20eb7c6693914d9bf185b8c44cd7f27e5b2d107e0c9cf7ff704bf63a261180f5b6b112e0d2cc8326b28c
6
+ metadata.gz: 49ca402649027d0091acb25b77848e46a9ffaa5bf876e16512f14378d507a3bf32a6dbbb6245967bc54d1eb6728f0672a530db0e92c8db476ca9bfbdb87497f6
7
+ data.tar.gz: 1741914b553ebe4f1828fa3a294c3ed0249a22072dd90f57185d92aafea717a97eaded37236c97ac3b406032f2919cd449818ba6cf7a6094fdd37a81c7f5c44e
data/CHANGELOG.md CHANGED
@@ -3,6 +3,30 @@
3
3
  All notable changes to AsciiChem are documented here.
4
4
  This project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.31.14] - 2026-11-12
7
+
8
+ ### Added
9
+ - The Common Chemistry adapter accepts a user-provided API key:
10
+ `CommonChemistry.new(api_key: ...)` or the
11
+ `COMMON_CHEMISTRY_API_KEY` environment variable (sent as the
12
+ `apikey` query parameter). CAS key-gated the API — without a key
13
+ live lookups answer 401 and the resolver skips the source.
14
+
15
+ ## [0.31.13] - 2026-11-11
16
+
17
+ ### Fixed
18
+ - `Resolver.resolve_all` isolates source-level failures: a live probe
19
+ found the Common Chemistry API now answers 401 without an API key
20
+ (CAS key-gated the endpoint); with it registered by default, that
21
+ 401 raised through the multi-source fan-out and took the other
22
+ sources down with it. A source-level error now skips that source —
23
+ callers see what answered.
24
+
25
+ ### Changed
26
+ - The README documents the full capability surface: structures,
27
+ lint, local identity, resolution with provenance, citations, and
28
+ the command line.
29
+
6
30
  ## [0.31.12] - 2026-11-11
7
31
 
8
32
  ### Changed
data/README.adoc CHANGED
@@ -45,6 +45,96 @@ formula.to_mathml # => "<math>...</math>"
45
45
  formula.to_text # => "H_2O" (round trip)
46
46
  ----
47
47
 
48
+ The parsed model is the canonical form: MathML, HTML, LaTeX, text,
49
+ linear and 2D structural SVG, and CML are views over the same tree.
50
+
51
+ === Structures: SMILES and molfile in, AsciiChem out
52
+
53
+ [source,ruby]
54
+ ----
55
+ formula = AsciiChem.parse_smiles("CC(=O)Oc1ccccc1C(=O)O")
56
+ formula.to_structural_svg # 2D depiction, deterministic layout
57
+ formula.to_smiles # canonical aromatic SMILES
58
+ formula.to_text # AsciiChem text
59
+
60
+ formula = AsciiChem.parse_molfile(molfile_text) # V2000 in and out
61
+ ----
62
+
63
+ === Lint
64
+
65
+ [source,ruby]
66
+ ----
67
+ AsciiChem::Linter.run(formula) # chemistry diagnostics: balance,
68
+ # valence, charge, isotopes, rings,
69
+ # identifier consistency, ...
70
+ AsciiChem::Linter.errors?(formula) # => true/false
71
+ ----
72
+
73
+ === Local identity: InChI, AuxInfo, InChIKey
74
+
75
+ [source,ruby]
76
+ ----
77
+ require "asciichem/inchi"
78
+
79
+ # the native chemomer engine (byte-exact vs the IUPAC oracle;
80
+ # CHEMOMER_LIB points at the built cdylib) or the IUPAC binary:
81
+ AsciiChem::Inchi.engine = AsciiChem::Inchi::NativeEngine.new
82
+
83
+ identity = AsciiChem::Inchi.identity_for(molecule)
84
+ identity.inchi # "InChI=1S/C9H8O4/..."
85
+ identity.inchikey # "BSYNRYMUTXBXSQ-UHFFFAOYSA-N"
86
+ ----
87
+
88
+ === Resolution: identifiers to substance records
89
+
90
+ [source,ruby]
91
+ ----
92
+ require "asciichem/resolver"
93
+
94
+ substance = AsciiChem::Resolver.resolve(value: "50-78-2", convention: "cas")
95
+ substance.preferred_name # => "Aspirin"
96
+ substance.structure.to_smiles # from the source record
97
+ substance.identifiers.first.provenance.source # "common_chemistry"
98
+
99
+ # all shipped sources (PubChem, OPSIN, CAS Common Chemistry) resolve
100
+ # by default; attribution rides every record. Limit the set when a
101
+ # use must stay NC-free:
102
+ AsciiChem::Resolver.resolve(value: "50-78-2", convention: "cas",
103
+ sources: [:pubchem, :opsin])
104
+ ----
105
+
106
+ CAS key-gated the Common Chemistry API: live lookups there need a
107
+ key (request one at cas.org/services/commonchemistry-api) exported
108
+ as `COMMON_CHEMISTRY_API_KEY`. Without it, that source answers 401
109
+ and is skipped — PubChem and OPSIN keep resolving.
110
+
111
+ === Citations: dataset bibitems with provenance
112
+
113
+ [source,ruby]
114
+ ----
115
+ require "asciichem/citation"
116
+
117
+ molecule = AsciiChem.parse("CC(=O)OC1=CC=CC=C1C(=O)O @cas(\"50-78-2\") @cite(\"pubchem\")").nodes.first
118
+ AsciiChem::Citation.for_molecule(molecule)
119
+ # => [["pubchem", #<Relaton bibitem>]] - one dataset-type bibitem per
120
+ # (substance, source), anchored by InChIKey, with a provenance note
121
+ # (retrieved from, version, attribution).
122
+ ----
123
+
124
+ == Command line
125
+
126
+ [source,sh]
127
+ ----
128
+ asciichem convert -i "H_2O" -t mathml # also: --from smiles|molfile
129
+ asciichem identity -i aspirin.smiles --from smiles
130
+ asciichem resolve --cas 50-78-2 --source common_chemistry
131
+ asciichem validate -i 'H_2O @cas("50-78-2")'
132
+ asciichem cite --cas 50-78-2 -t model-json
133
+ asciichem lint -i "2H_2 + O_2 -> 2H_2O"
134
+ asciichem roundtrip -i "C_6H_12O_6"
135
+ asciichem parse-cml -i formula.xml
136
+ ----
137
+
48
138
  == Conformance
49
139
 
50
140
  The reference implementation of https://www.asciichem.org[the AsciiChem
@@ -14,14 +14,32 @@ module AsciiChem
14
14
  # that must stay NC-free limit sources with the
15
15
  # `asciichem-sources` attribute (metanorma-plugin-asciichem) or
16
16
  # `Resolver.resolve(sources:)`.
17
+ #
18
+ # API access: CAS key-gated the API (401 without a key; request
19
+ # one at cas.org/services/commonchemistry-api — the exact contract
20
+ # arrives with the access email). The adapter sends the key as the
21
+ # `apikey` query parameter; set it per instance (`api_key:`) or
22
+ # through COMMON_CHEMISTRY_API_KEY, which the resolver's own
23
+ # instantiation picks up. Without a key, live lookups answer 401
24
+ # and the resolver skips this source (the other sources still
25
+ # resolve; seeded caches keep working).
17
26
  class CommonChemistry < Adapter
18
27
  source_name :common_chemistry
19
28
  supports "cas"
20
29
 
21
30
  BASE = "https://commonchemistry.cas.org/api/detail"
22
31
 
32
+ attr_reader :api_key
33
+
34
+ def initialize(api_key: ENV.fetch("COMMON_CHEMISTRY_API_KEY", nil))
35
+ super()
36
+ @api_key = api_key
37
+ end
38
+
23
39
  def fetch_record(value:, convention:, fetch:)
24
- body = fetch.get("#{BASE}?cas_rn=#{CGI.escape(value.to_s)}")
40
+ url = "#{BASE}?cas_rn=#{CGI.escape(value.to_s)}"
41
+ url = "#{url}&apikey=#{CGI.escape(api_key)}" if api_key
42
+ body = fetch.get(url)
25
43
  return nil unless body
26
44
 
27
45
  data = JSON.parse(body)
@@ -43,16 +43,23 @@ module AsciiChem
43
43
  raise Error, "unknown resolver source #{name.inspect} (registered: #{adapters.keys.sort.join(', ')})"
44
44
  end
45
45
 
46
- # Resolves against every opt-in source, returning one Substance
47
- # per source that answers (nil entries dropped). Sources answer
48
- # in registration order.
46
+ # Resolves against every registered source (or the selected
47
+ # subset), returning one Substance per source that answers (nil
48
+ # entries dropped). Sources answer in registration order. A
49
+ # source-level failure (HTTP error, rejected request) skips that
50
+ # source — one dead or key-gated endpoint must not poison the
51
+ # others; callers see what answered.
49
52
  def resolve_all(value:, convention:, sources: nil, fetch: nil, cache: nil, refresh: false)
50
53
  selected = sources ? Array(sources).map { |s| self[s] } : adapters.values
51
54
  selected.filter_map do |adapter_class|
52
55
  adapter = adapter_class.new
53
56
  next unless adapter.supports?(convention)
54
57
 
55
- adapter.resolve(value: value, convention: convention, fetch: fetch, cache: cache, refresh: refresh)
58
+ begin
59
+ adapter.resolve(value: value, convention: convention, fetch: fetch, cache: cache, refresh: refresh)
60
+ rescue AsciiChem::Error
61
+ nil
62
+ end
56
63
  end
57
64
  end
58
65
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AsciiChem
4
- VERSION = "0.31.12"
4
+ VERSION = "0.31.14"
5
5
  end
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.31.12
4
+ version: 0.31.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.