Index: trunk/scconfig/plugins.h =================================================================== --- trunk/scconfig/plugins.h (revision 32491) +++ trunk/scconfig/plugins.h (revision 32492) @@ -16,6 +16,7 @@ plugin_def("irc", "on-line support (IRC)", sbuildin, 1, 0) plugin_def("loghid", "diagnostics: log HID calls", sdisable, 1, 0) plugin_def("stroke", "mouse gestures", sbuildin, 1, 1) +plugin_def("script", "fungw turing complete scripting", sbuildin, 1, 1) plugin_header("\nHID plugins:\n") plugin_def("hid_batch", "batch process (no-gui HID)", sbuildin, 1, 1) Index: trunk/src/Makefile.dep =================================================================== --- trunk/src/Makefile.dep (revision 32491) +++ trunk/src/Makefile.dep (revision 32492) @@ -335,7 +335,7 @@ ../src_3rd/liblihata/lihata.h ../src/librnd/core/list_conf.h \ ../src_3rd/genlist/gentdlist_undef.h ../src_3rd/genlist/gentdlist_impl.h \ ../src_3rd/genlist/gendlist.h ../src/librnd/core/grid.h ../src/librnd/core/funchash.h \ - ../src/librnd/core/hid_menu.h + ../src/librnd/core/hid_menu.h ../src/librnd/core/../../../config.h ../src/librnd/core/hid_menu.o: ../src/librnd/core/hid_menu.c \ ../src_3rd/liblihata/lihata.h ../src_3rd/liblihata/tree.h \ ../src_3rd/liblihata/dom.h ../src_3rd/liblihata/lihata.h \ @@ -1592,6 +1592,7 @@ ../src/librnd/plugins/lib_hid_common/dlg_comm_m.h \ ../src/librnd/plugins/lib_hid_common/dlg_log.h \ ../src/librnd/plugins/lib_hid_common/act_dad.h \ + ../src/librnd/plugins/lib_hid_common/toolbar.h \ ../src/librnd/plugins/lib_hid_common/zoompan.h \ ../src/librnd/plugins/lib_hid_common/conf_internal.c \ ../src/librnd/plugins/lib_hid_common/dialogs_conf_fields.h @@ -1645,8 +1646,9 @@ ../src/librnd/core/conf.h ../src_3rd/liblihata/lihata.h ../src/librnd/core/list_conf.h \ ../src_3rd/genlist/gentdlist_undef.h ../src_3rd/genlist/gentdlist_impl.h \ ../src_3rd/genlist/gendlist.h ../src/librnd/core/conf_hid.h \ - ../src/librnd/plugins/lib_hid_common/toolbar.h ../src/librnd/core/event.h \ - ../src/librnd/core/hidlib.h + ../src/librnd/core/actions.h ../src_3rd/libfungw/fungw.h \ + ../src_3rd/genht/htpp.h ../src/librnd/plugins/lib_hid_common/toolbar.h \ + ../src/librnd/core/event.h ../src/librnd/core/hidlib.h ../src/librnd/plugins/lib_hid_common/xpm.o: \ ../src/librnd/plugins/lib_hid_common/xpm.c ../config.h ../src/librnd/config.h \ ../src/librnd/plugins/lib_hid_common/xpm/question.xpm \ @@ -1767,6 +1769,62 @@ ../src/librnd/plugins/stroke/conf_internal.c \ ../src/librnd/plugins/stroke/stroke_conf.h \ ../src/librnd/plugins/stroke/stroke_conf_fields.h +librnd/plugins/script/live_script.o: ../src/librnd/plugins/script/live_script.c \ + ../config.h ../src/librnd/config.h ../src_3rd/genvector/vtp0.h \ + ../src_3rd/genvector/genvector_impl.h \ + ../src_3rd/genvector/genvector_undef.h ../src_3rd/genht/htsp.h \ + ../src_3rd/genht/ht.h ../src_3rd/genht/hash.h ../src_3rd/puplug/util.h \ + ../src/librnd/core/actions.h ../src/librnd/core/hid.h ../src_3rd/liblihata/dom.h \ + ../src_3rd/liblihata/lihata.h ../src_3rd/liblihata/parser.h \ + ../src/librnd/core/error.h ../src/librnd/core/global_typedefs.h ../src/librnd/core/rnd_bool.h \ + ../src/librnd/core/box.h ../src/librnd/core/math_helper.h ../src/librnd/core/misc_util.h \ + ../src/librnd/core/unit.h ../src_3rd/libfungw/fungw.h ../src_3rd/genht/htpp.h \ + ../src/librnd/core/plugins.h ../src_3rd/puplug/puplug.h \ + ../src_3rd/puplug/libs.h ../src_3rd/puplug/os_dep.h \ + ../src_3rd/puplug/config.h ../src_3rd/puplug/libs.h \ + ../src_3rd/puplug/error.h ../src/librnd/core/hid_cfg.h ../src/librnd/core/hid_menu.h \ + ../src/librnd/core/hid_dad.h ../src/librnd/core/compat_misc.h ../src/librnd/core/hid_attrib.h \ + ../src_3rd/genlist/gendlist.h ../src/librnd/core/color.h \ + ../src/librnd/core/rnd_printf.h ../src_3rd/genvector/gds_char.h \ + ../src/librnd/core/hid_dad_spin.h ../src/librnd/core/safe_fs.h ../src/librnd/core/conf.h \ + ../src_3rd/liblihata/lihata.h ../src/librnd/core/list_conf.h \ + ../src_3rd/genlist/gentdlist_undef.h ../src_3rd/genlist/gentdlist_impl.h \ + ../src_3rd/genlist/gendlist.h ../src/librnd/core/safe_fs_dir.h \ + ../src/librnd/core/compat_inc.h ../src/librnd/core/compat_fs.h ../src/librnd/core/event.h \ + ../src/librnd/core/hidlib.h ../src/librnd/core/hid_menu.h \ + ../src/librnd/plugins/script/script.h ../src/librnd/plugins/script/live_script.h \ + ../src/librnd/plugins/script/menu_internal.c ../src/librnd/plugins/script/glue_undo.c +librnd/plugins/script/script.o: ../src/librnd/plugins/script/script.c \ + ../config.h ../src/librnd/config.h ../src_3rd/genht/htsp.h \ + ../src_3rd/genht/ht.h ../src_3rd/genht/hash.h ../src_3rd/puplug/puplug.h \ + ../src_3rd/puplug/libs.h ../src_3rd/puplug/os_dep.h \ + ../src_3rd/puplug/config.h ../src_3rd/puplug/libs.h \ + ../src_3rd/genregex/regex_se.h ../src_3rd/genregex/regex_templ.h \ + ../src_3rd/genregex/regex.h ../src/librnd/core/actions.h ../src/librnd/core/hid.h \ + ../src_3rd/liblihata/dom.h ../src_3rd/liblihata/lihata.h \ + ../src_3rd/liblihata/parser.h ../src/librnd/core/error.h \ + ../src/librnd/core/global_typedefs.h ../src/librnd/core/rnd_bool.h ../src/librnd/core/box.h \ + ../src/librnd/core/math_helper.h ../src/librnd/core/misc_util.h ../src/librnd/core/unit.h \ + ../src_3rd/libfungw/fungw.h ../src_3rd/genht/htpp.h \ + ../src/librnd/core/plugins.h ../src_3rd/puplug/error.h \ + ../src/librnd/core/compat_misc.h ../src/librnd/core/compat_fs.h ../src/librnd/core/safe_fs.h \ + ../src/librnd/core/conf.h ../src/librnd/core/rnd_printf.h \ + ../src_3rd/genvector/gds_char.h ../src_3rd/genvector/genvector_impl.h \ + ../src_3rd/genvector/genvector_undef.h ../src_3rd/liblihata/lihata.h \ + ../src_3rd/genvector/vtp0.h ../src/librnd/core/list_conf.h \ + ../src_3rd/genlist/gentdlist_undef.h ../src_3rd/genlist/gentdlist_impl.h \ + ../src_3rd/genlist/gendlist.h ../src/librnd/core/hid_menu.h \ + ../src/librnd/core/hid_cfg.h ../src/librnd/core/hid_menu.h ../src/librnd/core/event.h \ + ../src/librnd/core/hidlib.h ../src/librnd/core/globalconst.h ../src/librnd/core/hid_init.h \ + ../src/librnd/plugins/script/script.h ../src/librnd/plugins/script/guess_lang.c \ + ../src_3rd/genht/htss.h ../src_3rd/genht/ht_utils.h \ + ../src_3rd/puplug/util.h ../src/librnd/plugins/script/c_script.c \ + ../src/librnd/core/fptr_cast.h ../src/librnd/core/hidlib_conf.h ../src/librnd/core/color.h \ + ../src/librnd/plugins/script/timer.c ../src/librnd/plugins/script/perma.c \ + ../src/librnd/plugins/script/script_act.c ../src/librnd/core/hid_dad.h \ + ../src/librnd/core/hid_attrib.h ../src_3rd/genlist/gendlist.h \ + ../src/librnd/core/hid_dad_spin.h ../src/librnd/core/hid_dad_tree.h \ + ../src/librnd/plugins/script/live_script.h ../src_3rd/gensexpr/gsx_parse.o: ../src_3rd/gensexpr/gsx_parse.c \ ../src_3rd/gensexpr/gsx_parse.h ../src_3rd/gensexpr/gsxl.o: ../src_3rd/gensexpr/gsxl.c \ @@ -1787,6 +1845,14 @@ ../src_3rd/libporty_net/pnet_config.h \ ../src_3rd/libporty_net/phost_types.h ../src/librnd/core/compat_misc.h \ ../src/librnd/config.h +../src_3rd/genregex/regex.o: ../src_3rd/genregex/regex.c \ + ../src_3rd/genregex/regex.h +../src_3rd/genregex/regex_se.o: ../src_3rd/genregex/regex_se.c \ + ../src_3rd/genregex/regex_templ.c ../src_3rd/genregex/regex_templ.h \ + ../src_3rd/genregex/regex.h +../src_3rd/genregex/regex_sei.o: ../src_3rd/genregex/regex_sei.c \ + ../src_3rd/genregex/regex_templ.c ../src_3rd/genregex/regex_templ.h \ + ../src_3rd/genregex/regex.h ../src_3rd/genvector/gds_char.o: ../src_3rd/genvector/gds_char.c \ ../src_3rd/genvector/gds_char.h ../src_3rd/genvector/genvector_impl.h \ ../src_3rd/genvector/genvector_undef.h \ Index: trunk/src/Makefile.in =================================================================== --- trunk/src/Makefile.in (revision 32491) +++ trunk/src/Makefile.in (revision 32492) @@ -136,6 +136,9 @@ ../src_3rd/genvector/vtl0.o ../src_3rd/libulzw/libulzw_comp.o ../src_3rd/libulzw/libulzw_decomp.o + ../src_3rd/genregex/regex_sei.o + ../src_3rd/genregex/regex_se.o + ../src_3rd/genregex/regex.o @] # these need special rules for compilation Index: trunk/src/librnd/plugins/plugins_ALL.tmpasm =================================================================== --- trunk/src/librnd/plugins/plugins_ALL.tmpasm (revision 32491) +++ trunk/src/librnd/plugins/plugins_ALL.tmpasm (revision 32492) @@ -12,4 +12,5 @@ include {../src/librnd/plugins/lib_portynet/Plug.tmpasm} include {../src/librnd/plugins/lib_wget/Plug.tmpasm} include {../src/librnd/plugins/loghid/Plug.tmpasm} +include {../src/librnd/plugins/script/Plug.tmpasm} include {../src/librnd/plugins/stroke/Plug.tmpasm} Index: trunk/src/librnd/plugins/script/Makefile =================================================================== --- trunk/src/librnd/plugins/script/Makefile (nonexistent) +++ trunk/src/librnd/plugins/script/Makefile (revision 32492) @@ -0,0 +1,5 @@ +all: + cd ../../src && $(MAKE) mod_script + +clean: + rm *.o *.so 2>/dev/null ; true Index: trunk/src/librnd/plugins/script/Plug.tmpasm =================================================================== --- trunk/src/librnd/plugins/script/Plug.tmpasm (nonexistent) +++ trunk/src/librnd/plugins/script/Plug.tmpasm (revision 32492) @@ -0,0 +1,27 @@ +put /local/pcb/mod {script} +put /local/pcb/mod/OBJS [@ + $(PLUGDIR)/script/script.o + $(PLUGDIR)/script/live_script.o +@] +put /local/pcb/mod/MENUFILE {script-menu.lht} +put /local/pcb/mod/MENUVAR {script_menu} + +# Fallback scripting: if no system installed fungw is available, static link libfawk +if /local/pcb/fungw_system +then +else + append /local/pcb/mod/OBJS [@ + $(SRC_3RD_DIR)/libfungwbind/fawk/fungw_fawk.o + @] + append /local/pcb/RULES [@ + +#$(SRC_3RD_DIR)/libfungwbind/fawk/fungw_fawk.o: $(SRC_3RD_DIR)/libfungwbind/fawk/fungw_fawk.c +# $(CC) $(CFLAGS) -c $(SRC_3RD_DIR)/libfungwbind/fawk/fungw_fawk.c -o $(SRC_3RD_DIR)/libfungwbind/fawk/fungw_fawk.o +# @] +end + +switch /local/pcb/script/controls + case {buildin} include /local/pcb/tmpasm/buildin; end; + case {plugin} include /local/pcb/tmpasm/plugin; end; + case {disable} include /local/pcb/tmpasm/disable; end; +end Index: trunk/src/librnd/plugins/script/c_script.c =================================================================== --- trunk/src/librnd/plugins/script/c_script.c (nonexistent) +++ trunk/src/librnd/plugins/script/c_script.c (revision 32492) @@ -0,0 +1,124 @@ +/* + * COPYRIGHT + * + * pcb-rnd, interactive printed circuit board design + * Copyright (C) 2018 Tibor 'Igor2' Palinkas + * + * This module, debug, was written and is Copyright (C) 2016 by Tibor Palinkas + * this module is also subject to the GNU GPL as described below + * + * 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 Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact: + * Project page: http://repo.hu/projects/pcb-rnd + * lead developer: http://repo.hu/projects/pcb-rnd/contact.html + * mailing list: pcb-rnd (at) list.repo.hu (send "subscribe") + */ + +/* Fungw engine for loading and runnign "scripts" written in C. The .so files + are loaded with puplug low level */ + +#include +#include +#include + +#include + +static fgw_error_t fgws_c_call_script(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + fgw_error_t rv; + fgw_func_t *fnc = argv[0].val.func; + rv = fnc->func(res, argc, argv); + fgw_argv_free(fnc->obj->parent, argc, argv); + return rv; +} + + +int fgws_c_load(fgw_obj_t *obj, const char *filename, const char *opts) +{ + pup_plugin_t *library = calloc(sizeof(pup_plugin_t), 1); + typedef int (*init_t)(fgw_obj_t *obj, const char *opts); + init_t init; + + + if (pup_load_lib(&script_pup, library, filename) != 0) { + rnd_message(RND_MSG_ERROR, "Can't dlopen() %s\n", filename); + free(library); + return -1; + } + + init = (init_t)rnd_cast_d2f(pup_dlsym(library, "rnd_cscript_init")); + if (init == NULL) + init = (init_t)rnd_cast_d2f(pup_dlsym(library, "pcb_rnd_init")); + if (init == NULL) { + rnd_message(RND_MSG_ERROR, "Can't find rnd_cscript_init() in %s - not a pcb-rnd c \"script\".\n", filename); + free(library); + return -1; + } + + if (init(obj, opts) != 0) { + rnd_message(RND_MSG_ERROR, "%s rnd_cscript_init() returned error\n", filename); + free(library); + return -1; + } + + library->name = rnd_strdup(filename); + obj->script_data = library; + return 0; +} + +int fgws_c_unload(fgw_obj_t *obj) +{ + pup_plugin_t *library = obj->script_data; + typedef void (*uninit_t)(fgw_obj_t *obj); + uninit_t uninit; + + uninit = (uninit_t)rnd_cast_d2f(pup_dlsym(library, "rnd_cscript_uninit")); + if (uninit == NULL) + uninit = (uninit_t)rnd_cast_d2f(pup_dlsym(library, "pcb_rnd_uninit")); + if (uninit != NULL) + uninit(obj); + + pup_unload_lib(library); + + free(library->name); + free(library); + + return 0; +} + +static fgw_eng_t rnd_cscript_fgw_eng_compat = { + "pcb_rnd_c", + fgws_c_call_script, + NULL, + fgws_c_load, + fgws_c_unload, + NULL, NULL +}; + +static fgw_eng_t rnd_cscript_fgw_eng = { + "rnd_cscript", + fgws_c_call_script, + NULL, + fgws_c_load, + fgws_c_unload, + NULL, NULL +}; + +static void rnd_c_script_init(void) +{ + fgw_eng_reg(&rnd_cscript_fgw_eng); + fgw_eng_reg(&rnd_cscript_fgw_eng_compat); +} Index: trunk/src/librnd/plugins/script/glue_undo.c =================================================================== --- trunk/src/librnd/plugins/script/glue_undo.c (nonexistent) +++ trunk/src/librnd/plugins/script/glue_undo.c (revision 32492) @@ -0,0 +1,44 @@ +/* Live scripting needs to undo using the host app; to make this host app + API independent, the action interface should be used */ + +static long script_undo_action(rnd_hidlib_t *hl, const char *cmd) +{ + fgw_arg_t res, argv[2]; + + argv[1].type = FGW_STR; + argv[1].val.cstr = cmd; + if (rnd_actionv_bin(hl, "Undo", &res, 2, argv) != 0) + return -1; + + if (fgw_arg_conv(&rnd_fgw, &res, FGW_LONG) != 0) + return -1; + + return res.val.nat_long; +} + +static long get_undo_serial(rnd_hidlib_t *hl) +{ + return script_undo_action(hl, "GetSerial"); +} + +static long get_num_undo(rnd_hidlib_t *hl) +{ + return script_undo_action(hl, "GetNum"); +} + +static void inc_undo_serial(rnd_hidlib_t *hl) +{ + script_undo_action(hl, "IncSerial"); +} + +static void undo_above(rnd_hidlib_t *hl, long ser) +{ + fgw_arg_t res, argv[3]; + + argv[1].type = FGW_STR; + argv[1].val.cstr = "Above"; + argv[2].type = FGW_LONG; + argv[2].val.nat_long = ser; + rnd_actionv_bin(hl, "Undo", &res, 3, argv); + fgw_arg_free(&rnd_fgw, &res); +} Index: trunk/src/librnd/plugins/script/guess_lang.c =================================================================== --- trunk/src/librnd/plugins/script/guess_lang.c (nonexistent) +++ trunk/src/librnd/plugins/script/guess_lang.c (revision 32492) @@ -0,0 +1,208 @@ +/* + * COPYRIGHT + * + * pcb-rnd, interactive printed circuit board design + * Copyright (C) 2018,2019 Tibor 'Igor2' Palinkas + * + * This module, debug, was written and is Copyright (C) 2016 by Tibor Palinkas + * this module is also subject to the GNU GPL as described below + * + * 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 Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact: + * Project page: http://repo.hu/projects/pcb-rnd + * lead developer: http://repo.hu/projects/pcb-rnd/contact.html + * mailing list: pcb-rnd (at) list.repo.hu (send "subscribe") + */ + +#include +#include + +#include +#include +#include +#include +#include + +static int guess_lang_inited = 0; +static htsp_t guess_lang_ext2lang; /* of (vtp0_t *) listing (char *) languages */ +static htss_t guess_lang_lang2eng; +static htss_t guess_lang_alias; +static htsp_t guess_lang_engs; /* value is the same as key, value is not free'd */ +static char *guess_lang_eng; + +static int guess_lang_open(pup_list_parse_pup_t *ctx, const char *path, const char *basename) +{ + int len; + + if (strncmp(basename, "fungw_", 6) != 0) + return 1; + + len = strlen(basename); + guess_lang_eng = malloc(len+1); + if (len > 4) + len -= 4; + memcpy(guess_lang_eng, basename, len); + guess_lang_eng[len] = '\0'; + + htsp_set(&guess_lang_engs, guess_lang_eng, guess_lang_eng); + + return 0; +} + +static int guess_lang_line_split(pup_list_parse_pup_t *ctx, const char *fname, char *cmd, char *args) +{ + if (strcmp(cmd, "$script-ext") == 0) { + char *eng, *lang = args, *ext = strpbrk(args, " \t"); + + if (ext != NULL) { + *ext = 0; + ext++; + while(isspace(*ext)) ext++; + } + + eng = htss_get(&guess_lang_lang2eng, lang); + if (eng != NULL) { + if (strcmp(eng, guess_lang_eng) != 0) { + rnd_message(RND_MSG_ERROR, "Script setup error: language %s is claimed by both '%s' and '%s' fungw bindings (accepted %s)\n", lang, eng, guess_lang_eng, eng); + return 0; + } + } + else { +/*printf("ADD: '%s' -> '%s'\n", lang, guess_lang_eng);*/ + htss_set(&guess_lang_lang2eng, rnd_strdup(lang), rnd_strdup(guess_lang_eng)); + } + + if (ext != NULL) { /* append language to the extension's lang vector */ + vtp0_t *langs = htsp_get(&guess_lang_ext2lang, ext); + if (langs == NULL) { + langs = calloc(sizeof(vtp0_t), 1); + htsp_set(&guess_lang_ext2lang, rnd_strdup(ext), langs); + } + vtp0_append(langs, rnd_strdup(lang)); +/*printf("APP: '%s' -> '%s'\n", ext, lang);*/ + } + } + else if (strcmp(cmd, "$lang-alias") == 0) { + char *lang = args, *alias = strpbrk(args, " \t"); + + if (alias != NULL) { + *alias = 0; + alias++; + while(isspace(*alias)) alias++; + if (htss_get(&guess_lang_alias, alias) == NULL) + htss_set(&guess_lang_alias, rnd_strdup(alias), rnd_strdup(lang)); + } + } + return 0; +} + +static void rnd_script_guess_lang_init(void) +{ + if (!guess_lang_inited) { + pup_list_parse_pup_t ctx = {0}; + const char *paths[2]; + + htsp_init(&guess_lang_ext2lang, strhash, strkeyeq); + htss_init(&guess_lang_lang2eng, strhash, strkeyeq); + htss_init(&guess_lang_alias, strhash, strkeyeq); + htsp_init(&guess_lang_engs, strhash, strkeyeq); + + ctx.open = guess_lang_open; + ctx.line_split = guess_lang_line_split; + + paths[0] = FGW_CFG_PUPDIR; + paths[1] = NULL; + pup_list_parse_pups(&ctx, paths); + + guess_lang_inited = 1; + } +} + +static void rnd_script_guess_lang_uninit(void) +{ + if (guess_lang_inited) { + int n; + + genht_uninit_deep(htsp, &guess_lang_ext2lang, { + vtp0_t *langs = htent->value; + for(n = 0; n < langs->used; n++) + free(langs->array[n]); + vtp0_uninit(langs); + free(htent->key); + free(langs); + }); + genht_uninit_deep(htss, &guess_lang_lang2eng, { + free(htent->key); + free(htent->value); + }); + genht_uninit_deep(htss, &guess_lang_alias, { + free(htent->key); + free(htent->value); + }); + genht_uninit_deep(htsp, &guess_lang_engs, { + free(htent->key); + }); + guess_lang_inited = 0; + } +} + +const char *rnd_script_lang2eng(const char **lang) +{ + const char *eng, *alang = htss_get(&guess_lang_alias, *lang); + + if (alang != NULL) + *lang = alang; + + eng = htss_get(&guess_lang_lang2eng, *lang); + if (eng == NULL) { + /* last resort: maybe *lang is an eng, prefixewd with fungw_ */ + char name[RND_PATH_MAX]; + rnd_snprintf(name, RND_PATH_MAX, "fungw_%s", *lang); + eng = htsp_get(&guess_lang_engs, name); + } + return eng; +} + +const char *rnd_script_guess_lang(rnd_hidlib_t *hl, const char *fn, int is_filename) +{ + rnd_script_guess_lang_init(); + + if (!is_filename) { + const char *eng = rnd_script_lang2eng(&fn); + + /* find by engine name */ + if (eng != NULL) + return fn; + return NULL; + } + else { + const char *ext; + vtp0_t *langs; + + ext = strrchr(fn, '.'); + if (ext == NULL) + return NULL; + + langs = htsp_get(&guess_lang_ext2lang, ext); + if (langs == NULL) + return NULL; + + assert(langs->used > 0); + + return langs->array[0]; + } +} + Index: trunk/src/librnd/plugins/script/live_script.c =================================================================== --- trunk/src/librnd/plugins/script/live_script.c (nonexistent) +++ trunk/src/librnd/plugins/script/live_script.c (revision 32492) @@ -0,0 +1,568 @@ +/* + * COPYRIGHT + * + * pcb-rnd, interactive printed circuit board design + * Copyright (C) 2019,2020 Tibor 'Igor2' Palinkas + * + * 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 Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact: + * Project page: http://repo.hu/projects/pcb-rnd + * lead developer: http://repo.hu/projects/pcb-rnd/contact.html + * mailing list: pcb-rnd (at) list.repo.hu (send "subscribe") + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "script.h" + +#include "live_script.h" + +#include "menu_internal.c" + +static const char *lvs_cookie = "live_script"; + + +typedef struct { + RND_DAD_DECL_NOINIT(dlg) + rnd_hidlib_t *hidlib; + char *name, *longname, *fn; + char **langs; + char **lang_engines; + int wtxt, wrerun, wrun, wstop, wundo, wload, wsave, wreload, wpers, wlang; + long undo_pre, undo_post; /* undo serials pre-run and post-run */ + unsigned loaded:1; +} live_script_t; + +static htsp_t rnd_live_scripts; + +static void lvs_free_langs(live_script_t *lvs) +{ + char **s; + if (lvs->langs != NULL) + for(s = lvs->langs; *s != NULL; s++) free(*s); + if (lvs->lang_engines != NULL) + for(s = lvs->lang_engines; *s != NULL; s++) free(*s); + free(lvs->langs); + free(lvs->lang_engines); +} + + +static void lvs_close_cb(void *caller_data, rnd_hid_attr_ev_t ev) +{ + live_script_t *lvs = caller_data; + + htsp_popentry(&rnd_live_scripts, lvs->name); + + if (lvs->loaded) + rnd_script_unload(lvs->longname, NULL); + + if (rnd_gui != NULL) + RND_DAD_FREE(lvs->dlg); + lvs_free_langs(lvs); + free(lvs->name); + free(lvs->longname); + free(lvs->fn); + free(lvs); +} + +#ifdef RND_HAVE_SYS_FUNGW + + +typedef struct { + vtp0_t vl, ve; + const char *eng; +} lvs_lctx_t; + + +static int lvs_list_langs_open(pup_list_parse_pup_t *ctx, const char *path, const char *basename) +{ + lvs_lctx_t *lctx = ctx->user_data; + if (strncmp(basename, "fungw_", 6) != 0) + return 1; + lctx->eng = basename+6; /* remove the fungw_ prefix, the low level script runner will insert it */ + return 0; +} + +int lvs_list_langs_line_split(pup_list_parse_pup_t *ctx, const char *fname, char *cmd, char *args) +{ + lvs_lctx_t *lctx = ctx->user_data; + int el; + char *lang, *end, *eng; + + if (strcmp(cmd, "$script-ext") == 0) { /* first arg is a language */ + lang = rnd_strdup(args); + end = strpbrk(lang, " \t"); + if (end != NULL) + *end = '\0'; + } + else if (strcmp(cmd, "$lang-alias") == 0) { /* second arg is a language */ + end = strpbrk(args, " \t"); + if (end == NULL) + return 0; + while(isspace(*end)) end++; + lang = rnd_strdup(end); + } + else + return 0; + + /* remove duplicates (assumes lines within a .pup are sorted) */ + if (lctx->vl.used > 0) { + if (strcmp(lctx->vl.array[lctx->vl.used-1], lang) == 0) { + free(lang); + return 0; + } + } + + eng = rnd_strdup(lctx->eng); + el = strlen(eng); + eng[el-4] = '\0'; + vtp0_append(&lctx->ve, eng); + vtp0_append(&lctx->vl, lang); + return 0; +} + + +static int lvs_list_langs(rnd_hidlib_t *hl, live_script_t *lvs) +{ + const char *paths[2]; + pup_list_parse_pup_t ctx = {0}; + lvs_lctx_t lctx; + + ctx.open = lvs_list_langs_open; + ctx.line_split = lvs_list_langs_line_split; + ctx.user_data = &lctx; + + vtp0_init(&lctx.vl); + vtp0_init(&lctx.ve); + + paths[0] = FGW_CFG_PUPDIR; + paths[1] = NULL; + pup_list_parse_pups(&ctx, paths); + + lvs->langs = (char **)lctx.vl.array; + lvs->lang_engines = (char **)lctx.ve.array; + return lctx.vl.used; +} +#else +static int lvs_list_langs(rnd_hidlib_t *hl, live_script_t *lvs) +{ + vtp0_t vl, ve; + + vtp0_init(&vl); + vtp0_init(&ve); + + vtp0_append(&vl, rnd_strdup("fawk")); vtp0_append(&ve, rnd_strdup("fawk")); + vtp0_append(&vl, rnd_strdup("fbas")); vtp0_append(&ve, rnd_strdup("fbas")); + vtp0_append(&vl, rnd_strdup("fpas")); vtp0_append(&ve, rnd_strdup("fpas")); + + lvs->langs = (char **)vl.array; + lvs->lang_engines = (char **)ve.array; + return vl.used; +} + +#endif + +static void lvs_button_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr_btn) +{ + live_script_t *lvs = caller_data; + const char *arg; + int w = attr_btn - lvs->dlg; + + + if (w == lvs->wrerun) arg = "rerun"; + else if (w == lvs->wrun) arg = "run"; + else if (w == lvs->wstop) arg = "stop"; + else if (w == lvs->wundo) arg = "undo"; + else if (w == lvs->wload) arg = "load"; + else if (w == lvs->wsave) arg = "save"; + else if (w == lvs->wreload) arg = "reload-rerun"; + else { + rnd_message(RND_MSG_ERROR, "lvs_button_cb(): internal error: unhandled switch case\n"); + return; + } + + rnd_actionva(lvs->hidlib, "livescript", arg, lvs->name, NULL); +} + +static live_script_t *rnd_dlg_live_script(rnd_hidlib_t *hidlib, const char *name) +{ + rnd_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}}; + char *title; + live_script_t *lvs = calloc(sizeof(live_script_t), 1); + + if (lvs_list_langs(NULL, lvs) < 1) { + lvs_free_langs(lvs); + free(lvs); + rnd_message(RND_MSG_ERROR, "live_script: no scripting language engines found\nPlease compile and install fungw from source, then\nreconfigure and recompile pcb-rnd.\n"); + return NULL; + } + + lvs->hidlib = hidlib; + lvs->name = rnd_strdup(name); + lvs->longname = rnd_concat("_live_script_", name, NULL); + RND_DAD_BEGIN_VBOX(lvs->dlg); + RND_DAD_COMPFLAG(lvs->dlg, RND_HATF_EXPFILL); + RND_DAD_TEXT(lvs->dlg, lvs); + RND_DAD_COMPFLAG(lvs->dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL); + lvs->wtxt = RND_DAD_CURRENT(lvs->dlg); + + RND_DAD_BEGIN_HBOX(lvs->dlg); + RND_DAD_BUTTON(lvs->dlg, "re-run"); + lvs->wrerun = RND_DAD_CURRENT(lvs->dlg); + RND_DAD_CHANGE_CB(lvs->dlg, lvs_button_cb); + RND_DAD_HELP(lvs->dlg, "Stop the script if it is running\nundo the changes the script performed if no\nuser modification happened since\nrun the script again"); + RND_DAD_BUTTON(lvs->dlg, "run"); + lvs->wrun = RND_DAD_CURRENT(lvs->dlg); + RND_DAD_CHANGE_CB(lvs->dlg, lvs_button_cb); + RND_DAD_HELP(lvs->dlg, "Run the script:\nonce and unload, if not persistent\nor keep it running in persistent mode"); + RND_DAD_BUTTON(lvs->dlg, "stop"); + lvs->wstop = RND_DAD_CURRENT(lvs->dlg); + RND_DAD_CHANGE_CB(lvs->dlg, lvs_button_cb); + RND_DAD_HELP(lvs->dlg, "Halt and unload the script\nand unregister any action, menu, etc. it registered"); + RND_DAD_BUTTON(lvs->dlg, "undo"); + lvs->wundo = RND_DAD_CURRENT(lvs->dlg); + RND_DAD_CHANGE_CB(lvs->dlg, lvs_button_cb); + RND_DAD_HELP(lvs->dlg, "undo the changes the script performed if no\nuser modification happened since"); + RND_DAD_BUTTON(lvs->dlg, "save"); + lvs->wsave = RND_DAD_CURRENT(lvs->dlg); + RND_DAD_CHANGE_CB(lvs->dlg, lvs_button_cb); + RND_DAD_HELP(lvs->dlg, "Save script source to a file"); + RND_DAD_BUTTON(lvs->dlg, "load"); + lvs->wload = RND_DAD_CURRENT(lvs->dlg); + RND_DAD_CHANGE_CB(lvs->dlg, lvs_button_cb); + RND_DAD_HELP(lvs->dlg, "Load script source from a file"); + RND_DAD_BUTTON(lvs->dlg, "reload&rerun"); + lvs->wreload = RND_DAD_CURRENT(lvs->dlg); + RND_DAD_CHANGE_CB(lvs->dlg, lvs_button_cb); + RND_DAD_HELP(lvs->dlg, "Reload script source and rerun\n(Ideal with external editor)"); + RND_DAD_END(lvs->dlg); + RND_DAD_BEGIN_HBOX(lvs->dlg); + RND_DAD_BOOL(lvs->dlg); + lvs->wpers = RND_DAD_CURRENT(lvs->dlg); + RND_DAD_LABEL(lvs->dlg, "persistent"); + RND_DAD_HELP(lvs->dlg, "Persistent mode: keep the script loaded and running\n(useful if the script registers actions)\nNon-persistent mode: run once then unload."); + RND_DAD_ENUM(lvs->dlg, (const char **)lvs->langs); + lvs->wlang = RND_DAD_CURRENT(lvs->dlg); + RND_DAD_BEGIN_HBOX(lvs->dlg); + RND_DAD_COMPFLAG(lvs->dlg, RND_HATF_EXPFILL); + RND_DAD_END(lvs->dlg); + RND_DAD_BUTTON_CLOSES(lvs->dlg, clbtn); + RND_DAD_END(lvs->dlg); + RND_DAD_END(lvs->dlg); + RND_DAD_DEFSIZE(lvs->dlg, 300, 500); + + title = rnd_concat("Live Scripting: ", name, NULL); + RND_DAD_NEW("live_script", lvs->dlg, title, lvs, rnd_false, lvs_close_cb); + free(title); + rnd_gui->attr_dlg_widget_state(lvs->dlg_hid_ctx, lvs->wstop, 0); + return lvs; +} + +static int live_stop(live_script_t *lvs) +{ + if (lvs->loaded) { + rnd_script_unload(lvs->longname, NULL); + lvs->loaded = 0; + } + + rnd_gui->attr_dlg_widget_state(lvs->dlg_hid_ctx, lvs->wrun, 1); + rnd_gui->attr_dlg_widget_state(lvs->dlg_hid_ctx, lvs->wstop, 0); + return 0; +} + +#include "glue_undo.c" + +static int live_run(rnd_hidlib_t *hl, live_script_t *lvs) +{ + rnd_hid_attribute_t *atxt = &lvs->dlg[lvs->wtxt]; + rnd_hid_text_t *txt = atxt->wdata; + FILE *f; + char *src, *fn, *lang; + int res = 0; + long numu; + + fn = rnd_tempfile_name_new("live_script"); + f = rnd_fopen(hl, fn, "w"); + if (f == NULL) { + rnd_tempfile_unlink(fn); + rnd_message(RND_MSG_ERROR, "live_script: can't open temp file for write\n"); + return -1; + } + + src = txt->hid_get_text(atxt, lvs->dlg_hid_ctx); + fputs(src, f); + free(src); + fclose(f); + + lang = lvs->lang_engines[lvs->dlg[lvs->wlang].val.lng]; + + live_stop(lvs); + + lvs->undo_pre = get_undo_serial(hl); + numu = get_num_undo(hl); + + if (rnd_script_load(hl, lvs->longname, fn, lang) != 0) { + rnd_message(RND_MSG_ERROR, "live_script: can't load/parse the script\n"); + res = -1; + } + lvs->loaded = 1; + rnd_gui->attr_dlg_widget_state(lvs->dlg_hid_ctx, lvs->wrun, 0); + rnd_gui->attr_dlg_widget_state(lvs->dlg_hid_ctx, lvs->wstop, 1); + + if (!lvs->dlg[lvs->wpers].val.lng) + live_stop(lvs); + + if ((get_num_undo(hl) != numu) && (lvs->undo_pre == get_undo_serial(hl))) + inc_undo_serial(hl); + lvs->undo_post = get_undo_serial(hl); + + rnd_gui->invalidate_all(rnd_gui); /* if the script drew anything, get it displayed */ + + rnd_tempfile_unlink(fn); + return res; +} + +static const char *live_default_ext(live_script_t *lvs) +{ + char *lang = lvs->lang_engines[lvs->dlg[lvs->wlang].val.lng]; + fgw_eng_t *eng = htsp_get(&fgw_engines, lang); + + if (eng != NULL) + return eng->def_ext; + return NULL; +} + +static int live_undo(rnd_hidlib_t *hl, live_script_t *lvs) +{ + if (lvs->undo_pre == lvs->undo_post) + return 0; /* the script did nothing */ + if (lvs->undo_post < get_undo_serial(hl)) { + rnd_message(RND_MSG_WARNING, "Can not undo live script modifications:\nthere was user edit after script executaion.\n"); + return 1; + } + undo_above(hl, lvs->undo_pre); + rnd_gui->invalidate_all(rnd_gui); + return 0; +} + + +static int live_load(rnd_hidlib_t *hl, live_script_t *lvs, const char *fn) +{ + rnd_hid_attribute_t *atxt = &lvs->dlg[lvs->wtxt]; + rnd_hid_text_t *txt = atxt->wdata; + FILE *f; + gds_t tmp; + + if (fn == NULL) { + const char *default_ext = live_default_ext(lvs); + fn = rnd_gui->fileselect(rnd_gui, + "Load live script", "Load the a live script from file", + lvs->fn, default_ext, rnd_hid_fsd_filter_any, "live_script", RND_HID_FSD_READ, NULL); + if (fn == NULL) + return 0; + lvs->fn = rnd_strdup(fn); + } + + f = rnd_fopen(hl, fn, "r"); + if (f == NULL) { + rnd_message(RND_MSG_ERROR, "live_script: failed to open '%s' for read\n", fn); + return -1; + } + + gds_init(&tmp); + + while(!feof(f)) { + int len, ou = tmp.used; + gds_alloc_append(&tmp, 1024); + len = fread(tmp.array+ou, 1, 1024, f); + if (len > 0) { + tmp.used = ou+len; + tmp.array[tmp.used] = '\0'; + } + } + + txt->hid_set_text(atxt, lvs->dlg_hid_ctx, RND_HID_TEXT_REPLACE, tmp.array); + + gds_uninit(&tmp); + fclose(f); + return 0; +} + +static int live_save(rnd_hidlib_t *hl, live_script_t *lvs, const char *fn) +{ + rnd_hid_attribute_t *atxt = &lvs->dlg[lvs->wtxt]; + rnd_hid_text_t *txt = atxt->wdata; + FILE *f; + char *src; + int res = 0; + + if (fn == NULL) { + const char *default_ext = live_default_ext(lvs); + + if (lvs->fn == NULL) + lvs->fn = rnd_concat(lvs->name, ".", default_ext, NULL); + + fn = rnd_gui->fileselect(rnd_gui, + "Save live script", "Save the source of a live script", + lvs->fn, default_ext, rnd_hid_fsd_filter_any, "live_script", 0, NULL); + if (fn == NULL) + return 0; + } + + f = rnd_fopen(hl, fn, "w"); + if (f == NULL) { + rnd_message(RND_MSG_ERROR, "live_script: failed to open '%s' for write\n", fn); + return -1; + } + + src = txt->hid_get_text(atxt, lvs->dlg_hid_ctx); + if (fwrite(src, strlen(src), 1, f) != 1) { + rnd_message(RND_MSG_ERROR, "live_script: failed to write script source to '%s'\n", fn); + res = -1; + } + free(src); + + fclose(f); + return res; +} + + +const char pcb_acts_LiveScript[] = + "LiveScript([new], [name])\n" + "LiveScript(load|save, name, [filame])\n" + "LiveScript(run|stop|rerun|undo, name)\n"; +const char pcb_acth_LiveScript[] = "Manage a live script"; +/* DOC: livescript.html */ +fgw_error_t pcb_act_LiveScript(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + live_script_t *lvs; + const char *cmd = "new", *name = NULL, *arg = NULL; + + RND_ACT_MAY_CONVARG(1, FGW_STR, LiveScript, cmd = argv[1].val.str); + RND_ACT_MAY_CONVARG(2, FGW_STR, LiveScript, name = argv[2].val.str); + RND_ACT_MAY_CONVARG(3, FGW_STR, LiveScript, arg = argv[3].val.str); + + if (rnd_strcasecmp(cmd, "new") == 0) { + if (name == NULL) name = "default"; + lvs = htsp_get(&rnd_live_scripts, name); + if (lvs != NULL) { + rnd_message(RND_MSG_ERROR, "live script '%s' is already open\n", name); + RND_ACT_IRES(1); + return 0; + } + lvs = rnd_dlg_live_script(RND_ACT_HIDLIB, name); + if (lvs != NULL) { + htsp_set(&rnd_live_scripts, lvs->name, lvs); + RND_ACT_IRES(0); + } + else + RND_ACT_IRES(1); + return 0; + } + if (rnd_strcasecmp(cmd, "debug-list-langs") == 0) { + live_script_t l; + int n, len; + + memset(&l, 0, sizeof(l)); + len = lvs_list_langs(RND_ACT_HIDLIB, &l); + for(n = 0; n < len; n++) { + printf("'%s' '%s'\n", l.langs[n], l.lang_engines[n]); + free(l.langs[n]); + free(l.lang_engines[n]); + } + free(l.langs); + free(l.lang_engines); + return 0; + } + + if (name == NULL) { + rnd_message(RND_MSG_ERROR, "script name (second argument) required\n"); + RND_ACT_IRES(1); + return 0; + } + + lvs = htsp_get(&rnd_live_scripts, name); + if (lvs == NULL) { + rnd_message(RND_MSG_ERROR, "script '%s' does not exist\n", name); + RND_ACT_IRES(1); + return 0; + } + + RND_ACT_IRES(0); + if (rnd_strcasecmp(cmd, "load") == 0) { + RND_ACT_IRES(live_load(NULL, lvs, arg)); + } + else if (rnd_strcasecmp(cmd, "save") == 0) { + RND_ACT_IRES(live_save(NULL, lvs, arg)); + } + else if (rnd_strcasecmp(cmd, "undo") == 0) { + RND_ACT_IRES(live_undo(RND_ACT_HIDLIB, lvs)); + } + else if (rnd_strcasecmp(cmd, "run") == 0) { + live_run(RND_ACT_HIDLIB, lvs); + } + else if (rnd_strcasecmp(cmd, "stop") == 0) { + live_stop(lvs); + } + else if (rnd_strcasecmp(cmd, "rerun") == 0) { + live_stop(lvs); + live_undo(RND_ACT_HIDLIB, lvs); + live_run(RND_ACT_HIDLIB, lvs); + } + if (rnd_strcasecmp(cmd, "reload-rerun") == 0) { + RND_ACT_IRES(live_load(NULL, lvs, lvs->fn)); + live_stop(lvs); + live_undo(RND_ACT_HIDLIB,lvs); + live_run(RND_ACT_HIDLIB, lvs); + } + + return 0; +} + +void rnd_live_script_init(void) +{ + htsp_init(&rnd_live_scripts, strhash, strkeyeq); + rnd_hid_menu_load(rnd_gui, NULL, lvs_cookie, 110, NULL, 0, script_menu, "plugin: live scripting"); +} + +void rnd_live_script_uninit(void) +{ + htsp_entry_t *e; + for(e = htsp_first(&rnd_live_scripts); e != NULL; e = htsp_next(&rnd_live_scripts, e)) { + live_script_t *lvs = e->value; + lvs_close_cb(lvs, RND_HID_ATTR_EV_CODECLOSE); + } + htsp_uninit(&rnd_live_scripts); + rnd_event_unbind_allcookie(lvs_cookie); + rnd_hid_menu_unload(rnd_gui, lvs_cookie); +} + Index: trunk/src/librnd/plugins/script/live_script.h =================================================================== --- trunk/src/librnd/plugins/script/live_script.h (nonexistent) +++ trunk/src/librnd/plugins/script/live_script.h (revision 32492) @@ -0,0 +1,6 @@ +extern const char pcb_acts_LiveScript[]; +extern const char pcb_acth_LiveScript[]; +fgw_error_t pcb_act_LiveScript(fgw_arg_t *res, int argc, fgw_arg_t *argv); + +void rnd_live_script_init(void); +void rnd_live_script_uninit(void); Index: trunk/src/librnd/plugins/script/perma.c =================================================================== --- trunk/src/librnd/plugins/script/perma.c (nonexistent) +++ trunk/src/librnd/plugins/script/perma.c (revision 32492) @@ -0,0 +1,137 @@ +/* + * COPYRIGHT + * + * pcb-rnd, interactive printed circuit board design + * Copyright (C) 2019 Tibor 'Igor2' Palinkas + * + * This module, debug, was written and is Copyright (C) 2016 by Tibor Palinkas + * this module is also subject to the GNU GPL as described below + * + * 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 Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact: + * Project page: http://repo.hu/projects/pcb-rnd + * lead developer: http://repo.hu/projects/pcb-rnd/contact.html + * mailing list: pcb-rnd (at) list.repo.hu (send "subscribe") + */ + +#include "config.h" + +#include + +#include +#include + +static int perma_load(rnd_hidlib_t *hl, const char *dir, const char *id, const char *path_in, const char *lang) +{ + char spath[RND_PATH_MAX]; + const char *path; + + if (!rnd_is_path_abs(path_in)) { + path = spath; + rnd_snprintf(spath, sizeof(spath), "%s%c%s", dir, RND_DIR_SEPARATOR_C, path_in); + } + else + path = path_in; + + return rnd_script_load(hl, id, path, lang); +} + +static void perma_script_load_conf(rnd_hidlib_t *hl, const char *dir) +{ + char path[RND_PATH_MAX], *errmsg; + lht_doc_t *doc; + lht_node_t *n, *npath, *nlang; + FILE *f; + long succ = 0; + + rnd_snprintf(path, sizeof(path), "%s%c%s", dir, RND_DIR_SEPARATOR_C, "scripts.lht"); + f = rnd_fopen(NULL, path, "r"); + if (f == NULL) + return; /* non-existing or unreadable file is no error */ + doc = lht_dom_load_stream(f, path, &errmsg); + fclose(f); + + if (doc == NULL) { + rnd_message(RND_MSG_ERROR, "Failed to parse script config '%s':\n'%s'\n", path, errmsg); + goto end; + } + + n = doc->root; + if ((n->type != LHT_LIST) || (strcmp(n->name, "pcb-rnd-perma-script-v1") != 0)) { + rnd_message(RND_MSG_ERROR, "Failed to load script config '%s':\nroot node is not li:pcb-rnd-perma-script-v1\n", path); + goto end; + } + + for(n = n->data.list.first; n != NULL; n = n->next) { + const char *id = n->name, *path_in, *lang = NULL; + if (n->type != LHT_HASH) { + rnd_message(RND_MSG_ERROR, "ignoring non-hash child '%s' in '%s'\n", n->name, path); + continue; + } + + npath = lht_dom_hash_get(n, "path"); + if ((npath == NULL) || (npath->type != LHT_TEXT)) { + rnd_message(RND_MSG_ERROR, "ignoring '%s' in '%s': no path\n", n->name, path); + continue; + } + path_in = npath->data.text.value; + + nlang = lht_dom_hash_get(n, "lang"); + if (nlang != NULL) { + if (npath->type != LHT_TEXT) { + rnd_message(RND_MSG_ERROR, "ignoring '%s' in '%s': invalid lang node type\n", n->name, path); + continue; + } + lang = nlang->data.text.value; + } + else { /* guess from path */ + lang = rnd_script_guess_lang(NULL, path_in, 1); + if (lang == NULL) { + rnd_message(RND_MSG_ERROR, "ignoring '%s' in '%s': no lang specified and failed to guess/recognize the language\n", n->name, path); + continue; + } + } + + if (perma_load(hl, dir, id, path_in, lang) == 0) + succ++; + else + rnd_message(RND_MSG_ERROR, "failed to load script '%s' in '%s'\n", n->name, path); + } + + rnd_message(RND_MSG_INFO, "Loaded %ld scripts from '%s'\n", succ, path); + + end:; + lht_dom_uninit(doc); +} + +static void perma_script_init(rnd_hidlib_t *hl) +{ + static int inited = 0; + + if (inited) return; + + perma_script_load_conf(hl, rnd_conf_userdir_path); + perma_script_load_conf(hl, rnd_conf_sysdir_path); + + inited = 1; +} + +static void script_mainloop_perma_ev(rnd_hidlib_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[]) +{ + if (rnd_hid_in_main_loop) + perma_script_init(hidlib); +} + Index: trunk/src/librnd/plugins/script/script-menu.lht =================================================================== --- trunk/src/librnd/plugins/script/script-menu.lht (nonexistent) +++ trunk/src/librnd/plugins/script/script-menu.lht (revision 32492) @@ -0,0 +1,9 @@ +ha:rnd-menu-v1 { + li:anchored { + ha:@scripts { + li:submenu { + ha:Open live script dialog... = { action = LiveScript(new) } + } + } + } +} Index: trunk/src/librnd/plugins/script/script.c =================================================================== --- trunk/src/librnd/plugins/script/script.c (nonexistent) +++ trunk/src/librnd/plugins/script/script.c (revision 32492) @@ -0,0 +1,471 @@ +/* + * COPYRIGHT + * + * pcb-rnd, interactive printed circuit board design + * Copyright (C) 2018,2020 Tibor 'Igor2' Palinkas + * + * This module, debug, was written and is Copyright (C) 2016 by Tibor Palinkas + * this module is also subject to the GNU GPL as described below + * + * 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 Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact: + * Project page: http://repo.hu/projects/pcb-rnd + * lead developer: http://repo.hu/projects/pcb-rnd/contact.html + * mailing list: pcb-rnd (at) list.repo.hu (send "subscribe") + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "script.h" +#include "guess_lang.c" + +#ifndef RND_HAVE_SYS_FUNGW +int pplg_init_fungw_fawk(void); +int pplg_uninit_fungw_fawk(void); +#endif + +typedef struct { + char *id, *fn, *lang; + pup_plugin_t *pup; + fgw_obj_t *obj; +} script_t; + +static htsp_t scripts; /* ID->script_t */ + +static pup_context_t script_pup; + +#include "c_script.c" +#include +#include + +/* dir name under dotdir for saving script persistency data */ +#define SCRIPT_PERS "script_pers" + +/* allow only 1 megabyte of script persistency to be loaded */ +#define PERS_MAX_SIZE 1024*1024 + +static int script_save_preunload(script_t *s, const char *data) +{ + FILE *f; + gds_t fn; + + gds_init(&fn); + gds_append_str(&fn, rnd_conf.rc.path.home); + gds_append(&fn, RND_DIR_SEPARATOR_C); + gds_append_str(&fn, rnd_conf_dot_dir); + rnd_mkdir(NULL, fn.array, 0755); + + gds_append(&fn, RND_DIR_SEPARATOR_C); + gds_append_str(&fn, SCRIPT_PERS); + rnd_mkdir(NULL, fn.array, 0750); + + gds_append(&fn, RND_DIR_SEPARATOR_C); + gds_append_str(&fn, s->obj->name); + + f = rnd_fopen(NULL, fn.array, "w"); + if (f != NULL) { + gds_uninit(&fn); + fputs(data, f); + fclose(f); + return 0; + } + + gds_uninit(&fn); + return -1; +} + +/* auto-unregister from any central infra the script may have regitered in using + the standard script cookie */ +static void script_unreg(const char *cookie) +{ + rnd_hid_menu_unload(rnd_gui, cookie); +} + +/* unload a script, free all memory and remove it from all lists/hashes. + If preunload is not NULL, it's the unload reason the script's own + preunload() function is called and its return is saved. */ +static void script_free(script_t *s, const char *preunload, const char *cookie) +{ + if ((preunload != NULL) && (s->obj != NULL)) { + fgw_func_t *f; + fgw_arg_t res, argv[2]; + + f = fgw_func_lookup(s->obj, "preunload"); + if (f != NULL) { + argv[0].type = FGW_FUNC; + argv[0].val.argv0.func = f; + argv[0].val.argv0.user_call_ctx = NULL; + argv[1].type = FGW_STR; + argv[1].val.cstr = preunload; + res.type = FGW_INVALID; + if (f->func(&res, 2, argv) == 0) { + if ((fgw_arg_conv(&rnd_fgw, &res, FGW_STR) == 0) && (res.val.str != NULL) && (*res.val.str != '\0')) + script_save_preunload(s, res.val.str); + } + fgw_arg_free(&rnd_fgw, &res); + } + } + + if (cookie != NULL) + script_unreg(cookie); + + if (s->obj != NULL) + fgw_obj_unreg(&rnd_fgw, s->obj); +#ifdef RND_HAVE_SYS_FUNGW + if (s->pup != NULL) + pup_unload(&script_pup, s->pup, NULL); +#endif + free(s->id); + free(s->fn); + free(s); +} + +static void script_unload_entry(htsp_entry_t *e, const char *preunload, const char *cookie) +{ + script_t *s = (script_t *)e->value; + script_free(s, preunload, cookie); + e->key = NULL; + htsp_delentry(&scripts, e); +} + +static const char *script_persistency_id = NULL; +static int script_persistency(fgw_arg_t *res, const char *cmd) +{ + char *fn; + + if (script_persistency_id == NULL) { + rnd_message(RND_MSG_ERROR, "ScriptPersistency may be called only from the init part of a script\n"); + goto err; + } + + fn = rnd_concat(rnd_conf.rc.path.home, RND_DIR_SEPARATOR_S, rnd_conf_dot_dir, RND_DIR_SEPARATOR_S, SCRIPT_PERS, RND_DIR_SEPARATOR_S, script_persistency_id, NULL); + + if (strcmp(cmd, "remove") == 0) { + RND_ACT_IRES(rnd_remove(NULL, fn)); + goto succ; + } + + if (strcmp(cmd, "read") == 0) { + FILE *f; + long fsize = rnd_file_size(NULL, fn); + char *data; + + if ((fsize < 0) || (fsize > PERS_MAX_SIZE)) + goto err; + + data = malloc(fsize+1); + if (data == NULL) + goto err; + + f = rnd_fopen(NULL, fn, "r"); + if (f == NULL) { + free(data); + goto err; + } + + if (fread(data, 1, fsize, f) != fsize) { + free(data); + fclose(f); + goto err; + } + + fclose(f); + data[fsize] = '\0'; + res->type = FGW_STR | FGW_DYN; + res->val.str = data; + goto succ; + } + + rnd_message(RND_MSG_ERROR, "Unknown command for ScriptPersistency\n"); + + err:; + RND_ACT_IRES(-1); + return 0; + + succ: + free(fn); + return 0; /* assume IRES is set already */ +} + +static char *script_gen_cookie(const char *force_id) +{ + if (force_id == NULL) { + if (script_persistency_id == NULL) { + rnd_message(RND_MSG_ERROR, "ScriptCookie called from outside of script init, can not generate the cookie\n"); + return NULL; + } + force_id = script_persistency_id; + } + return rnd_concat("script::fungw::", force_id, NULL); +} + +int rnd_script_unload(const char *id, const char *preunload) +{ + char *cookie; + htsp_entry_t *e = htsp_getentry(&scripts, id); + if (e == NULL) + return -1; + + cookie = script_gen_cookie(id); + script_unload_entry(e, preunload, cookie); + free(cookie); + return 0; +} + +static char *script_fn(const char *fn) +{ + if (*fn != '~') + return rnd_strdup(fn); + return rnd_strdup_printf("%s%c%s", rnd_conf.rc.path.home, RND_DIR_SEPARATOR_C, fn+1); +} + +int rnd_script_load(rnd_hidlib_t *hl, const char *id, const char *fn, const char *lang) +{ + pup_plugin_t *pup; + script_t *s; + const char *old_id; + + if (htsp_has(&scripts, id)) { + rnd_message(RND_MSG_ERROR, "Can not load script %s from file %s: ID already in use\n", id, fn); + return -1; + } + + if (lang == NULL) + lang = rnd_script_guess_lang(hl, fn, 1); + if (lang == NULL) { + rnd_message(RND_MSG_ERROR, "Can not load script %s from file %s: failed to guess language from file name\n", id, fn); + return -1; + } + + if (strcmp(lang, "c") != 0) { +#ifdef RND_HAVE_SYS_FUNGW + const char *paths[2], *pupname; + int st; + + rnd_script_guess_lang_init(); + pupname = rnd_script_lang2eng(&lang); + + if (pupname == NULL) { + rnd_message(RND_MSG_ERROR, "No script engine found for language %s\n", lang); + return -1; + } + + old_id = script_persistency_id; + script_persistency_id = id; + paths[0] = FGW_CFG_PUPDIR; + paths[1] = NULL; + pup = pup_load(&script_pup, paths, pupname, 0, &st); + script_persistency_id = old_id; + if (pup == NULL) { + rnd_message(RND_MSG_ERROR, "Can not load script engine %s for language %s\n", pupname, lang); + return -1; + } +#endif + } + else { + lang = "rnd_cscript"; + pup = NULL; + } + + s = calloc(1, sizeof(script_t)); + s->pup = pup; + s->id = rnd_strdup(id); + s->fn = script_fn(fn); + s->lang = rnd_strdup(lang); + + old_id = script_persistency_id; + script_persistency_id = id; + s->obj = fgw_obj_new2(&rnd_fgw, s->id, s->lang, s->fn, NULL, hl); + script_persistency_id = old_id; + + if (s->obj == NULL) { + rnd_message(RND_MSG_ERROR, "Failed to parse/execute %s script from file %s (using %s)\n", id, fn, s->pup->name); + script_free(s, NULL, NULL); + return -1; + } + + htsp_set(&scripts, s->id, s); + return 0; +} + +static int script_reload(rnd_hidlib_t *hl, const char *id) +{ + int ret; + char *fn, *lang, *cookie; + script_t *s; + htsp_entry_t *e = htsp_getentry(&scripts, id); + + if (e == NULL) + return -1; + + s = e->value; + fn = rnd_strdup(s->fn); + lang = rnd_strdup(s->lang); + + cookie = script_gen_cookie(id); + script_unload_entry(e, "reload", cookie); + free(cookie); + + ret = rnd_script_load(hl, id, fn, lang); + free(fn); + free(lang); + return ret; +} + +void script_list(const char *pat) +{ + htsp_entry_t *e; + re_se_t *r = NULL; + + if ((pat != NULL) && (*pat != '\0')) + r = re_se_comp(pat); + + for(e = htsp_first(&scripts); e; e = htsp_next(&scripts, e)) { + script_t *s = (script_t *)e->value; + if ((r == NULL) || (re_se_exec(r, s->id)) || (re_se_exec(r, s->fn)) || (re_se_exec(r, s->lang))) + rnd_message(RND_MSG_INFO, "id=%s fn=%s lang=%s\n", s->id, s->fn, s->lang); + } + + if (r != NULL) + re_se_free(r); +} + +static void oneliner_boilerplate(FILE *f, const char *lang, int pre) +{ + if (strcmp(lang, "mawk") == 0) { + if (pre) + fputs("BEGIN {\n", f); + else + fputs("}\n", f); + } + else if (strcmp(lang, "fawk") == 0) { + if (pre) + fputs("function main(ARGS) {\n", f); + else + fputs("}\n", f); + } + else if (strcmp(lang, "fpas") == 0) { + if (pre) + fputs("function main(ARGS);\nbegin\n", f); + else + fputs("end;\n", f); + } + else if (strcmp(lang, "fbas") == 0) { + if (!pre) + fputs("\n", f); + } +} + +int script_oneliner(rnd_hidlib_t *hl, const char *lang, const char *src) +{ + FILE *f; + char *fn; + int res = 0; + + fn = rnd_tempfile_name_new("oneliner"); + f = rnd_fopen(NULL, fn, "w"); + if (f == NULL) { + rnd_tempfile_unlink(fn); + rnd_message(RND_MSG_ERROR, "script oneliner: can't open temp file for write\n"); + return -1; + } + oneliner_boilerplate(f, lang, 1); + fputs(src, f); + fputs("\n", f); + oneliner_boilerplate(f, lang, 0); + fclose(f); + + if (rnd_script_load(hl, "__oneliner", fn, lang) != 0) { + rnd_message(RND_MSG_ERROR, "script oneliner: can't load/parse the script\n"); + res = -1; + } + rnd_script_unload("__oneliner", NULL); + + rnd_tempfile_unlink(fn); + return res; +} + +#include "timer.c" +#include "perma.c" +#include "script_act.c" + +char *script_cookie = "script plugin"; + +int pplg_check_ver_script(int ver_needed) { return 0; } + +void pplg_uninit_script(void) +{ + htsp_entry_t *e; + + rnd_live_script_uninit(); + rnd_remove_actions_by_cookie(script_cookie); + for(e = htsp_first(&scripts); e; e = htsp_next(&scripts, e)) { + script_t *script = e->value; + char *cookie = script_gen_cookie(script->id); + script_unload_entry(e, "unload", cookie); + free(cookie); + } + + htsp_uninit(&scripts); + pup_uninit(&script_pup); + +#ifndef RND_HAVE_SYS_FUNGW + pplg_uninit_fungw_fawk(); +#endif + + rnd_event_unbind_allcookie(script_cookie); + + rnd_script_guess_lang_uninit(); +} + +int pplg_init_script(void) +{ + RND_API_CHK_VER; + RND_REGISTER_ACTIONS(script_action_list, script_cookie); + +#ifndef RND_HAVE_SYS_FUNGW + pplg_init_fungw_fawk(); +#endif + + rnd_c_script_init(); + htsp_init(&scripts, strhash, strkeyeq); + pup_init(&script_pup); + rnd_live_script_init(); + if (rnd_hid_in_main_loop) + perma_script_init(NULL); /* warning: no hidlib available */ + else + rnd_event_bind(RND_EVENT_MAINLOOP_CHANGE, script_mainloop_perma_ev, NULL, script_cookie); + rnd_event_bind(RND_EVENT_GUI_INIT, script_timer_gui_init_ev, NULL, script_cookie); + return 0; +} Index: trunk/src/librnd/plugins/script/script.h =================================================================== --- trunk/src/librnd/plugins/script/script.h (nonexistent) +++ trunk/src/librnd/plugins/script/script.h (revision 32492) @@ -0,0 +1,4 @@ +extern int rnd_script_load(rnd_hidlib_t *hl, const char *id, const char *fn, const char *lang); +extern int rnd_script_unload(const char *id, const char *preunload); +const char *rnd_script_guess_lang(rnd_hidlib_t *hl, const char *fn, int is_filename); + Index: trunk/src/librnd/plugins/script/script.pup =================================================================== --- trunk/src/librnd/plugins/script/script.pup (nonexistent) +++ trunk/src/librnd/plugins/script/script.pup (revision 32492) @@ -0,0 +1,8 @@ +$class feature +$short fungw turing complete scripting +$long Load and execute scripts written in any language supported by fungw +$package (core) +$state works +$hidlib 1 +default buildin +autoload 1 Index: trunk/src/librnd/plugins/script/script_act.c =================================================================== --- trunk/src/librnd/plugins/script/script_act.c (nonexistent) +++ trunk/src/librnd/plugins/script/script_act.c (revision 32492) @@ -0,0 +1,519 @@ +/* + * COPYRIGHT + * + * pcb-rnd, interactive printed circuit board design + * Copyright (C) 2018,2019 Tibor 'Igor2' Palinkas + * + * This module, debug, was written and is Copyright (C) 2016 by Tibor Palinkas + * this module is also subject to the GNU GPL as described below + * + * 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 Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact: + * Project page: http://repo.hu/projects/pcb-rnd + * lead developer: http://repo.hu/projects/pcb-rnd/contact.html + * mailing list: pcb-rnd (at) list.repo.hu (send "subscribe") + */ + +#include +#include +#include +#include "live_script.h" + +/*** dialog box ***/ +typedef struct { + RND_DAD_DECL_NOINIT(dlg) + int active; /* already open - allow only one instance */ + + int wslist; /* list of scripts */ + int walist; /* list of actions */ +} script_dlg_t; + +script_dlg_t script_dlg; + +static void script_dlg_s2d_act(script_dlg_t *ctx) +{ + rnd_hid_attribute_t *attr; + rnd_hid_tree_t *tree; + rnd_hid_row_t *r; + char *cell[2]; + htsp_entry_t *e; + script_t *sc; + + attr = &ctx->dlg[ctx->walist]; + tree = attr->wdata; + + /* remove existing items */ + for(r = gdl_first(&tree->rows); r != NULL; r = gdl_first(&tree->rows)) + rnd_dad_tree_remove(attr, r); + + r = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wslist]); + if (r == NULL) + return; + + sc = htsp_get(&scripts, r->cell[0]); + if (sc == NULL) + return; + + /* add all actions */ + cell[1] = NULL; + for(e = htsp_first(&sc->obj->func_tbl); e; e = htsp_next(&sc->obj->func_tbl, e)) { + cell[0] = rnd_strdup(e->key); + rnd_dad_tree_append(attr, NULL, cell); + } +} + + +static void script_dlg_s2d(script_dlg_t *ctx) +{ + rnd_hid_attribute_t *attr; + rnd_hid_tree_t *tree; + rnd_hid_row_t *r; + char *cell[4], *cursor_path = NULL; + htsp_entry_t *e; + + attr = &ctx->dlg[ctx->wslist]; + tree = attr->wdata; + + /* remember cursor */ + r = rnd_dad_tree_get_selected(attr); + if (r != NULL) + cursor_path = rnd_strdup(r->cell[0]); + + /* remove existing items */ + for(r = gdl_first(&tree->rows); r != NULL; r = gdl_first(&tree->rows)) + rnd_dad_tree_remove(attr, r); + + /* add all items */ + cell[3] = NULL; + for(e = htsp_first(&scripts); e; e = htsp_next(&scripts, e)) { + script_t *s = (script_t *)e->value; + cell[0] = rnd_strdup(s->id); + cell[1] = rnd_strdup(s->lang); + cell[2] = rnd_strdup(s->fn); + rnd_dad_tree_append(attr, NULL, cell); + } + + /* restore cursor */ + if (cursor_path != NULL) { + rnd_hid_attr_val_t hv; + hv.str = cursor_path; + rnd_gui->attr_dlg_set_value(ctx->dlg_hid_ctx, ctx->wslist, &hv); + free(cursor_path); + } + script_dlg_s2d_act(ctx); +} + +void script_dlg_update(void) +{ + if (script_dlg.active) + script_dlg_s2d(&script_dlg); +} + +static void script_dlg_close_cb(void *caller_data, rnd_hid_attr_ev_t ev) +{ + script_dlg_t *ctx = caller_data; + RND_DAD_FREE(ctx->dlg); + memset(ctx, 0, sizeof(script_dlg_t)); /* reset all states to the initial - includes ctx->active = 0; */ +} + +static void btn_unload_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr) +{ + script_dlg_t *ctx = caller_data; + rnd_hid_row_t *row = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wslist]); + if (row == NULL) + return; + + rnd_script_unload(row->cell[0], "unload"); + script_dlg_s2d(ctx); +} + +static void btn_reload_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr) +{ + script_dlg_t *ctx = caller_data; + rnd_hidlib_t *hl = rnd_gui->get_dad_hidlib(hid_ctx); + rnd_hid_row_t *row = rnd_dad_tree_get_selected(&ctx->dlg[ctx->wslist]); + if (row == NULL) + return; + + script_reload(hl, row->cell[0]); + script_dlg_s2d(ctx); +} + +static void slist_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr) +{ + script_dlg_s2d_act((script_dlg_t *)caller_data); +} + +static void btn_load_cb(void *hid_ctx, void *caller_data, rnd_hid_attribute_t *attr) +{ + script_dlg_t *ctx = caller_data; + rnd_hidlib_t *hl = rnd_gui->get_dad_hidlib(hid_ctx); + int failed; + char *tmp, *fn = rnd_gui->fileselect(rnd_gui, "script to load", "Select a script file to load", NULL, NULL, NULL, "script", RND_HID_FSD_READ, NULL); + rnd_hid_dad_buttons_t clbtn[] = {{"Cancel", -1}, {"ok", 0}, {NULL, 0}}; + typedef struct { + RND_DAD_DECL_NOINIT(dlg) + int wid, wlang; + } idlang_t; + idlang_t idlang; + + if (fn == NULL) + return; + + memset(&idlang, 0, sizeof(idlang)); + RND_DAD_BEGIN_VBOX(idlang.dlg); + RND_DAD_BEGIN_HBOX(idlang.dlg); + RND_DAD_LABEL(idlang.dlg, "ID:"); + RND_DAD_STRING(idlang.dlg); + idlang.wid = RND_DAD_CURRENT(idlang.dlg); + tmp = strrchr(fn, RND_DIR_SEPARATOR_C); + if (tmp != NULL) { + tmp++; + idlang.dlg[idlang.wid].val.str = tmp = rnd_strdup(tmp); + tmp = strchr(tmp, '.'); + if (tmp != NULL) + *tmp = '\0'; + } + RND_DAD_END(idlang.dlg); + RND_DAD_BEGIN_HBOX(idlang.dlg); + RND_DAD_LABEL(idlang.dlg, "language:"); + RND_DAD_STRING(idlang.dlg); + idlang.wlang = RND_DAD_CURRENT(idlang.dlg); + idlang.dlg[idlang.wlang].val.str = rnd_strdup_null(rnd_script_guess_lang(NULL, fn, 1)); + RND_DAD_END(idlang.dlg); + RND_DAD_BUTTON_CLOSES(idlang.dlg, clbtn); + RND_DAD_END(idlang.dlg); + + + RND_DAD_AUTORUN("script_load", idlang.dlg, "load script", NULL, failed); + + if ((!failed) && (rnd_script_load(hl, idlang.dlg[idlang.wid].val.str, fn, idlang.dlg[idlang.wlang].val.str) == 0)) + script_dlg_s2d(ctx); + + RND_DAD_FREE(idlang.dlg); +} + +static void script_dlg_open(void) +{ + static const char *hdr[] = {"ID", "language", "file", NULL}; + rnd_hid_dad_buttons_t clbtn[] = {{"Close", 0}, {NULL, 0}}; + if (script_dlg.active) + return; /* do not open another */ + + RND_DAD_BEGIN_VBOX(script_dlg.dlg); + RND_DAD_COMPFLAG(script_dlg.dlg, RND_HATF_EXPFILL); + RND_DAD_BEGIN_HPANE(script_dlg.dlg); + RND_DAD_COMPFLAG(script_dlg.dlg, RND_HATF_EXPFILL); + /* left side */ + RND_DAD_BEGIN_VBOX(script_dlg.dlg); + RND_DAD_COMPFLAG(script_dlg.dlg, RND_HATF_EXPFILL); + RND_DAD_TREE(script_dlg.dlg, 3, 0, hdr); + RND_DAD_COMPFLAG(script_dlg.dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL); + script_dlg.wslist = RND_DAD_CURRENT(script_dlg.dlg); + RND_DAD_CHANGE_CB(script_dlg.dlg, slist_cb); + RND_DAD_BEGIN_HBOX(script_dlg.dlg); + RND_DAD_BUTTON(script_dlg.dlg, "Unload"); + RND_DAD_HELP(script_dlg.dlg, "Unload the currently selected script"); + RND_DAD_CHANGE_CB(script_dlg.dlg, btn_unload_cb); + RND_DAD_BUTTON(script_dlg.dlg, "Reload"); + RND_DAD_HELP(script_dlg.dlg, "Reload the currently selected script\n(useful if the script has changed)"); + RND_DAD_CHANGE_CB(script_dlg.dlg, btn_reload_cb); + RND_DAD_BUTTON(script_dlg.dlg, "Load..."); + RND_DAD_HELP(script_dlg.dlg, "Load a new script from disk"); + RND_DAD_CHANGE_CB(script_dlg.dlg, btn_load_cb); + RND_DAD_END(script_dlg.dlg); + RND_DAD_END(script_dlg.dlg); + + /* right side */ + RND_DAD_BEGIN_VBOX(script_dlg.dlg); + RND_DAD_COMPFLAG(script_dlg.dlg, RND_HATF_EXPFILL); + RND_DAD_LABEL(script_dlg.dlg, "Actions:"); + RND_DAD_TREE(script_dlg.dlg, 1, 0, NULL); + RND_DAD_COMPFLAG(script_dlg.dlg, RND_HATF_EXPFILL | RND_HATF_SCROLL); + script_dlg.walist = RND_DAD_CURRENT(script_dlg.dlg); + RND_DAD_END(script_dlg.dlg); + RND_DAD_END(script_dlg.dlg); + RND_DAD_BUTTON_CLOSES(script_dlg.dlg, clbtn); + RND_DAD_END(script_dlg.dlg); + + /* set up the context */ + script_dlg.active = 1; + + RND_DAD_NEW("scripts", script_dlg.dlg, "pcb-rnd Scripts", &script_dlg, rnd_false, script_dlg_close_cb); + script_dlg_s2d(&script_dlg); +} + +/*** actions ***/ + +static int script_id_invalid(const char *id) +{ + for(; *id != '\0'; id++) + if (!isalnum(*id) && (*id != '_')) + return 1; + return 0; +} + +#define ID_VALIDATE(id, act) \ +do { \ + if (script_id_invalid(id)) { \ + rnd_message(RND_MSG_ERROR, #act ": Invalid script ID '%s' (must contain only alphanumeric characters and underscores)\n", id); \ + return FGW_ERR_ARG_CONV; \ + } \ +} while(0) + +static const char pcb_acth_LoadScript[] = "Load a fungw script"; +static const char pcb_acts_LoadScript[] = "LoadScript(id, filename, [language])"; +/* DOC: loadscript.html */ +static fgw_error_t pcb_act_LoadScript(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + const char *id, *fn, *lang = NULL; + RND_ACT_CONVARG(1, FGW_STR, LoadScript, id = argv[1].val.str); + RND_ACT_CONVARG(2, FGW_STR, LoadScript, fn = argv[2].val.str); + RND_ACT_MAY_CONVARG(3, FGW_STR, LoadScript, lang = argv[3].val.str); + + ID_VALIDATE(id, LoadScript); + + RND_ACT_IRES(rnd_script_load(RND_ACT_HIDLIB, id, fn, lang)); + script_dlg_update(); + return 0; +} + +static const char pcb_acth_UnloadScript[] = "Unload a fungw script"; +static const char pcb_acts_UnloadScript[] = "UnloadScript(id)"; +/* DOC: unloadscript.html */ +static fgw_error_t pcb_act_UnloadScript(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + const char *id = NULL; + RND_ACT_CONVARG(1, FGW_STR, UnloadScript, id = argv[1].val.str); + + ID_VALIDATE(id, UnloadScript); + + RND_ACT_IRES(rnd_script_unload(id, "unload")); + return 0; +} + +static const char pcb_acth_ReloadScript[] = "Reload a fungw script"; +static const char pcb_acts_ReloadScript[] = "ReloadScript(id)"; +/* DOC: reloadscript.html */ +static fgw_error_t pcb_act_ReloadScript(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + const char *id = NULL; + RND_ACT_CONVARG(1, FGW_STR, UnloadScript, id = argv[1].val.str); + + ID_VALIDATE(id, ReloadScript); + + RND_ACT_IRES(script_reload(RND_ACT_HIDLIB, id)); + script_dlg_update(); + return 0; +} + +static const char pcb_acth_ScriptPersistency[] = "Read or remove script persistency data savd on preunload"; +static const char pcb_acts_ScriptPersistency[] = "ScriptPersistency(read|remove)"; +/* DOC: scriptpersistency.html */ +static fgw_error_t pcb_act_ScriptPersistency(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + const char *cmd = NULL; + RND_ACT_CONVARG(1, FGW_STR, ScriptPersistency, cmd = argv[1].val.str); + return script_persistency(res, cmd); +} + +static const char pcb_acth_ListScripts[] = "List fungw scripts, optionally filtered wiht regex pat."; +static const char pcb_acts_ListScripts[] = "ListScripts([pat])"; +/* DOC: listscripts.html */ +static fgw_error_t pcb_act_ListScripts(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + const char *pat = NULL; + RND_ACT_MAY_CONVARG(1, FGW_STR, ListScripts, pat = argv[1].val.str); + + script_list(pat); + + RND_ACT_IRES(0); + return 0; +} + +static const char pcb_acth_BrowseScripts[] = "Present a dialog box for browsing scripts"; +static const char pcb_acts_BrowseScripts[] = "BrowseScripts()"; +static fgw_error_t pcb_act_BrowseScripts(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + script_dlg_open(); + RND_ACT_IRES(0); + return 0; +} + +static const char pcb_acth_ScriptCookie[] = "Return a cookie specific to the current script instance during script initialization"; +static const char pcb_acts_ScriptCookie[] = "ScriptCookie()"; +/* DOC: scriptcookie.html */ +static fgw_error_t pcb_act_ScriptCookie(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + res->type = FGW_STR | FGW_DYN; + res->val.str = script_gen_cookie(NULL); + if (res->val.str == NULL) + return -1; + return 0; +} + +static const char pcb_acth_Oneliner[] = "Execute a script one-liner using a specific language"; +static const char pcb_acts_Oneliner[] = "Oneliner(lang, script)"; +/* DOC: onliner.html */ +static fgw_error_t pcb_act_Oneliner(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + const char *first = NULL, *lang = argv[0].val.func->name, *scr = NULL; + + if (strcmp(lang, "oneliner") == 0) { + /* call to oneliner(lang, script) */ + RND_ACT_CONVARG(1, FGW_STR, Oneliner, lang = argv[1].val.str); + RND_ACT_CONVARG(2, FGW_STR, Oneliner, scr = argv[2].val.str); + } + else if (strcmp(lang, "/exit") == 0) { + RND_ACT_IRES(rnd_cli_leave()); + return 0; + } + else { + /* call to lang(script) */ + RND_ACT_MAY_CONVARG(1, FGW_STR, Oneliner, scr = argv[1].val.str); + } + + RND_ACT_MAY_CONVARG(1, FGW_STR, Oneliner, first = argv[1].val.str); + if (first != NULL) { + if (*first == '/') { + if (rnd_strcasecmp(scr, "/exit") == 0) { + RND_ACT_IRES(rnd_cli_leave()); + return 0; + } + RND_ACT_IRES(-1); /* ignore /click, /tab and others for now */ + return 0; + } + } + + lang = rnd_script_guess_lang(NULL, lang, 0); + + if (scr == NULL) { + RND_ACT_IRES(rnd_cli_enter(lang, lang)); + return 0; + } + + if (rnd_strcasecmp(scr, "/exit") == 0) { + RND_ACT_IRES(rnd_cli_leave()); + return 0; + } + + RND_ACT_IRES(script_oneliner(RND_ACT_HIDLIB, lang, scr)); + return 0; +} + +static const char pcb_acth_ActionString[] = "Execute a pcb-rnd action parsing a string; syntac: \"action(arg,arg,arg)\""; +static const char pcb_acts_ActionString[] = "ActionString(action)"; +static fgw_error_t pcb_act_ActionString(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + const char *act; + RND_ACT_CONVARG(1, FGW_STR, ActionString, act = argv[1].val.str); + return rnd_parse_command_res(RND_ACT_HIDLIB, res, act, 1); +} + + +static const char pcb_acth_pcb_math1[] = "Single-argument math functions"; +static const char pcb_acts_pcb_math1[] = "pcb_MATHFUNC(val)"; +static fgw_error_t pcb_act_pcb_math1(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + const char *actname = argv[0].val.func->name; + double a; + + RND_ACT_CONVARG(1, FGW_DOUBLE, pcb_math1, a = argv[1].val.nat_double); + res->type = FGW_DOUBLE; + switch(actname[4]) { + case 'a': + switch(actname[5]) { + case 's': res->val.nat_double = asin(a); return 0; + case 'c': res->val.nat_double = acos(a); return 0; + case 't': res->val.nat_double = atan(a); return 0; + } + break; + case 's': + switch(actname[5]) { + case 'i': res->val.nat_double = sin(a); return 0; + case 'q': res->val.nat_double = sqrt(a); return 0; + } + break; + case 'c': res->val.nat_double = cos(a); return 0; + case 't': res->val.nat_double = tan(a); return 0; + } + return FGW_ERR_ARG_CONV; +} + +static const char pcb_acth_pcb_math2[] = "Two-argument math functions"; +static const char pcb_acts_pcb_math2[] = "pcb_MATHFUNC(a,b)"; +static fgw_error_t pcb_act_pcb_math2(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + const char *actname = argv[0].val.func->name; + double a, b; + + RND_ACT_CONVARG(1, FGW_DOUBLE, pcb_math2, a = argv[1].val.nat_double); + RND_ACT_CONVARG(2, FGW_DOUBLE, pcb_math2, b = argv[2].val.nat_double); + res->type = FGW_DOUBLE; + switch(actname[4]) { + case 'a': res->val.nat_double = atan2(a, b); return 0; + } + return FGW_ERR_ARG_CONV; +} + + +static rnd_action_t script_action_list[] = { + {"LoadScript", pcb_act_LoadScript, pcb_acth_LoadScript, pcb_acts_LoadScript}, + {"UnloadScript", pcb_act_UnloadScript, pcb_acth_UnloadScript, pcb_acts_UnloadScript}, + {"ReloadScript", pcb_act_ReloadScript, pcb_acth_ReloadScript, pcb_acts_ReloadScript}, + {"ScriptPersistency", pcb_act_ScriptPersistency, pcb_acth_ScriptPersistency, pcb_acts_ScriptPersistency}, + {"ListScripts", pcb_act_ListScripts, pcb_acth_ListScripts, pcb_acts_ListScripts}, + {"BrowseScripts", pcb_act_BrowseScripts, pcb_acth_BrowseScripts, pcb_acts_BrowseScripts}, + {"AddTimer", pcb_act_AddTimer, pcb_acth_AddTimer, pcb_acts_AddTimer}, + {"ScriptCookie", pcb_act_ScriptCookie, pcb_acth_ScriptCookie, pcb_acts_ScriptCookie}, + {"LiveScript", pcb_act_LiveScript, pcb_acth_LiveScript, pcb_acts_LiveScript}, + + /* script shorthands */ + {"fawk", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"fpas", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"pas", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"fbas", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"bas", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, +#ifdef RND_HAVE_SYS_FUNGW + {"awk", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"mawk", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"lua", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"tcl", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"javascript", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"duktape", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"js", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"stt", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"estutter", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"perl", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"ruby", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"mruby", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"py", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"python", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, +#endif + {"Oneliner", pcb_act_Oneliner, pcb_acth_Oneliner, pcb_acts_Oneliner}, + {"ActionString", pcb_act_ActionString, pcb_acth_ActionString, pcb_acts_ActionString}, + + /* math */ + {"pcb_sin", pcb_act_pcb_math1, NULL, NULL}, + {"pcb_cos", pcb_act_pcb_math1, NULL, NULL}, + {"pcb_asin", pcb_act_pcb_math1, NULL, NULL}, + {"pcb_acos", pcb_act_pcb_math1, NULL, NULL}, + {"pcb_atan", pcb_act_pcb_math1, NULL, NULL}, + {"pcb_tan", pcb_act_pcb_math1, NULL, NULL}, + {"pcb_sqrt", pcb_act_pcb_math1, NULL, NULL}, + + {"pcb_atan2", pcb_act_pcb_math2, NULL, NULL} +}; Index: trunk/src/librnd/plugins/script/timer.c =================================================================== --- trunk/src/librnd/plugins/script/timer.c (nonexistent) +++ trunk/src/librnd/plugins/script/timer.c (revision 32492) @@ -0,0 +1,163 @@ +/* + * COPYRIGHT + * + * pcb-rnd, interactive printed circuit board design + * Copyright (C) 2018 Tibor 'Igor2' Palinkas + * + * This module, debug, was written and is Copyright (C) 2016 by Tibor Palinkas + * this module is also subject to the GNU GPL as described below + * + * 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 Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Contact: + * Project page: http://repo.hu/projects/pcb-rnd + * lead developer: http://repo.hu/projects/pcb-rnd/contact.html + * mailing list: pcb-rnd (at) list.repo.hu (send "subscribe") + */ + +#include "config.h" + +#include + +typedef struct { + double next, period; + long count; + char *user_data; + char aname[1]; +} script_timer_t; + +vtp0_t timers; +int timer_running = 0, want_timer = 0; + +static void start_timer(void); +static void timer_cb(rnd_hidval_t hv) +{ + long n, len; + double now; + + len = vtp0_len(&timers); + if (len == 0) { + timer_running = 0; + return; /* don't even restart the timer if we are not waiting for anything */ + } + + now = rnd_dtime(); + + /* we could do something clever here, e.g. sort timers by ->next but + in reality there will be so few timers at any given time that it's + probably not worth the effort */ + for(n = len-1; n >= 0; n--) { + script_timer_t *t = timers.array[n]; + fgw_func_t *f; + fgw_arg_t res, argv[4]; + + if (t->next <= now) { + t->next += t->period; + + f = fgw_func_lookup(&rnd_fgw, t->aname); + if (f == NULL) + goto remove; + argv[0].type = FGW_FUNC; + argv[0].val.argv0.func = f; + argv[0].val.argv0.user_call_ctx = NULL; + argv[1].type = FGW_DOUBLE; + argv[1].val.nat_double = now; + argv[2].type = FGW_LONG; + argv[2].val.nat_long = t->count; + if (t->user_data != NULL) { + argv[3].type = FGW_STR; + argv[3].val.str = t->user_data; + } + else { + argv[3].type = FGW_STR; + argv[3].val.str = ""; + } + + res.type = FGW_INVALID; + if (rnd_actionv_(f, &res, 4, argv) != 0) + goto remove; + fgw_arg_conv(&rnd_fgw, &res, FGW_INT); + if ((res.type != FGW_INT) || (res.val.nat_int != 0)) /* action requested timer removal */ + goto remove; + + if (t->count > 0) { + t->count--; + if (t->count == 0) { + remove:; + vtp0_remove(&timers, n, 1); + free(t->user_data); + free(t); + } + } + } + } + + start_timer(); +} + +static void start_timer(void) +{ + static rnd_hidval_t hv; + if (!rnd_gui->gui) { + want_timer = 1; + return; + } + timer_running = 1; + rnd_gui->add_timer(rnd_gui, timer_cb, 100, hv); +} + +static void script_timer_gui_init_ev(rnd_hidlib_t *hidlib, void *user_data, int argc, rnd_event_arg_t argv[]) +{ + if (want_timer && !timer_running) /* script created a timer before gui init */ + start_timer(); +} + +static const char pcb_acth_AddTimer[] = "Add a new timer"; +static const char pcb_acts_AddTimer[] = "AddTimer(action, period, [repeat], [userdata])"; +/* DOC: addtimer.html */ +static fgw_error_t pcb_act_AddTimer(fgw_arg_t *res, int argc, fgw_arg_t *argv) +{ + const char *act, *user_data = NULL; + double period; + int count = 1, len; + char fn[RND_ACTION_NAME_MAX]; + script_timer_t *t; + + RND_ACT_CONVARG(1, FGW_STR, AddTimer, act = argv[1].val.str); + RND_ACT_CONVARG(2, FGW_DOUBLE, AddTimer, period = argv[2].val.nat_double); + RND_ACT_MAY_CONVARG(3, FGW_INT, AddTimer, count = argv[3].val.nat_int); + RND_ACT_MAY_CONVARG(4, FGW_STR, AddTimer, user_data = argv[4].val.str); + + rnd_aname(fn, act); + len = strlen(fn); + t = malloc(sizeof(script_timer_t) + len); + t->next = rnd_dtime() + period; + t->period = period; + t->count = count; + strcpy(t->aname, fn); + if (user_data != NULL) + t->user_data = rnd_strdup(user_data); + else + t->user_data = NULL; + + vtp0_append(&timers, t); + + if (!timer_running) + start_timer(); + + RND_ACT_IRES(0); + return 0; +} + Index: trunk/src_3rd =================================================================== --- trunk/src_3rd (revision 32491) +++ trunk/src_3rd (revision 32492) Property changes on: trunk/src_3rd ___________________________________________________________________ Modified: svn:externals ## -1,3 +1,4 ## +genregex svn://svn.repo.hu/genregex/trunk/src genht svn://svn.repo.hu/genht/trunk/src genlist svn://svn.repo.hu/genlist/trunk/genlist genvector svn://svn.repo.hu/genvector/trunk/genvector