asciichem 0.5.1 → 0.19.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/release.yml +52 -0
- data/ARCHITECTURE.adoc +153 -0
- data/CHANGELOG.md +363 -5
- data/asciichem.gemspec +7 -4
- data/benchmarks/benchmark.rb +6 -1
- data/lib/asciichem/cli.rb +23 -1
- data/lib/asciichem/cml/conditions_extensions.rb +132 -0
- data/lib/asciichem/cml/extensions/atom_attributes.rb +192 -0
- data/lib/asciichem/cml/extensions/top_level.rb +186 -0
- data/lib/asciichem/cml/extensions.rb +32 -344
- data/lib/asciichem/cml/group_extensions.rb +10 -20
- data/lib/asciichem/cml/metadata_extensions.rb +145 -0
- data/lib/asciichem/cml/opaque_extensions.rb +144 -0
- data/lib/asciichem/cml/translator.rb +46 -144
- data/lib/asciichem/cml.rb +14 -0
- data/lib/asciichem/formatter/html.rb +117 -0
- data/lib/asciichem/formatter/latex.rb +117 -0
- data/lib/asciichem/formatter/mathml.rb +134 -0
- data/lib/asciichem/formatter/structural_svg.rb +38 -0
- data/lib/asciichem/formatter/svg.rb +38 -6
- data/lib/asciichem/formatter/text.rb +14 -10
- data/lib/asciichem/grammar.rb +27 -1
- data/lib/asciichem/identifiers/base.rb +25 -0
- data/lib/asciichem/identifiers/cas.rb +56 -0
- data/lib/asciichem/identifiers/inchi.rb +104 -0
- data/lib/asciichem/identifiers/inchikey.rb +33 -0
- data/lib/asciichem/identifiers/smiles.rb +147 -0
- data/lib/asciichem/identifiers.rb +66 -0
- data/lib/asciichem/linter/charge_balance_check.rb +117 -0
- data/lib/asciichem/linter/crystal_sanity_check.rb +90 -0
- data/lib/asciichem/linter/identifier_consistency_check.rb +100 -0
- data/lib/asciichem/linter/identifier_format_check.rb +48 -0
- data/lib/asciichem/linter/spectrum_peak_check.rb +72 -0
- data/lib/asciichem/linter/zmatrix_reference_check.rb +89 -0
- data/lib/asciichem/linter.rb +13 -2
- data/lib/asciichem/model/atom.rb +31 -0
- data/lib/asciichem/model/bond.rb +22 -0
- data/lib/asciichem/model/calculation.rb +4 -0
- data/lib/asciichem/model/crystal.rb +35 -6
- data/lib/asciichem/model/group.rb +30 -12
- data/lib/asciichem/model/mechanism.rb +3 -0
- data/lib/asciichem/model/molecule.rb +134 -0
- data/lib/asciichem/model/node.rb +8 -0
- data/lib/asciichem/model/opaque_cml.rb +34 -0
- data/lib/asciichem/model/reaction.rb +19 -4
- data/lib/asciichem/model/spectrum.rb +5 -0
- data/lib/asciichem/model.rb +1 -0
- data/lib/asciichem/model_adapter/from_canonical.rb +139 -24
- data/lib/asciichem/model_adapter/to_canonical.rb +211 -28
- data/lib/asciichem/parser.rb +57 -5
- data/lib/asciichem/periodic_table.rb +30 -1
- data/lib/asciichem/transform.rb +76 -47
- data/lib/asciichem/version.rb +1 -1
- data/lib/asciichem.rb +1 -0
- metadata +28 -12
- data/TODO.beyond-formulas/01-crystallography.md +0 -69
- data/TODO.beyond-formulas/02-spectroscopy.md +0 -39
- data/TODO.beyond-formulas/03-compchem.md +0 -31
- data/TODO.beyond-formulas/04-structural.md +0 -48
- data/TODO.beyond-formulas/05-mechanisms.md +0 -40
- data/TODO.beyond-formulas/README.md +0 -69
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 49f3af36729edc3293bb97099d74298533a509ae26aa3c14151a7cef743f654f
|
|
4
|
+
data.tar.gz: 24f5d0778e9f7dce477423b27b1ec5ebcab08472452389f126473234e8500a19
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 599beaeb33d3bf776076632c83d2c92c16a08571088f3bb272163f61a9082e250af150b3300a80821fc3bb6ab5ffa8d65cc8b5007b4860f0dace0a94b70aaf83
|
|
7
|
+
data.tar.gz: 6fbc557626b9767110bc142c54ae6858b644d563af73f431bd48d8dbd7dabdfeeb79b7ecbb498474d791b35f1717b9ae28689ee1630f352fa48a3e0abcf966a6
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
version:
|
|
7
|
+
description: "Version to release (must match lib/asciichem/version.rb)"
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: release
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
packages: write
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
ref: main
|
|
22
|
+
- name: Verify version matches input
|
|
23
|
+
run: |
|
|
24
|
+
actual=$(ruby -e 'require "./lib/asciichem/version"; print AsciiChem::VERSION')
|
|
25
|
+
if [ "$actual" != "${{ inputs.version }}" ]; then
|
|
26
|
+
echo "Version mismatch: input=${{ inputs.version }}, lib/asciichem/version.rb=$actual"
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
echo "Releasing $actual"
|
|
30
|
+
- uses: ruby/setup-ruby@v1
|
|
31
|
+
with:
|
|
32
|
+
ruby-version: "3.4"
|
|
33
|
+
bundler-cache: true
|
|
34
|
+
- name: Build gem
|
|
35
|
+
run: bundle exec rake build
|
|
36
|
+
- name: Set up RubyGems credentials
|
|
37
|
+
env:
|
|
38
|
+
RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
|
39
|
+
run: |
|
|
40
|
+
mkdir -p $HOME/.gem
|
|
41
|
+
cat > $HOME/.gem/credentials <<EOF
|
|
42
|
+
---
|
|
43
|
+
:rubygems_api_key: ${RUBYGEMS_API_KEY}
|
|
44
|
+
EOF
|
|
45
|
+
chmod 0600 $HOME/.gem/credentials
|
|
46
|
+
- name: Push to RubyGems
|
|
47
|
+
run: |
|
|
48
|
+
gem push pkg/asciichem-${{ inputs.version }}.gem
|
|
49
|
+
- name: Summary
|
|
50
|
+
run: |
|
|
51
|
+
echo "Released asciichem ${{ inputs.version }} to RubyGems"
|
|
52
|
+
echo "https://rubygems.org/gems/asciichem"
|
data/ARCHITECTURE.adoc
CHANGED
|
@@ -237,3 +237,156 @@ Plurimath handles it.
|
|
|
237
237
|
* No hand-rolled model serialisation. The model is in-memory only;
|
|
238
238
|
rendering to MathML / HTML / etc. is one-way output via Nokogiri or
|
|
239
239
|
string building, not `(de)serialisation`.
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
== Native CML wire (v0.9-v0.11)
|
|
243
|
+
|
|
244
|
+
All AsciiChem model classes have native CML wire representations
|
|
245
|
+
(chemicalml 0.3.0+). The mapping is one-way in production: the
|
|
246
|
+
adapter produces native wire objects; the CML Translator enriches
|
|
247
|
+
with aci: extensions only for backwards-compat fallback.
|
|
248
|
+
|
|
249
|
+
[cols="1,1,2", options="header"]
|
|
250
|
+
|===
|
|
251
|
+
| Model class | Wire container | Native child elements
|
|
252
|
+
| Crystal | | with cells, , (fractional coords)
|
|
253
|
+
| Spectrum | | with of (xValue/yValue/yMultiplicity/title)
|
|
254
|
+
| ZMatrix | | (text content; structural length/angle/torsion children are future work)
|
|
255
|
+
| Calculation | | of with (value, units)
|
|
256
|
+
| Mechanism | | (text via title; structural step list is future work)
|
|
257
|
+
| Reaction conditions | | with (above/below)
|
|
258
|
+
|===
|
|
259
|
+
|
|
260
|
+
== Extension channels (six parallel modules)
|
|
261
|
+
|
|
262
|
+
Each channel is a MECE module under :
|
|
263
|
+
|
|
264
|
+
* — per-atom aci: attributes (FIELDS registry)
|
|
265
|
+
* — top-level constructs via aci: elements (HANDLERS registry)
|
|
266
|
+
* — inside molecules (BRACKETS registry on Group)
|
|
267
|
+
* — preserves unknown top-level elements verbatim
|
|
268
|
+
* — per-molecule key/value metadata (aci:meta-*)
|
|
269
|
+
* — now mostly legacy; native <conditionList> preferred
|
|
270
|
+
|
|
271
|
+
The is a thin orchestrator (~80 lines) that calls each
|
|
272
|
+
module's // in pipeline order.
|
|
273
|
+
|
|
274
|
+
== Model-owned wire codes
|
|
275
|
+
|
|
276
|
+
Wire-format mappings live on the model classes as the single source
|
|
277
|
+
of truth:
|
|
278
|
+
|
|
279
|
+
* / /
|
|
280
|
+
* /
|
|
281
|
+
* /
|
|
282
|
+
* / / /
|
|
283
|
+
* (shared by adapter and extension modules)
|
|
284
|
+
|
|
285
|
+
Adding a new bond kind, bracket kind, arrow kind, or cell parameter
|
|
286
|
+
is one entry on the model; both adapter directions pick it up.
|
|
287
|
+
|
|
288
|
+
== Linter framework
|
|
289
|
+
|
|
290
|
+
Ten registered checks (auto-load via ):
|
|
291
|
+
|
|
292
|
+
* — stoichiometric atom conservation
|
|
293
|
+
* — charge conservation (v0.7+)
|
|
294
|
+
* — group bracket consistency
|
|
295
|
+
* — cell lengths/angles/fractional coords (v0.6+)
|
|
296
|
+
* — forward references and geometry (v0.6+)
|
|
297
|
+
* — peak data validation (v0.6+)
|
|
298
|
+
* — periodic-table membership
|
|
299
|
+
* — isotope mass >= atomic number
|
|
300
|
+
* — unmatched ring-closure digits
|
|
301
|
+
* — bond order + charge <= max valence
|
|
302
|
+
|
|
303
|
+
sorts by severity then deduplicates.
|
|
304
|
+
|
|
305
|
+
== Convenience methods on Molecule
|
|
306
|
+
|
|
307
|
+
* — total atoms (recurses through groups/nested molecules)
|
|
308
|
+
* — Hill-system canonical formula (C first, H second, alpha)
|
|
309
|
+
* — sum of atomic_mass × count (v0.13+)
|
|
310
|
+
|
|
311
|
+
== Native CML wire (v0.9-v0.11)
|
|
312
|
+
|
|
313
|
+
All AsciiChem model classes have native CML wire representations
|
|
314
|
+
(chemicalml 0.3.0+). The adapter produces native wire objects;
|
|
315
|
+
the CML Translator enriches with aci: extensions only as a
|
|
316
|
+
backwards-compat fallback.
|
|
317
|
+
|
|
318
|
+
|===
|
|
319
|
+
| Model class | Wire container | Native child elements
|
|
320
|
+
|
|
321
|
+
| Crystal
|
|
322
|
+
| `<molecule>`
|
|
323
|
+
| `<crystal>` with `<scalar>` cells, `<symmetry>`, `<atomArray>` (fractional coords)
|
|
324
|
+
|
|
325
|
+
| Spectrum
|
|
326
|
+
| `<molecule>`
|
|
327
|
+
| `<spectrum>` with `<peakList>` of `<peak>` (xValue/yValue/yMultiplicity/title)
|
|
328
|
+
|
|
329
|
+
| ZMatrix
|
|
330
|
+
| `<molecule>`
|
|
331
|
+
| `<zMatrix>` (text content; structural length/Angle/Torsion children are future work)
|
|
332
|
+
|
|
333
|
+
| Calculation
|
|
334
|
+
| `<molecule>`
|
|
335
|
+
| `<propertyList>` of `<property>` with `<scalar>` (value, units)
|
|
336
|
+
|
|
337
|
+
| Mechanism
|
|
338
|
+
| `<reaction>`
|
|
339
|
+
| `<mechanism>` (text via title; structural step list is future work)
|
|
340
|
+
|
|
341
|
+
| Reaction conditions
|
|
342
|
+
| `<reaction>`
|
|
343
|
+
| `<conditionList>` with `<scalar>` (above/below)
|
|
344
|
+
|===
|
|
345
|
+
|
|
346
|
+
== Six extension channels (MECE modules under Cml)
|
|
347
|
+
|
|
348
|
+
* `Extensions::AtomAttributes` — per-atom aci: attributes (FIELDS registry)
|
|
349
|
+
* `Extensions::TopLevel` — top-level constructs via aci: elements (HANDLERS registry)
|
|
350
|
+
* `GroupExtensions` — `<aci:group>` inside molecules
|
|
351
|
+
* `OpaqueExtensions` — preserves unknown top-level elements verbatim
|
|
352
|
+
* `MetadataExtensions` — per-molecule key/value metadata (aci:meta-*)
|
|
353
|
+
* `ConditionsExtensions` — legacy; native <conditionList> preferred (v0.11+)
|
|
354
|
+
|
|
355
|
+
The `Translator` is a thin orchestrator (~80 lines) that calls each
|
|
356
|
+
module's inject/extract/restore in pipeline order.
|
|
357
|
+
|
|
358
|
+
== Model-owned wire codes (single source of truth)
|
|
359
|
+
|
|
360
|
+
Wire-format mappings live on the model classes:
|
|
361
|
+
|
|
362
|
+
* `Bond::CML_ORDER_CODES` / `KIND_BY_CML_ORDER` / `CML_STEREO_CODES`
|
|
363
|
+
* `Group::BRACKETS` / `BRACKET_BY_WIRE`
|
|
364
|
+
* `Reaction::ARROWS` / `ARROW_BY_WIRE`
|
|
365
|
+
* `Crystal::CELL_PARAMS` / `LENGTH_FIELDS` / `ANGLE_FIELDS` / `CELL_LABELS`
|
|
366
|
+
* `Cml::ID_PREFIXES` (shared by adapter and extension modules)
|
|
367
|
+
|
|
368
|
+
Adding a new bond kind, bracket kind, arrow kind, or cell parameter
|
|
369
|
+
is one entry on the model; both adapter directions pick it up.
|
|
370
|
+
|
|
371
|
+
== Linter framework
|
|
372
|
+
|
|
373
|
+
Ten registered checks (auto-load via `Linter::Base.register`):
|
|
374
|
+
|
|
375
|
+
* `BalanceCheck` — stoichiometric atom conservation
|
|
376
|
+
* `ChargeBalanceCheck` — charge conservation (v0.7+)
|
|
377
|
+
* `BracketBalanceCheck` — group bracket consistency
|
|
378
|
+
* `CrystalSanityCheck` — cell lengths/angles/fractional coords (v0.6+)
|
|
379
|
+
* `ZMatrixReferenceCheck` — forward references and geometry (v0.6+)
|
|
380
|
+
* `SpectrumPeakCheck` — peak data validation (v0.6+)
|
|
381
|
+
* `ElementValidationCheck` — periodic-table membership
|
|
382
|
+
* `IsotopeSanityCheck` — isotope mass >= atomic number
|
|
383
|
+
* `UnclosedRingCheck` — unmatched ring-closure digits
|
|
384
|
+
* `ValenceCheck` — bond order + charge <= max valence
|
|
385
|
+
|
|
386
|
+
`Linter.run` sorts by severity (errors first) then deduplicates.
|
|
387
|
+
|
|
388
|
+
== Convenience methods on Molecule
|
|
389
|
+
|
|
390
|
+
* `#atom_count` — total atoms (recurses through groups/nested molecules)
|
|
391
|
+
* `#hill_formula` — Hill-system canonical formula (C first, H second, alpha)
|
|
392
|
+
* `#formula_weight` — sum of atomic_mass * count (v0.13+)
|
data/CHANGELOG.md
CHANGED
|
@@ -3,15 +3,373 @@
|
|
|
3
3
|
All notable changes to AsciiChem are documented here.
|
|
4
4
|
This project follows [Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
-
## [
|
|
6
|
+
## [0.19.0] - 2026-09-09
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `AsciiChem::Identifiers` — offline format validators for chemical
|
|
10
|
+
identifier conventions, self-registering like linter checks
|
|
11
|
+
(`cas` with check digit, `inchi` with formula-layer analysis,
|
|
12
|
+
`inchikey` 14-10-1 format, `smiles` structural sanity). Purely
|
|
13
|
+
lexical; no network, no assignment checking.
|
|
14
|
+
- `IdentifierFormatCheck` (linter) — validates `@cas`/`@inchi`/
|
|
15
|
+
`@inchikey`/`@smiles` annotation values against the registry;
|
|
16
|
+
unknown conventions are skipped.
|
|
17
|
+
- `IdentifierConsistencyCheck` (linter) — cross-checks identifier
|
|
18
|
+
values against the molecule they annotate: InChI formula-layer
|
|
19
|
+
element counts vs molecule composition (error), SMILES element set
|
|
20
|
+
vs molecule elements with implicit hydrogens tolerated (warning).
|
|
21
|
+
- `Model::Molecule#element_counts(with_coefficient:)` — public
|
|
22
|
+
composition API; `#hill_formula` and `#formula_weight` now share it.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
- RSpec suite aborted non-deterministically mid-run (randomly
|
|
26
|
+
truncated example counts, e.g. 56–748 of 788): CLI commands call
|
|
27
|
+
`Kernel#exit` even on success, and the cli specs' `run` helper did
|
|
28
|
+
not trap `SystemExit`, so under random ordering an uncaught exit
|
|
29
|
+
aborted the suite while reporting "0 failures". The helper now
|
|
30
|
+
traps it; five consecutive full runs execute all 788 examples.
|
|
31
|
+
- Linter registry re-registration in specs now globs all check files
|
|
32
|
+
instead of a hardcoded list (new checks no longer vanish from the
|
|
33
|
+
suite after `Registry.reset`).
|
|
34
|
+
|
|
35
|
+
## [0.18.1] - 2026-07-21
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- Updated gemspec summary and description to mention all
|
|
39
|
+
beyond-formulas constructs (crystals, spectra, calculations,
|
|
40
|
+
Z-matrices, reaction mechanisms) and the native CML round-trip
|
|
41
|
+
via chemicalml.
|
|
42
|
+
- Added `docs_uri` metadata pointing to the AsciiChem documentation
|
|
43
|
+
site.
|
|
44
|
+
|
|
45
|
+
## [0.18.0] - 2026-07-21
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
- Beyond-formulas body validation: MechanismBuilder, SpectrumBuilder,
|
|
49
|
+
and CalculationBuilder now validate each body line at parse time.
|
|
50
|
+
Malformed entries (missing `:` separator) raise `ParseError` with
|
|
51
|
+
the offending line number and content, instead of being silently
|
|
52
|
+
dropped.
|
|
53
|
+
|
|
54
|
+
## [0.17.0] - 2026-07-21
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
- `Atom::Point3` value object (Struct with `x`, `y`, `z`, `to_a`,
|
|
58
|
+
`magnitude`).
|
|
59
|
+
- `Atom#cartesian` and `Atom#fractional` accessors return Point3
|
|
60
|
+
bundling the flat coordinate fields. Backwards compatible: flat
|
|
61
|
+
accessors (`x2`/`y2`/`z2`, `x_fract`/`y_fract`/`z_fract`) remain.
|
|
62
|
+
|
|
63
|
+
## [0.16.0] - 2026-07-21
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- `Node#to_structural_svg` shortcut — invokes
|
|
67
|
+
`Formatter::StructuralSvg` for 2D diagram output. For Crystal
|
|
68
|
+
nodes, projects fractional coordinates onto the ab-plane with a
|
|
69
|
+
unit cell outline; for molecules with bonds, uses elkrb for
|
|
70
|
+
graph layout (existing behaviour); falls back to linear Svg
|
|
71
|
+
otherwise.
|
|
72
|
+
- `Formatter::StructuralSvg#visit_crystal` — renders a Crystal as
|
|
73
|
+
a 2D projection of fractional coordinates with unit cell outline.
|
|
74
|
+
|
|
75
|
+
## [0.15.0] - 2026-07-21
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
- Parser error messages now report `line N, col M` for multi-line
|
|
79
|
+
inputs instead of `char N`. The caret pointer is positioned on
|
|
80
|
+
the relevant line. Single-line inputs continue to use the
|
|
81
|
+
existing char-position format.
|
|
82
|
+
|
|
83
|
+
## [0.14.0] - 2026-07-21
|
|
84
|
+
|
|
85
|
+
### Added
|
|
86
|
+
- `lint -f json` CLI option — emits diagnostics as a JSON array for
|
|
87
|
+
tooling integration (CI, editors, downstream scripts). Each
|
|
88
|
+
entry has `severity`, `message`, and `node` fields.
|
|
89
|
+
|
|
90
|
+
## [0.13.0] - 2026-07-21
|
|
91
|
+
|
|
92
|
+
### Added
|
|
93
|
+
- `Molecule#formula_weight` — sum of `atomic_mass × count` across
|
|
94
|
+
all atoms (recurses through groups and nested molecules). Returns
|
|
95
|
+
nil if any element lacks atomic mass data. Examples:
|
|
96
|
+
`parse("H_2O").nodes.first.formula_weight` returns `18.015`;
|
|
97
|
+
`parse("C_6H_12O_6")` returns `180.156`.
|
|
98
|
+
- `PeriodicTable.atomic_mass(symbol)` — IUPAC 2021 standard atomic
|
|
99
|
+
weights for ~50 common elements. Single source of truth for
|
|
100
|
+
atomic mass data.
|
|
101
|
+
|
|
102
|
+
### Changed
|
|
103
|
+
- `PeriodicTable::Element` struct gains `atomic_mass` field (nil
|
|
104
|
+
when unpopulated).
|
|
105
|
+
|
|
106
|
+
## [0.12.0] - 2026-07-21
|
|
107
|
+
|
|
108
|
+
### Added
|
|
109
|
+
- `Molecule#hill_formula` — Hill-system canonical formula (C first,
|
|
110
|
+
then H, then others alphabetically). Useful for indexing and
|
|
111
|
+
display. `parse("C_2H_6O").nodes.first.hill_formula` returns
|
|
112
|
+
`"C2H6O"`; `parse("H_2SO_4")` returns `"H2O4S"`.
|
|
113
|
+
|
|
114
|
+
### Changed
|
|
115
|
+
- `Linter.run` sorts diagnostics by severity (errors first) then by
|
|
116
|
+
message for stable UX output. Also deduplicates identical
|
|
117
|
+
(message, node) pairs from different checks.
|
|
118
|
+
- `AsciiChem::Parser` caches `Grammar` and `Transform` instances at
|
|
119
|
+
the class level instead of allocating per parse. Benchmark: ~15%
|
|
120
|
+
throughput improvement on repeated parses.
|
|
121
|
+
|
|
122
|
+
## [0.11.0] - 2026-07-21
|
|
123
|
+
|
|
124
|
+
### Added
|
|
125
|
+
- **Native CML wire for ZMatrix, Calculation, Mechanism, and
|
|
126
|
+
reaction conditions.** Completes the native-wire migration arc
|
|
127
|
+
for all beyond-formulas constructs:
|
|
128
|
+
- ZMatrix emits as `<molecule>` with `<zMatrix>` child carrying
|
|
129
|
+
the text form (structural length/angle/torsion children are a
|
|
130
|
+
future enhancement).
|
|
131
|
+
- Calculation emits as `<molecule>` with `<propertyList>` of
|
|
132
|
+
`<property>` children, each with a `<scalar>` carrying value
|
|
133
|
+
and units.
|
|
134
|
+
- Mechanism emits as `<reaction>` with `<mechanism>` child.
|
|
135
|
+
Currently uses the text form via title attribute; structural
|
|
136
|
+
step-by-step representation is a future enhancement.
|
|
137
|
+
- Reaction conditions (`<=>[Fe][400C]`) now emit as native
|
|
138
|
+
`<conditionList>` with `<scalar>` children for above/below,
|
|
139
|
+
replacing the `aci:conditionsAbove`/`aci:conditionsBelow`
|
|
140
|
+
attributes used in v0.10.0 and earlier.
|
|
141
|
+
|
|
142
|
+
### Changed
|
|
143
|
+
- `ConditionsExtensions.inject`: now a no-op when native
|
|
144
|
+
`<conditionList>` is present (defensive fallback only).
|
|
145
|
+
- `ModelAdapter::FromCanonical`: reads native `<conditionList>`
|
|
146
|
+
via `conditions_from_canonical`.
|
|
147
|
+
|
|
148
|
+
## [0.10.0] - 2026-07-21
|
|
149
|
+
|
|
150
|
+
### Added
|
|
151
|
+
- **Native CML wire for Spectrum.** Spectrum nodes now emit as a
|
|
152
|
+
`<molecule>` containing a native `<spectrum>` child (with `format`
|
|
153
|
+
and `condition` attributes) plus a `<peakList>` of `<peak>`
|
|
154
|
+
elements. Each peak carries `xValue`, `yValue`, `yMultiplicity`,
|
|
155
|
+
and `title` (assignment) attributes per the CML Schema 3 spec.
|
|
156
|
+
|
|
157
|
+
### Changed
|
|
158
|
+
- `ModelAdapter::ToCanonical`: new `spectrum_to_canonical` mapper
|
|
159
|
+
wraps Spectrum in a Molecule wire object with spectrum child.
|
|
160
|
+
- `ModelAdapter::FromCanonical`: detects molecule-with-spectrum and
|
|
161
|
+
rebuilds as Spectrum node.
|
|
162
|
+
- `Translator::NATIVELY_WIRED`: now includes Spectrum.
|
|
163
|
+
|
|
164
|
+
## [0.9.0] - 2026-07-21
|
|
165
|
+
|
|
166
|
+
### Added
|
|
167
|
+
- **Native CML wire for Crystal.** Crystal nodes now emit as a
|
|
168
|
+
`<molecule>` containing a native `<crystal>` child (with `<scalar>`
|
|
169
|
+
cells and `<symmetry spaceGroup=...>`) plus an `<atomArray>` with
|
|
170
|
+
fractional coordinates. Other CML tools can now read AsciiChem
|
|
171
|
+
crystal output directly without needing the aci: extension
|
|
172
|
+
namespace. Round-trip preserves all fields. The legacy aci:
|
|
173
|
+
text-carrier form is still accepted on parse for backwards
|
|
174
|
+
compatibility.
|
|
175
|
+
|
|
176
|
+
### Changed
|
|
177
|
+
- `ModelAdapter::ToCanonical`: new `crystal_to_canonical` mapper
|
|
178
|
+
wraps Crystal in a Molecule wire object.
|
|
179
|
+
- `ModelAdapter::FromCanonical`: detects molecule-with-crystal and
|
|
180
|
+
rebuilds as Crystal node.
|
|
181
|
+
- `Extensions::TopLevel.collect`: accepts `skip_classes:` parameter
|
|
182
|
+
to suppress the aci: text carrier for constructs with native wire.
|
|
183
|
+
- `Translator::NATIVELY_WIRED`: registry of construct classes that
|
|
184
|
+
have been migrated to native wire (currently just Crystal).
|
|
185
|
+
|
|
186
|
+
## [0.8.0] - 2026-07-21
|
|
187
|
+
|
|
188
|
+
### Changed
|
|
189
|
+
- `chemicalml` dependency bumped from `~> 0.2.1` to `~> 0.3.0`.
|
|
190
|
+
chemicalml 0.3.0 fixes the wire serialization gaps that blocked
|
|
191
|
+
Phases 06-10, 15 (native CML wire for Crystal/Spectrum/ZMatrix/
|
|
192
|
+
Mechanism/Calculation/conditions). Molecule wire now serializes
|
|
193
|
+
`<crystal>`, `<spectrum>`, `<zMatrix>`, `<propertyList>` children;
|
|
194
|
+
Reaction wire now serializes `<conditionList>`, `<spectatorList>`,
|
|
195
|
+
`<mechanism>` children.
|
|
196
|
+
|
|
197
|
+
### Added
|
|
198
|
+
- `.github/workflows/release.yml` — manual-dispatch release workflow
|
|
199
|
+
that pushes the gem to RubyGems. Triggered via `gh workflow run
|
|
200
|
+
release.yml -f version=X.Y.Z` after the version-bump PR merges.
|
|
201
|
+
Verifies the input version matches `lib/asciichem/version.rb`.
|
|
202
|
+
|
|
203
|
+
## [0.7.0] - 2026-07-21
|
|
204
|
+
|
|
205
|
+
### Added
|
|
206
|
+
- Implicit subscripts on Hydrogen: `H2` now parses as `H_2`, so
|
|
207
|
+
users can write `H2O`, `CH4`, `NH3` without explicit underscores.
|
|
208
|
+
Resolves the long-pending feature request without breaking
|
|
209
|
+
SMILES-style ring closures on other elements (Hydrogen cannot
|
|
210
|
+
form ring closures — only 1 bond — so bare digits after H are
|
|
211
|
+
unambiguously subscripts).
|
|
212
|
+
|
|
213
|
+
### Changed
|
|
214
|
+
- Grammar: new `hydrogen_atom` rule placed before `plain_atom` in
|
|
215
|
+
the unit dispatch. The `match('[a-z]').absent?` lookahead ensures
|
|
216
|
+
`He`, `Ho`, etc. fall through to plain_atom unchanged.
|
|
217
|
+
|
|
218
|
+
## [0.6.0] - 2026-07-20
|
|
219
|
+
|
|
220
|
+
### Added
|
|
221
|
+
- `Model::OpaqueCml` node + `Cml::OpaqueExtensions` module: preserve
|
|
222
|
+
unknown CML elements through round-trip as opaque blobs.
|
|
223
|
+
- `Linter::ChargeBalanceCheck`: validates charge conservation in
|
|
224
|
+
reactions (parallel to BalanceCheck's atom conservation).
|
|
225
|
+
- `Linter::CrystalSanityCheck`, `ZMatrixReferenceCheck`,
|
|
226
|
+
`SpectrumPeakCheck`: validate cell parameters, ZMatrix references,
|
|
227
|
+
and spectrum peak data.
|
|
228
|
+
- `Molecule#atom_count`: total atom count recursing through groups
|
|
229
|
+
and nested molecules with subscripts and multiplicities applied.
|
|
230
|
+
- `Model::Molecule::Property`, `Meta`, `Label`, `Formula` Structs
|
|
231
|
+
replace Hash-with-magic-keys fields.
|
|
232
|
+
- `Model::Spectrum::Peak`, `Calculation::Property`, `Mechanism::Step`
|
|
233
|
+
Structs replace Hash-with-magic-keys fields.
|
|
234
|
+
- Formatter visit methods for `Crystal`, `Spectrum`, `Calculation`,
|
|
235
|
+
`ZMatrix`, `Mechanism`, `OpaqueCml` across MathML, HTML, LaTeX, SVG.
|
|
236
|
+
- CLI smoke coverage for beyond-formulas constructs.
|
|
237
|
+
- Fuzz corpus files 16-20 (crystal, spectrum, calculation, zmatrix,
|
|
238
|
+
mechanism).
|
|
239
|
+
- Benchmark cases for beyond-formulas constructs.
|
|
240
|
+
|
|
241
|
+
### Changed
|
|
242
|
+
- `Cml::Extensions` split into `Extensions::AtomAttributes` and
|
|
243
|
+
`Extensions::TopLevel` sub-modules; facade preserves public API.
|
|
244
|
+
- `Cml::MetadataExtensions` and `Cml::ConditionsExtensions` extracted
|
|
245
|
+
from inline Translator code as parallel extension channels.
|
|
246
|
+
- `Cml::ID_PREFIXES` is the single source of truth for canonical ID
|
|
247
|
+
prefixes (atom/bond/molecule/reaction/group).
|
|
248
|
+
- `Extensions.ensure_namespace(root)` shared helper replaces six
|
|
249
|
+
duplicated `namespace_declared?` / `ensure_namespace` methods.
|
|
250
|
+
- `Model::Bond` owns `CML_ORDER_CODES`, `KIND_BY_CML_ORDER`,
|
|
251
|
+
`CML_STEREO_CODES` (was duplicated across adapter directions).
|
|
252
|
+
- `Model::Group` owns `BRACKETS` with `{open, close, wire}` attrs.
|
|
253
|
+
- `Model::Reaction` owns `ARROW_BY_WIRE` inverse map.
|
|
254
|
+
- `Model::Crystal` owns `CELL_PARAMS`, `LENGTH_FIELDS`, `ANGLE_FIELDS`,
|
|
255
|
+
`CELL_LABELS`, `#each_cell_param(format)` as single source for cell
|
|
256
|
+
parameter labels across formatters.
|
|
257
|
+
- `Extensions::TopLevel::Handler.text_round_trip` and
|
|
258
|
+
`.source_with_wrapper` factory methods eliminate duplicate lambdas
|
|
259
|
+
across 8 handlers.
|
|
260
|
+
- `Transform::BuilderHelpers` shared module included by all 5 builder
|
|
261
|
+
classes.
|
|
262
|
+
- `Linter::Base.register(:name)` pattern — adding a check is one new
|
|
263
|
+
file + one autoload entry.
|
|
264
|
+
|
|
265
|
+
### Fixed
|
|
266
|
+
- Empty body braces (`crystal[x]{}`, `spectrum[nmr](){}`, etc.) now
|
|
267
|
+
parse correctly for all 5 beyond-formulas constructs. Root cause:
|
|
268
|
+
parslet represents zero-match `.repeat` as `[]` not `""`.
|
|
269
|
+
- Cascade step conditions (`A ->[c1] B ->[c2] C`) now survive CML
|
|
270
|
+
round-trip. Previously the inline translator code only walked
|
|
271
|
+
top-level reactions.
|
|
272
|
+
|
|
273
|
+
## [0.5.1] - 2026-07-16
|
|
274
|
+
|
|
275
|
+
### Added
|
|
276
|
+
- CML round-trip for all five beyond-formulas constructs via aci:
|
|
277
|
+
namespace extension elements.
|
|
278
|
+
|
|
279
|
+
## [0.5.0] - 2026-07-16
|
|
280
|
+
|
|
281
|
+
### Added
|
|
282
|
+
- Phase 5: reaction mechanism syntax (`mechanism{step1: A -> B;
|
|
283
|
+
spectator: Na+}`).
|
|
284
|
+
|
|
285
|
+
## [0.4.1] - 2026-07-16
|
|
286
|
+
|
|
287
|
+
### Added
|
|
288
|
+
- Phase 3: computational chemistry results (`calc(method/basis){...}`).
|
|
289
|
+
- Phase 4: Z-Matrix internal coordinates (`zmatrix{C1; H2 C1 1.09; ...}`).
|
|
290
|
+
|
|
291
|
+
## [0.4.0] - 2026-07-16
|
|
292
|
+
|
|
293
|
+
### Added
|
|
294
|
+
- Phase 1: crystallography syntax (`crystal[NaCl](a=...,sg=...){...}`).
|
|
295
|
+
- Phase 2: spectroscopy syntax (`spectrum[nmr](type=1H,...){...}`).
|
|
296
|
+
|
|
297
|
+
## [0.3.4] - 2026-07-16
|
|
298
|
+
|
|
299
|
+
### Added
|
|
300
|
+
- Spin multiplicity (`C@m(2)`), atom title (`C@t("alpha")`), and
|
|
301
|
+
fractional coordinates (`C@f(0.5,0.5,0.5)`) syntax.
|
|
302
|
+
|
|
303
|
+
## [0.3.3] - 2026-07-16
|
|
304
|
+
|
|
305
|
+
### Fixed
|
|
306
|
+
- Property and metadata CML round-trip for chemicalml 0.2.1.
|
|
307
|
+
|
|
308
|
+
## [0.3.2] - 2026-07-16
|
|
309
|
+
|
|
310
|
+
### Added
|
|
311
|
+
- Reaction conditions CML round-trip via aci: attributes.
|
|
312
|
+
|
|
313
|
+
## [0.3.1] - 2026-07-16
|
|
314
|
+
|
|
315
|
+
### Changed
|
|
316
|
+
- Adapted to chemicalml 0.2.1 (wire classes ARE the model).
|
|
317
|
+
|
|
318
|
+
## [0.3.0] - 2026-07-15
|
|
319
|
+
|
|
320
|
+
### Added
|
|
321
|
+
- Full CML support via chemicalml gem.
|
|
322
|
+
- aci: extension namespace for fields CML doesn't natively carry.
|
|
323
|
+
- New syntax: spin multiplicity, atom titles, fractional coordinates,
|
|
324
|
+
molecule annotations (`@name`, `@inchi`, `@meta`, `@formula`,
|
|
325
|
+
`@label`, `@mw`, `@title`).
|
|
326
|
+
- Group preservation through CML round-trip via `<aci:group>`.
|
|
327
|
+
- ElectronConfiguration and EmbeddedMath top-level constructs via aci:.
|
|
328
|
+
- `parse-cml` CLI subcommand.
|
|
329
|
+
|
|
330
|
+
## [0.2.0] - 2026-07-14
|
|
331
|
+
|
|
332
|
+
### Added
|
|
333
|
+
- Layout module (MoleculeWalker / GraphBuilder / ResultExtractor)
|
|
334
|
+
using elkrb for 2D structural SVG.
|
|
335
|
+
- StructuralSvg formatter with bond-kind renderer registry.
|
|
336
|
+
- Ring closures (SMILES-style): `C1-C-C-C-C-C1` is cyclohexane.
|
|
337
|
+
|
|
338
|
+
## [0.1.0] - 2026-07-10
|
|
7
339
|
|
|
8
340
|
### Added
|
|
9
341
|
- Initial gem scaffold: autoload tree, version, errors.
|
|
10
|
-
- Core model: `Formula`, `Atom`, `Molecule`, `Group`, `Bond`,
|
|
11
|
-
`ElectronConfiguration`, `EmbeddedMath`, `Text`.
|
|
342
|
+
- Core model: `Formula`, `Atom`, `Molecule`, `Group`, `Bond`,
|
|
343
|
+
`Reaction`, `ElectronConfiguration`, `EmbeddedMath`, `Text`.
|
|
12
344
|
- Parslet parser and transform for v1 constructs.
|
|
13
|
-
- Formatters: MathML
|
|
14
|
-
- Thor-based CLI: `convert`, `roundtrip`, `
|
|
345
|
+
- Formatters: MathML, Text (round-trip), HTML, LaTeX, SVG.
|
|
346
|
+
- Thor-based CLI: `convert`, `roundtrip`, `lint`, `parse-cml`,
|
|
347
|
+
`version`.
|
|
15
348
|
- Comprehensive RSpec suite with round-trip conformance.
|
|
16
349
|
|
|
17
350
|
[Unreleased]: https://github.com/asciichem/asciichem-ruby/commits/main
|
|
351
|
+
[0.18.1]: https://github.com/asciichem/asciichem-ruby/compare/v0.18.0...v0.18.1
|
|
352
|
+
[0.18.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.17.0...v0.18.0
|
|
353
|
+
[0.17.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.16.0...v0.17.0
|
|
354
|
+
[0.16.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.15.0...v0.16.0
|
|
355
|
+
[0.15.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.14.0...v0.15.0
|
|
356
|
+
[0.14.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.13.0...v0.14.0
|
|
357
|
+
[0.13.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.12.0...v0.13.0
|
|
358
|
+
[0.12.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.11.0...v0.12.0
|
|
359
|
+
[0.11.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.10.0...v0.11.0
|
|
360
|
+
[0.10.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.9.0...v0.10.0
|
|
361
|
+
[0.9.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.8.0...v0.9.0
|
|
362
|
+
[0.8.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.7.0...v0.8.0
|
|
363
|
+
[0.7.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.6.0...v0.7.0
|
|
364
|
+
[0.6.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.5.1...v0.6.0
|
|
365
|
+
[0.5.1]: https://github.com/asciichem/asciichem-ruby/compare/v0.5.0...v0.5.1
|
|
366
|
+
[0.5.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.4.1...v0.5.0
|
|
367
|
+
[0.4.1]: https://github.com/asciichem/asciichem-ruby/compare/v0.4.0...v0.4.1
|
|
368
|
+
[0.4.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.3.4...v0.4.0
|
|
369
|
+
[0.3.4]: https://github.com/asciichem/asciichem-ruby/compare/v0.3.3...v0.3.4
|
|
370
|
+
[0.3.3]: https://github.com/asciichem/asciichem-ruby/compare/v0.3.2...v0.3.3
|
|
371
|
+
[0.3.2]: https://github.com/asciichem/asciichem-ruby/compare/v0.3.1...v0.3.2
|
|
372
|
+
[0.3.1]: https://github.com/asciichem/asciichem-ruby/compare/v0.3.0...v0.3.1
|
|
373
|
+
[0.3.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.2.0...v0.3.0
|
|
374
|
+
[0.2.0]: https://github.com/asciichem/asciichem-ruby/compare/v0.1.0...v0.2.0
|
|
375
|
+
[0.1.0]: https://github.com/asciichem/asciichem-ruby/releases/tag/v0.1.0
|
data/asciichem.gemspec
CHANGED
|
@@ -8,10 +8,12 @@ Gem::Specification.new do |spec|
|
|
|
8
8
|
spec.authors = ["Ribose Inc."]
|
|
9
9
|
spec.email = ["open.source@ribose.com"]
|
|
10
10
|
|
|
11
|
-
spec.summary = "ASCII syntax for chemistry formulae, reactions, and
|
|
11
|
+
spec.summary = "ASCII syntax for chemistry formulae, reactions, structures, and spectroscopy."
|
|
12
12
|
spec.description = "AsciiChem is an ASCII syntax for representing chemical formulae, " \
|
|
13
|
-
"reactions, electron configurations,
|
|
14
|
-
"
|
|
13
|
+
"reactions, electron configurations, bonds, crystals, spectra, " \
|
|
14
|
+
"calculations, Z-matrices, and reaction mechanisms. It parses to a " \
|
|
15
|
+
"semantic model and renders to MathML, HTML, LaTeX, SVG, and CML. " \
|
|
16
|
+
"Math embedding uses Plurimath. Round-trips to native CML via chemicalml."
|
|
15
17
|
spec.homepage = "https://www.asciichem.org"
|
|
16
18
|
spec.license = "BSD-2-Clause"
|
|
17
19
|
spec.required_ruby_version = Gem::Requirement.new(">= 3.3.0")
|
|
@@ -19,6 +21,7 @@ Gem::Specification.new do |spec|
|
|
|
19
21
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
20
22
|
spec.metadata["source_code_uri"] = "https://github.com/asciichem/asciichem-ruby"
|
|
21
23
|
spec.metadata["changelog_uri"] = "https://github.com/asciichem/asciichem-ruby/blob/main/CHANGELOG.md"
|
|
24
|
+
spec.metadata["docs_uri"] = "https://www.asciichem.org"
|
|
22
25
|
spec.metadata["rubygems_mfa_required"] = "true"
|
|
23
26
|
|
|
24
27
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
@@ -30,7 +33,7 @@ Gem::Specification.new do |spec|
|
|
|
30
33
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
31
34
|
spec.require_paths = ["lib"]
|
|
32
35
|
|
|
33
|
-
spec.add_dependency "chemicalml", "~> 0.
|
|
36
|
+
spec.add_dependency "chemicalml", "~> 0.3.0"
|
|
34
37
|
spec.add_dependency "elkrb", "~> 1.0"
|
|
35
38
|
spec.add_dependency "nokogiri", "~> 1.16"
|
|
36
39
|
spec.add_dependency "parslet", "~> 2.0"
|
data/benchmarks/benchmark.rb
CHANGED
|
@@ -31,7 +31,12 @@ CASES = {
|
|
|
31
31
|
"equilibrium (Haber)" => "N_2 + 3H_2 <=>[Fe][400°C] 2NH_3",
|
|
32
32
|
"cascade (A -> B -> C -> D)" => "A -> B -> C -> D",
|
|
33
33
|
"electron config" => "1s^2 2s^2 2p^6 3s^2 3p^6 4s^2 3d^10",
|
|
34
|
-
"bonds (H-O-H=O#H)" => "H-O-H=O#H"
|
|
34
|
+
"bonds (H-O-H=O#H)" => "H-O-H=O#H",
|
|
35
|
+
"crystal (NaCl)" => "crystal[NaCl](a=5.64,b=5.64,c=5.64,alpha=90,beta=90,gamma=90,sg=Fm-3m){Na@f(0,0,0) Cl@f(0.5,0.5,0.5)}",
|
|
36
|
+
"spectrum (NMR)" => %(spectrum[nmr](type=1H,solvent=CDCl3){1.2: 3H s "CH3"}),
|
|
37
|
+
"calculation (DFT)" => "calc(b3lyp/6-31G*){energy: -234.5 Hartree}",
|
|
38
|
+
"zmatrix (methane)" => "zmatrix{\n C1\n H2 C1 1.09\n H3 C1 1.09 H2 109.5\n H4 C1 1.09 H2 109.5 H3 120.0\n}",
|
|
39
|
+
"mechanism (2-step)" => "mechanism{\n step1: A + B -> C\n step2: C -> D + E\n spectator: Na+\n}"
|
|
35
40
|
}.freeze
|
|
36
41
|
|
|
37
42
|
def parse_bench(label, source)
|