]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Revamp and simplify shell help extension interface, and provide for scripted shell...
authorlarrybr <larrybr@noemail.net>
Fri, 8 Apr 2022 03:20:39 +0000 (03:20 +0000)
committerlarrybr <larrybr@noemail.net>
Fri, 8 Apr 2022 03:20:39 +0000 (03:20 +0000)
FossilOrigin-Name: aa785473d948b9f05f32c2107fb302374573a2906d867b4599a7063943277b0f

ext/misc/tclshext.c.in
manifest
manifest.uuid
src/shell.c.in
src/shext_linkage.h
src/test_shellext.c

index aec4319366ce38d17070e283ccef62050b083ad2..8f6e0befa20e500de67f370c44e076d3db56741e 100644 (file)
@@ -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 <tclOO.h>
@@ -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);
index 07274af45a2aa2ac890a9115c448b38b855aa505..a093bfcfe12c65142c607235cc015bd3682ebf9d 100644 (file)
--- 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.
index 0be30e9e115217ab6d19d332c5d9938505dc443c..e7a319f172a6230899e3a45a58b8e69160b85022 100644 (file)
@@ -1 +1 @@
-588c3512286491e2128103bcef0d6b3bdd0a0d0dcdc6bdc3e3068db552d70ed4
\ No newline at end of file
+aa785473d948b9f05f32c2107fb302374573a2906d867b4599a7063943277b0f
\ No newline at end of file
index b9da9620e3f5d986d00c04b8fa60933a75726ce1..82fa918e31942223c12ee617f21c93a0e4058019 100755 (executable)
@@ -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 && extIx<psi->numExtLoaded ){
     ShExtInfo *psei = & psi->pShxLoaded[extIx];
     if( cmdIx>=0 && cmdIx<psei->numMetaCommands ){
@@ -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( pCI<command_table+numCommands ){
+      if( sqlite3_strglob(mmi.zPattern, pCI->cmdName)==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(extIx<psi->numExtLoaded && 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( ixct<numCommands ){
+      struct CommandInfo *pci = &command_table[ixct];
+      const char *zH = pci->azHelp[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
   {
index 77cf8033796dc6089b9462c40dc2bb1cb66462b3..0369d4aa35bf473409909b9e408fe1ed8bb79b22 100644 (file)
@@ -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"
index 23125ab096feeda9f91ea4beaaefb15b37aae7f3..5b4641f0e262b0646ea8c27893295617e1eeb804 100644 (file)
@@ -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 <stdio.h>
 #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,