From: larrybr Date: Fri, 8 Apr 2022 03:20:39 +0000 (+0000) Subject: Revamp and simplify shell help extension interface, and provide for scripted shell... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6b0f9f3304b21c638a51fb49167758a689da0f7b;p=thirdparty%2Fsqlite.git Revamp and simplify shell help extension interface, and provide for scripted shell extensions to be seen in .help output. FossilOrigin-Name: aa785473d948b9f05f32c2107fb302374573a2906d867b4599a7063943277b0f --- diff --git a/ext/misc/tclshext.c.in b/ext/misc/tclshext.c.in index aec4319366..8f6e0befa2 100644 --- a/ext/misc/tclshext.c.in +++ b/ext/misc/tclshext.c.in @@ -78,10 +78,12 @@ static const char * const zTclHelp = ** procedures (aka "TCL commands") for oft-repeated tasks. ** ** The added TCL commands are: -** udb shdb ; # which expose the user DB and shell DB for access via TCL -** now_interactive ; # which indicates whether input is interactive -** get_tcl_group ; # which gets a single TCL input line group -** .. ; # which does nothing, silently and without error +** udb shdb ; # exposes the user DB and shell DB for access via TCL +** now_interactive ; # indicates whether current input is interactive +** get_tcl_group ; # gets one TCL input line group from current input +** register_adhoc_command ; # aids creation of dot commands with help +** .. ; # does nothing, silently and without error +** ** The .. command exists so that a lone ".." on an input line suffices ** to ensure the TCL REPL is running. This is symmetric with a lone "." ** input to the TCL REPL because it either terminates the loop or, if @@ -107,11 +109,20 @@ SQLITE_EXTENSION_INIT1; * from db passed to extension init() and define a pair of static API refs. */ SHELL_EXTENSION_INIT1(pShExtApi, pExtHelpers, shextLinkFetcher); +#define SHX_API(entry) pShExtApi->entry +#define SHX_HELPER(entry) pExtHelpers->entry /* This is not found in the API pointer table published for extensions: */ -#define sqlite3_enable_load_extension pExtHelpers->enable_load_extension +#define sqlite3_enable_load_extension SHX_HELPER(enable_load_extension) + +/* Forward reference for use as ExtensionId */ +#ifdef _WIN32 +__declspec(dllexport) +#endif +int sqlite3_tclshext_init(sqlite3*, char**, const sqlite3_api_routines*); /* Control how tclsqlite.c compiles. (REPL is effected here, not there.) */ +#define STATIC_BUILD /* Not publishing TCL API */ #undef SQLITE_AMALGAMATION #undef TCLSH #include @@ -154,8 +165,8 @@ static void Tcl_TakeDown(void *pv){ static int Tcl_BringUp(int *pWithTk, char **pzErrMsg){ if( ++interpKeep.nRefs==1 ){ - const char *zShellName = pExtHelpers->shellInvokedAs(); - const char *zShellDir = pExtHelpers->shellStartupDir(); + const char *zShellName = SHX_HELPER(shellInvokedAs)(); + const char *zShellDir = SHX_HELPER(shellStartupDir)(); if( zShellDir!=0 ){ char cwd[FILENAME_MAX+1]; if( getDir(cwd) && 0==chdir(zShellDir) ){ @@ -206,13 +217,11 @@ DERIVED_METHOD(const char *, name, ScriptSupport,TclSS, 0,()){ } /* Provide help for users of this scripting implementation. */ -DERIVED_METHOD(const char *, help, ScriptSupport,TclSS, 1,( int more )){ +DERIVED_METHOD(const char *, help, ScriptSupport,TclSS, 1,(const char *zHK)){ (void)(pThis); - switch( more ){ - case 0: + if( zHK==0 ){ return "Provides TCL scripting support for SQLite extensible shell.\n"; - case 1: return zTclHelp; - } + }else if( *zHK==0 ) return zTclHelp; return 0; } @@ -302,10 +311,10 @@ DERIVED_METHOD(void, destruct, MetaCommand,TclCmd, 0, ()){ /* Nothing to do, instance data is static. */ (void)(pThis); } -DERIVED_METHOD(void, destruct, MetaCommand,UnkCmd, 0, ()){ - (void)(pThis); -} +static DERIVED_METHOD(void, destruct, MetaCommand,UnkCmd, 0, ()); + DERIVED_METHOD(void, destruct, ScriptSupport,TclSS, 0, ()){ + /* Nothing to do, instance data is static. */ (void)(pThis); } @@ -316,11 +325,13 @@ DERIVED_METHOD(const char *, name, MetaCommand,UnkCmd, 0,()){ return "unknown"; } -DERIVED_METHOD(const char *, help, MetaCommand,TclCmd, 1,(int more)){ - switch( more ){ - case 0: return +DERIVED_METHOD(const char *, help, MetaCommand,TclCmd, 1,(const char *zHK)){ + (void)(pThis); + if( zHK==0 ) + return ".tcl ?FILES? Run a TCL REPL or interpret files as TCL.\n"; - case 1: return + if( *zHK==0 ) + return " If FILES are provided, they name files to be read in as TCL.\n" " Otherwise, a read/evaluate/print loop is run until a lone \".\" is\n" " entered as complete TCL input or input end-of-stream is encountered.\n" @@ -332,24 +343,11 @@ DERIVED_METHOD(const char *, help, MetaCommand,TclCmd, 1,(int more)){ " are collected using TCL parsing rules and expanded as for TCL in\n" " the TCL base namespace. In this way, arguments may be \"computed\".\n" ; - default: return 0; - } -} -DERIVED_METHOD(const char *, help, MetaCommand,UnkCmd, 1,(int more)){ - switch( more ){ - case 0: return - ",unknown ?ARGS? Retry unknown dot command if it is a TCL command\n"; - case 1: return - " There is little use for this dot command without the TCL extension,\n" - " as the shell's version merely does some error reporting. However,\n" - " with it overridden, (as it is now), it provides a retry mechanism\n" - " whereby, if the command can be found defined in the TCL environment,\n" - " that command can be run with whatever arguments it was provided.\n" - ; - default: return 0; - } + return 0; } +DERIVED_METHOD(const char *, help, MetaCommand,UnkCmd, 1,(const char *zHK)); + DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,TclCmd, 3, (char **pzErrMsg, int nArgs, char *azArgs[])){ return DCR_Ok; @@ -511,7 +509,7 @@ static DotCmdRC runTclREPL(Tcl_Interp *interp, char **pzErrMsg){ DERIVED_METHOD(DotCmdRC, execute, MetaCommand,TclCmd, 4, (ShellExState *psx, char **pzErrMsg, int nArgs, char *azArgs[])){ - FILE *out = pExtHelpers->currentOutputFile(psx); + FILE *out = SHX_HELPER(currentOutputFile)(psx); TclCmd *ptc = (TclCmd *)pThis; if( nArgs>1 ){ /* Read named files into the interpreter. */ @@ -542,11 +540,11 @@ DERIVED_METHOD(DotCmdRC, execute, MetaCommand,UnkCmd, 4, sqlite3_snprintf(sizeof(zName), zName, ".%s", azArgs[0]); if( !Tcl_FindCommand(interp, zName, 0, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG) ){ - if( !pExtHelpers->nowInteractive(psx) ){ + if( !SHX_HELPER(nowInteractive)(psx) ){ *pzErrMsg = sqlite3_mprintf("Command %s not found.\n", zName); return DCR_Unknown; }else{ - FILE *out = pExtHelpers->currentOutputFile(psx); + FILE *out = SHX_HELPER(currentOutputFile)(psx); fprintf(stderr, "The %s command does not yet exist.\n", zName); fprintf(out, "Run .help to see existent dot commands," " or create %s as a TCL proc.\n", zName); @@ -588,17 +586,41 @@ INSTANCE_BEGIN(TclCmd); INSTANCE_END(TclCmd) tclcmd = { &tclcmd_methods }; -INSTANCE_BEGIN(UnkCmd); - /* no instance data */ -INSTANCE_END(UnkCmd) unkcmd = { - &unkcmd_methods -}; INSTANCE_BEGIN(TclSS); /* no instance data */ INSTANCE_END(TclSS) tclss = { &tclss_methods }; +INSTANCE_BEGIN(UnkCmd); + /* no instance data */ +INSTANCE_END(UnkCmd) unkcmd = { + &unkcmd_methods +}; + +static DERIVED_METHOD(void, destruct, MetaCommand,UnkCmd, 0, ()){ + (void)(pThis); +} + +DERIVED_METHOD(const char *, help, MetaCommand,UnkCmd, 1,(const char *zHK)){ + (void)(pThis); + if( !zHK ) + return + ",unknown ?ARGS? Retry unknown dot command if it is a TCL command\n"; + if( !*zHK ) + return + " There is little use for this dot command without the TCL extension, as\n" + " the shell's version merely does some error reporting. However, with it\n" + " overridden, (as it is now), it provides a retry mechanism whereby, if\n" + " the command can be found defined in the TCL environment, that command\n" + " can be run with whatever arguments it was provided.\n" + "\n" + " If the TCL command, register_adhoc_command is run, this command's help\n" + " method can be made to provide help text for the registered TCL command.\n" + ; + return 0; +} + #if TCL_REPL==2 #define GETLINE_MAXLEN 1000 @@ -608,8 +630,8 @@ static int getInputLine(void *pvSS, Tcl_Interp *interp, if( nArgs==1 ){ char buffer[GETLINE_MAXLEN+1]; ShellExState *psx = (ShellExState *)pvSS; - struct InSource *pis = pExtHelpers->currentInputSource(psx); - if( pExtHelpers->strLineGet(buffer, GETLINE_MAXLEN, pis) ){ + struct InSource *pis = SHX_HELPER(currentInputSource)(psx); + if( SHX_HELPER(strLineGet)(buffer, GETLINE_MAXLEN, pis) ){ Tcl_SetResult(interp, buffer, TCL_VOLATILE); }else{ Tcl_SetResult(interp, 0, 0); @@ -642,12 +664,12 @@ static int getTclGroup(void *pvSS, Tcl_Interp *interp, if( objc==1 ){ static Prompts cueTcl = { "tcl% ", " > " }; ShellExState *psx = (ShellExState *)pvSS; - struct InSource *pis = pExtHelpers->currentInputSource(psx); + struct InSource *pis = SHX_HELPER(currentInputSource)(psx); int isComplete = 0; char *zIn = 0; int isContinuation = 0; do { - zIn = pExtHelpers->oneInputLine(pis, zIn, isContinuation, &cueTcl); + zIn = SHX_HELPER(oneInputLine)(pis, zIn, isContinuation, &cueTcl); if( isContinuation ){ if( zIn ){ Tcl_AppendResult(interp, "\n", zIn, (char*)0); @@ -659,7 +681,7 @@ static int getTclGroup(void *pvSS, Tcl_Interp *interp, } isContinuation = 1; } while( zIn && !isComplete ); - if( zIn ) pExtHelpers->freeInputLine(zIn); + if( zIn ) SHX_HELPER(freeInputLine)(zIn); { Tcl_Obj *const objv[] = { Tcl_NewStringObj(Tcl_GetStringResult(interp) , -1), @@ -681,9 +703,9 @@ static int nowInteractive(void *pvSS, Tcl_Interp *interp, int nArgs, const char *azArgs[]){ if( nArgs==1 ){ ShellExState *psx = (ShellExState *)pvSS; - struct InSource *pis = pExtHelpers->currentInputSource(psx); + struct InSource *pis = SHX_HELPER(currentInputSource)(psx); static const char * zAns[2] = { "0","1" }; - int iiix = (pExtHelpers->nowInteractive(psx) != 0); + int iiix = (SHX_HELPER(nowInteractive)(psx) != 0); Tcl_SetResult(interp, (char *)zAns[iiix], TCL_STATIC); return TCL_OK; }else{ @@ -755,6 +777,24 @@ static int runTkGUI(void *pvSS, Tcl_Interp *interp, #define UNKNOWN_RENAME "::_original_unknown" +/* C implementation of TCL ::register_adhoc_command name ?help? */ +static int registerAdHocCommand(/* ShellExState */ void *pv, + Tcl_Interp *interp, + int nArgs, const char *azArgs[]){ + ShellExState *psx = (ShellExState*)pv; + if( nArgs>3 ){ + Tcl_SetResult(interp, "too many arguments", TCL_STATIC); + }else if( nArgs<2 ){ + Tcl_SetResult(interp, "too few arguments", TCL_STATIC); + }else{ + const char *zHT = (nArgs==3)? azArgs[2] : 0; + Tcl_ResetResult(interp); + SHX_API(registerAdHocCommand)(psx, sqlite3_tclshext_init, azArgs[1], zHT); + return TCL_OK; + } + return TCL_ERROR; +} + /* C implementation of TCL ::unknown to (maybe) delegate to dot commands */ static int unknownDotDelegate(void *pvSS, Tcl_Interp *interp, int nArgs, const char *azArgs[]){ @@ -764,15 +804,15 @@ static int unknownDotDelegate(void *pvSS, Tcl_Interp *interp, int nFound = 0; int ia, rc; - if( name ) pmc = pExtHelpers->findMetaCommand(name, psx, &nFound); + if( name ) pmc = SHX_HELPER(findMetaCommand)(name, psx, &nFound); if( pmc==(MetaCommand*)&tclcmd && nArgs==2 ){ /* Will not do a nested REPL, just silently semi-fake it. */ return TCL_OK; } if( pmc && nFound==1 ){ /* Run the dot command and interpret its returns. */ - DotCmdRC drc = pExtHelpers->runMetaCommand(pmc, (char **)azArgs+1, - nArgs-1, psx); + DotCmdRC drc = SHX_HELPER(runMetaCommand)(pmc, (char **)azArgs+1, + nArgs-1, psx); if( drc==DCR_Ok ) return TCL_OK; else if( drc==DCR_Return ){ return TCL_RETURN; @@ -804,9 +844,6 @@ static int unknownDotDelegate(void *pvSS, Tcl_Interp *interp, } } -/* Forward reference for use as ExtensionId */ -int sqlite3_tclshext_init(sqlite3*, char**, const sqlite3_api_routines*); - /* TCL dbu command: Acts like a (TCL) sqlite3 command created object except * that it defers to shell's DB and treats the close subcommand as an error. * The below struct and functions through userDbInit() support this feature. @@ -938,8 +975,8 @@ static int udbEventHandle(void *pv, NoticeKind nk, void *pvSubject, if( ix>=0 ) pudb->ixuSdb = -1; break; case NK_ExtensionUnload: - pShExtApi->subscribeEvents(psx, sqlite3_tclshext_init, 0, - NK_Unsubscribe, udbEventHandle); + SHX_API(subscribeEvents)(psx, sqlite3_tclshext_init, 0, + NK_Unsubscribe, udbEventHandle); /* fall thru */ case NK_DbAboutToClose: if( ix>=0 ) udbRemove(pudb, ix); @@ -978,8 +1015,8 @@ static struct UserDb *udbCreate(Tcl_Interp *interp, ShellExState *psx){ * ShellExState .dbUser member values which udb purports to wrap, * and that shdb ceases wrapping the .dbShell member at shutdown. * This subscription eventually leads to a udbCleanup() call. */ - pShExtApi->subscribeEvents(psx, sqlite3_tclshext_init, - rv, NK_CountOf, udbEventHandle); + SHX_API(subscribeEvents)(psx, sqlite3_tclshext_init, + rv, NK_CountOf, udbEventHandle); } return rv; } @@ -1053,14 +1090,23 @@ int sqlite3_tclshext_init( char **pzErrMsg, const sqlite3_api_routines *pApi ){ + static const char * const azLoadFailures[] = { + "Extension load failed unexpectedly.", + "No ShellExtensionLink.\n Use '.load tclshext -shext' to load.", + "Outdated shell host extension API.\n Update the shell.", + "Outdated shell host helper API.\n Use a newer shell.", + }; + int iLoadStatus; SQLITE_EXTENSION_INIT2(pApi); SHELL_EXTENSION_INIT2(pShExtLink, shextLinkFetcher, db); SHELL_EXTENSION_INIT3(pShExtApi, pExtHelpers, pShExtLink); - if( SHELL_EXTENSION_LOADFAIL(pShExtLink, 5, 10) ){ - *pzErrMsg - = sqlite3_mprintf("No ShellExtensionLink or shell API is too old.\n" - "Use '.load tclshext -shext' or update the shell.\n"); + iLoadStatus = SHELL_EXTENSION_LOADFAIL_WHY(pShExtLink, 6, 10); + if( iLoadStatus!=EXLD_Ok ){ + if( iLoadStatus>=sizeof(azLoadFailures)/sizeof(azLoadFailures[0]) ){ + iLoadStatus = 0; + } + *pzErrMsg = sqlite3_mprintf("%s\n", azLoadFailures[iLoadStatus]); return SQLITE_ERROR; }else{ ShellExState *psx = pShExtLink->pSXS; @@ -1082,10 +1128,10 @@ int sqlite3_tclshext_init( } } } - rc = pShExtApi->registerMetaCommand(psx, sqlite3_tclshext_init, - (MetaCommand *)&unkcmd); - rc = pShExtApi->registerMetaCommand(psx, sqlite3_tclshext_init, - (MetaCommand *)&tclcmd); + rc = SHX_API(registerMetaCommand)(psx, sqlite3_tclshext_init, + (MetaCommand *)&unkcmd); + rc = SHX_API(registerMetaCommand)(psx, sqlite3_tclshext_init, + (MetaCommand *)&tclcmd); if( rc==SQLITE_OK && (rc = Tcl_BringUp(&ldTk, pzErrMsg))==SQLITE_OK ){ Tcl_Interp *interp = getInterp(); if( TCL_OK==userDbInit(interp, psx) ){ @@ -1093,8 +1139,8 @@ int sqlite3_tclshext_init( pShExtLink->extensionDestruct = (void (*)(void*))udbCleanup; pShExtLink->pvExtensionObject = pudb; } - pShExtApi->registerScripting(psx, sqlite3_tclshext_init, - (ScriptSupport *)&tclss); + SHX_API(registerScripting)(psx, sqlite3_tclshext_init, + (ScriptSupport *)&tclss); #if TCL_REPL==1 || TCL_REPL==2 Tcl_CreateCommand(interp, "get_input_line", getInputLine, psx, 0); #endif @@ -1106,6 +1152,11 @@ int sqlite3_tclshext_init( /* Rename unknown so that calls to it can be intercepted. */ Tcl_Eval(interp, "rename unknown "UNKNOWN_RENAME); Tcl_CreateCommand(interp, "unknown", unknownDotDelegate, psx, 0); + /* Add a command to facilitate ad-hoc TCL dot commands showing up in + * the .help output with help text as specified by calling this: */ + Tcl_CreateCommand(interp, "register_adhoc_command", + registerAdHocCommand, (void*)psx, 0); + /* Define this proc so that ".." either gets to the TCL REPL loop * or does nothing (if already in it), as a user convenience. */ Tcl_Eval(interp, "proc .. {} {}"); @@ -1205,7 +1256,7 @@ int sqlite3_tclshext_init( Tcl_NewIntObj(tnarg), TCL_GLOBAL_ONLY); Tcl_SetVar2Ex(interp, "::argv", NULL, targv, TCL_GLOBAL_ONLY); Tcl_SetVar2Ex(interp, "::tcl_interactive", NULL, - Tcl_NewIntObj(pExtHelpers->nowInteractive(psx)), + Tcl_NewIntObj(SHX_HELPER(nowInteractive)(psx)), TCL_GLOBAL_ONLY); Tcl_SetVar2Ex(interp, "::isHosted", NULL, Tcl_NewIntObj(1), TCL_GLOBAL_ONLY); diff --git a/manifest b/manifest index 07274af45a..a093bfcfe1 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Get\sshell.c\spast\sclang\spreprocessor,\ssilently. -D 2022-04-06T17:07:42.374 +C Revamp\sand\ssimplify\sshell\shelp\sextension\sinterface,\sand\sprovide\sfor\sscripted\sshell\sextensions\sto\sbe\sseen\sin\s.help\soutput. +D 2022-04-08T03:20:39.765 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -329,7 +329,7 @@ F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52 F ext/misc/spellfix.c 94df9bbfa514a563c1484f684a2df3d128a2f7209a84ca3ca100c68a0163e29f F ext/misc/sqlar.c 0ace5d3c10fe736dc584bf1159a36b8e2e60fab309d310cd8a0eecd9036621b6 F ext/misc/stmt.c 35063044a388ead95557e4b84b89c1b93accc2f1c6ddea3f9710e8486a7af94a -F ext/misc/tclshext.c.in b09d1d38698f0d89fdfaae91231bf1ef017a56335b153f1bfb6d44e09d8d6674 +F ext/misc/tclshext.c.in 4ca2d623f9d2fd5b8de443a261a1e245b8935004c7c2aa1017e2ac68ef50bbb4 F ext/misc/templatevtab.c 8a16a91a5ceaccfcbd6aaaa56d46828806e460dd194965b3f77bf38f14b942c4 F ext/misc/totype.c fa4aedeb07f66169005dffa8de3b0a2b621779fd44f85c103228a42afa71853b F ext/misc/uint.c 053fed3bce2e89583afcd4bf804d75d659879bbcedac74d0fa9ed548839a030b @@ -556,8 +556,8 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c F src/resolve.c ea935b87d6fb36c78b70cdc7b28561dc8f33f2ef37048389549c7b5ef9b0ba5e F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c c366c05e48e836ea04f8ecefb9c1225745dc250c3f01bdb39e9cbb0dc25e3610 -F src/shell.c.in 3150d39954731f3d484e7df9f8a099e117b8fb2acef56c45fa83f5cca13e6613 x -F src/shext_linkage.h 307e241b9fdc42ca02387303b0abdffd5afd04a5a8540807a5061a97fb2c26cd +F src/shell.c.in 1cdd4be0d417a96f0c40202b6997e7c42a8ac34d993a2bac244393773bf6a22d x +F src/shext_linkage.h 8a3990b43db032451e8ea04030ed67d99b15c7af10400032e9e8f6985e88ff73 F src/sqlite.h.in 2a35f62185eb5e7ecc64a2f68442b538ce9be74f80f28a00abc24837edcf1c17 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h f49e28c25bd941e79794db5415fdf7b202deb3bc072ed6f1ed273d578703684e @@ -606,7 +606,7 @@ F src/test_quota.h 2a8ad1952d1d2ca9af0ce0465e56e6c023b5e15d F src/test_rtree.c 671f3fae50ff116ef2e32a3bf1fe21b5615b4b7b F src/test_schema.c f5d6067dfc2f2845c4dd56df63e66ee826fb23877855c785f75cc2ca83fd0c1b F src/test_server.c a2615049954cbb9cfb4a62e18e2f0616e4dc38fe -F src/test_shellext.c dabab63e70d3e0d5a911b3fb0c9a51a5b2c06ac79651553d46c2b99f3d482934 +F src/test_shellext.c f66d9e7ab6df95edcd3e6d578fbbc6536c377c30478ae3ba51e4ec1ba3c70d81 F src/test_sqllog.c 540feaea7280cd5f926168aee9deb1065ae136d0bbbe7361e2ef3541783e187a F src/test_superlock.c 4839644b9201da822f181c5bc406c0b2385f672e F src/test_syscall.c 1073306ba2e9bfc886771871a13d3de281ed3939 @@ -1951,8 +1951,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P efc9f08dcb1d64b92dd08e8776abc805d911fbb905758f8af6977f31a6633bb4 -R a86f52f34ea3ff9e2588abf1ce4c4954 +P 588c3512286491e2128103bcef0d6b3bdd0a0d0dcdc6bdc3e3068db552d70ed4 +R 780767747c65f5eb7755f095f488e743 U larrybr -Z 6c3643c3b0e82a3d3fee7c29abbccf02 +Z 31343ddc807d11a76817aa4117ba46c9 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0be30e9e11..e7a319f172 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -588c3512286491e2128103bcef0d6b3bdd0a0d0dcdc6bdc3e3068db552d70ed4 \ No newline at end of file +aa785473d948b9f05f32c2107fb302374573a2906d867b4599a7063943277b0f \ No newline at end of file diff --git a/src/shell.c.in b/src/shell.c.in index b9da9620e3..82fa918e31 100755 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -456,13 +456,6 @@ static volatile int seenInterrupt = 0; ** in a number of other places, mostly for error messages. */ static char *Argv0; -static char startupDir[PATH_MAX+1] = {0}; -#if defined(_WIN32) || defined(WIN32) -# define initStartupDir() (_getwd(startupDir)!=0) -#else -# define initStartupDir() (getcwd(startupDir, sizeof(startupDir))!=0) - /* The useless expression silences a "unused result" warning. */ -#endif /* ** Prompt strings. Initialized in main. Settable with @@ -1247,6 +1240,32 @@ struct EQPGraph { #endif #if SHELL_DYNAMIC_EXTENSION + +/* This is only used to support extensions that need this information. + * For example, they might need to locate and load related files. */ +static char startupDir[PATH_MAX+1] = {0}; +# if defined(_WIN32) || defined(WIN32) +# define initStartupDir() (_getwd(startupDir)!=0) +# define IS_PATH_SEP(c) ((c)=='/'||(c)=='\\') +# else +# define initStartupDir() (getcwd(startupDir, sizeof(startupDir))!=0) + /* Above useless expression avoids an "unused result" warning. */ +# define IS_PATH_SEP(c) ((c)=='/') +# endif + +# ifndef SHELL_OMIT_EXTBYNAME +/* Is a program invocation name one used for a shell to start as extensible? */ +static int isExtendedBasename(const char *zPgm){ + int ixe = (zPgm)? strlen(zPgm)-1 : 0; + if( ixe==0 ) return 0; + while( ixe>=0 && !IS_PATH_SEP(zPgm[ixe]) ) --ixe; + /* ixe is just before the basename with extension(s) */ + return sqlite3_strnicmp(&zPgm[ixe+1], "sqlite3x", 8)==0; +} +# else +# define isExtendedBasename(pathname) 0 +# endif + /* Tracking and use info for loaded shell extensions * An instance is kept for each shell extension that is currently loaded. * They are kept in a simple list (aka dynamic array), index into which @@ -1268,6 +1287,7 @@ typedef struct ShExtInfo { ExportHandler **ppExportHandlers; int numImportHandlers; ImportHandler **ppImportHandlers; + MetaCommand *pUnknown; /* .unknown registered for this extension */ } ShExtInfo; #endif @@ -1408,6 +1428,7 @@ typedef struct ShellInState { ShellEventNotify eventHandler; } *pSubscriptions; /* The current shell event subscriptions */ u8 bDbDispatch; /* Cache fact of dbShell dispatch table */ + MetaCommand *pUnknown; /* Last registered "unknown" dot command */ #endif ShellExState *pSXS; /* Pointer to companion, exposed shell state */ @@ -2430,7 +2451,9 @@ static int progress_handler(void *pClientData) { #define SHELL_DISP_SCHEMA "main" #define SHELL_DISP_TAB "ShellCommands" +#define SHELL_AHELP_TAB "ShellAdHocHelp" #define SHELL_DISP_VIEW "ShellActiveCmds" +#define SHELL_HELP_VIEW "ShellHelpedCmds" /* ** Ensure dbShell exists and return SQLITE_OK, @@ -2470,15 +2493,34 @@ static int ensure_dispatch_table(ShellExState *psx){ #ifdef SHELL_DB_FILE sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS "SHELL_DISP_TAB, 0,0,0); sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS "SHELL_DISP_VIEW, 0,0,0); + sqlite3_exec(psx->dbShell, "DROP TABLE IF EXISTS "SHELL_AHELP_TAB, 0,0,0); + sqlite3_exec(psx->dbShell, "DROP VIEW IF EXISTS "SHELL_HELP_VIEW, 0,0,0); #endif + rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE "SHELL_AHELP_TAB"(" + "name TEXT, extIx INT, helpText TEXT," + "PRIMARY KEY(name,extIx)) WITHOUT ROWID", 0, 0, &zErr); rc1 = sqlite3_exec(psx->dbShell, "CREATE TABLE "SHELL_DISP_TAB"(" "name TEXT, extIx INT, cmdIx INT," "PRIMARY KEY(extIx,cmdIx)) WITHOUT ROWID", 0, 0, &zErr); rc2 = sqlite3_exec(psx->dbShell, - "CREATE VIEW "SHELL_DISP_VIEW - " AS SELECT s.name AS name," + /* name, extIx, cmdIx */ + "CREATE VIEW "SHELL_DISP_VIEW" AS" + " SELECT s.name AS name," " max(s.extIx) AS extIx, s.cmdIx AS cmdIx" - " FROM "SHELL_DISP_TAB" s GROUP BY name", + " FROM "SHELL_DISP_TAB" s GROUP BY name" + " ORDER BY name", + 0, 0, &zErr); + rc2 = sqlite3_exec(psx->dbShell, + /* name, extIx, cmdIx, help */ + "CREATE VIEW "SHELL_HELP_VIEW" AS" + " SELECT s.name AS name, max(s.extIx) AS extIx," + " s.cmdIx AS cmdIx, NULL as help" + " FROM "SHELL_DISP_TAB" s GROUP BY name" + " UNION" + " SELECT s.name AS name, max(s.extIx) AS extIx," + " -1 AS cmdIx, s.helpText AS help" + " FROM "SHELL_AHELP_TAB" s GROUP BY name" + " ORDER BY name", 0, 0, &zErr); if( rc1!=SQLITE_OK || rc2!=SQLITE_OK || zErr!=0 ){ utf8_printf(STD_ERR, "Shell DB init failure, %s\n", zErr? zErr : "?"); @@ -7937,6 +7979,10 @@ static int register_meta_command(ShellExState *p, psei->ppMetaCommands[nc++] = pMC; psei->numMetaCommands = nc; notify_subscribers(psi, NK_NewDotCommand, pMC); + if( strcmp("unknown", zName)==0 ){ + psi->pUnknown = pMC; + psei->pUnknown = pMC; + } return SQLITE_OK; }else{ psei->ppMetaCommands[nc] = 0; @@ -7957,6 +8003,7 @@ static int register_importer(ShellExState *p, return SQLITE_ERROR; } +/* See registerScripting API in shext_linkage.h */ static int register_scripting(ShellExState *p, ExtensionId eid, ScriptSupport *pSS){ ShellInState *psi = ISS(p); @@ -7973,6 +8020,41 @@ static int register_scripting(ShellExState *p, ExtensionId eid, return SQLITE_OK; } +/* See registerAdHocCommand API in shext_linkage.h re detailed behavior. + * Depending on zHelp==0, either register or unregister ad-hoc treatment + * of zName for this extension (identified by eid.) + */ +static int register_adhoc_command(ShellExState *p, ExtensionId eid, + const char *zName, const char *zHelp){ + ShellInState *psi = ISS(p); + u8 bRegNotRemove = zHelp!=0; + const char *zSql = bRegNotRemove + ? "INSERT OR REPLACE INTO "SHELL_AHELP_TAB + "(name, extIx, helpText) VALUES(?, ?, ?||?)" + : "DELETE FROM "SHELL_AHELP_TAB" WHERE name=? AND extIx=?"; + sqlite3_stmt *pStmt; + int rc, ie; + + assert(psi->pShxLoaded!=0 && p->dbShell!=0); + for( ie=psi->numExtLoaded-1; ie>0; --ie ){ + if( psi->pShxLoaded[ie].extId==eid ) break; + } + if( !zName || ie==0 || psi->pShxLoaded[ie].pUnknown==0 ) return SQLITE_MISUSE; + rc = sqlite3_prepare_v2(p->dbShell, zSql, -1, &pStmt, 0); + if( rc!=SQLITE_OK ) return rc; + sqlite3_bind_text(pStmt, 1, zName, -1, 0); + sqlite3_bind_int(pStmt, 2, ie); + if( bRegNotRemove ){ + int nc = strlen30(zHelp); + const char *zLE = (nc>0 && zHelp[nc-1]!='\n')? "\n" : ""; + sqlite3_bind_text(pStmt, 3, zHelp, -1, 0); + sqlite3_bind_text(pStmt, 4, zLE, -1, 0); + } + rc = sqlite3_step(pStmt); + sqlite3_finalize(pStmt); + return (rc==SQLITE_DONE)? SQLITE_OK : SQLITE_ERROR; +} + /* * Subscribe to (or unsubscribe from) messages about various changes. * Unsubscribe, effected when nkMin==NK_Unsubscribe, always succeeds. @@ -8091,12 +8173,13 @@ static ExtensionHelpers extHelpers = { }; static ShellExtensionAPI shellExtAPI = { - &extHelpers, 5, { + &extHelpers, 6, { register_meta_command, register_exporter, register_importer, register_scripting, subscribe_events, + register_adhoc_command, 0 } }; @@ -8257,6 +8340,7 @@ static void free_all_shext_tracking(ShellInState *psi){ } static MetaCommand *command_by_index(ShellInState *psi, int extIx, int cmdIx){ + assert(extIx>=0); if( extIx>=0 && extIxnumExtLoaded ){ ShExtInfo *psei = & psi->pShxLoaded[extIx]; if( cmdIx>=0 && cmdIxnumMetaCommands ){ @@ -12919,13 +13003,14 @@ DISPATCHABLE_COMMAND( trace ? 0 0 ){ * The .unknown command (undocumented) */ COLLECT_HELP_TEXT[ - ",unknown ?ARGS? Handle attempt to invoke an unknown dot command", + ",unknown ?ARGS? Handle attempt to use an unknown dot command", + " The invocation dispatcher calls this after replacing azArg[0] with the", + " mystery command name, leaving remaining arguments as originally passed.", + " An extension may override this to provide new dot commands dynamically.", + " This name and operation were inspired by a similar feature of TCL.", ]; DISPATCHABLE_COMMAND( unknown ? 1 0 ){ - /* Dispatcher will call this after replacing azArg[0] with the mystery - * command name, leaving remaining arguments as passed to dispatcher. - * An extension may override this to provide new commands dynamically. - * This name and operation were inspired by a similar TCL feature. */ + /* Dispatcher will call this for dot commands it cannot find. */ return DCR_Unknown|0; } @@ -13029,7 +13114,7 @@ COLLECT_HELP_TEXT[ ".vars ?OPTIONS? ... Manipulate and display shell variables", " clear ?NAMES? Erase all or only given named variables", #ifndef SQLITE_NOHAVE_SYSTEM - " edit ?-e? NAME Use edit() to create or alter variable NAME", + " edit ?-e? NAME Use edit() to create or alter variable NAME", " With a -e option, the edited value is evaluated as a SQL expression.", #endif " list ?PATTERNS? List shell variables table values", @@ -13505,7 +13590,7 @@ COLLECT_HELP_TEXT[ static void MetaCommand_dtor(MetaCommand *); static const char * MetaCommand_name(MetaCommand *); -static const char * MetaCommand_help(MetaCommand *, int); +static const char * MetaCommand_help(MetaCommand *, const char *); static DotCmdRC MetaCommand_argsCheck(MetaCommand *, char **, int nArgs, char *azArgs[]); static DotCmdRC @@ -13554,8 +13639,10 @@ static const char * MetaCommand_name(MetaCommand *pMe){ return ((struct CommandInfo *)pMe)->cmdName; } -static const char * MetaCommand_help(MetaCommand *pMe, int more){ - if( more>=0 && more<2 ) return ((struct CommandInfo *)pMe)->azHelp[more]; +static const char * MetaCommand_help(MetaCommand *pMe, const char * zWhat){ + struct CommandInfo *pci = (struct CommandInfo *)pMe; + if( zWhat==0 ) return pci->azHelp[0]; + if( *zWhat==0 ) return pci->azHelp[1]; else return 0; } @@ -13586,9 +13673,12 @@ static DotCmdRC } /***************** -** MetaCommand iteration by name match, used by the .help meta-command -** MetaCommands with matching names are produced in lexical order. Any -** returned MetaMatchIter must eventually be passed to freeMetaMatchIter(). +** MetaCommand iteration by name match, used by the .help meta-command. +** MetaCommands, or their ad-hoc stand-ins, having matching names are produced +** in lexical order, with the iterator indicating which has been produced. +** If .zAdhocHelpName == 0, it is a regular MetaCommand. Otherwise, the +** ".unknown" MetaCommand is returned, whose help() method is to be used. +** Any returned MetaMatchIter must eventually be passed to freeMetaMatchIter(). */ typedef struct MetaMatchIter { ShellExState *psx; @@ -13600,30 +13690,75 @@ typedef struct MetaMatchIter { sqlite3_stmt *stmt; #endif }; +#if SHELL_DYNAMIC_EXTENSION + char *zAdhocHelpText; /* registered extension ad-hoc help */ +#endif } MetaMatchIter; +/* Release resources held by the iterator and clear it. */ +static void freeMetaMatchIter(MetaMatchIter *pMMI){ + if( pMMI->zPattern!=0 ){ + sqlite3_free((void *)pMMI->zPattern); + pMMI->zPattern = 0; + pMMI->pMC = 0; + } +#if SHELL_DYNAMIC_EXTENSION + else{ + sqlite3_finalize(pMMI->stmt); + pMMI->stmt = 0; + } + sqlite3_free(pMMI->zAdhocHelpText); + pMMI->zAdhocHelpText = 0; +#endif +} + /* Prepare an iterator that will produce a sequence of MetaCommand - * pointers whose referents names match the given cmdFragment. */ -static MetaMatchIter findMatchingMetaCmds(const char *cmdFragment, - ShellExState *psx){ - MetaMatchIter rv = { psx, 0, 0 }; + * pointers whose referents' names match the given cmdFragment. + * Return how many will match (if iterated upon return.) */ +static int findMatchingMetaCmds(const char *cmdFragment, + MetaMatchIter *pMMI, + ShellExState *psx){ + MetaMatchIter mmi = { psx, 0, 0 }; + int rv = 0; #if SHELL_DYNAMIC_EXTENSION - if( psx->dbShell!=0 && ISS(psx)->bDbDispatch ){ + if( ISS(psx)->bDbDispatch ){ + sqlite3_stmt *stmtCount = 0; /* Prepare rv.stmt to yield results glob-matching cmdFragment. */ - const char *zSql = - "SELECT name, extIx, cmdIx FROM "SHELL_DISP_VIEW" " + static const char * const zSqlIter = + "SELECT name, extIx, cmdIx, help FROM "SHELL_HELP_VIEW" " "WHERE name glob (?||'*') ORDER BY name"; - sqlite3_prepare_v2(psx->dbShell, zSql, -1, &rv.stmt, 0); - sqlite3_bind_text(rv.stmt, 1, cmdFragment? cmdFragment : "", -1, 0); + static const char * const zSqlCount = + "SELECT count(*) FROM "SHELL_HELP_VIEW" " + "WHERE name glob (?||'*')"; + if( pMMI ){ + sqlite3_prepare_v2(psx->dbShell, zSqlIter, -1, &mmi.stmt, 0); + sqlite3_bind_text(mmi.stmt, 1, cmdFragment? cmdFragment : "", -1, 0); + } + sqlite3_prepare_v2(psx->dbShell, zSqlCount, -1, &stmtCount, 0); + sqlite3_bind_text(stmtCount, 1, cmdFragment? cmdFragment : "", -1, 0); + if( SQLITE_ROW==sqlite3_step(stmtCount) ){ + rv = sqlite3_column_int(stmtCount, 0); + }else assert(0); + sqlite3_finalize(stmtCount); }else #endif { - rv.zPattern = smprintf("%s*", cmdFragment? cmdFragment : ""); - shell_check_oom((void *)rv.zPattern); - rv.pMC = (MetaCommand *)command_table; + int i = 0; + mmi.zPattern = smprintf("%s*", cmdFragment? cmdFragment : ""); + shell_check_oom((void *)mmi.zPattern); + + struct CommandInfo *pCI = command_table; + mmi.pMC = (MetaCommand *)command_table; + while( pCIcmdName)==0 ) ++rv; + ++pCI; + } } + if( pMMI ) *pMMI = mmi; + else freeMetaMatchIter(&mmi); return rv; } + /* Produce the next MetaCommand pointer from the iterator, or 0 if no next. */ static MetaCommand * nextMatchingMetaCmd(MetaMatchIter *pMMI){ MetaCommand *rv = 0; @@ -13631,9 +13766,22 @@ static MetaCommand * nextMatchingMetaCmd(MetaMatchIter *pMMI){ if( pMMI->zPattern==0 ){ int rc = sqlite3_step(pMMI->stmt); if( rc==SQLITE_ROW ){ + /* name, extIx, cmdIx, help */ int extIx = sqlite3_column_int(pMMI->stmt, 1); int cmdIx = sqlite3_column_int(pMMI->stmt, 2); - return command_by_index(ISS(pMMI->psx), extIx, cmdIx); + ShellInState *psi = ISS(pMMI->psx); + sqlite3_free(pMMI->zAdhocHelpText); + if( cmdIx>=0 ){ + pMMI->zAdhocHelpText = 0; + return command_by_index(psi, extIx, cmdIx); + }else{ + const unsigned char *zHT = sqlite3_column_text(pMMI->stmt, 3); + assert(psi->pUnknown!=0); + assert(extIxnumExtLoaded && extIx>0); + if( zHT==0 ) zHT = sqlite3_column_text(pMMI->stmt, 0); + pMMI->zAdhocHelpText = sqlite3_mprintf(".%s", zHT); + return psi->pShxLoaded[extIx].pUnknown; + } }else{ sqlite3_finalize(pMMI->stmt); pMMI->stmt = 0; @@ -13651,20 +13799,6 @@ static MetaCommand * nextMatchingMetaCmd(MetaMatchIter *pMMI){ } return rv; } -/* Release resources held by the iterator and clear it. */ -static void freeMetaMatchIter(MetaMatchIter *pMMI){ - if( pMMI->zPattern!=0 ){ - sqlite3_free((void *)pMMI->zPattern); - pMMI->zPattern = 0; - pMMI->pMC = 0; - } -#if SHELL_DYNAMIC_EXTENSION - else{ - sqlite3_finalize(pMMI->stmt); - pMMI->stmt = 0; - } -#endif -} /***************** ** MetaCommand lookup @@ -13700,7 +13834,7 @@ MetaCommand *findMetaCommand(const char *cmdName, ShellExState *psx, if( rc!= SQLITE_ROW ) return 0; if( pnFound ) *pnFound = nf; if( nf!=1 ) return 0; /* Future: indicate collisions if > 1 */ - return command_by_index(ISS(psx), extIx, cmdIx); + return (cmdIx<0)? 0 : command_by_index(ISS(psx), extIx, cmdIx); }else #endif { @@ -13737,27 +13871,112 @@ MetaCommand *findMetaCommand(const char *cmdName, ShellExState *psx, } } +/* +** Given a MetaCommand, desired help level, +** ( possibly retreived ad-hoc help text for extensible shell, ) +** and an optional all-text search pattern, then +** when level==0 and primary help available, output it +** when level==1 and primary or secondary help available, output it +** when level==2 and any help text matches pattern, output it +** when level>2 or no pattern: output all help text +** If cLead==0, anything meeting above criteria is output. Otherwise, output +** is restricted to those commands whose primary help begins with cLead. +** Return 1 if anything output, else 0. +*/ +static int putSelectedCmdHelp(MetaCommand *pmc, int iLevel, char cLead, +#if SHELL_DYNAMIC_EXTENSION + const char *zHelpText, +#endif + FILE *out, const char *zSearch){ + int rc = 0; + assert(pmc!=0); +#if SHELL_DYNAMIC_EXTENSION + if( zHelpText ){ + if( cLead && *zHelpText!= cLead ) return 0; + const char *zLE = zHelpText; + if( iLevel<2 ){ + while( *zLE && *zLE++!='\n' ) {} + } + switch( iLevel ){ + case 0: + utf8_printf(out,"%.*s", (int)(zLE-zHelpText), zHelpText); + rc = 1; + break; + case 1: + utf8_printf(out,"%s", zHelpText); + rc = 1; + break; + case 2: + if( zSearch ){ + if( !sqlite3_strlike(zSearch, zHelpText, 0) ) break; + } + /* else fall thru */ + default: + utf8_printf(out,"%s", zHelpText); + rc = 1; + } + }else +#endif + { + const char *zHTp = pmc->pMethods->help(pmc, 0); + const char *zHTs = pmc->pMethods->help(pmc, ""); + if( !zHTp && !zHTs ) return 0; + if( cLead && zHTp && *zHTp!= cLead ) return 0; + switch( iLevel ){ + case 0: + if( zHTp ){ + utf8_printf(out, HELP_TEXT_FMTP, zHTp+1); + rc = 1; + } + break; + case 1: + if( zHTs ){ + utf8_printf(out, HELP_TEXT_FMTS, zHTs); + rc = 1; + } + break; + case 2: + if( zSearch ){ + int m = 0; + if( zHTp && !sqlite3_strlike(zSearch, zHTp, 0) ) ++m; + if( zHTs && !sqlite3_strlike(zSearch, zHTs, 0) ) ++m; + if( m==0 ) break; + } + /* else fall thru */ + default: + if( zHTp ) utf8_printf(out, HELP_TEXT_FMTP, zHTp+1); + if( zHTs ) utf8_printf(out, HELP_TEXT_FMTS, zHTs); + rc = 1; + } + } + return rc; +} + /* ** Output primary (single-line) help for a known command. */ static void showPrimaryHelp(FILE *out, const char *zCmd, ShellExState *psx){ - MetaMatchIter mmi = findMatchingMetaCmds(zCmd, psx); + MetaMatchIter mmi = {0}; + int nm = findMatchingMetaCmds(zCmd, &mmi, psx); MetaCommand *pmc = nextMatchingMetaCmd(&mmi); if( pmc!=0 ){ - const char *zH = pmc->pMethods->help(pmc, 0); - if( zH!=0 && *zH ) utf8_printf(out, HELP_TEXT_FMTP, zH+1); + putSelectedCmdHelp(pmc, 0, 0, +#if SHELL_DYNAMIC_EXTENSION + mmi.zAdhocHelpText, +#endif + out, 0); } freeMetaMatchIter(&mmi); } /* -** Output various subsets of help text. These 5 are defined: -** 1. For all commands, primary help text only. -** 2. For all commands, complete help text. -** 3. For multiple commands matching a pattern, primary help text only. -** 4. For a single matched command, complete help text. -** 5. For commands whose help contains a pattern, complete help text. -** 6. For the set of "undocumented" (without normal help) commands. +** Output various subsets of help text. These 6 are defined: +** 1. HO_AllP For all commands, primary help text only. +** 2. HO_AllX For all commands, complete help text. +** 3. HO_LikeP For multiple commands matching pattern, primary help text only. +** 4. HO_OneX For a single matched command, complete help text. +** 5. HO_LikeT For commands whose help contains a pattern, complete help text. +** 6. HO_Undoc For all internal "undocumented" (without normal help) commands. ** These variations are indicated thusly: ** 1. zPattern is NULL ** 2. zPattern is "" @@ -13773,59 +13992,72 @@ static int showHelp(FILE *out, const char *zPattern, ShellExState *psx){ u8 bShowUndoc = zPattern==zHelpAll; u8 bEmptyPattern = !bNullPattern && (*zPattern==0 || bShowUndoc); int npm = 0; /* track how many matches found */ - MetaMatchIter mmi = findMatchingMetaCmds(bShowUndoc? "" : zPattern, psx); - MetaCommand *pmc, *pmcLastShown = 0; + MetaMatchIter mmi = {0}; + MetaCommand *pmc; char *zPat = 0; char cLead = (bShowUndoc)? ',' : '.'; + int iLevel = 0; + enum { + HO_Tbd, HO_AllP, HO_AllX, HO_LikeP, HO_OneX, HO_LikeT, HO_Undoc + } hoKind = bShowUndoc? HO_Undoc : HO_Tbd; - if( bShowUndoc ){ + if( hoKind==HO_Undoc ){ + int ixct = 0; utf8_printf(out, "%s\n%s\n", "The following commands are used for internal SQLite testing.", "They are undocumented and subject to change without notice."); - } - while( 0 != (pmc = nextMatchingMetaCmd(&mmi)) ){ - const char *zH = pmc->pMethods->help(pmc, 0); - if( zH!=0 && *zH==cLead){ - ++npm; - pmcLastShown = pmc; - utf8_printf(out, HELP_TEXT_FMTP, zH+1); - if( bEmptyPattern ){ - zH = pmc->pMethods->help(pmc, 1); - if( zH!=0 ){ - utf8_printf(out, HELP_TEXT_FMTS, zH); - } - } + /* Bypass command lookup/resolution. This is just for internal commands. */ + while( ixctazHelp[0]; + if( zH && *zH==cLead ){ + utf8_printf(out, HELP_TEXT_FMTP, zH+1); + zH = pci->azHelp[1]; + if( zH ) utf8_printf(out, HELP_TEXT_FMTS, zH); + ++npm; + } + ++ixct; } - } - freeMetaMatchIter(&mmi); - if( npm==1 && !bEmptyPattern ){ - /* When zPattern is a prefix of exactly one command, then emit - * the secondary help of that command, even if not requested, - * unless it was already emitted. */ - const char *zH = pmcLastShown->pMethods->help(pmcLastShown, 1); - if( zH!=0 ) utf8_printf(out, HELP_TEXT_FMTS, zH); return npm; } - /* If found anything with provided (or NULL or empty) pattern, it's done. */ - if( npm>0 ) return npm; - /* Otherwise, look for the pattern in all of the help text and show the - * complete help for those meta-commands whose help matches. */ - mmi = findMatchingMetaCmds("", psx); - zPat = smprintf("%%%s%%", zPattern); - shell_check_oom(zPat); + npm = findMatchingMetaCmds(zPattern, &mmi, psx); + if( bNullPattern ) hoKind = HO_AllP; + else if( bEmptyPattern ) hoKind = HO_AllX; + else if( npm>1 ) hoKind = HO_LikeP; + else if( npm==1 ) hoKind = HO_OneX; + else{ + hoKind = HO_LikeT; + zPat = smprintf("%%%s%%", zPattern); + shell_check_oom(zPat); + } + zPattern = 0; + switch( hoKind ){ + case HO_AllP: case HO_LikeP: + iLevel = 0; + break; + case HO_AllX: + iLevel = 1; + break; + case HO_OneX: + iLevel = 1; + cLead = 0; + break; + case HO_LikeT: + zPattern = zPat; + iLevel = 1; + break; + default: return 0; + } + npm = 0; while( 0 != (pmc = nextMatchingMetaCmd(&mmi)) ){ - const char *zHp = pmc->pMethods->help(pmc, 0); - const char *zHs = pmc->pMethods->help(pmc, 1); - if( zHp==0 || *zHp!='.' ) continue; - if( sqlite3_strlike(zPat, zHp, 0)==0 - || (zHs!=0 && sqlite3_strlike(zPat, zHs, 0)==0) ){ - utf8_printf(out, HELP_TEXT_FMTP, zHp+1); - if( zHs ) utf8_printf(out, HELP_TEXT_FMTS, zHs); - ++npm; - } + npm += putSelectedCmdHelp(pmc, iLevel, cLead, +#if SHELL_DYNAMIC_EXTENSION + mmi.zAdhocHelpText, +#endif + out, zPattern); } - sqlite3_free(zPat); freeMetaMatchIter(&mmi); + sqlite3_free(zPat); return npm; } @@ -14054,9 +14286,11 @@ static DotCmdRC do_meta_command(char *zLine, ShellExState *psx){ MetaCommand *pmc = findMetaCommand(azArg[0], psx, &nFound); if( pmc==0 || nFound>1 ){ if( nFound==0 ){ - pmc = findMetaCommand("unknown", psx, &nFound); - if( pmc && nFound<2 ) dcr = runMetaCommand(pmc, azArg, nArg, psx); - else dcr = DCR_Unknown; + dcr = DCR_Unknown; +#if SHELL_DYNAMIC_EXTENSION + pmc = ISS(psx)->pUnknown; + if( pmc ) dcr = runMetaCommand(pmc, azArg, nArg, psx); +#endif }else{ dcr = DCR_Ambiguous; } @@ -15134,7 +15368,10 @@ int SQLITE_CDECL SHELL_MAIN(int argc, wchar_t **wargv){ assert( argc>=1 && argv && argv[0] ); Argv0 = argv[0]; +#if SHELL_DYNAMIC_EXTENSION initStartupDir(); + if( isExtendedBasename(Argv0) ) data.bExtendedDotCmds = SHELL_ALL_EXTENSIONS; +#endif #ifdef SQLITE_SHELL_DBNAME_PROC { diff --git a/src/shext_linkage.h b/src/shext_linkage.h index 77cf803379..0369d4aa35 100644 --- a/src/shext_linkage.h +++ b/src/shext_linkage.h @@ -130,10 +130,14 @@ typedef struct ShellExState { * shell to make new or overriding meta-commands available to it. */ INTERFACE_BEGIN( MetaCommand ); + /* The whole, true name for this command */ PURE_VMETHOD(const char *, name, MetaCommand, 0,()); -PURE_VMETHOD(const char *, help, MetaCommand, 1,(int more)); + /* Help text; zWhat=0 => primary, zWhat="" => secondary, other ? */ +PURE_VMETHOD(const char *, help, MetaCommand, 1,(const char *zWhat)); + /* Validate arguments, blocking execute for returns != DCR_Ok */ PURE_VMETHOD(DotCmdRC, argsCheck, MetaCommand, 3, (char **pzErrMsg, int nArgs, char *azArgs[])); + /* Do whatever this command does, or return error of some kind */ PURE_VMETHOD(DotCmdRC, execute, MetaCommand, 4,(ShellExState *, char **pzErrMsg, int nArgs, char *azArgs[])); INTERFACE_END( MetaCommand ); @@ -143,7 +147,7 @@ INTERFACE_END( MetaCommand ); */ INTERFACE_BEGIN( ExportHandler ); PURE_VMETHOD(const char *, name, ExportHandler, 0,()); -PURE_VMETHOD(const char *, help, ExportHandler, 1,(int more)); +PURE_VMETHOD(const char *, help, ExportHandler, 1,(const char *zWhat)); PURE_VMETHOD(int, openResultsOutStream, ExportHandler, 5,( ShellExState *pSES, char **pzErr, int numArgs, char *azArgs[], const char * zName )); @@ -162,7 +166,7 @@ INTERFACE_END( ExportHandler ); */ INTERFACE_BEGIN( ImportHandler ); PURE_VMETHOD(const char *, name, ImportHandler, 0,()); -PURE_VMETHOD(const char *, help, ImportHandler, 1,( int more )); +PURE_VMETHOD(const char *, help, ImportHandler, 1,(const char *zWhat)); PURE_VMETHOD(int, openDataInStream, ImportHandler, 5,( ShellExState *pSES, char **pzErr, int numArgs, char *azArgs[], const char * zName )); @@ -219,7 +223,7 @@ INTERFACE_END( ImportHandler ); */ INTERFACE_BEGIN( ScriptSupport ); PURE_VMETHOD(const char *, name, ScriptSupport, 0,()); -PURE_VMETHOD(const char *, help, ScriptSupport, 1,( int more )); +PURE_VMETHOD(const char *, help, ScriptSupport, 1,(const char *zWhat)); PURE_VMETHOD(int, configure, ScriptSupport, 4,( ShellExState *pSES, char **pzErr, int numArgs, char *azArgs[] )); @@ -237,7 +241,7 @@ INTERFACE_END( ScriptSupport ); #define ScriptSupport_IMPLEMENT_VTABLE(Derived, vtname) \ CONCRETE_BEGIN(ScriptSupport, Derived); \ CONCRETE_METHOD(const char *, name, ScriptSupport, 0,()); \ -CONCRETE_METHOD(const char *, help, ScriptSupport, 1,( int more )); \ +CONCRETE_METHOD(const char *, help, ScriptSupport, 1,(const char *zWhat)); \ CONCRETE_METHOD(int, configure, ScriptSupport, \ 4,( ShellExState *pSES, char **pzErr, int numArgs, char *azArgs[] )); \ CONCRETE_METHOD(int, isScriptLeader, ScriptSupport, \ @@ -263,7 +267,7 @@ CONCRETE_END(Derived) vtname = { \ #define MetaCommand_IMPLEMENT_VTABLE(Derived, vtname) \ CONCRETE_BEGIN(MetaCommand, Derived); \ CONCRETE_METHOD(const char *, name, MetaCommand, 0,()); \ -CONCRETE_METHOD(const char *, help, MetaCommand, 1,(int more)); \ +CONCRETE_METHOD(const char *, help, MetaCommand, 1,(const char *zWhat)); \ CONCRETE_METHOD(DotCmdRC, argsCheck, MetaCommand, 3, \ (char **pzErrMsg, int nArgs, char *azArgs[])); \ CONCRETE_METHOD(DotCmdRC, execute, MetaCommand, 4, \ @@ -354,7 +358,7 @@ typedef struct ShellExtensionAPI { ExtensionHelpers * pExtHelpers; /* Functions for an extension to register its implementors with shell */ - const int numRegistrars; /* 5 for this version */ + const int numRegistrars; /* 6 for this version */ union { struct ShExtAPI { /* Register a meta-command */ @@ -373,10 +377,19 @@ typedef struct ShellExtensionAPI { * See above NoticeKind enum and ShellEventNotify callback typedef. */ int (*subscribeEvents)(ShellExState *p, ExtensionId eid, void *pvUserData, NoticeKind nkMin, ShellEventNotify eventHandler); + /* Notify host shell that an ad-hoc dot command exists and provide for + * its help text to appear in .help output. Only an extension which has + * registered an "unknown" MetaCommand may use this. + * If zHelp==0, any such provision is removed. If zHelp!=0, original or + * replacement help text is associated with command zName. + * Help text before the first newline is primary, issued as summary help. + * Text beyond that is secondary, issued as the complete command help. */ + int (*registerAdHocCommand)(ShellExState *p, ExtensionId eid, + const char *zName, const char *zHelp); /* Preset to 0 at extension load, a sentinel for expansion */ void (*sentinel)(void); } named; - void (*pFunctions[5+1])(); /* 0-terminated sequence of function pointers */ + void (*pFunctions[6+1])(); /* 0-terminated sequence of function pointers */ } api; } ShellExtensionAPI; @@ -475,6 +488,18 @@ typedef struct ShellExtensionLink { || SHELL_API_COUNT(link_ptr)<(minNumApi) \ || SHELL_HELPER_COUNT(link_ptr)<(minNumHelpers) \ ) +/* Like above, except it is an enum expression. The value is EXLD_Ok for + * success or one of the next three values telling why the load failed. + */ +typedef enum { + EXLD_Ok, EXLD_NoLink, EXLD_OutdatedApi, EXLD_OutdatedHelpers +} ExtensionLoadStatus; +#define SHELL_EXTENSION_LOADFAIL_WHY(link_ptr, minNumApi, minNumHelpers) ( \ + (!SHELL_EXTENSION_LINKED(link_ptr) ? EXLD_NoLink \ + : SHELL_API_COUNT(link_ptr)<(minNumApi) ? EXLD_OutdatedApi \ + : SHELL_HELPER_COUNT(link_ptr)<(minNumHelpers) ? EXLD_OutdatedHelpers \ + : EXLD_Ok ) \ +) #ifdef __cplusplus } // extern "C" diff --git a/src/test_shellext.c b/src/test_shellext.c index 23125ab096..5b4641f0e2 100644 --- a/src/test_shellext.c +++ b/src/test_shellext.c @@ -10,7 +10,7 @@ ** ************************************************************************* ** Test extension for testing the shell's .load -shellext ... function. -** gcc -shared -fPIC -Wall -I$srcdir -I.. -g test_shellext.c -o test_shellext.so +** gcc -shared -fPIC -Wall -I. -I.. -g test_shellext.c -o test_shellext.so */ #include #include "shext_linkage.h" @@ -36,13 +36,12 @@ DERIVED_METHOD(const char *, name, MetaCommand,BatBeing, 0,()){ return "bat_being"; } -DERIVED_METHOD(const char *, help, MetaCommand,BatBeing, 1,(int more)){ - switch( more ){ - case 0: return - ".bat_being ?whatever? Demonstrates vigilantism weekly\n"; - case 1: return " Options summon side-kick and villains.\n"; - default: return 0; - } +DERIVED_METHOD(const char *, help, MetaCommand,BatBeing, 1,(const char *zHK)){ + if( !zHK ) + return ".bat_being ?whatever? Demonstrates vigilantism weekly\n"; + if( !*zHK ) + return " Options summon side-kick and villains.\n"; + return 0; } DERIVED_METHOD(DotCmdRC, argsCheck, MetaCommand,BatBeing, 3,