diff --git a/lib/mu-script.cc b/lib/mu-script.cc index 05fe3dcc..61b71ca7 100644 --- a/lib/mu-script.cc +++ b/lib/mu-script.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2012-2021 Dirk-Jan C. Binnema +** Copyright (C) 2022 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the @@ -16,360 +16,150 @@ ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ - #include "config.h" -#ifdef BUILD_GUILE +#include "mu-script.hh" +#ifdef BUILD_GUILE #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wredundant-decls" #include #pragma GCC diagnostic pop #endif /*BUILD_GUILE*/ -#include -#include -#include -#include -#include -#include +#include "mu/mu-options.hh" +#include "utils/mu-utils.hh" +#include "utils/mu-option.hh" -#include "mu-script.hh" -#include "utils/mu-util.h" +#include +#include -/** - * Structure with information about a certain script. - * the values will be *freed* when MuScriptInfo is freed - */ -struct MuScriptInfo { - char* _name; /* filename-sans-extension */ - char* _path; /* full path to script */ - char* _oneline; /* one-line description */ - char* _descr; /* longer description */ -}; +using namespace Mu; -/* create a new MuScriptInfo* object*/ -static MuScriptInfo* -script_info_new(void) +static std::string +get_name(const std::string& path) { - return g_slice_new0(MuScriptInfo); + auto pos = path.find_last_of("/"); + if (pos == std::string::npos) + return path; + + auto name = path.substr(pos + 1); + + pos = name.find_last_of("."); + if (pos == std::string::npos) + return name; + + return name.substr(0, pos); } -/* destroy a MuScriptInfo* object */ -static void -script_info_destroy(MuScriptInfo* msi) + +static Mu::Option +get_info(std::string&& path, const std::string& prefix) { - if (!msi) - return; - - g_free(msi->_name); - g_free(msi->_path); - g_free(msi->_oneline); - g_free(msi->_descr); - - g_slice_free(MuScriptInfo, msi); -} - -/* compare two MuScripInfo* objects (for sorting) */ -static int -script_info_cmp(MuScriptInfo* msi1, MuScriptInfo* msi2) -{ - return strcmp(msi1->_name, msi2->_name); -} - -const char* -mu_script_info_name(MuScriptInfo* msi) -{ - g_return_val_if_fail(msi, NULL); - return msi->_name; -} - -const char* -mu_script_info_path(MuScriptInfo* msi) -{ - g_return_val_if_fail(msi, NULL); - return msi->_path; -} - -const char* -mu_script_info_one_line(MuScriptInfo* msi) -{ - g_return_val_if_fail(msi, NULL); - return msi->_oneline; -} - -const char* -mu_script_info_description(MuScriptInfo* msi) -{ - g_return_val_if_fail(msi, NULL); - return msi->_descr; -} - -gboolean -mu_script_info_matches_regex(MuScriptInfo* msi, const char* rxstr, GError** err) -{ - GRegex* rx; - gboolean match; - - g_return_val_if_fail(msi, FALSE); - g_return_val_if_fail(rxstr, FALSE); - - rx = g_regex_new(rxstr, - (GRegexCompileFlags)(G_REGEX_CASELESS | G_REGEX_OPTIMIZE), - (GRegexMatchFlags)0, - err); - if (!rx) - return FALSE; - - match = FALSE; - if (msi->_name) - match = g_regex_match(rx, msi->_name, (GRegexMatchFlags)0, NULL); - if (!match && msi->_oneline) - match = g_regex_match(rx, msi->_oneline, (GRegexMatchFlags)0, NULL); - - return match; -} - -void -mu_script_info_list_destroy(GSList* lst) -{ - g_slist_free_full(lst, (GDestroyNotify)script_info_destroy); -} - -static GIOChannel* -open_channel(const char* path) -{ - GError* err; - GIOChannel* io_chan; - - err = NULL; - - io_chan = g_io_channel_new_file(path, "r", &err); - if (!io_chan) { - g_warning("failed to open '%s': %s", - path, - err ? err->message : "something went wrong"); - g_clear_error(&err); - return NULL; + std::ifstream file{path}; + if (!file.is_open()) { + g_warning ("failed to open %s", path.c_str()); + return Nothing; } - return io_chan; -} + Mu::ScriptInfo info{}; + info.path = path; + info.name = get_name(path); -static void -end_channel(GIOChannel* io_chan) -{ - GIOStatus status; - GError* err; + std::string line; + while (std::getline(file, line)) { - err = NULL; - status = g_io_channel_shutdown(io_chan, FALSE, &err); - if (status != G_IO_STATUS_NORMAL) { - g_warning("failed to shutdown io-channel: %s", - err ? err->message : "something went wrong"); - g_clear_error(&err); - } - - g_io_channel_unref(io_chan); -} - -static gboolean -get_descriptions(MuScriptInfo* msi, const char* prefix) -{ - GIOStatus io_status; - GIOChannel* script_io; - GError* err; - - char *line, *descr, *oneline; - - if (!prefix) - return TRUE; /* not an error */ - - if (!(script_io = open_channel(msi->_path))) - return FALSE; - - err = NULL; - line = descr = oneline = NULL; - - do { - g_free(line); - io_status = g_io_channel_read_line(script_io, &line, NULL, NULL, &err); - if (io_status != G_IO_STATUS_NORMAL) - break; - - if (!g_str_has_prefix(line, prefix)) + if (line.find(prefix) != 0) continue; - if (!oneline) - oneline = g_strdup(line + strlen(prefix)); - else { - char* tmp; - tmp = descr; - descr = g_strdup_printf("%s%s", descr ? descr : "", line + strlen(prefix)); - g_free(tmp); - } + line = line.substr(prefix.length()); - } while (TRUE); - - if (io_status != G_IO_STATUS_EOF) { - g_warning("error reading %s: %s", - msi->_path, - err ? err->message : "something went wrong"); - g_clear_error(&err); + if (info.oneline.empty()) + info.oneline = line; + else + info.description += line; } - end_channel(script_io); - msi->_oneline = oneline; - msi->_descr = descr; + // std::cerr << "ONELINE: " << info.oneline << '\n'; + // std::cerr << "DESCR : " << info.description << '\n'; - return TRUE; + return info; } -GSList* -mu_script_get_script_info_list(const char* path, - const char* ext, - const char* descprefix, - GError** err) + + +static void +script_infos_in_dir(const std::string& scriptdir, Mu::ScriptInfos& infos) { - DIR* dir; - GSList* lst; - struct dirent* dentry; - - g_return_val_if_fail(path, NULL); - - dir = opendir(path); + DIR *dir = opendir(scriptdir.c_str()); if (!dir) { - mu_util_g_set_error(err, - MU_ERROR_FILE_CANNOT_OPEN, - "failed to open '%s': %s", - path, - g_strerror(errno)); - return NULL; + g_debug("failed to open '%s': %s", scriptdir.c_str(), + g_strerror(errno)); + return; } - /* create a list of names, paths */ - lst = NULL; + const std::string ext{".scm"}; + + struct dirent *dentry; while ((dentry = readdir(dir))) { - MuScriptInfo* msi; - /* only consider files with certain extensions, - * if ext != NULL */ - if (ext && !g_str_has_suffix(dentry->d_name, ext)) + + if (!g_str_has_suffix(dentry->d_name, ext.c_str())) continue; - msi = script_info_new(); - msi->_name = g_strdup(dentry->d_name); - if (ext) /* strip the extension */ - msi->_name[strlen(msi->_name) - strlen(ext)] = '\0'; - msi->_path = g_strdup_printf("%s%c%s", path, G_DIR_SEPARATOR, dentry->d_name); - /* set the one-line and long description */ - get_descriptions(msi, descprefix); - lst = g_slist_prepend(lst, msi); + + auto&& info = get_info(scriptdir + "/" + dentry->d_name, ";; INFO: "); + if (!info) + continue; + + infos.emplace_back(std::move(*info)); } closedir(dir); /* ignore error checking... */ - - return g_slist_sort(lst, (GCompareFunc)script_info_cmp); } -MuScriptInfo* -mu_script_find_script_with_name(GSList* lst, const char* name) + +Mu::ScriptInfos +Mu::script_infos(const Mu::ScriptPaths& paths) { - GSList* cur; - - g_return_val_if_fail(name, NULL); - - for (cur = lst; cur; cur = g_slist_next(cur)) { - MuScriptInfo* msi; - msi = (MuScriptInfo*)cur->data; - - if (g_strcmp0(name, mu_script_info_name(msi)) == 0) - return msi; + /* create a list of names, paths */ + ScriptInfos infos; + for (auto&& dir: paths) { + script_infos_in_dir(dir, infos); } - return NULL; + std::sort(infos.begin(), infos.end(), [](auto&& i1, auto&& i2) { + return i1.name < i2.name; + }); + + return infos; } - - -#ifdef BUILD_GUILE -static char* -quoted_from_strv (const gchar **params) +Result +Mu::run_script(const std::string& path, + const std::vector& args) { - GString *str; - int i; +#ifndef BUILD_GUILE + return Err(Error::Code::Script, + "guile script support is not available"); +#else + std::string mainargs; + for (auto&& arg: args) + mainargs += format("%s\"%s\"", + mainargs.empty() ? "" : " ", arg.c_str()); + auto expr = format("(main '(\"%s\" %s))", + get_name(path).c_str(), mainargs.c_str()); - g_return_val_if_fail (params, NULL); + std::vector argv = { + GUILE_BINARY, + "-l", path.c_str(), + "-c", expr.c_str(), + }; - if (!params[0]) - return g_strdup (""); + /* does not return */ + scm_boot_guile(argv.size(), const_cast(argv.data()), + [](void *closure, int argc, char **argv) { + scm_shell(argc, argv); + }, NULL); - str = g_string_sized_new (64); /* just a guess */ - - for (i = 0; params[i]; ++i) { - - if (i > 0) - g_string_append_c (str, ' '); - - g_string_append_c (str, '"'); - g_string_append (str, params[i]); - g_string_append_c (str, '"'); - } - - return g_string_free (str, FALSE); + return Ok(); +#endif /*BUILD_GUILE*/ } - - -static void -guile_shell(void* closure, int argc, char** argv) -{ - scm_shell(argc, argv); -} - -gboolean -mu_script_guile_run(MuScriptInfo* msi, const char* muhome, const char** args, GError** err) -{ - const char* s; - char * mainargs, *expr; - char** argv; - - g_return_val_if_fail(msi, FALSE); - g_return_val_if_fail(muhome, FALSE); - - if (access(mu_script_info_path(msi), R_OK) != 0) { - mu_util_g_set_error(err, - MU_ERROR_FILE_CANNOT_READ, - "failed to read script: %s", - g_strerror(errno)); - return FALSE; - } - - argv = g_new0(char*, 6); - argv[0] = g_strdup(GUILE_BINARY); - argv[1] = g_strdup("-l"); - - s = mu_script_info_path(msi); - argv[2] = g_strdup(s ? s : ""); - - mainargs = quoted_from_strv(args); - expr = g_strdup_printf("(main '(\"%s\" \"--muhome=%s\" %s))", - mu_script_info_name(msi), - muhome, - mainargs ? mainargs : ""); - - g_free(mainargs); - argv[3] = g_strdup("-c"); - argv[4] = expr; - - scm_boot_guile(5, argv, guile_shell, NULL); - - /* never reached but let's be correct(TM)*/ - g_strfreev(argv); - return TRUE; -} -#else /*!BUILD_GUILE*/ -gboolean -mu_script_guile_run(MuScriptInfo* msi, const char* muhome, const char** args, GError** err) -{ - mu_util_g_set_error(err, MU_ERROR_INTERNAL, "this mu does not have guile support"); - return FALSE; -} -#endif /*!BUILD_GUILE*/ diff --git a/lib/mu-script.hh b/lib/mu-script.hh index d3f7b1eb..48ff45a3 100644 --- a/lib/mu-script.hh +++ b/lib/mu-script.hh @@ -1,5 +1,5 @@ /* -** Copyright (C) 2012-2020 Dirk-Jan C. Binnema +** Copyright (C) 2022 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the @@ -16,110 +16,50 @@ ** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ** */ - #ifndef MU_SCRIPT_HH__ #define MU_SCRIPT_HH__ -#include +#include +#include -/* Opaque structure with information about a script */ -struct MuScriptInfo; +#include + +namespace Mu { /** - * get the name of the script (sans-extension, if some extension was - * provided to mu_script_get_scripts) - * - * @param msi a MuScriptInfo structure - * - * @return the name + * Information about a script. + * */ -const char* mu_script_info_name(MuScriptInfo* msi); +struct ScriptInfo { + std::string name; /**< Name of script */ + std::string path; /**< Full path to script */ + std::string oneline; /**< One-line description */ + std::string description; /**< More help */ +}; -/** - * get the full filesystem path of the script - * - * @param msi a MuScriptInfo structure - * - * @return the path +/// Sequence of script infos. +using ScriptInfos = std::vector; + +/** + * Get information about the available scripts + * + * @return infos */ -const char* mu_script_info_path(MuScriptInfo* msi); +using ScriptPaths = std::vector; +ScriptInfos script_infos(const ScriptPaths& paths); -/** - * get a one-line description for the script - * - * @param msi a MuScriptInfo structure - * - * @return the description, or NULL if there was none + +/** + * Run some specific script + * + * @param path full path to the scripts + * @param args argument vector to pass to the script + * + * @return Ok() or some error; however, note that this does not return after succesfully + * starting a script. */ -const char* mu_script_info_one_line(MuScriptInfo* msi); +Result run_script(const std::string& path, const std::vector& args); -/** - * get a full description for the script - * - * @param msi a MuScriptInfo structure - * - * @return the description, or NULL if there was none - */ -const char* mu_script_info_description(MuScriptInfo* msi); +} // namepace Mu -/** - * check whether either the name or one-line description of a - * MuScriptInfo matches regular expression rxstr - * - * @param msi a MuScriptInfo - * @param rxstr a regular expression string - * @param err receives error information - * - * @return TRUE if it matches, FALSE if not or in case of error - */ -gboolean mu_script_info_matches_regex(MuScriptInfo* msi, const char* rxstr, GError** err); - -/** - * Get the list of all scripts in path with extension ext - * - * @param path a file system path - * @param ext an extension (e.g., ".scm"), or NULL - * @param prefix for the one-line description - * (e.g., ";; DESCRIPTION: "), or NULL - * @param err receives error information, if any - * - * @return a list of Mu - */ -GSList* mu_script_get_script_info_list(const char* path, - const char* ext, - const char* descprefix, - GError** err); - -/** - * destroy a list of MuScriptInfo* objects - * - * @param scriptslst a list of MuScriptInfo* objects - */ -void mu_script_info_list_destroy(GSList* lst); - -/** - * find the MuScriptInfo object for the first script with a certain - * name, or return NULL if not found. - * - * @param lst a list of MuScriptInfo* objects - * @param name the name to search for - * - * @return a MuScriptInfo* object, or NULL if not found. - */ -MuScriptInfo* mu_script_find_script_with_name(GSList* lst, const char* name); - -/** - * run the guile script at path - * - * @param msi MuScriptInfo object for the script - * @param muhome path to the mu home dir - * @param args NULL-terminated array of strings (argv for the script) - * @param err receives error information - * - * @return FALSE in case of error -- otherwise, this function will - * _not return_ - */ -gboolean -mu_script_guile_run(MuScriptInfo* msi, const char* muhome, const char** args, GError** err); - -#endif /*MU_SCRIPT_HH__*/ +#endif /* MU_SCRIPT_HH__ */ diff --git a/lib/utils/mu-error.hh b/lib/utils/mu-error.hh index 71eea784..9aa1c8ec 100644 --- a/lib/utils/mu-error.hh +++ b/lib/utils/mu-error.hh @@ -56,6 +56,8 @@ struct Error final : public std::exception { Play = ERROR_ENUM(1,0), Query = ERROR_ENUM(1,0), SchemaMismatch = ERROR_ENUM(1,0), + Script = ERROR_ENUM(1,0), + ScriptNotFound = ERROR_ENUM(1,0), Store = ERROR_ENUM(1,0), StoreLock = ERROR_ENUM(19,0), UnverifiedSignature = ERROR_ENUM(1,0), diff --git a/mu/mu-cmd-script.cc b/mu/mu-cmd-script.cc index 62ef9c12..096f1cfc 100644 --- a/mu/mu-cmd-script.cc +++ b/mu/mu-cmd-script.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2012-2020 Dirk-Jan C. Binnema +** Copyright (C) 2012-2022 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the @@ -17,184 +17,33 @@ ** */ -#if HAVE_CONFIG_H #include "config.h" -#endif /*HAVE_CONFIG_H*/ - -#include -#include -#include -#include -#include #include "mu-cmd.hh" #include "mu-script.hh" -#include "mu-runtime.hh" - -#include "utils/mu-util.h" - -#define MU_GUILE_EXT ".scm" -#define MU_GUILE_DESCR_PREFIX ";; INFO: " - -#define COL(C) ((color) ? C : "") +#include "utils/mu-utils.hh" using namespace Mu; -static void -print_script(const char* name, - const char* oneline, - const char* descr, - gboolean color, - gboolean verbose) +Result +Mu::mu_cmd_script(const Options& opts) { - g_print("%s%s%s%s%s%s%s%s", - verbose ? "\n" : " * ", - COL(MU_COLOR_GREEN), - name, - COL(MU_COLOR_DEFAULT), - oneline ? ": " : "", - COL(MU_COLOR_BLUE), - oneline ? oneline : "", - MU_COLOR_DEFAULT); + ScriptPaths paths = { MU_SCRIPTS_DIR }; + const auto&& scriptinfos{script_infos(paths)}; + auto script_it = Mu::seq_find_if(scriptinfos, [&](auto&& item) { + return item.name == opts.script.name; + }); - if (verbose && descr) - g_print("%s%s%s", COL(MU_COLOR_MAGENTA), descr, COL(MU_COLOR_DEFAULT)); -} - -static gboolean -print_scripts(GSList* scripts, gboolean color, gboolean verbose, const char* rxstr, GError** err) -{ - GSList* cur; - const char* verb; - - if (!scripts) { - g_print("No scripts available\n"); - return TRUE; /* not an error */ - } - - verb = verbose ? "" : " (use --verbose for details)"; - - if (rxstr) - g_print("Available scripts matching '%s'%s:\n", rxstr, verb); - else - g_print("Available scripts%s:\n", verb); - - for (cur = scripts; cur; cur = g_slist_next(cur)) { - MuScriptInfo* msi; - const char * descr, *oneline, *name; - - msi = (MuScriptInfo*)cur->data; - name = mu_script_info_name(msi); - oneline = mu_script_info_one_line(msi); - descr = mu_script_info_description(msi); - - /* if rxstr is provide, only consider matching scriptinfos */ - if (rxstr && !mu_script_info_matches_regex(msi, rxstr, err)) { - if (err && *err) - return FALSE; - continue; - } - - print_script(name, oneline, descr, color, verbose); - } - - return TRUE; -} - -static char* -get_userpath(const char* muhome) -{ - if (muhome) - return g_build_path(G_DIR_SEPARATOR_S, muhome, "scripts", NULL); - else - return g_build_path(G_DIR_SEPARATOR_S, - g_get_user_data_dir(), - "mu", - "scripts", - NULL); -} - -static GSList* -get_script_info_list(const char* muhome, GError** err) -{ - GSList *scripts, *userscripts, *last; - char* userpath; - - scripts = mu_script_get_script_info_list(MU_SCRIPTS_DIR, - MU_GUILE_EXT, - MU_GUILE_DESCR_PREFIX, - err); - - if (err && *err) - return NULL; - - userpath = get_userpath(muhome); - - /* is there are userdir for scripts? */ - if (!mu_util_check_dir(userpath, TRUE, FALSE)) { - g_free(userpath); - return scripts; - } - - /* append it to the list we already have */ - userscripts = - mu_script_get_script_info_list(userpath, MU_GUILE_EXT, MU_GUILE_DESCR_PREFIX, err); - g_free(userpath); - - /* some error, return nothing */ - if (err && *err) { - mu_script_info_list_destroy(userscripts); - mu_script_info_list_destroy(scripts); - return NULL; - } - - /* append the user scripts */ - last = g_slist_last(scripts); - if (last) { - last->next = userscripts; - return scripts; - } else - return userscripts; /* apparently, scripts was NULL */ -} - -Mu::Result -Mu::mu_cmd_script(const MuConfig* opts) -{ - GError *err{}; - MuScriptInfo* msi; - GSList* scripts; - - if (!mu_util_supports(MU_FEATURE_GUILE)) + if (script_it == scriptinfos.cend()) return Err(Error::Code::InvalidArgument, - "