From c9c42c87a1f7b2e39705da95e4f20fb7830646a4 Mon Sep 17 00:00:00 2001 From: rswindell <> Date: Thu, 30 Apr 2020 03:34:48 +0000 Subject: [PATCH] Support JavaScript property name expansion using JS:<name> @-code where name is the name of a scalar property in either the current scope (by default) or the scope of the object passed to: - bbs.menu() - console.putmsg() - console.printfile() - console.printtail() The 'name' cannot be an array element (e.g. myprop[0]) or a nested object reference (e.g. myobj.myprop): just a single property name that can be converted to a string. Also, bbs.menu() now accepts an optional print-mode argument (default: P_NONE). --- src/sbbs3/atcodes.cpp | 13 +++++-- src/sbbs3/js_bbs.cpp | 28 +++++++++++--- src/sbbs3/js_console.cpp | 80 ++++++++++++++++++++++++++-------------- src/sbbs3/prntfile.cpp | 14 +++---- src/sbbs3/putmsg.cpp | 4 +- src/sbbs3/sbbs.h | 12 +++--- 6 files changed, 100 insertions(+), 51 deletions(-) diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp index fde47dec29..e6c658966d 100644 --- a/src/sbbs3/atcodes.cpp +++ b/src/sbbs3/atcodes.cpp @@ -71,7 +71,7 @@ static char* separate_thousands(const char* src, char *dest, size_t maxlen, char /****************************************************************************/ /* Returns 0 if invalid @ code. Returns length of @ code if valid. */ /****************************************************************************/ -int sbbs_t::show_atcode(const char *instr) +int sbbs_t::show_atcode(const char *instr, JSObject* obj) { char str[128],str2[128],*tp,*sp,*p; int len; @@ -144,7 +144,7 @@ int sbbs_t::show_atcode(const char *instr) *p=0; } - cp = atcode(sp, str2, sizeof(str2), &pmode, centered); + cp = atcode(sp, str2, sizeof(str2), &pmode, centered, obj); if(cp==NULL) return(0); @@ -210,7 +210,7 @@ static const char* getpath(scfg_t* cfg, const char* path) return path; } -const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool centered) +const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool centered, JSObject* obj) { char* tp = NULL; uint i; @@ -1092,6 +1092,13 @@ const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool return nulstr; } + if(strncmp(sp, "JS:", 3) == 0) { + jsval val; + if(JS_GetProperty(js_cx, obj == NULL ? js_glob : obj, sp + 3, &val)) + JSVALUE_TO_STRBUF(js_cx, val, str, maxlen, NULL); + return str; + } + if(!strncmp(sp,"EXEC:",5)) { exec_bin(sp+5,&main_csi); return(nulstr); diff --git a/src/sbbs3/js_bbs.cpp b/src/sbbs3/js_bbs.cpp index 6e2fc26c85..ce0c075695 100644 --- a/src/sbbs3/js_bbs.cpp +++ b/src/sbbs3/js_bbs.cpp @@ -1170,6 +1170,8 @@ js_menu(JSContext *cx, uintN argc, jsval *arglist) sbbs_t* sbbs; jsrefcount rc; char *menu; + int32 mode = P_NONE; + JSObject* obj = JS_GetScopeChain(cx); if(!js_argc(cx, argc, 1)) return(JS_FALSE); @@ -1181,11 +1183,23 @@ js_menu(JSContext *cx, uintN argc, jsval *arglist) if (!str) return(JS_FALSE); + uintN argn = 1; + if(argc > argn && JSVAL_IS_NUMBER(argv[argn])) { + if(!JS_ValueToInt32(cx,argv[argn], &mode)) + return JS_FALSE; + argn++; + } + if(argc > argn && JSVAL_IS_OBJECT(argv[argn])) { + if((obj = JSVAL_TO_OBJECT(argv[argn])) == NULL) + return JS_FALSE; + argn++; + } + JSSTRING_TO_MSTRING(cx, str, menu, NULL); if(!menu) return JS_FALSE; rc=JS_SUSPENDREQUEST(cx); - bool result = sbbs->menu(menu); + bool result = sbbs->menu(menu, mode, obj); free(menu); JS_RESUMEREQUEST(cx, rc); @@ -4425,8 +4439,10 @@ static jsSyncMethodSpec js_bbs_functions[] = { ,314 }, /* menuing */ - {"menu", js_menu, 1, JSTYPE_BOOLEAN, JSDOCSTR("base_filename") - ,JSDOCSTR("display a menu file from the text/menu directory") + {"menu", js_menu, 1, JSTYPE_BOOLEAN, JSDOCSTR("base_filename [,mode=<tt>P_NONE</tt>] [,object scope]") + ,JSDOCSTR("display a menu file from the text/menu directory.<br>" + "See <tt>P_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.<br>" + "When <i>scope</i> is specified, <tt>@JS:property@</tt> codes will expand the referenced property names.") ,310 }, {"menu_exists", js_menu_exists, 1, JSTYPE_BOOLEAN, JSDOCSTR("base_filename") @@ -4453,7 +4469,7 @@ static jsSyncMethodSpec js_bbs_functions[] = { /* xtrn programs/modules */ {"exec", js_exec, 2, JSTYPE_NUMBER, JSDOCSTR("cmdline [,mode=<tt>EX_NONE</tt>] [,startup_dir]") ,JSDOCSTR("execute a program, optionally changing current directory to <i>startup_dir</i> " - "(see <tt>EX_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> bits)") + "(see <tt>EX_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> flags.)") ,310 }, {"exec_xtrn", js_exec_xtrn, 1, JSTYPE_BOOLEAN, JSDOCSTR("xtrn_number_or_code") @@ -4466,11 +4482,11 @@ static jsSyncMethodSpec js_bbs_functions[] = { ,310 }, {"telnet_gate", js_telnet_gate, 1, JSTYPE_VOID, JSDOCSTR("address [,mode=<tt>TG_NONE</tt>]") - ,JSDOCSTR("external Telnet gateway (see <tt>TG_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> bits)") + ,JSDOCSTR("external Telnet gateway (see <tt>TG_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> flags.)") ,310 }, {"rlogin_gate", js_rlogin_gate, 1, JSTYPE_VOID, JSDOCSTR("address [,client-user-name=<tt>user.alias</tt>, server-user-name=<tt>user.name</tt>, terminal=<tt>console.terminal</tt>] [,mode=<tt>TG_NONE</tt>]") - ,JSDOCSTR("external RLogin gateway (see <tt>TG_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> bits)") + ,JSDOCSTR("external RLogin gateway (see <tt>TG_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> flags.)") ,316 }, /* security */ diff --git a/src/sbbs3/js_console.cpp b/src/sbbs3/js_console.cpp index 6f5343daf6..bede632e9b 100644 --- a/src/sbbs3/js_console.cpp +++ b/src/sbbs3/js_console.cpp @@ -1216,6 +1216,7 @@ js_writeln(JSContext *cx, uintN argc, jsval *arglist) static JSBool js_putmsg(JSContext *cx, uintN argc, jsval *arglist) { + uintN argn; jsval *argv=JS_ARGV(cx, arglist); int32 mode=0; int32 columns=0; @@ -1223,6 +1224,7 @@ js_putmsg(JSContext *cx, uintN argc, jsval *arglist) sbbs_t* sbbs; char* cstr; jsrefcount rc; + JSObject* obj = JS_GetScopeChain(cx); if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL) return(JS_FALSE); @@ -1233,20 +1235,28 @@ js_putmsg(JSContext *cx, uintN argc, jsval *arglist) if (!str) return(JS_FALSE); - if(argc>1 && JSVAL_IS_NUMBER(argv[1])) { - if(!JS_ValueToInt32(cx,argv[1],&mode)) + argn = 1; + if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) { + if(!JS_ValueToInt32(cx,argv[argn],&mode)) + return JS_FALSE; + argn++; + } + if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) { + if(!JS_ValueToInt32(cx,argv[argn],&columns)) return JS_FALSE; + argn++; } - if(argc>2 && JSVAL_IS_NUMBER(argv[2])) { - if(!JS_ValueToInt32(cx,argv[2],&columns)) + if(argc>argn && JSVAL_IS_OBJECT(argv[argn])) { + if((obj = JSVAL_TO_OBJECT(argv[argn])) == NULL) return JS_FALSE; + argn++; } JSSTRING_TO_MSTRING(cx, str, cstr, NULL); if(cstr==NULL) return JS_FALSE; rc=JS_SUSPENDREQUEST(cx); - sbbs->putmsg(cstr, mode, columns); + sbbs->putmsg(cstr, mode, columns, obj); free(cstr); JS_RESUMEREQUEST(cx, rc); return(JS_TRUE); @@ -1262,6 +1272,7 @@ js_printfile(JSContext *cx, uintN argc, jsval *arglist) sbbs_t* sbbs; char* cstr; jsrefcount rc; + JSObject* obj = JS_GetScopeChain(cx); if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL) return(JS_FALSE); @@ -1270,20 +1281,28 @@ js_printfile(JSContext *cx, uintN argc, jsval *arglist) if (!str) return(JS_FALSE); - if(argc>1 && JSVAL_IS_NUMBER(argv[1])) { - if(!JS_ValueToInt32(cx,argv[1],&mode)) + uintN argn = 1; + if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) { + if(!JS_ValueToInt32(cx,argv[argn],&mode)) return JS_FALSE; + argn++; } - if(argc>2 && JSVAL_IS_NUMBER(argv[2])) { - if(!JS_ValueToInt32(cx,argv[2],&columns)) + if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) { + if(!JS_ValueToInt32(cx,argv[argn],&columns)) return JS_FALSE; + argn++; + } + if(argc>argn && JSVAL_IS_OBJECT(argv[argn])) { + if((obj = JSVAL_TO_OBJECT(argv[argn])) == NULL) + return JS_FALSE; + argn++; } JSSTRING_TO_MSTRING(cx, str, cstr, NULL); if(cstr==NULL) return JS_FALSE; rc=JS_SUSPENDREQUEST(cx); - bool result = sbbs->printfile(cstr,mode,columns); + bool result = sbbs->printfile(cstr,mode,columns, obj); free(cstr); JS_RESUMEREQUEST(cx, rc); @@ -1304,6 +1323,7 @@ js_printtail(JSContext *cx, uintN argc, jsval *arglist) JSString* js_str=NULL; char* cstr; jsrefcount rc; + JSObject* obj = JS_GetScopeChain(cx); if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL) return(JS_FALSE); @@ -1322,8 +1342,12 @@ js_printtail(JSContext *cx, uintN argc, jsval *arglist) if(!JS_ValueToInt32(cx,argv[i],&columns)) return JS_FALSE; } - } else if(JSVAL_IS_STRING(argv[i])) + } else if(JSVAL_IS_STRING(argv[i])) { js_str = JS_ValueToString(cx, argv[i]); + } else if(JSVAL_IS_OBJECT(argv[i])) { + if((obj = JSVAL_TO_OBJECT(argv[i])) == NULL) + return JS_FALSE; + } } if(js_str==NULL) @@ -1336,7 +1360,7 @@ js_printtail(JSContext *cx, uintN argc, jsval *arglist) if(cstr==NULL) return JS_FALSE; rc=JS_SUSPENDREQUEST(cx); - bool result = sbbs->printtail(cstr,lines,mode,columns); + bool result = sbbs->printtail(cstr,lines,mode,columns,obj); free(cstr); JS_RESUMEREQUEST(cx, rc); @@ -1951,19 +1975,19 @@ js_term_supports(JSContext *cx, uintN argc, jsval *arglist) static jsSyncMethodSpec js_console_functions[] = { {"inkey", js_inkey, 0, JSTYPE_STRING, JSDOCSTR("[mode=<tt>K_NONE</tt>] [,timeout=<tt>0</tt>]") - ,JSDOCSTR("get a single key with optional <i>timeout</i> in milliseconds (defaults to 0, for no wait), " - "returns an empty string if there is no input (e.g. timeout occurs), " - "see <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> bits") + ,JSDOCSTR("get a single key with optional <i>timeout</i> in milliseconds (defaults to 0, for no wait).<br>" + "Returns an empty string if there is no input (e.g. timeout occurs).<br>" + "See <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.") ,311 }, {"getkey", js_getkey, 0, JSTYPE_STRING, JSDOCSTR("[mode=<tt>K_NONE</tt>]") - ,JSDOCSTR("get a single key, with wait, " - "see <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> bits") + ,JSDOCSTR("get a single key, with wait.<br>" + "See <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.") ,310 }, {"getstr", js_getstr, 0, JSTYPE_STRING, JSDOCSTR("[string] [,maxlen=<tt>128</tt>] [,mode=<tt>K_NONE</tt>] [,history[]]") - ,JSDOCSTR("get a text string from the user, " - "see <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> bits.<br>" + ,JSDOCSTR("get a text string from the user.<br>" + "See <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.<br>" "<i>history[]</i>, added in v3.17, allows a command history (string array) to be recalled using the up/down arrow keys." ) ,310 @@ -2042,10 +2066,11 @@ static jsSyncMethodSpec js_console_functions[] = { ,JSDOCSTR("display one or more values as raw strings followed by a single carriage-return/line-feed pair (new-line)") ,315 }, - {"putmsg", js_putmsg, 1, JSTYPE_VOID, JSDOCSTR("text [,mode=<tt>P_NONE</tt>] [,orig_columns=0]") - ,JSDOCSTR("display message text (Ctrl-A codes, @-codes, pipe codes, etc), " - "see <tt>P_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> bits.<br>" - "When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known") + {"putmsg", js_putmsg, 1, JSTYPE_VOID, JSDOCSTR("text [,mode=<tt>P_NONE</tt>] [,orig_columns=0] [,object scope]") + ,JSDOCSTR("display message text (Ctrl-A codes, @-codes, pipe codes, etc.).<br> " + "See <tt>P_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.<br>" + "When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known.<br>" + "When <i>scope</i> is specified, <tt>@JS:property@</tt> codes will expand the referenced property names.") ,310 }, {"center", js_center, 1, JSTYPE_VOID, JSDOCSTR("text [,width]") @@ -2060,13 +2085,14 @@ static jsSyncMethodSpec js_console_functions[] = { ,JSDOCSTR("returns the printed-length (number of columns) of the specified <i>text</i>, accounting for Ctrl-A codes") ,310 }, - {"printfile", js_printfile, 1, JSTYPE_BOOLEAN, JSDOCSTR("filename [,mode=<tt>P_NONE</tt>] [,orig_columns=0") + {"printfile", js_printfile, 1, JSTYPE_BOOLEAN, JSDOCSTR("filename [,mode=<tt>P_NONE</tt>] [,orig_columns=0] [,object scope]") ,JSDOCSTR("print a message text file with optional mode.<br>" - "When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known") + "When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known.<br>" + "When <i>scope</i> is specified, <tt>@JS:property@</tt> codes will expand the referenced property names.") ,310 }, - {"printtail", js_printtail, 2, JSTYPE_BOOLEAN, JSDOCSTR("filename, lines [,mode=<tt>P_NONE</tt>] [,orig_columns=0]") - ,JSDOCSTR("print last x lines of file with optional mode") + {"printtail", js_printtail, 2, JSTYPE_BOOLEAN, JSDOCSTR("filename, lines [,mode=<tt>P_NONE</tt>] [,orig_columns=0] [,object scope]") + ,JSDOCSTR("print the last <i>n</i> lines of file with optional mode, original column width, and scope.") ,310 }, {"editfile", js_editfile, 1, JSTYPE_BOOLEAN, JSDOCSTR("filename") diff --git a/src/sbbs3/prntfile.cpp b/src/sbbs3/prntfile.cpp index cea34a9da9..5cba2ab3e3 100644 --- a/src/sbbs3/prntfile.cpp +++ b/src/sbbs3/prntfile.cpp @@ -48,7 +48,7 @@ /* for pauses, aborts and ANSI. 'str' is the path of the file to print */ /* Called from functions menu and text_sec */ /****************************************************************************/ -bool sbbs_t::printfile(const char* fname, long mode, long org_cols) +bool sbbs_t::printfile(const char* fname, long mode, long org_cols, JSObject* obj) { char* buf; char fpath[MAX_PATH+1]; @@ -119,7 +119,7 @@ bool sbbs_t::printfile(const char* fname, long mode, long org_cols) buf[l]=0; if((mode&P_UTF8) && !term_supports(UTF8)) utf8_normalize_str(buf); - putmsg(buf,mode,org_cols); + putmsg(buf,mode,org_cols, obj); } free(buf); } else { // Line-at-a-time mode @@ -138,7 +138,7 @@ bool sbbs_t::printfile(const char* fname, long mode, long org_cols) break; if((mode&P_UTF8) && !term_supports(UTF8)) utf8_normalize_str(buf); - if(putmsg(buf, mode|P_SAVEATR, org_cols) != '\0') // early-EOF? + if(putmsg(buf, mode|P_SAVEATR, org_cols, obj) != '\0') // early-EOF? break; } free(buf); @@ -157,7 +157,7 @@ bool sbbs_t::printfile(const char* fname, long mode, long org_cols) return true; } -bool sbbs_t::printtail(const char* fname, int lines, long mode, long org_cols) +bool sbbs_t::printtail(const char* fname, int lines, long mode, long org_cols, JSObject* obj) { char* buf; char fpath[MAX_PATH+1]; @@ -215,7 +215,7 @@ bool sbbs_t::printtail(const char* fname, int lines, long mode, long org_cols) } p--; } - putmsg(p,mode,org_cols); + putmsg(p,mode,org_cols, obj); } if(mode&P_NOABORT && online==ON_REMOTE) { SYNC; @@ -228,7 +228,7 @@ bool sbbs_t::printtail(const char* fname, int lines, long mode, long org_cols) /****************************************************************************/ /* Displays a menu file (e.g. from the text/menu directory) */ /****************************************************************************/ -bool sbbs_t::menu(const char *code, long mode) +bool sbbs_t::menu(const char *code, long mode, JSObject* obj) { char path[MAX_PATH+1]; const char *next= "msg"; @@ -261,7 +261,7 @@ bool sbbs_t::menu(const char *code, long mode) mode |= P_OPENCLOSE | P_CPM_EOF; if(column == 0) mode |= P_NOCRLF; - return printfile(path, mode); + return printfile(path, mode, /* org_cols: */0, obj); } bool sbbs_t::menu_exists(const char *code, const char* ext, char* path) diff --git a/src/sbbs3/putmsg.cpp b/src/sbbs3/putmsg.cpp index af494fe933..067ffc8753 100644 --- a/src/sbbs3/putmsg.cpp +++ b/src/sbbs3/putmsg.cpp @@ -49,7 +49,7 @@ /* the attributes prior to displaying the message are always restored. */ /* Stops parsing/displaying upon CTRL-Z (only in P_CPM_EOF mode). */ /****************************************************************************/ -char sbbs_t::putmsg(const char *buf, long mode, long org_cols) +char sbbs_t::putmsg(const char *buf, long mode, long org_cols, JSObject* obj) { uint tmpatr; char tmp2[256],tmp3[128]; @@ -366,7 +366,7 @@ char sbbs_t::putmsg(const char *buf, long mode, long org_cols) continue; } bool was_tos = tos; - i=show_atcode((char *)str+l); /* returns 0 if not valid @ code */ + i=show_atcode((char *)str+l, obj); /* returns 0 if not valid @ code */ l+=i; /* i is length of code string */ if(tos && !was_tos && (sys_status&SS_ABORT) && !lines_printed) /* Aborted at (auto) pause prompt (e.g. due to CLS)? */ sys_status &= ~SS_ABORT; /* Clear the abort flag (keep displaying the msg/file) */ diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h index bc3f108a97..771223efaa 100644 --- a/src/sbbs3/sbbs.h +++ b/src/sbbs3/sbbs.h @@ -656,7 +656,7 @@ public: ,const char *to, const char* from, const char** editor=NULL, const char** charset=NULL); char* quotes_fname(int xedit, char* buf, size_t len); char* msg_tmp_fname(int xedit, char* fname, size_t len); - char putmsg(const char *str, long mode, long org_cols = 0); + char putmsg(const char *str, long mode, long org_cols = 0, JSObject* obj = NULL); bool msgabort(void); bool email(int usernumber, const char *top = NULL, const char *title = NULL , long mode = WM_NONE, smb_t* resmb = NULL, smbmsg_t* remsg = NULL); @@ -801,9 +801,9 @@ public: char handle_ctrlkey(char ch, long mode=0); /* prntfile.cpp */ - bool printfile(const char* fname, long mode, long org_cols = 0); - bool printtail(const char* fname, int lines, long mode, long org_cols = 0); - bool menu(const char *code, long mode = 0); + bool printfile(const char* fname, long mode, long org_cols = 0, JSObject* obj = NULL); + bool printtail(const char* fname, int lines, long mode, long org_cols = 0, JSObject* obj = NULL); + bool menu(const char *code, long mode = 0, JSObject* obj = NULL); bool menu_exists(const char *code, const char* ext=NULL, char* realpath=NULL); int uselect(int add, uint n, const char *title, const char *item, const uchar *ar); @@ -814,8 +814,8 @@ public: void redrwstr(char *strin, int i, int l, long mode); /* atcodes.cpp */ - int show_atcode(const char *code); - const char* atcode(char* sp, char* str, size_t maxlen, long* pmode = NULL, bool centered = false); + int show_atcode(const char *code, JSObject* obj = NULL); + const char* atcode(char* sp, char* str, size_t maxlen, long* pmode = NULL, bool centered = false, JSObject* obj = NULL); /* getnode.cpp */ int getsmsg(int usernumber, bool clearline = false); -- GitLab