From cd8e63238fe238ea8a005f718f9fa4ec058c7c9c Mon Sep 17 00:00:00 2001 From: Benjy Date: Sun, 19 Mar 2023 18:03:55 +1300 Subject: [PATCH 1/9] Added initial InChI conversion and tests --- build.rs | 1 + src/bridge/ro_mol.rs | 3 +++ tests/test_ro_mol.rs | 25 +++++++++++++++++++++++++ wrapper/include/ro_mol.h | 3 +++ wrapper/src/ro_mol.cc | 12 ++++++++++++ 5 files changed, 44 insertions(+) diff --git a/build.rs b/build.rs index 2923640..62c2b61 100644 --- a/build.rs +++ b/build.rs @@ -134,6 +134,7 @@ fn main() { "SmilesParse", // "Subgraphs", "SubstructMatch", + "RDInchiLib", ] { println!("cargo:rustc-link-lib=dylib=RDKit{}", lib); } diff --git a/src/bridge/ro_mol.rs b/src/bridge/ro_mol.rs index 0505c5a..7fbb091 100644 --- a/src/bridge/ro_mol.rs +++ b/src/bridge/ro_mol.rs @@ -23,6 +23,9 @@ pub mod ffi { pub fn mol_to_smiles(mol: SharedPtr) -> String; + pub fn mol_to_inchi(mol: SharedPtr) -> String; + pub fn inchi_to_mol(inchi: &CxxString) -> Result>; + // 0b11111111 pub type MolSanitizeException; pub fn detect_chemistry_problems(mol: SharedPtr) -> UniquePtr>; diff --git a/tests/test_ro_mol.rs b/tests/test_ro_mol.rs index d4d8faf..da54e6c 100644 --- a/tests/test_ro_mol.rs +++ b/tests/test_ro_mol.rs @@ -47,3 +47,28 @@ fn parse_without_sanitize_test() { "AtomValenceException" ); } + +#[test] +fn test_mol_to_inchi() { + cxx::let_cxx_string!(smile = "C"); + let romol = rdkit_sys::ro_mol_ffi::smiles_to_mol(&smile).unwrap(); + let inchi = rdkit_sys::ro_mol_ffi::mol_to_inchi(romol); + assert_eq!(inchi, "InChI=1S/CH4/h1H4"); +} + +#[test] +fn test_good_inchi_to_mol() { + cxx::let_cxx_string!(inchi = "InChI=1S/C2H6/c1-2/h1-2H3"); + let romol = rdkit_sys::ro_mol_ffi::inchi_to_mol(&inchi).unwrap(); + assert!(!romol.is_null()); + assert_eq!(rdkit_sys::ro_mol_ffi::mol_to_inchi(romol.clone()), "InChI=1S/C2H6/c1-2/h1-2H3"); + assert_eq!(rdkit_sys::ro_mol_ffi::mol_to_smiles(romol), "CC"); +} + +#[test] +fn test_bad_inchi_to_mol() { + cxx::let_cxx_string!(bad_inchi = "asd"); + let romol = rdkit_sys::ro_mol_ffi::inchi_to_mol(&bad_inchi); + assert!(romol.is_ok()); + assert!(romol.unwrap().is_null()); +} diff --git a/wrapper/include/ro_mol.h b/wrapper/include/ro_mol.h index 41eac20..29b3cc9 100644 --- a/wrapper/include/ro_mol.h +++ b/wrapper/include/ro_mol.h @@ -18,4 +18,7 @@ namespace RDKit { void smiles_parser_params_set_sanitize(std::shared_ptr params, bool sanitize); std::unique_ptr> detect_chemistry_problems(std::shared_ptr mol); + + rust::String mol_to_inchi(std::shared_ptr mol); + std::shared_ptr inchi_to_mol(const std::string &inchi); } \ No newline at end of file diff --git a/wrapper/src/ro_mol.cc b/wrapper/src/ro_mol.cc index f11ef5a..cdbea31 100644 --- a/wrapper/src/ro_mol.cc +++ b/wrapper/src/ro_mol.cc @@ -6,6 +6,7 @@ #include #include #include +#include namespace RDKit { using ExplicitBitVect = ::ExplicitBitVect; @@ -47,4 +48,15 @@ namespace RDKit { return std::unique_ptr>(get_types); } + + rust::String mol_to_inchi(std::shared_ptr mol) { + ExtraInchiReturnValues rv; + return MolToInchi(*mol, rv, ""); + } + + std::shared_ptr inchi_to_mol(const std::string &inchi) { + ExtraInchiReturnValues rv; + ROMol *mol = InchiToMol(inchi, rv); + return std::shared_ptr(mol); + } } \ No newline at end of file From 59d8724fda39dc601bf185991555147a1e2811b2 Mon Sep 17 00:00:00 2001 From: Benjy Date: Sun, 19 Mar 2023 18:05:09 +1300 Subject: [PATCH 2/9] Separated InChI tests --- tests/test_inchi.rs | 24 ++++++++++++++++++++++++ tests/test_ro_mol.rs | 25 ------------------------- 2 files changed, 24 insertions(+), 25 deletions(-) create mode 100644 tests/test_inchi.rs diff --git a/tests/test_inchi.rs b/tests/test_inchi.rs new file mode 100644 index 0000000..517d486 --- /dev/null +++ b/tests/test_inchi.rs @@ -0,0 +1,24 @@ +#[test] +fn test_mol_to_inchi() { + cxx::let_cxx_string!(smile = "C"); + let romol = rdkit_sys::ro_mol_ffi::smiles_to_mol(&smile).unwrap(); + let inchi = rdkit_sys::ro_mol_ffi::mol_to_inchi(romol); + assert_eq!(inchi, "InChI=1S/CH4/h1H4"); +} + +#[test] +fn test_good_inchi_to_mol() { + cxx::let_cxx_string!(inchi = "InChI=1S/C2H6/c1-2/h1-2H3"); + let romol = rdkit_sys::ro_mol_ffi::inchi_to_mol(&inchi).unwrap(); + assert!(!romol.is_null()); + assert_eq!(rdkit_sys::ro_mol_ffi::mol_to_inchi(romol.clone()), "InChI=1S/C2H6/c1-2/h1-2H3"); + assert_eq!(rdkit_sys::ro_mol_ffi::mol_to_smiles(romol), "CC"); +} + +#[test] +fn test_bad_inchi_to_mol() { + cxx::let_cxx_string!(bad_inchi = "asd"); + let romol = rdkit_sys::ro_mol_ffi::inchi_to_mol(&bad_inchi); + assert!(romol.is_ok()); + assert!(romol.unwrap().is_null()); +} diff --git a/tests/test_ro_mol.rs b/tests/test_ro_mol.rs index da54e6c..d4d8faf 100644 --- a/tests/test_ro_mol.rs +++ b/tests/test_ro_mol.rs @@ -47,28 +47,3 @@ fn parse_without_sanitize_test() { "AtomValenceException" ); } - -#[test] -fn test_mol_to_inchi() { - cxx::let_cxx_string!(smile = "C"); - let romol = rdkit_sys::ro_mol_ffi::smiles_to_mol(&smile).unwrap(); - let inchi = rdkit_sys::ro_mol_ffi::mol_to_inchi(romol); - assert_eq!(inchi, "InChI=1S/CH4/h1H4"); -} - -#[test] -fn test_good_inchi_to_mol() { - cxx::let_cxx_string!(inchi = "InChI=1S/C2H6/c1-2/h1-2H3"); - let romol = rdkit_sys::ro_mol_ffi::inchi_to_mol(&inchi).unwrap(); - assert!(!romol.is_null()); - assert_eq!(rdkit_sys::ro_mol_ffi::mol_to_inchi(romol.clone()), "InChI=1S/C2H6/c1-2/h1-2H3"); - assert_eq!(rdkit_sys::ro_mol_ffi::mol_to_smiles(romol), "CC"); -} - -#[test] -fn test_bad_inchi_to_mol() { - cxx::let_cxx_string!(bad_inchi = "asd"); - let romol = rdkit_sys::ro_mol_ffi::inchi_to_mol(&bad_inchi); - assert!(romol.is_ok()); - assert!(romol.unwrap().is_null()); -} From 67bf8ee794def64b20a6fc4a4ffaf450625d6587 Mon Sep 17 00:00:00 2001 From: Benjy Date: Sun, 19 Mar 2023 18:22:43 +1300 Subject: [PATCH 3/9] Shifted InChI functions to separate files --- src/bridge/inchi.rs | 12 ++++++++++++ src/bridge/mod.rs | 3 +++ src/bridge/ro_mol.rs | 3 --- tests/test_inchi.rs | 8 ++++---- wrapper/include/inchi.h | 14 ++++++++++++++ wrapper/include/ro_mol.h | 3 --- wrapper/src/inchi.cc | 22 ++++++++++++++++++++++ wrapper/src/ro_mol.cc | 12 ------------ 8 files changed, 55 insertions(+), 22 deletions(-) create mode 100644 src/bridge/inchi.rs create mode 100644 wrapper/include/inchi.h create mode 100644 wrapper/src/inchi.cc diff --git a/src/bridge/inchi.rs b/src/bridge/inchi.rs new file mode 100644 index 0000000..cc139ba --- /dev/null +++ b/src/bridge/inchi.rs @@ -0,0 +1,12 @@ +#[cxx::bridge(namespace = "RDKit")] +pub mod ffi { + unsafe extern "C++" { + include!("wrapper/include/ro_mol.h"); + include!("wrapper/include/inchi.h"); + + pub type ROMol = crate::ro_mol_ffi::ROMol; + + pub fn mol_to_inchi(mol: SharedPtr) -> String; + pub fn inchi_to_mol(inchi: &CxxString) -> Result>; + } +} diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index f3cde4f..6299c2c 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -4,6 +4,9 @@ pub use descriptors::ffi as descriptors_ffi; mod fingerprint; pub use fingerprint::ffi as fingerprint_ffi; +mod inchi; +pub use inchi::ffi as inchi_ffi; + mod mol_standardize; pub use mol_standardize::ffi as mol_standardize_ffi; diff --git a/src/bridge/ro_mol.rs b/src/bridge/ro_mol.rs index 7fbb091..0505c5a 100644 --- a/src/bridge/ro_mol.rs +++ b/src/bridge/ro_mol.rs @@ -23,9 +23,6 @@ pub mod ffi { pub fn mol_to_smiles(mol: SharedPtr) -> String; - pub fn mol_to_inchi(mol: SharedPtr) -> String; - pub fn inchi_to_mol(inchi: &CxxString) -> Result>; - // 0b11111111 pub type MolSanitizeException; pub fn detect_chemistry_problems(mol: SharedPtr) -> UniquePtr>; diff --git a/tests/test_inchi.rs b/tests/test_inchi.rs index 517d486..6dbf5eb 100644 --- a/tests/test_inchi.rs +++ b/tests/test_inchi.rs @@ -2,23 +2,23 @@ fn test_mol_to_inchi() { cxx::let_cxx_string!(smile = "C"); let romol = rdkit_sys::ro_mol_ffi::smiles_to_mol(&smile).unwrap(); - let inchi = rdkit_sys::ro_mol_ffi::mol_to_inchi(romol); + let inchi = rdkit_sys::inchi_ffi::mol_to_inchi(romol); assert_eq!(inchi, "InChI=1S/CH4/h1H4"); } #[test] fn test_good_inchi_to_mol() { cxx::let_cxx_string!(inchi = "InChI=1S/C2H6/c1-2/h1-2H3"); - let romol = rdkit_sys::ro_mol_ffi::inchi_to_mol(&inchi).unwrap(); + let romol = rdkit_sys::inchi_ffi::inchi_to_mol(&inchi).unwrap(); assert!(!romol.is_null()); - assert_eq!(rdkit_sys::ro_mol_ffi::mol_to_inchi(romol.clone()), "InChI=1S/C2H6/c1-2/h1-2H3"); + assert_eq!(rdkit_sys::inchi_ffi::mol_to_inchi(romol.clone()), "InChI=1S/C2H6/c1-2/h1-2H3"); assert_eq!(rdkit_sys::ro_mol_ffi::mol_to_smiles(romol), "CC"); } #[test] fn test_bad_inchi_to_mol() { cxx::let_cxx_string!(bad_inchi = "asd"); - let romol = rdkit_sys::ro_mol_ffi::inchi_to_mol(&bad_inchi); + let romol = rdkit_sys::inchi_ffi::inchi_to_mol(&bad_inchi); assert!(romol.is_ok()); assert!(romol.unwrap().is_null()); } diff --git a/wrapper/include/inchi.h b/wrapper/include/inchi.h new file mode 100644 index 0000000..bee4629 --- /dev/null +++ b/wrapper/include/inchi.h @@ -0,0 +1,14 @@ +#pragma once + +#include "rust/cxx.h" +#include +#include +#include +#include +#include +#include + +namespace RDKit { + rust::String mol_to_inchi(std::shared_ptr mol); + std::shared_ptr inchi_to_mol(const std::string &inchi); +} \ No newline at end of file diff --git a/wrapper/include/ro_mol.h b/wrapper/include/ro_mol.h index 29b3cc9..41eac20 100644 --- a/wrapper/include/ro_mol.h +++ b/wrapper/include/ro_mol.h @@ -18,7 +18,4 @@ namespace RDKit { void smiles_parser_params_set_sanitize(std::shared_ptr params, bool sanitize); std::unique_ptr> detect_chemistry_problems(std::shared_ptr mol); - - rust::String mol_to_inchi(std::shared_ptr mol); - std::shared_ptr inchi_to_mol(const std::string &inchi); } \ No newline at end of file diff --git a/wrapper/src/inchi.cc b/wrapper/src/inchi.cc new file mode 100644 index 0000000..db996b9 --- /dev/null +++ b/wrapper/src/inchi.cc @@ -0,0 +1,22 @@ +#include "rust/cxx.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace RDKit { + rust::String mol_to_inchi(std::shared_ptr mol) { + ExtraInchiReturnValues rv; + return MolToInchi(*mol, rv, ""); + } + + std::shared_ptr inchi_to_mol(const std::string &inchi) { + ExtraInchiReturnValues rv; + ROMol *mol = InchiToMol(inchi, rv); + return std::shared_ptr(mol); + } +} \ No newline at end of file diff --git a/wrapper/src/ro_mol.cc b/wrapper/src/ro_mol.cc index cdbea31..f11ef5a 100644 --- a/wrapper/src/ro_mol.cc +++ b/wrapper/src/ro_mol.cc @@ -6,7 +6,6 @@ #include #include #include -#include namespace RDKit { using ExplicitBitVect = ::ExplicitBitVect; @@ -48,15 +47,4 @@ namespace RDKit { return std::unique_ptr>(get_types); } - - rust::String mol_to_inchi(std::shared_ptr mol) { - ExtraInchiReturnValues rv; - return MolToInchi(*mol, rv, ""); - } - - std::shared_ptr inchi_to_mol(const std::string &inchi) { - ExtraInchiReturnValues rv; - ROMol *mol = InchiToMol(inchi, rv); - return std::shared_ptr(mol); - } } \ No newline at end of file From f66b5733d1243d8ab7c0f89376cc41df0d3d2572 Mon Sep 17 00:00:00 2001 From: Benjy Date: Sun, 19 Mar 2023 18:29:59 +1300 Subject: [PATCH 4/9] Removed unnecessary includes --- wrapper/include/inchi.h | 6 ------ wrapper/src/inchi.cc | 7 ------- 2 files changed, 13 deletions(-) diff --git a/wrapper/include/inchi.h b/wrapper/include/inchi.h index bee4629..51930ab 100644 --- a/wrapper/include/inchi.h +++ b/wrapper/include/inchi.h @@ -1,12 +1,6 @@ #pragma once #include "rust/cxx.h" -#include -#include -#include -#include -#include -#include namespace RDKit { rust::String mol_to_inchi(std::shared_ptr mol); diff --git a/wrapper/src/inchi.cc b/wrapper/src/inchi.cc index db996b9..c059a7f 100644 --- a/wrapper/src/inchi.cc +++ b/wrapper/src/inchi.cc @@ -1,11 +1,4 @@ #include "rust/cxx.h" -#include -#include -#include -#include -#include -#include -#include #include namespace RDKit { From 6246e358072631c3fccb8b4a01f6e63bba415252 Mon Sep 17 00:00:00 2001 From: Benjy Date: Sun, 19 Mar 2023 19:57:50 +1300 Subject: [PATCH 5/9] Added inchi feature --- Cargo.toml | 1 + src/bridge/mod.rs | 2 ++ tests/test_inchi.rs | 3 +++ 3 files changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 1a088f1..212759b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,4 @@ which = "4.2.5" [features] default = [] dynamic-linking-from-conda = [] +inchi = [] diff --git a/src/bridge/mod.rs b/src/bridge/mod.rs index 6299c2c..8310d82 100644 --- a/src/bridge/mod.rs +++ b/src/bridge/mod.rs @@ -4,7 +4,9 @@ pub use descriptors::ffi as descriptors_ffi; mod fingerprint; pub use fingerprint::ffi as fingerprint_ffi; +#[cfg(feature = "inchi")] mod inchi; +#[cfg(feature = "inchi")] pub use inchi::ffi as inchi_ffi; mod mol_standardize; diff --git a/tests/test_inchi.rs b/tests/test_inchi.rs index 6dbf5eb..c4d9878 100644 --- a/tests/test_inchi.rs +++ b/tests/test_inchi.rs @@ -1,4 +1,5 @@ #[test] +#[cfg(feature = "inchi")] fn test_mol_to_inchi() { cxx::let_cxx_string!(smile = "C"); let romol = rdkit_sys::ro_mol_ffi::smiles_to_mol(&smile).unwrap(); @@ -7,6 +8,7 @@ fn test_mol_to_inchi() { } #[test] +#[cfg(feature = "inchi")] fn test_good_inchi_to_mol() { cxx::let_cxx_string!(inchi = "InChI=1S/C2H6/c1-2/h1-2H3"); let romol = rdkit_sys::inchi_ffi::inchi_to_mol(&inchi).unwrap(); @@ -16,6 +18,7 @@ fn test_good_inchi_to_mol() { } #[test] +#[cfg(feature = "inchi")] fn test_bad_inchi_to_mol() { cxx::let_cxx_string!(bad_inchi = "asd"); let romol = rdkit_sys::inchi_ffi::inchi_to_mol(&bad_inchi); From f938450466f4f8c7ee446461a3a7857862d04d19 Mon Sep 17 00:00:00 2001 From: Benjy Date: Sun, 19 Mar 2023 20:16:04 +1300 Subject: [PATCH 6/9] Added inchi_to_inchi_key --- src/bridge/inchi.rs | 2 ++ tests/test_inchi.rs | 17 +++++++++++++++++ wrapper/include/inchi.h | 2 ++ wrapper/src/inchi.cc | 4 ++++ 4 files changed, 25 insertions(+) diff --git a/src/bridge/inchi.rs b/src/bridge/inchi.rs index cc139ba..21e2a0e 100644 --- a/src/bridge/inchi.rs +++ b/src/bridge/inchi.rs @@ -8,5 +8,7 @@ pub mod ffi { pub fn mol_to_inchi(mol: SharedPtr) -> String; pub fn inchi_to_mol(inchi: &CxxString) -> Result>; + + pub fn inchi_to_inchi_key(inchi: &CxxString) -> Result; } } diff --git a/tests/test_inchi.rs b/tests/test_inchi.rs index c4d9878..778b1bd 100644 --- a/tests/test_inchi.rs +++ b/tests/test_inchi.rs @@ -25,3 +25,20 @@ fn test_bad_inchi_to_mol() { assert!(romol.is_ok()); assert!(romol.unwrap().is_null()); } + +#[test] +#[cfg(feature = "inchi")] +fn test_good_inchi_to_inchi_key() { + cxx::let_cxx_string!(inchi = "InChI=1S/CH4/h1H4"); + let inchi_key = rdkit_sys::inchi_ffi::inchi_to_inchi_key(&inchi).unwrap(); + assert_eq!(inchi_key, "VNWKTOKETHGBQD-UHFFFAOYSA-N"); +} + +#[test] +#[cfg(feature = "inchi")] +fn test_bad_inchi_to_inchi_key() { + cxx::let_cxx_string!(bad_inchi = "asd"); + let inchi_key = rdkit_sys::inchi_ffi::inchi_to_inchi_key(&bad_inchi); + assert!(inchi_key.is_ok()); + assert!(inchi_key.unwrap().is_empty()); +} diff --git a/wrapper/include/inchi.h b/wrapper/include/inchi.h index 51930ab..377b538 100644 --- a/wrapper/include/inchi.h +++ b/wrapper/include/inchi.h @@ -5,4 +5,6 @@ namespace RDKit { rust::String mol_to_inchi(std::shared_ptr mol); std::shared_ptr inchi_to_mol(const std::string &inchi); + + rust::String inchi_to_inchi_key(const std::string &inchi); } \ No newline at end of file diff --git a/wrapper/src/inchi.cc b/wrapper/src/inchi.cc index c059a7f..82e41da 100644 --- a/wrapper/src/inchi.cc +++ b/wrapper/src/inchi.cc @@ -12,4 +12,8 @@ namespace RDKit { ROMol *mol = InchiToMol(inchi, rv); return std::shared_ptr(mol); } + + rust::String inchi_to_inchi_key(const std::string &inchi) { + return InchiToInchiKey(inchi); + } } \ No newline at end of file From 9a8a1b0944758f08a6ec534a21b9f00fc28e0889 Mon Sep 17 00:00:00 2001 From: Benjy Date: Mon, 20 Mar 2023 17:27:43 +1300 Subject: [PATCH 7/9] Added end-of-file newlines --- wrapper/include/inchi.h | 2 +- wrapper/src/inchi.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wrapper/include/inchi.h b/wrapper/include/inchi.h index 377b538..2578d59 100644 --- a/wrapper/include/inchi.h +++ b/wrapper/include/inchi.h @@ -7,4 +7,4 @@ namespace RDKit { std::shared_ptr inchi_to_mol(const std::string &inchi); rust::String inchi_to_inchi_key(const std::string &inchi); -} \ No newline at end of file +} diff --git a/wrapper/src/inchi.cc b/wrapper/src/inchi.cc index 82e41da..f404ba3 100644 --- a/wrapper/src/inchi.cc +++ b/wrapper/src/inchi.cc @@ -16,4 +16,4 @@ namespace RDKit { rust::String inchi_to_inchi_key(const std::string &inchi) { return InchiToInchiKey(inchi); } -} \ No newline at end of file +} From cd8b0b859626fd49a9785b024ec6753c024b4cca Mon Sep 17 00:00:00 2001 From: Benjy Date: Mon, 20 Mar 2023 17:33:22 +1300 Subject: [PATCH 8/9] Only link to RDInchiLib if inchi feature is enabled --- build.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 62c2b61..17c5548 100644 --- a/build.rs +++ b/build.rs @@ -134,10 +134,13 @@ fn main() { "SmilesParse", // "Subgraphs", "SubstructMatch", - "RDInchiLib", ] { println!("cargo:rustc-link-lib=dylib=RDKit{}", lib); } + if cfg!(feature = "inchi") { + println!("cargo:rustc-link-lib=dylib=RDKitRDInchiLib"); + } + println!("cargo:rustc-link-lib=dylib=boost_serialization"); } From 256b5f5e018c25583994bb15897b5602ec34d23a Mon Sep 17 00:00:00 2001 From: Benjy Date: Mon, 20 Mar 2023 17:58:53 +1300 Subject: [PATCH 9/9] Added Benjamin Smith as author --- AUTHORS | 3 ++- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 06370e0..702a411 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,3 +1,4 @@ Xavier Lange (xrlange@gmail.com) chrissly31415 -Axel Pahl (apahl) \ No newline at end of file +Axel Pahl (apahl) +Benjamin Smith \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 212759b..d380cbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rdkit-sys" -authors = ["Xavier Lange (xrlange@gmail.com)", "chrissly31415"] +authors = ["Xavier Lange (xrlange@gmail.com)", "chrissly31415", "Benjamin Smith"] version = "0.3.0" edition = "2021" license = "MIT"