asciichem 0.31.16 → 1.0.0.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/.github/workflows/ci.yml +1 -1
- data/CHANGELOG.md +27 -0
- data/benchmarks/parg_recheck.rb +1 -1
- data/lib/asciichem/engine/parg_engine.rb +2 -2
- data/lib/asciichem/engine.rb +1 -1
- data/lib/asciichem/resolver/adapter.rb +10 -6
- data/lib/asciichem/resolver/common_chemistry.rb +4 -4
- data/lib/asciichem/resolver/opsin.rb +4 -4
- data/lib/asciichem/resolver/pubchem.rb +4 -4
- data/lib/asciichem/resolver.rb +4 -3
- data/lib/asciichem/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1bd4b9584ac1e0126a98efc020036d2532a9567f8f1cfb0107e66b9c15680135
|
|
4
|
+
data.tar.gz: 6c284fa95ed81aa18328046d02863f709113cb27aaaa2a9fe29a81fbe8cf6586
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e047c4735a8ddf3a5f8c9afb36384a3b7b7f4e65be6a855bfa6b0a24bed4f5e19197f21ca30e37064809f4655d0fed5345bcf2166c087c643f10de48f5f552e3
|
|
7
|
+
data.tar.gz: db2212af6d815e7caa68facc95b636fb43f6ba7f50659d32fbc09050d7caefda4852b2664d66a364c646b8468d336a106b0d76d46357d9cc887d72d9044e66b8
|
data/.github/workflows/ci.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,33 @@
|
|
|
3
3
|
All notable changes to AsciiChem are documented here.
|
|
4
4
|
This project follows [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [1.0.0.0] - 2026-11-13
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- **The gem renumbers onto the 1.0.0 engine.** The version scheme is
|
|
10
|
+
`{engine-semver}.{runtime-increment}`: the asciichem-rs engine is
|
|
11
|
+
1.0.0 and the gem's increment resets at the new engine major
|
|
12
|
+
(maintainer's call). The 0.31.x line was the pre-Rust gem line;
|
|
13
|
+
every future gem release derives from the engine semver. Was
|
|
14
|
+
0.31.17.
|
|
15
|
+
|
|
16
|
+
## [0.31.17] - 2026-11-13
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- The resolver clock seam: `resolve_all` and every adapter accept a
|
|
20
|
+
`now:` ISO-8601 UTC timestamp (nil = the wall clock) that pins
|
|
21
|
+
`retrievedAt` — recorded-fixture runs and the citation goldens that
|
|
22
|
+
consume them compare byte-stably. Mirrors the asciichem-py seam of
|
|
23
|
+
the same name.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- The conformance runner gates the corpus 0.11.0 parity families
|
|
27
|
+
(linter-expectations with engine posture + node matching, resolver
|
|
28
|
+
replay, citation byte-goldens, the 22 CLI contracts); the CI
|
|
29
|
+
corpus pin moves v0.8.0 → v0.11.0. Claims matrix: linter 27 run +
|
|
30
|
+
3 posture skips, resolver 6/6, citation 4/4 byte-equal, CLI 22/22
|
|
31
|
+
(asciichem-ruby#159).
|
|
32
|
+
|
|
6
33
|
## [0.31.16] - 2026-11-12
|
|
7
34
|
|
|
8
35
|
### Changed
|
data/benchmarks/parg_recheck.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# PARG gate + benchmark: AsciiChem::Engine.use(:parg) parses through
|
|
4
|
-
# the compiled PARG artifact vendored from asciichem-
|
|
4
|
+
# the compiled PARG artifact vendored from asciichem-grammar, adapted to
|
|
5
5
|
# the shared TransformRules. (1) gates on the shared corpus, forked
|
|
6
6
|
# per case so a native crash aborts the child — reported, not fatal;
|
|
7
7
|
# (2) gates on capture completeness (a grammar edit leaving a
|
|
@@ -15,7 +15,7 @@ end
|
|
|
15
15
|
module AsciiChem
|
|
16
16
|
module Engine
|
|
17
17
|
# The PARG engine: parses through the compiled AsciiChem PARG
|
|
18
|
-
# artifact (grammar/asciichem.parg in asciichem-
|
|
18
|
+
# artifact (grammar/asciichem.parg in asciichem-grammar, vendored at
|
|
19
19
|
# lib/asciichem/engine/parg/asciichem.json) and adapts its parse
|
|
20
20
|
# tree to the DSL shapes TransformRules consumes. The shared
|
|
21
21
|
# conformance corpus passes model-identically (227/227) against
|
|
@@ -171,7 +171,7 @@ module AsciiChem
|
|
|
171
171
|
raise NotAvailableError,
|
|
172
172
|
"vendored PARG artifact version #{version.inspect} does not match " \
|
|
173
173
|
"the engine's pinned grammar #{PARG_GRAMMAR_VERSION.inspect} — " \
|
|
174
|
-
're-vendor from asciichem-
|
|
174
|
+
're-vendor from asciichem-grammar'
|
|
175
175
|
end
|
|
176
176
|
end
|
|
177
177
|
end
|
data/lib/asciichem/engine.rb
CHANGED
|
@@ -5,7 +5,7 @@ module AsciiChem
|
|
|
5
5
|
# slice 9; the maintainer's PARG-only directive). The legacy
|
|
6
6
|
# parslet and DSL-parsanol engines are deleted — the checksummed
|
|
7
7
|
# AsciiChem PARG artifact (vendored at engine/parg/asciichem.json,
|
|
8
|
-
# sourced from asciichem-
|
|
8
|
+
# sourced from asciichem-grammar) is the one grammar, and
|
|
9
9
|
# PargEngine adapts its tree to the DSL shapes TransformRules
|
|
10
10
|
# consumes.
|
|
11
11
|
module Engine
|
|
@@ -41,7 +41,11 @@ module AsciiChem
|
|
|
41
41
|
|
|
42
42
|
# Cache-aware resolution. `fetch` must answer #get(url) -> body
|
|
43
43
|
# String (NetFetch by default; specs inject recorded responses).
|
|
44
|
-
|
|
44
|
+
# `now` pins the retrieval clock (an ISO-8601 UTC string) for
|
|
45
|
+
# recorded-fixture runs — the shared corpus pins `retrievedAt`
|
|
46
|
+
# so replayed records compare byte-stably; nil means the wall
|
|
47
|
+
# clock.
|
|
48
|
+
def resolve(value:, convention:, fetch: nil, cache: nil, refresh: false, now: nil)
|
|
45
49
|
cache ||= Cache.default
|
|
46
50
|
fetch ||= NetFetch.new
|
|
47
51
|
key = cache.key_for(self.class.source_name, convention, value)
|
|
@@ -51,22 +55,22 @@ module AsciiChem
|
|
|
51
55
|
return cached if cached
|
|
52
56
|
end
|
|
53
57
|
|
|
54
|
-
substance = fetch_record(value: value, convention: convention, fetch: fetch)
|
|
55
|
-
.tap { |s| s&.identifiers&.each { |i| i.provenance ||= default_provenance } }
|
|
58
|
+
substance = fetch_record(value: value, convention: convention, fetch: fetch, now: now)
|
|
59
|
+
.tap { |s| s&.identifiers&.each { |i| i.provenance ||= default_provenance(now) } }
|
|
56
60
|
cache.write(key, substance) if substance
|
|
57
61
|
substance
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
# Not-found is nil, not an error — sources legitimately answer
|
|
61
65
|
# "unknown substance" during multi-source resolution.
|
|
62
|
-
def fetch_record(
|
|
66
|
+
def fetch_record(value:, convention:, fetch:, now: nil)
|
|
63
67
|
raise NotImplementedError
|
|
64
68
|
end
|
|
65
69
|
|
|
66
|
-
def default_provenance
|
|
70
|
+
def default_provenance(now: nil)
|
|
67
71
|
Provenance.new(
|
|
68
72
|
source: self.class.source_name,
|
|
69
|
-
retrieved_at: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
73
|
+
retrieved_at: now || Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
70
74
|
attribution: attribution
|
|
71
75
|
)
|
|
72
76
|
end
|
|
@@ -43,7 +43,7 @@ module AsciiChem
|
|
|
43
43
|
@api_key = api_key
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
def fetch_record(value:, convention:, fetch:)
|
|
46
|
+
def fetch_record(value:, convention:, fetch:, now: nil)
|
|
47
47
|
headers = {
|
|
48
48
|
"Accept" => "application/json",
|
|
49
49
|
"x-origin" => ORIGIN
|
|
@@ -55,7 +55,7 @@ module AsciiChem
|
|
|
55
55
|
data = JSON.parse(body)
|
|
56
56
|
return nil if data.empty?
|
|
57
57
|
|
|
58
|
-
substance_from(data)
|
|
58
|
+
substance_from(data, now)
|
|
59
59
|
rescue JSON::ParserError => e
|
|
60
60
|
raise Error, "common_chemistry returned malformed JSON: #{e.message}"
|
|
61
61
|
end
|
|
@@ -71,8 +71,8 @@ module AsciiChem
|
|
|
71
71
|
(File.read(KEY_FILE).strip if File.readable?(KEY_FILE))
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
def substance_from(data)
|
|
75
|
-
provenance = default_provenance
|
|
74
|
+
def substance_from(data, now)
|
|
75
|
+
provenance = default_provenance(now: now)
|
|
76
76
|
identifiers = [Identifier.new(value: data["rn"], convention: "cas",
|
|
77
77
|
provenance: provenance)]
|
|
78
78
|
add_identifier(identifiers, data, "inchikey", "inchikey", provenance)
|
|
@@ -27,11 +27,11 @@ module AsciiChem
|
|
|
27
27
|
BASE = 'https://opsin.ch.cam.ac.uk/opsin'
|
|
28
28
|
private_constant :BASE
|
|
29
29
|
|
|
30
|
-
def fetch_record(value:, convention: nil, fetch:)
|
|
30
|
+
def fetch_record(value:, convention: nil, fetch:, now: nil)
|
|
31
31
|
body = fetch.get("#{BASE}/#{CGI.escape(value.to_s)}.smiles")
|
|
32
32
|
return nil unless body
|
|
33
33
|
|
|
34
|
-
substance_from(body.strip)
|
|
34
|
+
substance_from(body.strip, now)
|
|
35
35
|
rescue StandardError => e
|
|
36
36
|
raise Error, "opsin request failed: #{e.message}"
|
|
37
37
|
end
|
|
@@ -42,10 +42,10 @@ module AsciiChem
|
|
|
42
42
|
|
|
43
43
|
private
|
|
44
44
|
|
|
45
|
-
def substance_from(smiles)
|
|
45
|
+
def substance_from(smiles, now)
|
|
46
46
|
return nil if smiles.empty?
|
|
47
47
|
|
|
48
|
-
provenance = default_provenance
|
|
48
|
+
provenance = default_provenance(now: now)
|
|
49
49
|
Substance.new(
|
|
50
50
|
preferred_name: nil,
|
|
51
51
|
identifiers: [Identifier.new(value: smiles, convention: 'canonical-smiles',
|
|
@@ -26,14 +26,14 @@ module AsciiChem
|
|
|
26
26
|
MolecularWeight IUPACName InChI InChIKey].freeze
|
|
27
27
|
private_constant :BASE, :PROPERTIES
|
|
28
28
|
|
|
29
|
-
def fetch_record(value:, convention:, fetch:)
|
|
29
|
+
def fetch_record(value:, convention:, fetch:, now: nil)
|
|
30
30
|
body = fetch.get(url_for(value, convention))
|
|
31
31
|
return nil unless body
|
|
32
32
|
|
|
33
33
|
props = JSON.parse(body).dig("PropertyTable", "Properties", 0)
|
|
34
34
|
return nil unless props
|
|
35
35
|
|
|
36
|
-
substance_from(props)
|
|
36
|
+
substance_from(props, now)
|
|
37
37
|
rescue JSON::ParserError => e
|
|
38
38
|
raise Error, "pubchem returned malformed JSON: #{e.message}"
|
|
39
39
|
end
|
|
@@ -53,8 +53,8 @@ module AsciiChem
|
|
|
53
53
|
"#{BASE}/#{namespace}/#{CGI.escape(value.to_s)}/property/#{PROPERTIES.join(',')}/JSON"
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
def substance_from(props)
|
|
57
|
-
provenance = default_provenance
|
|
56
|
+
def substance_from(props, now)
|
|
57
|
+
provenance = default_provenance(now: now)
|
|
58
58
|
identifiers = []
|
|
59
59
|
identifiers << Identifier.new(value: props["CID"].to_s, convention: "pubchem-cid", provenance: provenance) if props["CID"]
|
|
60
60
|
add_identifier(identifiers, props, "InChIKey", "inchikey", provenance)
|
data/lib/asciichem/resolver.rb
CHANGED
|
@@ -48,15 +48,16 @@ module AsciiChem
|
|
|
48
48
|
# entries dropped). Sources answer in registration order. A
|
|
49
49
|
# source-level failure (HTTP error, rejected request) skips that
|
|
50
50
|
# source — one dead or key-gated endpoint must not poison the
|
|
51
|
-
# others; callers see what answered.
|
|
52
|
-
|
|
51
|
+
# others; callers see what answered. `now` pins the retrieval
|
|
52
|
+
# clock for recorded-fixture runs (the adapters' seam).
|
|
53
|
+
def resolve_all(value:, convention:, sources: nil, fetch: nil, cache: nil, refresh: false, now: nil)
|
|
53
54
|
selected = sources ? Array(sources).map { |s| self[s] } : adapters.values
|
|
54
55
|
selected.filter_map do |adapter_class|
|
|
55
56
|
adapter = adapter_class.new
|
|
56
57
|
next unless adapter.supports?(convention)
|
|
57
58
|
|
|
58
59
|
begin
|
|
59
|
-
adapter.resolve(value: value, convention: convention, fetch: fetch, cache: cache, refresh: refresh)
|
|
60
|
+
adapter.resolve(value: value, convention: convention, fetch: fetch, cache: cache, refresh: refresh, now: now)
|
|
60
61
|
rescue AsciiChem::Error
|
|
61
62
|
nil
|
|
62
63
|
end
|
data/lib/asciichem/version.rb
CHANGED