diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 0000000..ff95e18 --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "inchi-gem", + "image": "ruby:3.3.10", + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": true, + "configureZshAsDefaultShell": true, + "installOhMyZsh": true, + "installOhMyZshConfig": true, + "upgradePackages": true, + "username": "automatic", + "userUid": "automatic", + "userGid": "automatic" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "eamodio.gitlens" + ] + } + }, + "postCreateCommand": "apt-get update && apt-get install cmake -y" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9cd31e4..2af8972 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,9 @@ Gemfile.lock pkg/* # ignore build artifacts -ext/inchi-gem/Makefile -ext/inchi-gem/inchi/ -ext/inchi-gem/inchi.o -ext/inchi-gem/inchi_wrap.* +ext/inchi-gem/* +!ext/inchi-gem/extconf.rb +!ext/inchi-gem/inchi.c .tool-versions diff --git a/ext/inchi-gem/extconf.rb b/ext/inchi-gem/extconf.rb index 1a5ba93..0efa2b8 100644 --- a/ext/inchi-gem/extconf.rb +++ b/ext/inchi-gem/extconf.rb @@ -2,153 +2,28 @@ require 'rbconfig' require 'mkmf' -# current dir main_dir = File.expand_path(File.join(File.dirname(__FILE__))) +inchi_src = File.join(main_dir, "inchi_src") +libinchi_src = File.join(inchi_src, "INCHI-1-SRC/INCHI_API/libinchi/src") +libinchi_build = File.join(inchi_src, "build") +inchi_version = "d124ee52b4de0ed3d608d2a3d522b939f01f2549" -# ext/inchi-gem/inchi -inchi_src_dir = File.join(main_dir, "inchi") - -FileUtils.rm_rf inchi_src_dir - +### Download InChI source +FileUtils.rm_rf inchi_src Dir.chdir main_dir -puts "Downloading InChI sources" -# ext/inchi-gem/inchi/ -system "git clone https://github.com/CamAnNguyen/inchi" or abort - -# ext/inchi-gem/inchi/INCHI_BASE/src -> inchi-gem/build/inchi/INCHI_API/libinchi/src -FileUtils.cp_r("#{inchi_src_dir}/INCHI_BASE/src/.", "#{inchi_src_dir}/INCHI_API/libinchi/src") - -inc_dirs = '-I. -I./inchi/INCHI_API/libinchi/src' +system("git clone --no-checkout --filter=tree:0 https://github.com/IUPAC-InChI/InChI.git #{inchi_src}") or abort +Dir.chdir inchi_src +system("git fetch --depth 1 origin #{inchi_version}") or abort +system("git sparse-checkout set --no-cone /INCHI-1-SRC") or abort +system("git checkout #{inchi_version}") or abort -system("swig #{inc_dirs} -c++ -ruby inchi.i") or abort +### Compile InChI +system("cmake -B #{libinchi_build} -S #{libinchi_src}") or abort +system("cmake --build #{libinchi_build}") or abort -$INCFLAGS << inc_dirs -$CFLAGS << " -DTARGET_API_LIB" +### Configure Ruby extension +find_library('inchi', "MakeINCHIFromMolfileText", File.join(libinchi_build, "lib")) +find_header('inchi_api.h', File.join(inchi_src, "/INCHI-1-SRC/INCHI_BASE/src")) -$srcs = [ - 'inchi.cpp', - 'inchi_wrap.cxx', - './inchi/INCHI_API/libinchi/src/ichi_bns.c', - './inchi/INCHI_API/libinchi/src/ichi_io.c', - './inchi/INCHI_API/libinchi/src/ichican2.c', - './inchi/INCHI_API/libinchi/src/ichicano.c', - './inchi/INCHI_API/libinchi/src/ichicans.c', - './inchi/INCHI_API/libinchi/src/ichierr.c', - './inchi/INCHI_API/libinchi/src/ichiisot.c', - './inchi/INCHI_API/libinchi/src/ichilnct.c', - './inchi/INCHI_API/libinchi/src/ichimak2.c', - './inchi/INCHI_API/libinchi/src/ichimake.c', - './inchi/INCHI_API/libinchi/src/ichimap1.c', - './inchi/INCHI_API/libinchi/src/ichimap2.c', - './inchi/INCHI_API/libinchi/src/ichimap4.c', - './inchi/INCHI_API/libinchi/src/ichinorm.c', - './inchi/INCHI_API/libinchi/src/ichiparm.c', - './inchi/INCHI_API/libinchi/src/ichiprt1.c', - './inchi/INCHI_API/libinchi/src/ichiprt2.c', - './inchi/INCHI_API/libinchi/src/ichiprt3.c', - './inchi/INCHI_API/libinchi/src/ichiqueu.c', - './inchi/INCHI_API/libinchi/src/ichiread.c', - './inchi/INCHI_API/libinchi/src/ichiring.c', - './inchi/INCHI_API/libinchi/src/ichirvr1.c', - './inchi/INCHI_API/libinchi/src/ichirvr2.c', - './inchi/INCHI_API/libinchi/src/ichirvr3.c', - './inchi/INCHI_API/libinchi/src/ichirvr4.c', - './inchi/INCHI_API/libinchi/src/ichirvr5.c', - './inchi/INCHI_API/libinchi/src/ichirvr6.c', - './inchi/INCHI_API/libinchi/src/ichirvr7.c', - './inchi/INCHI_API/libinchi/src/ichisort.c', - './inchi/INCHI_API/libinchi/src/ichister.c', - './inchi/INCHI_API/libinchi/src/ichitaut.c', - './inchi/INCHI_API/libinchi/src/ikey_base26.c', - './inchi/INCHI_API/libinchi/src/ikey_dll.c', - './inchi/INCHI_API/libinchi/src/inchi_dll.c', - './inchi/INCHI_API/libinchi/src/inchi_dll_a.c', - './inchi/INCHI_API/libinchi/src/inchi_dll_a2.c', - './inchi/INCHI_API/libinchi/src/inchi_dll_b.c', - './inchi/INCHI_API/libinchi/src/inchi_dll_main.c', - './inchi/INCHI_API/libinchi/src/inchi_gui.c', - './inchi/INCHI_API/libinchi/src/mol2atom.c', - './inchi/INCHI_API/libinchi/src/mol_fmt1.c', - './inchi/INCHI_API/libinchi/src/mol_fmt2.c', - './inchi/INCHI_API/libinchi/src/mol_fmt3.c', - './inchi/INCHI_API/libinchi/src/mol_fmt4.c', - './inchi/INCHI_API/libinchi/src/readinch.c', - './inchi/INCHI_API/libinchi/src/runichi.c', - './inchi/INCHI_API/libinchi/src/runichi2.c', - './inchi/INCHI_API/libinchi/src/runichi3.c', - './inchi/INCHI_API/libinchi/src/runichi4.c', - './inchi/INCHI_API/libinchi/src/sha2.c', - './inchi/INCHI_API/libinchi/src/strutil.c', - './inchi/INCHI_API/libinchi/src/util.c', - './inchi/INCHI_API/libinchi/src/ixa/ixa_builder.c', - './inchi/INCHI_API/libinchi/src/ixa/ixa_inchikey_builder.c', - './inchi/INCHI_API/libinchi/src/ixa/ixa_mol.c', - './inchi/INCHI_API/libinchi/src/ixa/ixa_read_inchi.c', - './inchi/INCHI_API/libinchi/src/ixa/ixa_read_mol.c', - './inchi/INCHI_API/libinchi/src/ixa/ixa_status.c', -] - -$objs = [ - 'inchi.o', - 'inchi_wrap.o', - './inchi/INCHI_API/libinchi/src/ichi_bns.o', - './inchi/INCHI_API/libinchi/src/ichi_io.o', - './inchi/INCHI_API/libinchi/src/ichican2.o', - './inchi/INCHI_API/libinchi/src/ichicano.o', - './inchi/INCHI_API/libinchi/src/ichicans.o', - './inchi/INCHI_API/libinchi/src/ichierr.o', - './inchi/INCHI_API/libinchi/src/ichiisot.o', - './inchi/INCHI_API/libinchi/src/ichilnct.o', - './inchi/INCHI_API/libinchi/src/ichimak2.o', - './inchi/INCHI_API/libinchi/src/ichimake.o', - './inchi/INCHI_API/libinchi/src/ichimap1.o', - './inchi/INCHI_API/libinchi/src/ichimap2.o', - './inchi/INCHI_API/libinchi/src/ichimap4.o', - './inchi/INCHI_API/libinchi/src/ichinorm.o', - './inchi/INCHI_API/libinchi/src/ichiparm.o', - './inchi/INCHI_API/libinchi/src/ichiprt1.o', - './inchi/INCHI_API/libinchi/src/ichiprt2.o', - './inchi/INCHI_API/libinchi/src/ichiprt3.o', - './inchi/INCHI_API/libinchi/src/ichiqueu.o', - './inchi/INCHI_API/libinchi/src/ichiread.o', - './inchi/INCHI_API/libinchi/src/ichiring.o', - './inchi/INCHI_API/libinchi/src/ichirvr1.o', - './inchi/INCHI_API/libinchi/src/ichirvr2.o', - './inchi/INCHI_API/libinchi/src/ichirvr3.o', - './inchi/INCHI_API/libinchi/src/ichirvr4.o', - './inchi/INCHI_API/libinchi/src/ichirvr5.o', - './inchi/INCHI_API/libinchi/src/ichirvr6.o', - './inchi/INCHI_API/libinchi/src/ichirvr7.o', - './inchi/INCHI_API/libinchi/src/ichisort.o', - './inchi/INCHI_API/libinchi/src/ichister.o', - './inchi/INCHI_API/libinchi/src/ichitaut.o', - './inchi/INCHI_API/libinchi/src/ikey_base26.o', - './inchi/INCHI_API/libinchi/src/ikey_dll.o', - './inchi/INCHI_API/libinchi/src/inchi_dll.o', - './inchi/INCHI_API/libinchi/src/inchi_dll_a.o', - './inchi/INCHI_API/libinchi/src/inchi_dll_a2.o', - './inchi/INCHI_API/libinchi/src/inchi_dll_b.o', - './inchi/INCHI_API/libinchi/src/inchi_dll_main.o', - './inchi/INCHI_API/libinchi/src/inchi_gui.o', - './inchi/INCHI_API/libinchi/src/mol2atom.o', - './inchi/INCHI_API/libinchi/src/mol_fmt1.o', - './inchi/INCHI_API/libinchi/src/mol_fmt2.o', - './inchi/INCHI_API/libinchi/src/mol_fmt3.o', - './inchi/INCHI_API/libinchi/src/mol_fmt4.o', - './inchi/INCHI_API/libinchi/src/readinch.o', - './inchi/INCHI_API/libinchi/src/runichi.o', - './inchi/INCHI_API/libinchi/src/runichi2.o', - './inchi/INCHI_API/libinchi/src/runichi3.o', - './inchi/INCHI_API/libinchi/src/runichi4.o', - './inchi/INCHI_API/libinchi/src/sha2.o', - './inchi/INCHI_API/libinchi/src/strutil.o', - './inchi/INCHI_API/libinchi/src/util.o', - './inchi/INCHI_API/libinchi/src/ixa/ixa_builder.o', - './inchi/INCHI_API/libinchi/src/ixa/ixa_inchikey_builder.o', - './inchi/INCHI_API/libinchi/src/ixa/ixa_mol.o', - './inchi/INCHI_API/libinchi/src/ixa/ixa_read_inchi.o', - './inchi/INCHI_API/libinchi/src/ixa/ixa_read_mol.o', - './inchi/INCHI_API/libinchi/src/ixa/ixa_status.o', -] - -create_makefile('inchi') +Dir.chdir main_dir +create_makefile("inchi") diff --git a/ext/inchi-gem/inchi.c b/ext/inchi-gem/inchi.c new file mode 100644 index 0000000..b1ca967 --- /dev/null +++ b/ext/inchi-gem/inchi.c @@ -0,0 +1,122 @@ +#include +#include +#include + + +typedef struct { + int returnCode; + VALUE message; + VALUE log; + VALUE auxInfo; +} rb_ExtraInchiReturnValues; + +static void +rb_eirv_free(void *ptr) +{ + ruby_xfree(ptr); +} + +static const rb_data_type_t rb_eirv_type = { + "ExtraInchiReturnValues", + {0, rb_eirv_free, 0}, + 0, 0, + RUBY_TYPED_FREE_IMMEDIATELY +}; + +static VALUE +rb_eirv_alloc(VALUE klass) +{ + rb_ExtraInchiReturnValues *p; + VALUE obj = TypedData_Make_Struct(klass, rb_ExtraInchiReturnValues, + &rb_eirv_type, p); + p->returnCode = 0; + p->message = Qnil; + p->log = Qnil; + p->auxInfo = Qnil; + return obj; +} + +static void +fix_option_symbols(const char *in, char *out) +{ + const char *s = in; + char *d = out; + while ((*d = *s)) { +#ifdef _WIN32 + if (*d == '-') *d = '/'; +#else + if (*d == '/') *d = '-'; +#endif + ++d; ++s; + } +} + +static VALUE +rb_molfile_to_inchi(int argc, VALUE *argv, VALUE self) +{ + VALUE rb_mol, rb_rv, rb_opts; + rb_scan_args(argc, argv, "21", &rb_mol, &rb_rv, &rb_opts); + if (NIL_P(rb_opts)) rb_opts = rb_str_new2(""); + + Check_Type(rb_mol, T_STRING); + Check_Type(rb_opts, T_STRING); + + rb_ExtraInchiReturnValues *rv; + TypedData_Get_Struct(rb_rv, rb_ExtraInchiReturnValues, + &rb_eirv_type, rv); + + const char *mol_text = RSTRING_PTR(rb_mol); + const char *opts_in = RSTRING_LEN(rb_opts) ? RSTRING_PTR(rb_opts) : NULL; + + char *opts_copy = NULL; + if (opts_in) { + opts_copy = ALLOCA_N(char, RSTRING_LEN(rb_opts) + 1); + fix_option_symbols(opts_in, opts_copy); + } + + inchi_Output out; + memset(&out, 0, sizeof(out)); + + int ret = MakeINCHIFromMolfileText(mol_text, opts_copy, &out); + + rv->returnCode = ret; + rv->message = (out.szMessage ? rb_str_new2(out.szMessage) : Qnil); + rv->log = (out.szLog ? rb_str_new2(out.szLog) : Qnil); + rv->auxInfo = (out.szAuxInfo ? rb_str_new2(out.szAuxInfo) : Qnil); + VALUE result = out.szInChI ? rb_str_new2(out.szInChI) : rb_str_new2(""); + FreeINCHI(&out); + return result; +} + +static VALUE +rb_inchi_to_inchi_key(VALUE self, VALUE rb_inchi) +{ + Check_Type(rb_inchi, T_STRING); + const char *inchi = RSTRING_PTR(rb_inchi); + + char inchi_key[29], xtra1[65], xtra2[65]; + int rc = GetINCHIKeyFromINCHI(inchi, 0, 0, inchi_key, xtra1, xtra2); + + if (rc == INCHIKEY_OK) + return rb_str_new2(inchi_key); + + return Qnil; +} + +void +Init_inchi(void) +{ + VALUE mInchi = rb_define_module("Inchi"); + + VALUE cRV = rb_define_class_under(mInchi, "ExtraInchiReturnValues", rb_cObject); + rb_define_alloc_func(cRV, rb_eirv_alloc); + rb_define_attr(cRV, "returnCode", 1, 1); + rb_define_attr(cRV, "message", 1, 1); + rb_define_attr(cRV, "log", 1, 1); + rb_define_attr(cRV, "auxInfo", 1, 1); + + rb_define_module_function(mInchi, "molfileToInchi", + rb_molfile_to_inchi, -1); + rb_define_module_function(mInchi, "InchiToInchiKey", + rb_inchi_to_inchi_key, 1); +} diff --git a/ext/inchi-gem/inchi.cpp b/ext/inchi-gem/inchi.cpp deleted file mode 100644 index 2da17e6..0000000 --- a/ext/inchi-gem/inchi.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "inchi.h" - -typedef std::pair INT_PAIR; -typedef std::vector INT_PAIR_VECT; - -void fixOptionSymbol(const char* in, char* out) { - unsigned int i; - for (i = 0; i < strlen(in); i++) { -#ifdef _WIN32 - if (in[i] == '-') { - out[i] = '/'; - -#else - if (in[i] == '/') { - out[i] = '-'; - -#endif - } else { - out[i] = in[i]; - } - } - out[i] = '\0'; -} - -std::string molfileToInchi(const std::string& molBlock, - ExtraInchiReturnValues& rv, const char* options) { - // create output - inchi_Output output; - memset((void*)&output, 0, sizeof(output)); - // call DLL - std::string inchi; - { - char* _options = nullptr; - if (options) { - _options = new char[strlen(options) + 1]; - fixOptionSymbol(options, _options); - options = _options; - } - - int retcode = MakeINCHIFromMolfileText(molBlock.c_str(), (char*)options, &output); - - // generate output - rv.returnCode = retcode; - if (output.szInChI) { - inchi = std::string(output.szInChI); - } - if (output.szMessage) { - rv.messagePtr = std::string(output.szMessage); - } - if (output.szLog) { - rv.logPtr = std::string(output.szLog); - } - if (output.szAuxInfo) { - rv.auxInfoPtr = std::string(output.szAuxInfo); - } - - // clean up - FreeINCHI(&output); - delete[] _options; - } - - return inchi; -} - -std::string InchiToInchiKey(const std::string& inchi) { - char inchiKey[29]; - char xtra1[65], xtra2[65]; - int ret = 0; - - { - ret = GetINCHIKeyFromINCHI(inchi.c_str(), 0, 0, inchiKey, xtra1, xtra2); - } - - std::string error; - switch (ret) { - case INCHIKEY_OK: - return std::string(inchiKey); - case INCHIKEY_UNKNOWN_ERROR: - error = "Unknown error"; - break; - case INCHIKEY_EMPTY_INPUT: - error = "Empty input"; - break; - case INCHIKEY_INVALID_INCHI_PREFIX: - error = "Invalid InChI prefix"; - break; - case INCHIKEY_NOT_ENOUGH_MEMORY: - error = "Not enough memory"; - break; - case INCHIKEY_INVALID_INCHI: - error = "Invalid input InChI string"; - break; - case INCHIKEY_INVALID_STD_INCHI: - error = "Invalid standard InChI string"; - break; - } - - return std::string(); -} diff --git a/ext/inchi-gem/inchi.h b/ext/inchi-gem/inchi.h deleted file mode 100644 index bddede8..0000000 --- a/ext/inchi-gem/inchi.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef INCHI_H_ -#define INCHI_H_ - -#include - -typedef std::vector MolVect; - -struct ExtraInchiReturnValues { - int returnCode; - std::string messagePtr; - std::string logPtr; - std::string auxInfoPtr; // not used for InchiToMol -}; - -/* options: space-delimited; - each is preceded by '/' or '-' depending on OS and compiler */ -std::string molfileToInchi(const std::string& mol, - ExtraInchiReturnValues& rv, const char* options = NULL); -std::string InchiToInchiKey(const std::string& inchi); - -#endif /* INCHI_H_ */ diff --git a/ext/inchi-gem/inchi.i b/ext/inchi-gem/inchi.i deleted file mode 100644 index 54be3a8..0000000 --- a/ext/inchi-gem/inchi.i +++ /dev/null @@ -1,9 +0,0 @@ -%module inchi - -%include stl.i - -%{ -#include "inchi.h" -%} - -%include "inchi.h"