** 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
* 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>
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) ){
}
/* 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;
}
/* 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);
}
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"
" 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;
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. */
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);
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
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);
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);
}
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),
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{
#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[]){
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;
}
}
-/* 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.
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);
* 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;
}
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;
}
}
}
- 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) ){
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
/* 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 .. {} {}");
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);
-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
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
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
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
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.
-588c3512286491e2128103bcef0d6b3bdd0a0d0dcdc6bdc3e3068db552d70ed4
\ No newline at end of file
+aa785473d948b9f05f32c2107fb302374573a2906d867b4599a7063943277b0f
\ No newline at end of file
** 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
#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
ExportHandler **ppExportHandlers;
int numImportHandlers;
ImportHandler **ppImportHandlers;
+ MetaCommand *pUnknown; /* .unknown registered for this extension */
} ShExtInfo;
#endif
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 */
#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,
#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 : "?");
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;
return SQLITE_ERROR;
}
+/* See registerScripting API in shext_linkage.h */
static int register_scripting(ShellExState *p, ExtensionId eid,
ScriptSupport *pSS){
ShellInState *psi = ISS(p);
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.
};
static ShellExtensionAPI shellExtAPI = {
- &extHelpers, 5, {
+ &extHelpers, 6, {
register_meta_command,
register_exporter,
register_importer,
register_scripting,
subscribe_events,
+ register_adhoc_command,
0
}
};
}
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 ){
* 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;
}
".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",
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
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;
}
}
/*****************
-** 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;
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;
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;
}
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
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
{
}
}
+/*
+** 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 ""
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;
}
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;
}
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
{
* 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 );
*/
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 ));
*/
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 ));
*/
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[] ));
#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, \
#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, \
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 */
* 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;
|| 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"
**
*************************************************************************
** 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"
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,