asciichem 0.23.0 → 0.24.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 +17 -1
- data/asciichem.gemspec +1 -0
- data/lib/asciichem/citation.rb +98 -0
- data/lib/asciichem/resolver/adapter.rb +4 -2
- data/lib/asciichem/resolver/common_chemistry.rb +82 -0
- data/lib/asciichem/resolver.rb +5 -3
- data/lib/asciichem/version.rb +1 -1
- data/lib/asciichem.rb +1 -0
- metadata +23 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d733aab290b1e54dfd0139d75a92888dfb8fc2af088544a07cc4395e523e583f
|
|
4
|
+
data.tar.gz: 5dfe21e949307366a6afbcc94bf2c52f9e9cfedb4a1f328996fbde3a20b5ec99
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cdeb663f26df5189cd73df0d7c5f6a9ad3918d981d924ec08f1a781728415af474840de566a8d8f1075cb0cdc0d7086f41f92b80250efee6f71ebd11cdc62680
|
|
7
|
+
data.tar.gz: 371e88363eb2e143b2b8a1b3f1a3cbc339d3eb81d5bcd521fdc00d02fd90e6068c769864185198c8e35e5ae6e2fc95b0088885d760844f527118a50469eaa9fb
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
All notable changes to AsciiChem are documented here.
|
|
4
4
|
This project follows [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [0.24.0] - 2026-09-13
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `AsciiChem::Citation` (TODO.impl 44): one dataset-type Relaton
|
|
10
|
+
bibitem per (substance, source) — the citation track's core
|
|
11
|
+
contract. Source-specific fields live in per-source profiles
|
|
12
|
+
(PubChem, Common Chemistry); every identifier rides along as a
|
|
13
|
+
keyword so citations are machine-checkable. Runtime dependency:
|
|
14
|
+
relaton-bib (classic model).
|
|
15
|
+
- `Resolver::CommonChemistry` (TODO.impl 40, maintainer sign-off
|
|
16
|
+
2026-09-13): CAS RN lookups against CAS Common Chemistry. CC BY-NC
|
|
17
|
+
4.0 posture is enforced in code — the adapter never self-registers;
|
|
18
|
+
users opt in explicitly, and every result carries the required
|
|
19
|
+
attribution.
|
|
20
|
+
|
|
6
21
|
## [0.23.0] - 2026-09-12
|
|
7
22
|
|
|
8
23
|
### Added
|
|
@@ -411,7 +426,8 @@ This project follows [Semantic Versioning](https://semver.org/).
|
|
|
411
426
|
`version`.
|
|
412
427
|
- Comprehensive RSpec suite with round-trip conformance.
|
|
413
428
|
|
|
414
|
-
[Unreleased]: https://github.com/asciichem/asciichem-ruby/compare/v0.
|
|
429
|
+
[Unreleased]: https://github.com/asciichem/asciichem-ruby/compare/v0.24.0...HEAD
|
|
430
|
+
[0.24.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.23.0...v0.24.0
|
|
415
431
|
[0.23.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.22.0...v0.23.0
|
|
416
432
|
[0.22.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.21.0...v0.22.0
|
|
417
433
|
[0.21.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.20.0...v0.21.0
|
data/asciichem.gemspec
CHANGED
|
@@ -38,6 +38,7 @@ Gem::Specification.new do |spec|
|
|
|
38
38
|
spec.add_dependency "lutaml-model", ">= 0.8", "< 2"
|
|
39
39
|
spec.add_dependency "nokogiri", "~> 1.16"
|
|
40
40
|
spec.add_dependency "parslet", "~> 2.0"
|
|
41
|
+
spec.add_dependency "relaton-bib", ">= 0.1", "< 2"
|
|
41
42
|
spec.add_dependency "plurimath", "~> 0.8"
|
|
42
43
|
spec.add_dependency "thor", "~> 1.3"
|
|
43
44
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "relaton_bib"
|
|
4
|
+
|
|
5
|
+
module AsciiChem
|
|
6
|
+
# Citation track (TODO.v2 08; TODO.impl 44): a bibitem is a function
|
|
7
|
+
# of (substance, source) — one dataset-type Relaton bibitem per
|
|
8
|
+
# source the user chooses to cite, because two databases agreeing on
|
|
9
|
+
# a substance are still two different documents.
|
|
10
|
+
#
|
|
11
|
+
# Source-specific fields (link shape, publisher string, docidentifier
|
|
12
|
+
# scheme) live in PROFILES; adding a source is one entry (OCP).
|
|
13
|
+
module Citation
|
|
14
|
+
# Source-specific citation profile. Default fields fall back to
|
|
15
|
+
# the substance's provenance.
|
|
16
|
+
Profile = Struct.new(:publisher, :link_for, :identifier_for, keyword_init: true)
|
|
17
|
+
|
|
18
|
+
PROFILES = {
|
|
19
|
+
"pubchem" => Profile.new(
|
|
20
|
+
publisher: "PubChem, U.S. National Library of Medicine",
|
|
21
|
+
link_for: ->(substance) do
|
|
22
|
+
cid = substance.identifier_value("pubchem-cid")
|
|
23
|
+
"https://pubchem.ncbi.nlm.nih.gov/compound/#{cid}" if cid
|
|
24
|
+
end,
|
|
25
|
+
identifier_for: ->(substance) do
|
|
26
|
+
cid = substance.identifier_value("pubchem-cid")
|
|
27
|
+
"PubChem CID #{cid}" if cid
|
|
28
|
+
end
|
|
29
|
+
),
|
|
30
|
+
"common_chemistry" => Profile.new(
|
|
31
|
+
publisher: "CAS Common Chemistry",
|
|
32
|
+
link_for: ->(substance) do
|
|
33
|
+
cas = substance.identifier_value("cas")
|
|
34
|
+
"https://commonchemistry.cas.org/detail?cas_rn=#{cas}" if cas
|
|
35
|
+
end,
|
|
36
|
+
identifier_for: ->(substance) do
|
|
37
|
+
cas = substance.identifier_value("cas")
|
|
38
|
+
"CAS RN #{cas}" if cas
|
|
39
|
+
end
|
|
40
|
+
)
|
|
41
|
+
}.freeze
|
|
42
|
+
|
|
43
|
+
DEFAULT_PROFILE = Profile.new(
|
|
44
|
+
publisher: nil,
|
|
45
|
+
link_for: ->(_substance) { nil },
|
|
46
|
+
identifier_for: ->(substance) do
|
|
47
|
+
key = substance.identifiers.first
|
|
48
|
+
"#{key.convention}: #{key.value}" if key
|
|
49
|
+
end
|
|
50
|
+
).freeze
|
|
51
|
+
private_constant :DEFAULT_PROFILE
|
|
52
|
+
|
|
53
|
+
class << self
|
|
54
|
+
# Builds a Relaton dataset-type bibitem for the substance as
|
|
55
|
+
# cited from its recorded source. Raises when the substance
|
|
56
|
+
# carries no provenance (hand-built, not resolved).
|
|
57
|
+
def bibitem(substance)
|
|
58
|
+
provenance = substance.provenance
|
|
59
|
+
unless provenance&.source
|
|
60
|
+
raise Error, "substance has no provenance - resolve it first (AsciiChem::Resolver)"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
profile = PROFILES.fetch(provenance.source, DEFAULT_PROFILE)
|
|
64
|
+
RelatonBib::BibliographicItem.new(
|
|
65
|
+
type: "dataset",
|
|
66
|
+
title: [{ type: "main",
|
|
67
|
+
content: "#{title_base(substance)} - #{profile.publisher || provenance.source} substance record" }],
|
|
68
|
+
docid: [RelatonBib::DocumentIdentifier.new(
|
|
69
|
+
id: profile.identifier_for.call(substance) || "#{provenance.source} substance",
|
|
70
|
+
type: provenance.source)],
|
|
71
|
+
contributor: [{ entity: RelatonBib::Organization.new(name: profile.publisher || provenance.source),
|
|
72
|
+
role: [{ type: "publisher" }] }],
|
|
73
|
+
date: [{ type: "accessed", on: accessed_on(provenance) }],
|
|
74
|
+
link: [{ type: "src", content: profile.link_for.call(substance) }].compact,
|
|
75
|
+
keyword: substance.identifiers.map { |i| "#{i.convention}=#{i.value}" }
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Convenience: bibitem XML (what a document pipeline embeds).
|
|
80
|
+
def to_xml(substance)
|
|
81
|
+
bibitem(substance).to_xml
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def title_base(substance)
|
|
87
|
+
substance.preferred_name || substance.identifier_value("cas") ||
|
|
88
|
+
substance.identifier_value("inchikey") || "Substance"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def accessed_on(provenance)
|
|
92
|
+
return provenance.retrieved_at[0, 10] if provenance.retrieved_at
|
|
93
|
+
|
|
94
|
+
Time.now.utc.strftime("%Y-%m-%d")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -17,11 +17,13 @@ module AsciiChem
|
|
|
17
17
|
# Declares the source name and registers the adapter (called
|
|
18
18
|
# from the class body — `inherited` fires before the body
|
|
19
19
|
# runs, so it cannot see the subclass configuration).
|
|
20
|
-
|
|
20
|
+
# `register: false` for opt-in sources (NC-licensed): the
|
|
21
|
+
# adapter exists but users must register it explicitly.
|
|
22
|
+
def source_name(name = nil, register: true)
|
|
21
23
|
return @source_name if name.nil?
|
|
22
24
|
|
|
23
25
|
@source_name = name.to_s
|
|
24
|
-
AsciiChem::Resolver.register(@source_name, self)
|
|
26
|
+
AsciiChem::Resolver.register(@source_name, self) if register
|
|
25
27
|
end
|
|
26
28
|
|
|
27
29
|
def supports(*conventions)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cgi"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module AsciiChem
|
|
7
|
+
module Resolver
|
|
8
|
+
# CAS Common Chemistry source adapter (TODO.impl 40; TODO.v2 07).
|
|
9
|
+
#
|
|
10
|
+
# License posture (maintainer sign-off 2026-09-13): Common
|
|
11
|
+
# Chemistry data is CC BY-NC 4.0 — this adapter is OPT-IN and never
|
|
12
|
+
# self-registers; users register it explicitly:
|
|
13
|
+
#
|
|
14
|
+
# AsciiChem::Resolver.register(:common_chemistry,
|
|
15
|
+
# AsciiChem::Resolver::CommonChemistry)
|
|
16
|
+
#
|
|
17
|
+
# Every result carries the required attribution.
|
|
18
|
+
class CommonChemistry < Adapter
|
|
19
|
+
source_name :common_chemistry, register: false
|
|
20
|
+
supports "cas"
|
|
21
|
+
|
|
22
|
+
BASE = "https://commonchemistry.cas.org/api/detail"
|
|
23
|
+
|
|
24
|
+
def fetch_record(value:, convention:, fetch:)
|
|
25
|
+
body = fetch.get("#{BASE}?cas_rn=#{CGI.escape(value.to_s)}")
|
|
26
|
+
return nil unless body
|
|
27
|
+
|
|
28
|
+
data = JSON.parse(body)
|
|
29
|
+
return nil if data.empty?
|
|
30
|
+
|
|
31
|
+
substance_from(data)
|
|
32
|
+
rescue JSON::ParserError => e
|
|
33
|
+
raise Error, "common_chemistry returned malformed JSON: #{e.message}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def attribution
|
|
37
|
+
"CAS Common Chemistry (CC BY-NC 4.0)"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def substance_from(data)
|
|
43
|
+
provenance = default_provenance
|
|
44
|
+
identifiers = [Identifier.new(value: data["rn"], convention: "cas",
|
|
45
|
+
provenance: provenance)]
|
|
46
|
+
add_identifier(identifiers, data, "inchikey", "inchikey", provenance)
|
|
47
|
+
add_identifier(identifiers, data, "smiles", "canonical-smiles", provenance)
|
|
48
|
+
add_identifier(identifiers, data, "inchi", "inchi", provenance)
|
|
49
|
+
add_identifier(identifiers, data, "iupacName", "iupac-name", provenance)
|
|
50
|
+
|
|
51
|
+
Substance.new(
|
|
52
|
+
preferred_name: data["name"],
|
|
53
|
+
formula: data["formula"],
|
|
54
|
+
identifiers: identifiers,
|
|
55
|
+
properties: [
|
|
56
|
+
Property.new(name: "molecular-formula", value: data["formula"], provenance: provenance),
|
|
57
|
+
Property.new(name: "molecular-weight", value: data["molecularMass"]&.to_s,
|
|
58
|
+
units: "g/mol", provenance: provenance)
|
|
59
|
+
].compact,
|
|
60
|
+
# Best effort within the supported SMILES subset; the SMILES
|
|
61
|
+
# stays as an identifier when stereo keeps it out.
|
|
62
|
+
structure: parse_structure(data["smiles"])
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def add_identifier(list, data, key, convention, provenance)
|
|
67
|
+
return unless data[key]
|
|
68
|
+
|
|
69
|
+
list << Identifier.new(value: data[key], convention: convention,
|
|
70
|
+
provenance: provenance)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parse_structure(smiles)
|
|
74
|
+
return nil unless smiles
|
|
75
|
+
|
|
76
|
+
AsciiChem.parse_smiles(smiles).nodes.first
|
|
77
|
+
rescue AsciiChem::ParseError
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/asciichem/resolver.rb
CHANGED
|
@@ -17,6 +17,7 @@ module AsciiChem
|
|
|
17
17
|
autoload :Adapter, "asciichem/resolver/adapter"
|
|
18
18
|
autoload :Cache, "asciichem/resolver/cache"
|
|
19
19
|
autoload :PubChem, "asciichem/resolver/pubchem"
|
|
20
|
+
autoload :CommonChemistry, "asciichem/resolver/common_chemistry"
|
|
20
21
|
autoload :Substance, "asciichem/resolver/substance"
|
|
21
22
|
|
|
22
23
|
# Raised when sources disagree on substance identity (cross-check
|
|
@@ -69,9 +70,10 @@ module AsciiChem
|
|
|
69
70
|
end
|
|
70
71
|
end
|
|
71
72
|
|
|
72
|
-
# Open, permissive sources register eagerly
|
|
73
|
-
#
|
|
74
|
-
|
|
73
|
+
# Open, permissive sources register eagerly. CommonChemistry is
|
|
74
|
+
# CC BY-NC 4.0: it loads (autoload) but never self-registers —
|
|
75
|
+
# users opt in explicitly via Resolver.register.
|
|
76
|
+
adapters
|
|
75
77
|
constants.each { |c| const_get(c) unless c == :Conflict }
|
|
76
78
|
end
|
|
77
79
|
end
|
data/lib/asciichem/version.rb
CHANGED
data/lib/asciichem.rb
CHANGED
|
@@ -10,6 +10,7 @@ require "plurimath"
|
|
|
10
10
|
# AsciiChem::Cli.start # CLI dispatch
|
|
11
11
|
module AsciiChem
|
|
12
12
|
autoload :Cli, "asciichem/cli"
|
|
13
|
+
autoload :Citation, "asciichem/citation"
|
|
13
14
|
autoload :Cml, "asciichem/cml"
|
|
14
15
|
autoload :Error, "asciichem/errors"
|
|
15
16
|
autoload :ParseError, "asciichem/errors"
|
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.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -85,6 +85,26 @@ dependencies:
|
|
|
85
85
|
- - "~>"
|
|
86
86
|
- !ruby/object:Gem::Version
|
|
87
87
|
version: '2.0'
|
|
88
|
+
- !ruby/object:Gem::Dependency
|
|
89
|
+
name: relaton-bib
|
|
90
|
+
requirement: !ruby/object:Gem::Requirement
|
|
91
|
+
requirements:
|
|
92
|
+
- - ">="
|
|
93
|
+
- !ruby/object:Gem::Version
|
|
94
|
+
version: '0.1'
|
|
95
|
+
- - "<"
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: '2'
|
|
98
|
+
type: :runtime
|
|
99
|
+
prerelease: false
|
|
100
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0.1'
|
|
105
|
+
- - "<"
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '2'
|
|
88
108
|
- !ruby/object:Gem::Dependency
|
|
89
109
|
name: plurimath
|
|
90
110
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -156,6 +176,7 @@ files:
|
|
|
156
176
|
- benchmarks/benchmark.rb
|
|
157
177
|
- exe/asciichem
|
|
158
178
|
- lib/asciichem.rb
|
|
179
|
+
- lib/asciichem/citation.rb
|
|
159
180
|
- lib/asciichem/cli.rb
|
|
160
181
|
- lib/asciichem/cml.rb
|
|
161
182
|
- lib/asciichem/cml/conditions_extensions.rb
|
|
@@ -231,6 +252,7 @@ files:
|
|
|
231
252
|
- lib/asciichem/resolver.rb
|
|
232
253
|
- lib/asciichem/resolver/adapter.rb
|
|
233
254
|
- lib/asciichem/resolver/cache.rb
|
|
255
|
+
- lib/asciichem/resolver/common_chemistry.rb
|
|
234
256
|
- lib/asciichem/resolver/pubchem.rb
|
|
235
257
|
- lib/asciichem/resolver/substance.rb
|
|
236
258
|
- lib/asciichem/ring_bonds.rb
|