metanorma-plugin-asciichem 0.1.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 +7 -0
- data/.github/workflows/ci.yml +22 -0
- data/.github/workflows/release.yml +49 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +48 -0
- data/Gemfile +12 -0
- data/LICENSE +24 -0
- data/README.md +143 -0
- data/Rakefile +8 -0
- data/docs/example.adoc +42 -0
- data/lib/metanorma/plugin/asciichem/citations.rb +116 -0
- data/lib/metanorma/plugin/asciichem/extension.rb +142 -0
- data/lib/metanorma/plugin/asciichem/renderer.rb +30 -0
- data/lib/metanorma/plugin/asciichem/version.rb +9 -0
- data/lib/metanorma/plugin/asciichem.rb +28 -0
- data/lib/metanorma-plugin-asciichem.rb +4 -0
- data/metanorma-plugin-asciichem.gemspec +45 -0
- metadata +107 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 582411f25fabc8527cec867947b12b663e2e3944724910676c56c562e485de3e
|
|
4
|
+
data.tar.gz: d49a888d06069c133646bdc2f7e7ecb628662804a508c35858454d27437af862
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d51b92da35d3cf24e9d1da8acca09f8d95abf1f98e6ed9aa3b9e8ada5912c9218f8341b1f7fa4627a16e072bf6d270b2b597d8457ced0d6466082c7298d0650a
|
|
7
|
+
data.tar.gz: 73213d86ba4cbc8d7c4adbbc91920e55f2a3c67aa3da2989a442c8af26f7c31a3caf7a6a45d0ae56df3f1bf28f6f5738413ed399c2cea56369511f1b8e2cfc52
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
ruby: ["3.3", "3.4"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v7
|
|
17
|
+
- uses: ruby/setup-ruby@v1
|
|
18
|
+
with:
|
|
19
|
+
ruby-version: ${{ matrix.ruby }}
|
|
20
|
+
bundler-cache: true
|
|
21
|
+
- run: bundle exec rspec
|
|
22
|
+
- run: bundle exec rubocop
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
version:
|
|
7
|
+
description: "Version to release (must match lib/metanorma/plugin/asciichem/version.rb)"
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
# Trusted publishing (OIDC): register the publisher on RubyGems.org
|
|
15
|
+
# as repository metanorma/metanorma-plugin-asciichem + workflow
|
|
16
|
+
# release.yml, with no environment — so this job must not claim
|
|
17
|
+
# one.
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
id-token: write
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v7
|
|
23
|
+
with:
|
|
24
|
+
ref: main
|
|
25
|
+
persist-credentials: false
|
|
26
|
+
- name: Verify version matches input
|
|
27
|
+
run: |
|
|
28
|
+
actual=$(ruby -e 'require "./lib/metanorma/plugin/asciichem/version"; print Metanorma::Plugin::Asciichem::VERSION')
|
|
29
|
+
if [ "$actual" != "${{ inputs.version }}" ]; then
|
|
30
|
+
echo "Version mismatch: input=${{ inputs.version }}, version.rb=$actual"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
echo "Releasing $actual"
|
|
34
|
+
- uses: ruby/setup-ruby@v1
|
|
35
|
+
with:
|
|
36
|
+
ruby-version: "3.4"
|
|
37
|
+
bundler-cache: true
|
|
38
|
+
# CI never pushes to git (read-only contents). `rake release`
|
|
39
|
+
# attempts `git push origin main` after publishing unless the
|
|
40
|
+
# version tag already exists locally — bundler then skips its
|
|
41
|
+
# git stage entirely (see asciichem-ruby PR 86 for the incident).
|
|
42
|
+
- name: Pre-create the release tag (skips rake's git stage)
|
|
43
|
+
run: git tag "v${{ inputs.version }}"
|
|
44
|
+
# Builds and pushes using the GitHub OIDC identity — no API keys.
|
|
45
|
+
- uses: rubygems/release-gem@v1
|
|
46
|
+
- name: Summary
|
|
47
|
+
run: |
|
|
48
|
+
echo "Released metanorma-plugin-asciichem ${{ inputs.version }} to RubyGems"
|
|
49
|
+
echo "https://rubygems.org/gems/metanorma-plugin-asciichem"
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require:
|
|
2
|
+
- rubocop
|
|
3
|
+
|
|
4
|
+
AllCops:
|
|
5
|
+
NewCops: enable
|
|
6
|
+
TargetRubyVersion: 3.3
|
|
7
|
+
Exclude:
|
|
8
|
+
- "pkg/**/*"
|
|
9
|
+
- "vendor/**/*"
|
|
10
|
+
- "coverage/**/*"
|
|
11
|
+
|
|
12
|
+
# The extension rewrites nodes inside Asciidoctor's visitor contract;
|
|
13
|
+
# methods mirror that contract and read top-to-bottom.
|
|
14
|
+
Metrics/MethodLength:
|
|
15
|
+
Max: 30
|
|
16
|
+
|
|
17
|
+
Metrics/BlockLength:
|
|
18
|
+
Exclude:
|
|
19
|
+
- "spec/**/*"
|
|
20
|
+
- "*.gemspec"
|
|
21
|
+
|
|
22
|
+
# Gem-name entry file (require "metanorma-plugin-asciichem"), the
|
|
23
|
+
# plugin-family convention (metanorma-plugin-lutaml does the same).
|
|
24
|
+
Naming/FileName:
|
|
25
|
+
Exclude:
|
|
26
|
+
- "lib/metanorma-plugin-asciichem.rb"
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Requires asciichem >= 0.29.2 (relaton-bib `< 3`), enabling
|
|
10
|
+
co-resolution with current metanorma gems; metanorma-standoc is
|
|
11
|
+
now a development dependency.
|
|
12
|
+
- End-to-end standoc compile wired into the suite
|
|
13
|
+
(`standoc_spec.rb`): `[chem]` → `<formula><stem type="MathML">`,
|
|
14
|
+
dataset bibitems verbatim in `<references normative="false">`,
|
|
15
|
+
InChIKey anchors.
|
|
16
|
+
- Citation anchors are derived from the emitted bibitem XML (works
|
|
17
|
+
under both relaton-bib major lines) instead of vendor object APIs.
|
|
18
|
+
|
|
19
|
+
## [0.1.0] - 2026-09-16
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- `[chem]` block: AsciiChem source parses to the semantic model and
|
|
24
|
+
renders as a MathML stem (`<formula><stem type="MathML">` in
|
|
25
|
+
Metanorma semantic XML). Block ids and titles carry over; invalid
|
|
26
|
+
sources log an error and keep the block as sourcecode.
|
|
27
|
+
- `chem:[]` inline macro (target and attribute forms) for inline
|
|
28
|
+
chemistry.
|
|
29
|
+
- Substance citations: `@cite`-annotated molecules in `[chem]` blocks
|
|
30
|
+
resolve through `AsciiChem::Citation` to dataset-type Relaton
|
|
31
|
+
bibitems, one per (source, substance), deduplicated, anchored by
|
|
32
|
+
InChIKey, appended as a `[bibliography]` section.
|
|
33
|
+
- `:asciichem-cache-dir:` document attribute for offline,
|
|
34
|
+
reproducible citation resolution.
|
|
35
|
+
|
|
36
|
+
## [0.1.0-renamed] - 2026-09-17
|
|
37
|
+
### Renamed
|
|
38
|
+
|
|
39
|
+
- The gem moves to the metanorma org as
|
|
40
|
+
`metanorma-plugin-asciichem` (from asciichem/metanorma-asciichem),
|
|
41
|
+
following the plugin-family convention (lutaml, glossarist,
|
|
42
|
+
plantuml). Namespace is now `Metanorma::Plugin::Asciichem` with
|
|
43
|
+
lib at `metanorma/plugin/asciichem`; the require-by-name entry is
|
|
44
|
+
`metanorma-plugin-asciichem`. Per the plugin contract the gem no
|
|
45
|
+
longer self-registers with Asciidoctor — metanorma-standoc
|
|
46
|
+
requires and registers it (as it does for lutaml/glossarist).
|
|
47
|
+
Not released under the previous name, so the rename is invisible
|
|
48
|
+
to users.
|
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Ribose Inc.
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
16
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
17
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
19
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# metanorma-plugin-asciichem
|
|
2
|
+
|
|
3
|
+
[AsciiChem](https://www.asciichem.org) chemistry for
|
|
4
|
+
[Metanorma](https://www.metanorma.org) documents: `[chem]` blocks and
|
|
5
|
+
`chem:[]` inline macros render chemistry as MathML, and
|
|
6
|
+
`@cite`-annotated molecules resolve into dataset-type Relaton
|
|
7
|
+
bibitems — one per (source, substance), anchored by InChIKey —
|
|
8
|
+
collected automatically into the document bibliography.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
gem install metanorma-plugin-asciichem
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The plugin follows the lutaml/glossarist contract: it exposes the
|
|
17
|
+
extension classes and does not register them itself — the host
|
|
18
|
+
does. [metanorma-standoc](https://github.com/metanorma/metanorma-standoc)
|
|
19
|
+
requires and registers this gem (see its converter), so with the
|
|
20
|
+
gem installed, `[chem]` and `chem:[]` are active in every Metanorma
|
|
21
|
+
compile. Outside Metanorma, register manually:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
require "metanorma-plugin-asciichem"
|
|
25
|
+
|
|
26
|
+
Asciidoctor::Extensions.register do
|
|
27
|
+
treeprocessor Metanorma::Plugin::Asciichem::Extension::ChemTreeprocessor
|
|
28
|
+
inline_macro Metanorma::Plugin::Asciichem::Extension::ChemInlineMacro, :chem
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### Chemistry blocks
|
|
35
|
+
|
|
36
|
+
```adoc
|
|
37
|
+
[chem]
|
|
38
|
+
----
|
|
39
|
+
2H_2 + O_2 -> 2H_2O
|
|
40
|
+
----
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The block's AsciiChem source parses into the semantic model and
|
|
44
|
+
renders as MathML. In the semantic XML this becomes
|
|
45
|
+
`<formula><stem type="MathML">…</stem></formula>` — the same shape
|
|
46
|
+
Metanorma uses for math, so every flavour renders it. Block ids and
|
|
47
|
+
titles carry over (`[chem#reaction,title="Hydrolysis"]`).
|
|
48
|
+
|
|
49
|
+
Invalid AsciiChem logs an error and keeps the original block as
|
|
50
|
+
sourcecode — the build stays reproducible, the problem stays visible.
|
|
51
|
+
|
|
52
|
+
### Inline chemistry
|
|
53
|
+
|
|
54
|
+
```adoc
|
|
55
|
+
Water is chem:H_2O[] in prose, or chem:[2H_2 + O_2 -> 2H_2O] for
|
|
56
|
+
sources with spaces.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Inline macros render; they do not contribute citations (inline
|
|
60
|
+
substitution runs after the document pass that collects them).
|
|
61
|
+
|
|
62
|
+
### Substance citations
|
|
63
|
+
|
|
64
|
+
Annotate a molecule with `@cite` naming the source to cite from; the
|
|
65
|
+
molecule's other identifiers say who to resolve it as:
|
|
66
|
+
|
|
67
|
+
```adoc
|
|
68
|
+
[chem]
|
|
69
|
+
----
|
|
70
|
+
CC(=O)OC1=CC=CC=C1C(=O)O @cas("50-78-2") @cite("pubchem")
|
|
71
|
+
----
|
|
72
|
+
|
|
73
|
+
The record is <<BSYNRYMUTXBXSQ-UHFFFAOYSA-N>>.
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The build resolves the molecule (CAS RN → PubChem) and appends a
|
|
77
|
+
`[bibliography]` section containing one `<bibitem type="dataset">`
|
|
78
|
+
per (source, substance):
|
|
79
|
+
|
|
80
|
+
- **Same substance cited twice** → one deduplicated entry per source.
|
|
81
|
+
- **Two sources for one substance** → two entries (PubChem and CAS
|
|
82
|
+
Common Chemistry are different documents), both anchored by the
|
|
83
|
+
same InChIKey — the cross-document join key.
|
|
84
|
+
- Every identifier the source returned rides along as a `keyword`, so
|
|
85
|
+
citations stay machine-checkable long after page numbers change.
|
|
86
|
+
|
|
87
|
+
Resolution goes through `AsciiChem::Citation.for_molecule` — the same
|
|
88
|
+
code path as the `asciichem cite` CLI — so the document pipeline and
|
|
89
|
+
the command line can never drift.
|
|
90
|
+
|
|
91
|
+
**Offline/reproducible builds.** Results come from the AsciiChem
|
|
92
|
+
resolver cache (user cache directory, TTL'd). Point the document at a
|
|
93
|
+
seeded cache to build without network:
|
|
94
|
+
|
|
95
|
+
```adoc
|
|
96
|
+
:asciichem-cache-dir: ./.asciichem-cache
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Seed it with `asciichem resolve --name aspirin`. Resolution failures
|
|
100
|
+
warn and emit no bibitem; they never break the build.
|
|
101
|
+
|
|
102
|
+
**Sources and licensing.** PubChem resolves by default. CAS Common
|
|
103
|
+
Chemistry (CC BY-NC 4.0) is opt-in:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
AsciiChem::Resolver.register(:common_chemistry,
|
|
107
|
+
AsciiChem::Resolver::CommonChemistry)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
A worked example document ships in `docs/example.adoc`.
|
|
111
|
+
|
|
112
|
+
## Compatibility
|
|
113
|
+
|
|
114
|
+
Runtime dependencies are `asciichem` (>= 0.29.2), `asciidoctor`, and
|
|
115
|
+
`nokogiri` — the extension operates at the Asciidoctor AST level and
|
|
116
|
+
needs no Metanorma gem to run.
|
|
117
|
+
|
|
118
|
+
The full metanorma-standoc compile is exercised by the suite:
|
|
119
|
+
`spec/metanorma/plugin/asciichem/standoc_spec.rb` compiles a document
|
|
120
|
+
through metanorma-standoc (dev dependency) and asserts the semantic
|
|
121
|
+
XML — `[chem]` → `<formula><stem type="MathML">`, bibitems verbatim
|
|
122
|
+
inside `<references normative="false">` via the
|
|
123
|
+
`formats="metanorma"` passthrough, InChIKey anchors. That became
|
|
124
|
+
possible with asciichem 0.29.2, which widened its relaton-bib
|
|
125
|
+
constraint to `< 3` so asciichem and current metanorma gems
|
|
126
|
+
co-resolve in one bundle. Citation anchors read the emitted wire XML,
|
|
127
|
+
so both relaton-bib major lines (1.x and 2.x keyword nestings) work.
|
|
128
|
+
|
|
129
|
+
## Development
|
|
130
|
+
|
|
131
|
+
```sh
|
|
132
|
+
bundle install
|
|
133
|
+
bundle exec rspec # 16 examples, network-free
|
|
134
|
+
bundle exec rubocop
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Spec fixtures are real PubChem / CAS Common Chemistry payloads; the
|
|
138
|
+
fetchers are Structs shaped like the asciidoctor HTTP surface — no
|
|
139
|
+
network, no test doubles.
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
BSD-2-Clause.
|
data/Rakefile
ADDED
data/docs/example.adoc
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
= Aspirin monograph (worked example)
|
|
2
|
+
|
|
3
|
+
This document exercises metanorma-asciichem end to end: chemistry
|
|
4
|
+
rendered from `[chem]` blocks, an inline formula, and a
|
|
5
|
+
`@cite`-annotated molecule whose PubChem record becomes a
|
|
6
|
+
dataset-type bibitem — anchored by InChIKey, so the `<<BSYNRYMUTXBXSQ-UHFFFAOYSA-N>>`
|
|
7
|
+
cross-reference in the body lands on the emitted entry.
|
|
8
|
+
|
|
9
|
+
Seed the resolver cache first for an offline, reproducible build:
|
|
10
|
+
|
|
11
|
+
asciichem resolve --name aspirin
|
|
12
|
+
|
|
13
|
+
then set `:asciichem-cache-dir:` to your cache directory (or omit the
|
|
14
|
+
attribute to resolve live).
|
|
15
|
+
|
|
16
|
+
= Aspirin monograph
|
|
17
|
+
Author
|
|
18
|
+
:asciichem-cache-dir: ~/.cache/asciichem
|
|
19
|
+
|
|
20
|
+
== Synthesis
|
|
21
|
+
|
|
22
|
+
Aspirin is synthesised from salicylic acid. The overall hydrolysis
|
|
23
|
+
equilibrium:
|
|
24
|
+
|
|
25
|
+
[chem]
|
|
26
|
+
----
|
|
27
|
+
2H_2 + O_2 -> 2H_2O
|
|
28
|
+
----
|
|
29
|
+
|
|
30
|
+
Water, inline: chem:H_2O[], appears on both sides of the mechanism.
|
|
31
|
+
|
|
32
|
+
== Structure and identity
|
|
33
|
+
|
|
34
|
+
[chem]
|
|
35
|
+
----
|
|
36
|
+
CC(=O)OC1=CC=CC=C1C(=O)O @cas("50-78-2") @cite("pubchem")
|
|
37
|
+
----
|
|
38
|
+
|
|
39
|
+
The substance record is <<BSYNRYMUTXBXSQ-UHFFFAOYSA-N>>. Citing a
|
|
40
|
+
second source for the same substance (after registering the opt-in
|
|
41
|
+
adapter) yields a second, distinct entry — one bibitem per
|
|
42
|
+
(substance, source).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
|
|
5
|
+
module Metanorma
|
|
6
|
+
module Plugin
|
|
7
|
+
module Asciichem
|
|
8
|
+
# Bibliography emission (TODO.impl 49; TODO.v2 08 section 3):
|
|
9
|
+
# `@cite`-annotated molecules resolve to dataset-type Relaton
|
|
10
|
+
# bibitems - one per (source, substance), deduplicated, anchored by
|
|
11
|
+
# InChIKey - collected into a [bibliography] section appended to
|
|
12
|
+
# the document. Resolution goes through AsciiChem::Citation (the
|
|
13
|
+
# same code path as the CLI), so the document pipeline and the
|
|
14
|
+
# `asciichem cite` command can never drift.
|
|
15
|
+
module Citations
|
|
16
|
+
include Asciidoctor::Logging
|
|
17
|
+
|
|
18
|
+
BIBLIOGRAPHY_TITLE = 'Bibliography'
|
|
19
|
+
INCHIKEY_PREFIX = 'inchikey='
|
|
20
|
+
ANCHOR_SANITIZE = /[^A-Za-z0-9-]/
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# molecules: parsed AsciiChem model nodes (Model::Molecule) as
|
|
25
|
+
# harvested from [chem] blocks. cache:/fetch: pass through to
|
|
26
|
+
# the resolver (same knobs as `asciichem resolve --refresh` and
|
|
27
|
+
# the asciichem CLI specs). Appends nothing when none of them
|
|
28
|
+
# carries @cite or none resolves (warns instead - builds stay
|
|
29
|
+
# green and reproducible via the resolver cache).
|
|
30
|
+
def append_bibliography(document, molecules, cache: nil, fetch: nil)
|
|
31
|
+
entries = resolve(molecules, cache: cache, fetch: fetch)
|
|
32
|
+
return if entries.empty?
|
|
33
|
+
|
|
34
|
+
document << bibliography_section(document, entries)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def resolve(molecules, cache:, fetch:)
|
|
38
|
+
entries = {}
|
|
39
|
+
molecules.each do |molecule|
|
|
40
|
+
# First-wins: a later identical (source, substance) keeps the
|
|
41
|
+
# first-mention entry and order.
|
|
42
|
+
entries.merge!(entries_for(molecule, cache: cache, fetch: fetch)) do |_key, first, _last|
|
|
43
|
+
first
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
entries.values
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def entries_for(molecule, cache:, fetch:)
|
|
50
|
+
AsciiChem::Citation.for_molecule(molecule, cache: cache, fetch: fetch)
|
|
51
|
+
.map { |source, bibitem| entry(source, bibitem) }
|
|
52
|
+
.reduce({}, :merge)
|
|
53
|
+
rescue AsciiChem::Error => e
|
|
54
|
+
logger.warn(message_with_context(
|
|
55
|
+
"cannot build substance citation: #{e.message} " \
|
|
56
|
+
'(no bibitem emitted)',
|
|
57
|
+
source_location: nil
|
|
58
|
+
))
|
|
59
|
+
{}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# [source, bibitem] -> { [source, anchor] => [anchor, xml] }:
|
|
63
|
+
# the compound key dedupes same (source, substance) while the
|
|
64
|
+
# insertion-ordered hash preserves first-mention order.
|
|
65
|
+
def entry(source, bibitem)
|
|
66
|
+
xml = bibitem.to_xml
|
|
67
|
+
anchor = anchor_for(xml) || fallback_anchor(source, xml)
|
|
68
|
+
{ [source, anchor] => [anchor, with_anchor(xml, anchor)] }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The InChIKey is the cross-source join key: every bibitem for
|
|
72
|
+
# the same substance carries the same keyword, so two spellings
|
|
73
|
+
# of one substance (CAS RN vs name) collapse onto one anchor.
|
|
74
|
+
# Anchors read the emitted wire XML, not vendor object APIs:
|
|
75
|
+
# relaton-bib 1 writes <keyword>inchikey=...</keyword> and
|
|
76
|
+
# relaton-bib 2 nests it (<keyword><vocab>...</vocab></keyword>)
|
|
77
|
+
# — one pattern serves both.
|
|
78
|
+
def anchor_for(xml)
|
|
79
|
+
xml[/#{INCHIKEY_PREFIX}([A-Za-z0-9-]+)/, 1]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def fallback_anchor(source, xml)
|
|
83
|
+
docid = xml[%r{<docidentifier[^>]*>([^<]+)</docidentifier>}, 1]
|
|
84
|
+
slug = docid ? docid.gsub(ANCHOR_SANITIZE, '') : 'substance'
|
|
85
|
+
"#{source}-#{slug}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Relaton emits <bibitem id="..."> with an id derived from the
|
|
89
|
+
# docidentifier; the anchor (InChIKey) replaces it so document
|
|
90
|
+
# cross-references <<INCHIKEY>> land on the entry.
|
|
91
|
+
def with_anchor(xml, anchor)
|
|
92
|
+
root = Nokogiri::XML(xml).root
|
|
93
|
+
root['id'] = anchor
|
|
94
|
+
root.to_xml
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def bibliography_section(document, entries)
|
|
98
|
+
section = Asciidoctor::Section.new(document, 1, false)
|
|
99
|
+
section.style = 'bibliography'
|
|
100
|
+
section.title = BIBLIOGRAPHY_TITLE
|
|
101
|
+
entries.each { |anchor, xml| section << pass_block(section, anchor, xml) }
|
|
102
|
+
section
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# A [pass] block's content reaches the semantic XML unwrapped
|
|
106
|
+
# (passthrough formats="metanorma"), placing the bibitem
|
|
107
|
+
# verbatim inside <references>.
|
|
108
|
+
def pass_block(section, _anchor, xml)
|
|
109
|
+
Asciidoctor::Block.new(section, :pass, content_model: :raw,
|
|
110
|
+
source: xml,
|
|
111
|
+
attributes: { 'style' => 'pass' })
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Asciichem
|
|
6
|
+
# Asciidoctor extension surface (TODO.impl 49): one treeprocessor
|
|
7
|
+
# for block-level chemistry + citation collection, one inline macro
|
|
8
|
+
# for inline chemistry.
|
|
9
|
+
#
|
|
10
|
+
# Block form (citations are collected here - inline macros resolve
|
|
11
|
+
# at substitution time, after treeprocessors have run):
|
|
12
|
+
#
|
|
13
|
+
# [chem]
|
|
14
|
+
# ----
|
|
15
|
+
# 2H_2 + O_2 -> 2H_2O
|
|
16
|
+
# ----
|
|
17
|
+
#
|
|
18
|
+
# Inline form (render-only):
|
|
19
|
+
#
|
|
20
|
+
# chem:H_2O[] or chem:[2H_2 + O_2 -> 2H_2O]
|
|
21
|
+
#
|
|
22
|
+
module Extension
|
|
23
|
+
# Rewrites every style-"chem" block into a stem block carrying
|
|
24
|
+
# the formula's MathML, then appends the substance bibliography
|
|
25
|
+
# built from the harvested molecules.
|
|
26
|
+
#
|
|
27
|
+
# Document attributes:
|
|
28
|
+
# asciichem-cache-dir - resolve citations from this resolver
|
|
29
|
+
# cache directory (reproducible, offline
|
|
30
|
+
# builds; seed it with `asciichem
|
|
31
|
+
# resolve`).
|
|
32
|
+
class ChemTreeprocessor < Asciidoctor::Extensions::Treeprocessor
|
|
33
|
+
include Asciidoctor::Logging
|
|
34
|
+
|
|
35
|
+
def process(document)
|
|
36
|
+
molecules = []
|
|
37
|
+
document.find_by(style: 'chem').each do |node|
|
|
38
|
+
next unless node.is_a?(Asciidoctor::Block)
|
|
39
|
+
|
|
40
|
+
molecules.concat(rewrite(node))
|
|
41
|
+
end
|
|
42
|
+
Citations.append_bibliography(document, molecules,
|
|
43
|
+
cache: cache_for(document))
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def cache_for(document)
|
|
50
|
+
dir = document.attr('asciichem-cache-dir')
|
|
51
|
+
dir && AsciiChem::Resolver::Cache.new(dir: dir)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Parses the block source once; replaces the node with a stem
|
|
55
|
+
# block of its MathML. Returns the formula's molecule nodes
|
|
56
|
+
# (only molecules can carry @cite annotations), or [] when the
|
|
57
|
+
# block does not parse.
|
|
58
|
+
def rewrite(node)
|
|
59
|
+
formula = parse_or_log(node, '[chem] block', '(block kept as sourcecode)')
|
|
60
|
+
return [] unless formula
|
|
61
|
+
|
|
62
|
+
replace_with_stem(node, formula)
|
|
63
|
+
formula.nodes.grep(AsciiChem::Model::Molecule)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Parses, or logs the failure and returns nil (the caller
|
|
67
|
+
# keeps the offending block visible instead of crashing the
|
|
68
|
+
# build).
|
|
69
|
+
def parse_or_log(node, where, remedy)
|
|
70
|
+
Renderer.parse(node.lines.join("\n"))
|
|
71
|
+
rescue AsciiChem::ParseError => e
|
|
72
|
+
logger.error(message_with_context(
|
|
73
|
+
"invalid AsciiChem in #{where}: #{e.message} #{remedy}",
|
|
74
|
+
source_location: node.source_location
|
|
75
|
+
))
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def replace_with_stem(node, formula)
|
|
80
|
+
stem = Asciidoctor::Block.new(
|
|
81
|
+
node.parent, :stem,
|
|
82
|
+
content_model: :verbatim, source: Renderer.mathml(formula),
|
|
83
|
+
# Standoc reads node.lines (raw); :default subs keep plain
|
|
84
|
+
# Asciidoctor/HTML5 previews working (escaped text).
|
|
85
|
+
subs: :default
|
|
86
|
+
)
|
|
87
|
+
# Style "asciimath" matches core stem blocks; standoc still
|
|
88
|
+
# types the stem MathML from the <math> content itself.
|
|
89
|
+
stem.style = 'asciimath'
|
|
90
|
+
stem.id = node.id if node.id
|
|
91
|
+
stem.title = node.title if node.title
|
|
92
|
+
siblings = node.parent.blocks
|
|
93
|
+
siblings[siblings.index(node)] = stem
|
|
94
|
+
stem
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Inline chemistry: chem:H_2O[] (target form, no spaces) or
|
|
99
|
+
# chem:[source] (attribute form, any single-line source).
|
|
100
|
+
# Render-only - inline macros resolve after treeprocessors run,
|
|
101
|
+
# so they cannot contribute bibliography entries.
|
|
102
|
+
class ChemInlineMacro < Asciidoctor::Extensions::InlineMacroProcessor
|
|
103
|
+
include Asciidoctor::Logging
|
|
104
|
+
|
|
105
|
+
use_dsl
|
|
106
|
+
name_positional_attributes 'text'
|
|
107
|
+
|
|
108
|
+
def process(parent, target, attrs)
|
|
109
|
+
source = target.empty? ? attrs['text'] : target
|
|
110
|
+
return empty_inline(parent) if source.nil? || source.empty?
|
|
111
|
+
|
|
112
|
+
formula = Renderer.parse(source)
|
|
113
|
+
# Context :quoted with type :asciimath is the shape core
|
|
114
|
+
# stem:[] macros produce; converters dispatch inline_quoted
|
|
115
|
+
# and type the stem MathML from the <math> content. (A
|
|
116
|
+
# :asciimath-context node dispatches inline_asciimath,
|
|
117
|
+
# which metanorma converters do not define.)
|
|
118
|
+
create_inline(parent, :quoted, Renderer.mathml(formula), type: :asciimath)
|
|
119
|
+
rescue AsciiChem::ParseError => e
|
|
120
|
+
logger.error(message_with_context(
|
|
121
|
+
"invalid AsciiChem in chem: macro: #{e.message} " \
|
|
122
|
+
'(rendered verbatim)',
|
|
123
|
+
source_location: parent.source_location
|
|
124
|
+
))
|
|
125
|
+
create_inline(parent, :quoted, source, type: :monospaced)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private
|
|
129
|
+
|
|
130
|
+
def empty_inline(parent)
|
|
131
|
+
logger.error(message_with_context(
|
|
132
|
+
'empty chem: macro - provide source ' \
|
|
133
|
+
'(chem:H_2O[] or chem:[...])',
|
|
134
|
+
source_location: parent.source_location
|
|
135
|
+
))
|
|
136
|
+
create_inline(parent, :quoted, '')
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Metanorma
|
|
4
|
+
module Plugin
|
|
5
|
+
module Asciichem
|
|
6
|
+
# The parse/render seam between AsciiDoc and AsciiChem. Parsing is
|
|
7
|
+
# separated from rendering so the treeprocessor can parse once and
|
|
8
|
+
# both render MathML and harvest citation molecules from the same
|
|
9
|
+
# semantic model.
|
|
10
|
+
module Renderer
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
# Parses AsciiChem source into the semantic model. Raises
|
|
14
|
+
# AsciiChem::ParseError on invalid input.
|
|
15
|
+
def parse(source)
|
|
16
|
+
AsciiChem.parse(source)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Renders the parsed model to a MathML <math> document. Standoc's
|
|
20
|
+
# stem handling detects the <math> root and types the stem
|
|
21
|
+
# accordingly (type="MathML"), so no converter-side support is
|
|
22
|
+
# needed. The formatter's XML declaration is stripped: embedded
|
|
23
|
+
# MathML is a fragment inside the document, not a document.
|
|
24
|
+
def mathml(formula)
|
|
25
|
+
formula.to_mathml.sub(/\A<\?xml[^>]*>\s*/, '')
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'asciidoctor' unless defined?(Asciidoctor)
|
|
4
|
+
require 'asciichem'
|
|
5
|
+
|
|
6
|
+
module Metanorma
|
|
7
|
+
module Plugin
|
|
8
|
+
# AsciiChem integration for Metanorma (TODO.impl 49; TODO.v2 08
|
|
9
|
+
# section 3): chemistry in AsciiDoc documents parses to MathML for
|
|
10
|
+
# rendering, and `@cite`-annotated molecules resolve to dataset-type
|
|
11
|
+
# Relaton bibitems collected into the document bibliography - one
|
|
12
|
+
# bibitem per (source, substance), anchored by InChIKey.
|
|
13
|
+
#
|
|
14
|
+
# Following the plugin contract (lutaml, glossarist), this gem
|
|
15
|
+
# exposes the extension classes and does NOT register them with
|
|
16
|
+
# Asciidoctor itself; the host converter registers what it needs:
|
|
17
|
+
#
|
|
18
|
+
# Asciidoctor::Extensions.register do
|
|
19
|
+
# treeprocessor Metanorma::Plugin::Asciichem::Extension::ChemTreeprocessor
|
|
20
|
+
# inline_macro Metanorma::Plugin::Asciichem::Extension::ChemInlineMacro, :chem
|
|
21
|
+
# end
|
|
22
|
+
module Asciichem
|
|
23
|
+
autoload :Citations, 'metanorma/plugin/asciichem/citations'
|
|
24
|
+
autoload :Extension, 'metanorma/plugin/asciichem/extension'
|
|
25
|
+
autoload :Renderer, 'metanorma/plugin/asciichem/renderer'
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/metanorma/plugin/asciichem/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'metanorma-plugin-asciichem'
|
|
7
|
+
spec.version = Metanorma::Plugin::Asciichem::VERSION
|
|
8
|
+
spec.authors = ['Ribose Inc.']
|
|
9
|
+
spec.email = ['open.source@ribose.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'AsciiChem chemistry blocks and substance citations for Metanorma documents.'
|
|
12
|
+
spec.description = 'Adds [chem] blocks and chem:[] inline macros to Metanorma ' \
|
|
13
|
+
'AsciiDoc: chemistry written in AsciiChem parses to MathML ' \
|
|
14
|
+
'for rendering, and @cite-annotated molecules resolve to ' \
|
|
15
|
+
'dataset-type Relaton bibitems - one per (source, substance), ' \
|
|
16
|
+
'anchored by InChIKey - automatically collected into the ' \
|
|
17
|
+
"document's bibliography."
|
|
18
|
+
|
|
19
|
+
spec.homepage = 'https://www.asciichem.org'
|
|
20
|
+
spec.license = 'BSD-2-Clause'
|
|
21
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.3.0')
|
|
22
|
+
|
|
23
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
24
|
+
spec.metadata['source_code_uri'] = 'https://github.com/metanorma/metanorma-plugin-asciichem'
|
|
25
|
+
spec.metadata['changelog_uri'] = 'https://github.com/metanorma/metanorma-plugin-asciichem/blob/main/CHANGELOG.md'
|
|
26
|
+
spec.metadata['docs_uri'] = 'https://www.asciichem.org'
|
|
27
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
28
|
+
|
|
29
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
30
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
31
|
+
f.match(%r{^(test|spec|features)/})
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
spec.require_paths = ['lib']
|
|
35
|
+
|
|
36
|
+
spec.add_dependency 'asciichem', '>= 0.29.2'
|
|
37
|
+
spec.add_dependency 'asciidoctor', '~> 2.0'
|
|
38
|
+
spec.add_dependency 'nokogiri', '~> 1.16'
|
|
39
|
+
|
|
40
|
+
# metanorma-standoc lives in the Gemfile dev group: it is the
|
|
41
|
+
# integration-test backend (spec/metanorma/plugin/asciichem/
|
|
42
|
+
# standoc_spec.rb), not a runtime requirement — following the
|
|
43
|
+
# plugin contract, metanorma-standoc requires and registers this
|
|
44
|
+
# gem, like it does for metanorma-plugin-lutaml.
|
|
45
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: metanorma-plugin-asciichem
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ribose Inc.
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: asciichem
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.29.2
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.29.2
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: asciidoctor
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: nokogiri
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.16'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.16'
|
|
54
|
+
description: 'Adds [chem] blocks and chem:[] inline macros to Metanorma AsciiDoc:
|
|
55
|
+
chemistry written in AsciiChem parses to MathML for rendering, and @cite-annotated
|
|
56
|
+
molecules resolve to dataset-type Relaton bibitems - one per (source, substance),
|
|
57
|
+
anchored by InChIKey - automatically collected into the document''s bibliography.'
|
|
58
|
+
email:
|
|
59
|
+
- open.source@ribose.com
|
|
60
|
+
executables: []
|
|
61
|
+
extensions: []
|
|
62
|
+
extra_rdoc_files: []
|
|
63
|
+
files:
|
|
64
|
+
- ".github/workflows/ci.yml"
|
|
65
|
+
- ".github/workflows/release.yml"
|
|
66
|
+
- ".gitignore"
|
|
67
|
+
- ".rubocop.yml"
|
|
68
|
+
- CHANGELOG.md
|
|
69
|
+
- Gemfile
|
|
70
|
+
- LICENSE
|
|
71
|
+
- README.md
|
|
72
|
+
- Rakefile
|
|
73
|
+
- docs/example.adoc
|
|
74
|
+
- lib/metanorma-plugin-asciichem.rb
|
|
75
|
+
- lib/metanorma/plugin/asciichem.rb
|
|
76
|
+
- lib/metanorma/plugin/asciichem/citations.rb
|
|
77
|
+
- lib/metanorma/plugin/asciichem/extension.rb
|
|
78
|
+
- lib/metanorma/plugin/asciichem/renderer.rb
|
|
79
|
+
- lib/metanorma/plugin/asciichem/version.rb
|
|
80
|
+
- metanorma-plugin-asciichem.gemspec
|
|
81
|
+
homepage: https://www.asciichem.org
|
|
82
|
+
licenses:
|
|
83
|
+
- BSD-2-Clause
|
|
84
|
+
metadata:
|
|
85
|
+
homepage_uri: https://www.asciichem.org
|
|
86
|
+
source_code_uri: https://github.com/metanorma/metanorma-plugin-asciichem
|
|
87
|
+
changelog_uri: https://github.com/metanorma/metanorma-plugin-asciichem/blob/main/CHANGELOG.md
|
|
88
|
+
docs_uri: https://www.asciichem.org
|
|
89
|
+
rubygems_mfa_required: 'true'
|
|
90
|
+
rdoc_options: []
|
|
91
|
+
require_paths:
|
|
92
|
+
- lib
|
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
94
|
+
requirements:
|
|
95
|
+
- - ">="
|
|
96
|
+
- !ruby/object:Gem::Version
|
|
97
|
+
version: 3.3.0
|
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '0'
|
|
103
|
+
requirements: []
|
|
104
|
+
rubygems_version: 3.6.9
|
|
105
|
+
specification_version: 4
|
|
106
|
+
summary: AsciiChem chemistry blocks and substance citations for Metanorma documents.
|
|
107
|
+
test_files: []
|