From 80d2d6e828f6d55feed95b0636d003ef7ee950e8 Mon Sep 17 00:00:00 2001 From: larrybr Date: Tue, 6 Jul 2021 02:07:21 +0000 Subject: [PATCH] More commands dispathed. Dispatch translation parameterized. FossilOrigin-Name: 372e3241c9e87144ffb19f7e880e9c75ec80260b0db5aed24474bc2a8d7381e4 --- manifest | 14 +- manifest.uuid | 2 +- src/shell.c.in | 991 ++++++++++++++++++++++++---------------------- tool/mkshellc.tcl | 108 ++++- 4 files changed, 624 insertions(+), 491 deletions(-) diff --git a/manifest b/manifest index 37b3989078..836c2d1f8a 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Streamline\smost\scommon\scommand\sdefinition -D 2021-07-04T22:38:20.110 +C More\scommands\sdispathed.\sDispatch\stranslation\sparameterized. +D 2021-07-06T02:07:21.072 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -545,7 +545,7 @@ F src/random.c 097dc8b31b8fba5a9aca1697aeb9fd82078ec91be734c16bffda620ced7ab83c F src/resolve.c b379c5ffe3b692e9c64fa37817cc0efa204b7c9468a818309dde85fd132d9d81 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 F src/select.c 4fa607bab6bcc580f12dbaf9c800b2250a1e408f10321a1d3bcb1dd30c447e62 -F src/shell.c.in 0a1460f9daacc06ce7618981d2d3d3b206a64ca30258a5968ffd421eccaabaeb +F src/shell.c.in ce74a51f548a268436cba07b0082c9b36161ac7f8bdc4c476f88f2d506ba248c F src/sqlite.h.in ecf5aa981da30c33da3e9f353bf3ebf055d3c380c80d6a4f954e58d18ccd6df1 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h e97f4e9b509408fea4c4e9bef5a41608dfac343b4d3c7a990dedde1e19af9510 @@ -1853,7 +1853,7 @@ F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c F tool/mkopcodeh.tcl 130b88697da6ec5b89b41844d955d08fb62c2552e889dec8c7bcecb28d8f50bd F tool/mkopts.tcl 680f785fdb09729fd9ac50632413da4eadbdf9071535e3f26d03795828ab07fa F tool/mkpragmatab.tcl ae5585ae76ca26e4d6ccd5ea9cdebaf5efefb318bf989497a0e846cd711d9ab1 -F tool/mkshellc.tcl 270ddd503b3c6c2ea1fa6559a086cd2517ff84088b54909960fffeda4d0b816e +F tool/mkshellc.tcl e671cba0dfdab17a652530c59ad7c44170f9aa31cda04222ec77079adbf73c9b F tool/mksourceid.c 36aa8020014aed0836fd13c51d6dc9219b0df1761d6b5f58ff5b616211b079b9 F tool/mkspeedsql.tcl a1a334d288f7adfe6e996f2e712becf076745c97 F tool/mksqlite3c-noext.tcl 4f7cfef5152b0c91920355cbfc1d608a4ad242cb819f1aea07f6d0274f584a7f @@ -1919,7 +1919,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 5e7e0d4ef8665e924f499238b1469a5fc06d24f6cf96864b502e62734d92e7ee -R 1911848c8c9c967a495f2da60a0478ec +P dd76b41a72aca94450fb6f45ff56af95d2adb9275eebe9ca67ebd04a52a63c33 +R 8168cef1e5221cce40797275ada85323 U larrybr -Z 9f01a27b7f49ee2ea4a875a6945797b7 +Z b0378e746b391ae098a6bfe801e6d972 diff --git a/manifest.uuid b/manifest.uuid index 7ee1d1065d..60446aa03f 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -dd76b41a72aca94450fb6f45ff56af95d2adb9275eebe9ca67ebd04a52a63c33 \ No newline at end of file +372e3241c9e87144ffb19f7e880e9c75ec80260b0db5aed24474bc2a8d7381e4 \ No newline at end of file diff --git a/src/shell.c.in b/src/shell.c.in index fe5db11388..f4312d3508 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -3345,6 +3345,9 @@ static int expertFinish( return rc; } +#define NO_SUCH_COMMAND -0x7fff +#define INVALID_ARGS -0x7ffe + /* ** Implementation of ".expert" dot command. */ @@ -7131,6 +7134,98 @@ static int recoverDatabaseCmd(ShellState *pState, int nArg, char **azArg){ } #endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ +static int writeDb( char *azArg[], int nArg, ShellState *p ){ + int rc = 0; + const char *zDestFile = 0; + const char *zDb = 0; + sqlite3 *pDest; + sqlite3_backup *pBackup; + int j; + int bAsync = 0; + const char *zVfs = 0; + for(j=1; jdb, zDb); + if( pBackup==0 ){ + utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); + close_db(pDest); + return 1; + } + while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){} + sqlite3_backup_finish(pBackup); + if( rc==SQLITE_DONE ){ + rc = 0; + }else{ + utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); + rc = 1; + } + close_db(pDest); + return rc; +} + +/* + * Define meta-commands and provide for their dispatch and .help text. + * These should be kept in command name order for coding convenience + * except where meta-commands share implementation. (The ordering + * required for dispatch and help text is effected regardless.) + */ + +DISPATCH_CONFIG[ + RETURN_TYPE=int + STORAGE_CLASS=static + ARGS_SIGNATURE=char *$arg4\[\], int $arg5, ShellState *$arg6 + DISPATCH_ENTRY={ "$cmd", ${cmd}Command, $arg1, $arg2, $arg3 }, + CMD_CAPTURE_RE=^\s*{\s*"(\w+)" + DISPATCHEE_NAME=${cmd}Command + DC_ARG1_DEFAULT=[string length $cmd] + DC_ARG2_DEFAULT=0 + DC_ARG3_DEFAULT=0 + DC_ARG4_DEFAULT=azArg + DC_ARG5_DEFAULT=nArg + DC_ARG6_DEFAULT=p + DC_ARG_COUNT=7 +]; + + CONDITION_COMMAND(seeargs defined(SQLITE_GIMME_SEEARGS)); /***************** * The .seeargs command @@ -7146,6 +7241,36 @@ DISPATCHABLE_COMMAND( seeargs ? 0 0 azArg nArg p ){ return rc; } +CONDITION_COMMAND(archive !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB)); +/***************** + * The .archive command + */ +COLLECT_HELP_TEXT[ + ".archive ... Manage SQL archives", + " Each command must have exactly one of the following options:", + " -c, --create Create a new archive", + " -u, --update Add or update files with changed mtime", + " -i, --insert Like -u but always add even if unchanged", + " -t, --list List contents of archive", + " -x, --extract Extract files from archive", + " Optional arguments:", + " -v, --verbose Print each filename as it is processed", + " -f FILE, --file FILE Use archive FILE (default is current db)", + " -a FILE, --append FILE Open FILE using the apndvfs VFS", + " -C DIR, --directory DIR Read/extract files from directory DIR", + " -n, --dryrun Show the SQL that would have occurred", + " Examples:", + " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar", + " .ar -tf ARCHIVE # List members of ARCHIVE", + " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE", + " See also:", + " http://sqlite.org/cli.html#sqlite_archive_support", +]; +DISPATCHABLE_COMMAND( archive ? 3 0 azArg nArg p ){ + open_db(p, 0); + return arDotCommand(p, 0, azArg, nArg); +} + /***************** * The .auth command */ @@ -7164,17 +7289,397 @@ DISPATCHABLE_COMMAND( auth 3 2 2 azArg nArg p ){ return rc; } +/***************** + * The .backup and .save commands (aliases for each other) + * These defer to writeDb in the dispatch table, so are not here. + */ +COLLECT_HELP_TEXT[ + ".backup ?DB? FILE Backup DB (default \"main\") to FILE", + " Options:", + " --append Use the appendvfs", + " --async Write the FILE without journal and fsync()", + ".save ?DB? FILE Write DB (default \"main\") to FILE", + " Options:", + " --append Use the appendvfs", + " --async Write the FILE without journal and fsync()", +]; +COLLECT_DISPATCH( * )[ + { "backup", writeDb, 4, 2, 5 }, + { "save", writeDb, 3, 2, 5 }, +]; + +/***************** + * The .bail command + */ +COLLECT_HELP_TEXT[ + ".bail on|off Stop after hitting an error. Default OFF", +]; +DISPATCHABLE_COMMAND( bail 3 2 2 ){ + if( nArg==2 ){ + bail_on_error = booleanValue(azArg[1]); + return 0; + }else{ + raw_printf(stderr, "Usage: .bail on|off\n"); + return 1; + } +} + +/***************** + * The .binary and .cd commands + */ +COLLECT_HELP_TEXT[ + ".binary on|off Turn binary output on or off. Default OFF", + ".cd DIRECTORY Change the working directory to DIRECTORY", +]; +DISPATCHABLE_COMMAND( binary 3 2 2 ){ + if( booleanValue(azArg[1]) ){ + setBinaryMode(p->out, 1); + }else{ + setTextMode(p->out, 1); + } + return 0; +} + +DISPATCHABLE_COMMAND( cd ? 2 2 ){ + int rc=0; +#if defined(_WIN32) || defined(WIN32) + wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]); + rc = !SetCurrentDirectoryW(z); + sqlite3_free(z); +#else + rc = chdir(azArg[1]); +#endif + if( rc ){ + utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]); + rc = 1; + } + return rc; +} + +/* The undocumented ".breakpoint" command causes a call +** to the no-op routine named test_breakpoint(). +*/ +DISPATCHABLE_COMMAND( breakpoint 3 1 1 ){ + test_breakpoint(); + return 0; +} + +/***************** + * The .changes, .check and .clone commands + */ +COLLECT_HELP_TEXT[ + ".changes on|off Show number of rows changed by SQL", + ".check GLOB Fail if output since .testcase does not match", + ".clone NEWDB Clone data into NEWDB from the existing database", +]; +DISPATCHABLE_COMMAND( changes 3 2 2 ){ + setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); + return 0; +} +DISPATCHABLE_COMMAND( check 3 0 0 ){ + /* Cancel output redirection, if it is currently set (by .testcase) + ** Then read the content of the testcase-out.txt file and compare against + ** azArg[1]. If there are differences, report an error and exit. + */ + char *zRes = 0; + int rc=0; + output_reset(p); + if( nArg!=2 ){ + raw_printf(stderr, "Usage: .check GLOB-PATTERN\n"); + rc = 2; + }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ + raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n"); + rc = 2; + }else if( testcase_glob(azArg[1],zRes)==0 ){ + utf8_printf(stderr, + "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", + p->zTestcase, azArg[1], zRes); + rc = 1; + }else{ + utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase); + p->nCheck++; + } + sqlite3_free(zRes); + return rc; +} +DISPATCHABLE_COMMAND( clone ? 2 2 ){ + tryToClone(p, azArg[1]); + return 0; +} + + +/***************** + * The .databases, .dbconfig and .dbinfo commands + */ +COLLECT_HELP_TEXT[ + ".databases List names and files of attached databases", + ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", + ".dbinfo ?DB? Show status information about the database", +]; +DISPATCHABLE_COMMAND( databases 2 1 1 ){ + int rc; + char **azName = 0; + int nName = 0; + sqlite3_stmt *pStmt; + int i; + open_db(p, 0); + rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); + if( rc ){ + utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); + rc = 1; + }else{ + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zSchema = (const char *)sqlite3_column_text(pStmt,1); + const char *zFile = (const char*)sqlite3_column_text(pStmt,2); + azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*)); + if( azName==0 ){ shell_out_of_memory(); /* Does not return */ } + azName[nName*2] = strdup(zSchema); + azName[nName*2+1] = strdup(zFile); + nName++; + } + } + sqlite3_finalize(pStmt); + for(i=0; idb, azName[i*2]); + int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]); + const char *z = azName[i*2+1]; + utf8_printf(p->out, "%s: %s %s%s\n", + azName[i*2], + z && z[0] ? z : "\"\"", + bRdonly ? "r/o" : "r/w", + eTxn==SQLITE_TXN_NONE ? "" : + eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); + free(azName[i*2]); + free(azName[i*2+1]); + } + sqlite3_free(azName); + return rc; +} +DISPATCHABLE_COMMAND( dbconfig 3 1 3 ){ + static const struct DbConfigChoices { + const char *zName; + int op; + } aDbConfig[] = { + { "defensive", SQLITE_DBCONFIG_DEFENSIVE }, + { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL }, + { "dqs_dml", SQLITE_DBCONFIG_DQS_DML }, + { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY }, + { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG }, + { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER }, + { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW }, + { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, + { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE }, + { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, + { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, + { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE }, + { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE }, + { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP }, + { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA }, + { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA }, + }; + int ii, v; + open_db(p, 0); + for(ii=0; ii1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; + if( nArg>=3 ){ + sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); + } + sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); + utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); + if( nArg>1 ) break; + } + if( nArg>1 && ii==ArraySize(aDbConfig) ){ + utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); + utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); + return 1; + } + return 0; +} +DISPATCHABLE_COMMAND( dbinfo 3 1 2 ){ + return shell_dbinfo_command(p, nArg, azArg); +} + +/***************** + * The .dump, .echo and .eqp commands + */ +COLLECT_HELP_TEXT[ + ".dump ?OBJECTS? Render database content as SQL", + " Options:", + " --data-only Output only INSERT statements", + " --newlines Allow unescaped newline characters in output", + " --nosys Omit system tables (ex: \"sqlite_stat1\")", + " --preserve-rowids Include ROWID values in the output", + " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump", + " Additional LIKE patterns can be given in subsequent arguments", + ".echo on|off Turn command echo on or off", + ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN", + " Other Modes:", +#ifdef SQLITE_DEBUG + " test Show raw EXPLAIN QUERY PLAN output", + " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"", +#endif + " trigger Like \"full\" but also show trigger bytecode", +]; +DISPATCHABLE_COMMAND( dump ? 1 2 ){ + char *zLike = 0; + char *zSql; + int i; + int savedShowHeader = p->showHeader; + int savedShellFlags = p->shellFlgs; + ShellClearFlag(p, + SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo + |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); + for(i=1; ishellFlgs & SHFLG_DumpDataOnly)==0 ){ + /* When playing back a "dump", the content might appear in an order + ** which causes immediate foreign key constraints to be violated. + ** So disable foreign-key constraint enforcement to prevent problems. */ + raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n"); + raw_printf(p->out, "BEGIN TRANSACTION;\n"); + } + p->writableSchema = 0; + p->showHeader = 0; + /* Set writable_schema=ON since doing so forces SQLite to initialize + ** as much of the schema as it can even if the sqlite_schema table is + ** corrupt. */ + sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); + p->nErr = 0; + if( zLike==0 ) zLike = sqlite3_mprintf("true"); + zSql = sqlite3_mprintf( + "SELECT name, type, sql FROM sqlite_schema AS o " + "WHERE (%s) AND type=='table'" + " AND sql NOT NULL" + " ORDER BY tbl_name='sqlite_sequence', rowid", + zLike + ); + run_schema_dump_query(p,zSql); + sqlite3_free(zSql); + if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + zSql = sqlite3_mprintf( + "SELECT sql FROM sqlite_schema AS o " + "WHERE (%s) AND sql NOT NULL" + " AND type IN ('index','trigger','view')", + zLike + ); + run_table_dump_query(p, zSql); + sqlite3_free(zSql); + } + sqlite3_free(zLike); + if( p->writableSchema ){ + raw_printf(p->out, "PRAGMA writable_schema=OFF;\n"); + p->writableSchema = 0; + } + sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); + sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); + if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ + raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); + } + p->showHeader = savedShowHeader; + p->shellFlgs = savedShellFlags; + + return 0; +} +DISPATCHABLE_COMMAND( echo ? 2 2 ){ + setOrClearFlag(p, SHFLG_Echo, azArg[1]); + return 0; +} +DISPATCHABLE_COMMAND( eqp ? 0 0 ){ + if( nArg==2 ){ + p->autoEQPtest = 0; + if( p->autoEQPtrace ){ + if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0); + p->autoEQPtrace = 0; + } + if( strcmp(azArg[1],"full")==0 ){ + p->autoEQP = AUTOEQP_full; + }else if( strcmp(azArg[1],"trigger")==0 ){ + p->autoEQP = AUTOEQP_trigger; +#ifdef SQLITE_DEBUG + }else if( strcmp(azArg[1],"test")==0 ){ + p->autoEQP = AUTOEQP_on; + p->autoEQPtest = 1; + }else if( strcmp(azArg[1],"trace")==0 ){ + p->autoEQP = AUTOEQP_full; + p->autoEQPtrace = 1; + open_db(p, 0); + sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0); + sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0); +#endif + }else{ + p->autoEQP = (u8)booleanValue(azArg[1]); + } + }else{ + raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n"); + return 1; + } +} + +/***************** + * The . command + */ +COLLECT_HELP_TEXT[ +]; +DISPATCHABLE_COMMAND( ? ? 1 1 ){ +} + + /***************** * The .help command */ COLLECT_HELP_TEXT[ ".help ?-all? ?PATTERN? Show help text for PATTERN", ]; -COLLECT_DISPATCH( help )[ - { "help", commandHelp, 3, 1, 0 }, -]; -static int commandHelp(char *azArg[], int nArg, ShellState *p) -{ +DISPATCHABLE_COMMAND( help 3 0 0 ){ if( nArg>=2 ){ if( showHelp(p->out, azArg[1])==0 ){ utf8_printf(p->out, "Nothing matches '%s'\n", azArg[1]); @@ -7192,7 +7697,7 @@ static struct DispatchEntry { int (*cmdDoer)(char *azArg[], int nArg, ShellState *); unsigned char minLen, minArgs, maxArgs; } command_table[] = { - EMIT_DISPATCH(); + EMIT_DISPATCH(2); { 0, 0, 0, -1, -1 } }; static unsigned numCommands = sizeof(command_table)/sizeof(struct DispatchEntry) - 1; @@ -7200,22 +7705,24 @@ static unsigned numCommands = sizeof(command_table)/sizeof(struct DispatchEntry) /***************** * Command dispatcher */ -#define NO_SUCH_COMMAND -0x7fff -#define INVALID_ARGS -0x7ffe int dispatchCommand(char *azArg[], int nArg, ShellState *pSS) { const char *cmdName = azArg[0]; + int cmdLen = strlen30(cmdName); struct DispatchEntry *pde = 0; int ixb = 0, ixe = numCommands-1; while( ixb <= ixe ){ int ixm = (ixb+ixe)/2; - int md = strncmp(cmdName, command_table[ixm].cmdName, command_table[ixm].minLen); + int md = strncmp(cmdName, command_table[ixm].cmdName, cmdLen); if( md>0 ){ ixb = ixm+1; }else if( md<0 ){ ixe = ixm-1; }else{ + if( command_table[ixm].minLen > cmdLen ){ + return NO_SUCH_COMMAND; + } pde = &command_table[ixm]; break; } @@ -7281,412 +7788,9 @@ static int do_meta_command(char *zLine, ShellState *p){ c = azArg[0][0]; clearTempFile(p); - -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_HAVE_ZLIB) - if( c=='a' && strncmp(azArg[0], "archive", n)==0 ){ - open_db(p, 0); - rc = arDotCommand(p, 0, azArg, nArg); - }else -#endif - - if( (c=='b' && n>=3 && strncmp(azArg[0], "backup", n)==0) - || (c=='s' && n>=3 && strncmp(azArg[0], "save", n)==0) - ){ - const char *zDestFile = 0; - const char *zDb = 0; - sqlite3 *pDest; - sqlite3_backup *pBackup; - int j; - int bAsync = 0; - const char *zVfs = 0; - for(j=1; jdb, zDb); - if( pBackup==0 ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); - close_db(pDest); - return 1; - } - while( (rc = sqlite3_backup_step(pBackup,100))==SQLITE_OK ){} - sqlite3_backup_finish(pBackup); - if( rc==SQLITE_DONE ){ - rc = 0; - }else{ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(pDest)); - rc = 1; - } - close_db(pDest); - }else - - if( c=='b' && n>=3 && strncmp(azArg[0], "bail", n)==0 ){ - if( nArg==2 ){ - bail_on_error = booleanValue(azArg[1]); - }else{ - raw_printf(stderr, "Usage: .bail on|off\n"); - rc = 1; - } - }else - - if( c=='b' && n>=3 && strncmp(azArg[0], "binary", n)==0 ){ - if( nArg==2 ){ - if( booleanValue(azArg[1]) ){ - setBinaryMode(p->out, 1); - }else{ - setTextMode(p->out, 1); - } - }else{ - raw_printf(stderr, "Usage: .binary on|off\n"); - rc = 1; - } - }else - - if( c=='c' && strcmp(azArg[0],"cd")==0 ){ - if( nArg==2 ){ -#if defined(_WIN32) || defined(WIN32) - wchar_t *z = sqlite3_win32_utf8_to_unicode(azArg[1]); - rc = !SetCurrentDirectoryW(z); - sqlite3_free(z); -#else - rc = chdir(azArg[1]); -#endif - if( rc ){ - utf8_printf(stderr, "Cannot change to directory \"%s\"\n", azArg[1]); - rc = 1; - } - }else{ - raw_printf(stderr, "Usage: .cd DIRECTORY\n"); - rc = 1; - } - }else - - /* The undocumented ".breakpoint" command causes a call to the no-op - ** routine named test_breakpoint(). - */ - if( c=='b' && n>=3 && strncmp(azArg[0], "breakpoint", n)==0 ){ - test_breakpoint(); - }else - - if( c=='c' && n>=3 && strncmp(azArg[0], "changes", n)==0 ){ - if( nArg==2 ){ - setOrClearFlag(p, SHFLG_CountChanges, azArg[1]); - }else{ - raw_printf(stderr, "Usage: .changes on|off\n"); - rc = 1; - } - }else - - /* Cancel output redirection, if it is currently set (by .testcase) - ** Then read the content of the testcase-out.txt file and compare against - ** azArg[1]. If there are differences, report an error and exit. + /* Check for the special, non-dispatched meta-commands. */ - if( c=='c' && n>=3 && strncmp(azArg[0], "check", n)==0 ){ - char *zRes = 0; - output_reset(p); - if( nArg!=2 ){ - raw_printf(stderr, "Usage: .check GLOB-PATTERN\n"); - rc = 2; - }else if( (zRes = readFile("testcase-out.txt", 0))==0 ){ - raw_printf(stderr, "Error: cannot read 'testcase-out.txt'\n"); - rc = 2; - }else if( testcase_glob(azArg[1],zRes)==0 ){ - utf8_printf(stderr, - "testcase-%s FAILED\n Expected: [%s]\n Got: [%s]\n", - p->zTestcase, azArg[1], zRes); - rc = 1; - }else{ - utf8_printf(stdout, "testcase-%s ok\n", p->zTestcase); - p->nCheck++; - } - sqlite3_free(zRes); - }else - - if( c=='c' && strncmp(azArg[0], "clone", n)==0 ){ - if( nArg==2 ){ - tryToClone(p, azArg[1]); - }else{ - raw_printf(stderr, "Usage: .clone FILENAME\n"); - rc = 1; - } - }else - - if( c=='d' && n>1 && strncmp(azArg[0], "databases", n)==0 ){ - char **azName = 0; - int nName = 0; - sqlite3_stmt *pStmt; - int i; - open_db(p, 0); - rc = sqlite3_prepare_v2(p->db, "PRAGMA database_list", -1, &pStmt, 0); - if( rc ){ - utf8_printf(stderr, "Error: %s\n", sqlite3_errmsg(p->db)); - rc = 1; - }else{ - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zSchema = (const char *)sqlite3_column_text(pStmt,1); - const char *zFile = (const char*)sqlite3_column_text(pStmt,2); - azName = sqlite3_realloc(azName, (nName+1)*2*sizeof(char*)); - if( azName==0 ){ shell_out_of_memory(); /* Does not return */ } - azName[nName*2] = strdup(zSchema); - azName[nName*2+1] = strdup(zFile); - nName++; - } - } - sqlite3_finalize(pStmt); - for(i=0; idb, azName[i*2]); - int bRdonly = sqlite3_db_readonly(p->db, azName[i*2]); - const char *z = azName[i*2+1]; - utf8_printf(p->out, "%s: %s %s%s\n", - azName[i*2], - z && z[0] ? z : "\"\"", - bRdonly ? "r/o" : "r/w", - eTxn==SQLITE_TXN_NONE ? "" : - eTxn==SQLITE_TXN_READ ? " read-txn" : " write-txn"); - free(azName[i*2]); - free(azName[i*2+1]); - } - sqlite3_free(azName); - }else - - if( c=='d' && n>=3 && strncmp(azArg[0], "dbconfig", n)==0 ){ - static const struct DbConfigChoices { - const char *zName; - int op; - } aDbConfig[] = { - { "defensive", SQLITE_DBCONFIG_DEFENSIVE }, - { "dqs_ddl", SQLITE_DBCONFIG_DQS_DDL }, - { "dqs_dml", SQLITE_DBCONFIG_DQS_DML }, - { "enable_fkey", SQLITE_DBCONFIG_ENABLE_FKEY }, - { "enable_qpsg", SQLITE_DBCONFIG_ENABLE_QPSG }, - { "enable_trigger", SQLITE_DBCONFIG_ENABLE_TRIGGER }, - { "enable_view", SQLITE_DBCONFIG_ENABLE_VIEW }, - { "fts3_tokenizer", SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER }, - { "legacy_alter_table", SQLITE_DBCONFIG_LEGACY_ALTER_TABLE }, - { "legacy_file_format", SQLITE_DBCONFIG_LEGACY_FILE_FORMAT }, - { "load_extension", SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION }, - { "no_ckpt_on_close", SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE }, - { "reset_database", SQLITE_DBCONFIG_RESET_DATABASE }, - { "trigger_eqp", SQLITE_DBCONFIG_TRIGGER_EQP }, - { "trusted_schema", SQLITE_DBCONFIG_TRUSTED_SCHEMA }, - { "writable_schema", SQLITE_DBCONFIG_WRITABLE_SCHEMA }, - }; - int ii, v; - open_db(p, 0); - for(ii=0; ii1 && strcmp(azArg[1], aDbConfig[ii].zName)!=0 ) continue; - if( nArg>=3 ){ - sqlite3_db_config(p->db, aDbConfig[ii].op, booleanValue(azArg[2]), 0); - } - sqlite3_db_config(p->db, aDbConfig[ii].op, -1, &v); - utf8_printf(p->out, "%19s %s\n", aDbConfig[ii].zName, v ? "on" : "off"); - if( nArg>1 ) break; - } - if( nArg>1 && ii==ArraySize(aDbConfig) ){ - utf8_printf(stderr, "Error: unknown dbconfig \"%s\"\n", azArg[1]); - utf8_printf(stderr, "Enter \".dbconfig\" with no arguments for a list\n"); - } - }else - - if( c=='d' && n>=3 && strncmp(azArg[0], "dbinfo", n)==0 ){ - rc = shell_dbinfo_command(p, nArg, azArg); - }else - -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) - if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){ - open_db(p, 0); - rc = recoverDatabaseCmd(p, nArg, azArg); - }else -#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ - if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ - char *zLike = 0; - char *zSql; - int i; - int savedShowHeader = p->showHeader; - int savedShellFlags = p->shellFlgs; - ShellClearFlag(p, - SHFLG_PreserveRowid|SHFLG_Newlines|SHFLG_Echo - |SHFLG_DumpDataOnly|SHFLG_DumpNoSys); - for(i=1; ishellFlgs & SHFLG_DumpDataOnly)==0 ){ - /* When playing back a "dump", the content might appear in an order - ** which causes immediate foreign key constraints to be violated. - ** So disable foreign-key constraint enforcement to prevent problems. */ - raw_printf(p->out, "PRAGMA foreign_keys=OFF;\n"); - raw_printf(p->out, "BEGIN TRANSACTION;\n"); - } - p->writableSchema = 0; - p->showHeader = 0; - /* Set writable_schema=ON since doing so forces SQLite to initialize - ** as much of the schema as it can even if the sqlite_schema table is - ** corrupt. */ - sqlite3_exec(p->db, "SAVEPOINT dump; PRAGMA writable_schema=ON", 0, 0, 0); - p->nErr = 0; - if( zLike==0 ) zLike = sqlite3_mprintf("true"); - zSql = sqlite3_mprintf( - "SELECT name, type, sql FROM sqlite_schema AS o " - "WHERE (%s) AND type=='table'" - " AND sql NOT NULL" - " ORDER BY tbl_name='sqlite_sequence', rowid", - zLike - ); - run_schema_dump_query(p,zSql); - sqlite3_free(zSql); - if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - zSql = sqlite3_mprintf( - "SELECT sql FROM sqlite_schema AS o " - "WHERE (%s) AND sql NOT NULL" - " AND type IN ('index','trigger','view')", - zLike - ); - run_table_dump_query(p, zSql); - sqlite3_free(zSql); - } - sqlite3_free(zLike); - if( p->writableSchema ){ - raw_printf(p->out, "PRAGMA writable_schema=OFF;\n"); - p->writableSchema = 0; - } - sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0); - sqlite3_exec(p->db, "RELEASE dump;", 0, 0, 0); - if( (p->shellFlgs & SHFLG_DumpDataOnly)==0 ){ - raw_printf(p->out, p->nErr?"ROLLBACK; -- due to errors\n":"COMMIT;\n"); - } - p->showHeader = savedShowHeader; - p->shellFlgs = savedShellFlags; - }else - - if( c=='e' && strncmp(azArg[0], "echo", n)==0 ){ - if( nArg==2 ){ - setOrClearFlag(p, SHFLG_Echo, azArg[1]); - }else{ - raw_printf(stderr, "Usage: .echo on|off\n"); - rc = 1; - } - }else - - if( c=='e' && strncmp(azArg[0], "eqp", n)==0 ){ - if( nArg==2 ){ - p->autoEQPtest = 0; - if( p->autoEQPtrace ){ - if( p->db ) sqlite3_exec(p->db, "PRAGMA vdbe_trace=OFF;", 0, 0, 0); - p->autoEQPtrace = 0; - } - if( strcmp(azArg[1],"full")==0 ){ - p->autoEQP = AUTOEQP_full; - }else if( strcmp(azArg[1],"trigger")==0 ){ - p->autoEQP = AUTOEQP_trigger; -#ifdef SQLITE_DEBUG - }else if( strcmp(azArg[1],"test")==0 ){ - p->autoEQP = AUTOEQP_on; - p->autoEQPtest = 1; - }else if( strcmp(azArg[1],"trace")==0 ){ - p->autoEQP = AUTOEQP_full; - p->autoEQPtrace = 1; - open_db(p, 0); - sqlite3_exec(p->db, "SELECT name FROM sqlite_schema LIMIT 1", 0, 0, 0); - sqlite3_exec(p->db, "PRAGMA vdbe_trace=ON;", 0, 0, 0); -#endif - }else{ - p->autoEQP = (u8)booleanValue(azArg[1]); - } - }else{ - raw_printf(stderr, "Usage: .eqp off|on|trace|trigger|full\n"); - rc = 1; - } - }else if( c=='e' && strncmp(azArg[0], "exit", n)==0 ){ if( nArg>1 && (rc = (int)integerValue(azArg[1]))!=0 ) exit(rc); @@ -8905,6 +9009,13 @@ static int do_meta_command(char *zLine, ShellState *p){ p->lineno = savedLineno; }else +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) + if( c=='r' && strncmp(azArg[0], "recover", n)==0 ){ + open_db(p, 0); + rc = recoverDatabaseCmd(p, nArg, azArg); + }else +#endif /* !(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_DBPAGE_VTAB) */ + if( c=='r' && n>=3 && strncmp(azArg[0], "restore", n)==0 ){ const char *zSrcFile; const char *zDb; @@ -10567,57 +10678,6 @@ static void process_sqliterc( } COLLECT_HELP_TEXT[ -#if defined(SQLITE_HAVE_ZLIB) && !defined(SQLITE_OMIT_VIRTUALTABLE) - ".archive ... Manage SQL archives", - " Each command must have exactly one of the following options:", - " -c, --create Create a new archive", - " -u, --update Add or update files with changed mtime", - " -i, --insert Like -u but always add even if unchanged", - " -t, --list List contents of archive", - " -x, --extract Extract files from archive", - " Optional arguments:", - " -v, --verbose Print each filename as it is processed", - " -f FILE, --file FILE Use archive FILE (default is current db)", - " -a FILE, --append FILE Open FILE using the apndvfs VFS", - " -C DIR, --directory DIR Read/extract files from directory DIR", - " -n, --dryrun Show the SQL that would have occurred", - " Examples:", - " .ar -cf ARCHIVE foo bar # Create ARCHIVE from files foo and bar", - " .ar -tf ARCHIVE # List members of ARCHIVE", - " .ar -xvf ARCHIVE # Verbosely extract files from ARCHIVE", - " See also:", - " http://sqlite.org/cli.html#sqlite_archive_support", -#endif -]; -COLLECT_HELP_TEXT[ - ".backup ?DB? FILE Backup DB (default \"main\") to FILE", - " --append Use the appendvfs", - " --async Write to FILE without journal and fsync()", - ".bail on|off Stop after hitting an error. Default OFF", - ".binary on|off Turn binary output on or off. Default OFF", - ".cd DIRECTORY Change the working directory to DIRECTORY", - ".changes on|off Show number of rows changed by SQL", - ".check GLOB Fail if output since .testcase does not match", - ".clone NEWDB Clone data into NEWDB from the existing database", - ".databases List names and files of attached databases", - ".dbconfig ?op? ?val? List or change sqlite3_db_config() options", - ".dbinfo ?DB? Show status information about the database", - ".dump ?OBJECTS? Render database content as SQL", - " Options:", - " --data-only Output only INSERT statements", - " --newlines Allow unescaped newline characters in output", - " --nosys Omit system tables (ex: \"sqlite_stat1\")", - " --preserve-rowids Include ROWID values in the output", - " OBJECTS is a LIKE pattern for tables, indexes, triggers or views to dump", - " Additional LIKE patterns can be given in subsequent arguments", - ".echo on|off Turn command echo on or off", - ".eqp on|off|full|... Enable or disable automatic EXPLAIN QUERY PLAN", - " Other Modes:", -#ifdef SQLITE_DEBUG - " test Show raw EXPLAIN QUERY PLAN output", - " trace Like \"full\" but enable \"PRAGMA vdbe_trace\"", -#endif - " trigger Like \"full\" but also show trigger bytecode", ".excel Display the output of next command in spreadsheet", " --bom Put a UTF8 byte-order mark on intermediate file", ".exit ?CODE? Exit this program with return-code CODE", @@ -10752,7 +10812,6 @@ COLLECT_HELP_TEXT[ ]; COLLECT_HELP_TEXT[ ".restore ?DB? FILE Restore content of DB (default \"main\") from FILE", - ".save FILE Write in-memory database into FILE", ".scanstats on|off Turn sqlite3_stmt_scanstatus() metrics on or off", ".schema ?PATTERN? Show the CREATE statements matching PATTERN", " Options:", @@ -10863,7 +10922,7 @@ static const char *(azHelp[]) = { ".whatever ?arg? ... Summary of effects (limited to this line's length)", " ^ ^ ^ ^ ", */ - EMIT_HELP_TEXT(); + EMIT_HELP_TEXT(2); 0 /* Sentinel */ }; diff --git a/tool/mkshellc.tcl b/tool/mkshellc.tcl index 8ca4259ded..b47e4eb92e 100644 --- a/tool/mkshellc.tcl +++ b/tool/mkshellc.tcl @@ -37,7 +37,28 @@ set ::cmd_help [dict create] set ::cmd_dispatch [dict create] set ::cmd_condition [dict create] set ::iShuffleErrors 0 -set ::commandFuncSuffix "Command" + +# Setup dispatching function signature and table entry struct . +set ::dispCfg [dict create \ + RETURN_TYPE int \ + STORAGE_CLASS static \ + ARGS_SIGNATURE "char *\$arg4\\\[\\\], int \$arg5, ShellState *\$arg6" \ + DISPATCH_ENTRY \ + "\x7B \"\$cmd\", \$\x7Bcmd\x7DCommand, \$arg1,\$arg2,\$arg3 \x7D," \ + DISPATCHEE_NAME {${cmd}Command} \ + CMD_CAPTURE_RE "^\\s*\x7B\\s*\"(\\w+)\"" \ +] +# Other config keys: +# DC_ARG_COUNT= +# DC_ARG#_DEFAULT= +# Variables $cmd and $arg# (where # = 0 .. DC_ARG_COUNT-1) have values +# when ARGS_SIGNATURE, DISPATCH_ENTRY, and DISPATCHEE_NAME are evaluated. + +# proc dump_cfg {} { +# foreach k [dict keys $::dispCfg] { +# puts stderr "$k=[dict get $::dispCfg $k]" +# } +# } proc condition_command {cmd pp_expr} { if {[regexp {^(!)?defined\(\s*(\w+)\s*\)} $pp_expr ma bang pp_var]} { @@ -52,12 +73,19 @@ proc condition_command {cmd pp_expr} { dict set ::cmd_condition $cmd $pp_expr } -proc emit_conditionally {cmd lines ostrm} { +proc emit_conditionally {cmd lines ostrm {indent ""}} { set wrapped [dict exists $::cmd_condition $cmd] if {$wrapped} { puts $ostrm [dict get $::cmd_condition $cmd] } - puts $ostrm [join $lines "\n"] + if {[regexp {^\s*(\d+)\s*$} $indent ma inum]} { + set lead [string repeat " " $inum] + foreach line $lines { + puts $ostrm "$lead[string trimleft $line]" + } + } else { + puts $ostrm [join $lines "\n"] + } if {$wrapped} { puts $ostrm "#endif" } @@ -118,7 +146,9 @@ proc chunkify_help {htin} { # in which case it is emitted as-is. proc do_shuffle {hFile lx ostrm} { set iAte 0 - if {[regexp {^COLLECT_HELP_TEXT\[} $lx]} { + if {![regexp {^\s{0,4}[A-Z]+} $lx]} { + puts $ostrm $lx + } elseif {[regexp {^COLLECT_HELP_TEXT\[} $lx]} { incr iAte set help_frag {} set lx [gets $hFile] @@ -133,9 +163,22 @@ proc do_shuffle {hFile lx ostrm} { && $tc eq "\x7B"} { set args [split [regsub {\s+} [string trim $args] " "]] incr iAte - if {[llength $args] != 7} { + set na [llength $args] + set cmd [lindex $args 0] + set naPass [dict get $::dispCfg DC_ARG_COUNT] + if {$na > $naPass} { puts stderr "Bad args: $lx" } else { + while {$na < $naPass} { + if {![dict exists $::dispCfg "DC_ARG${na}_DEFAULT"]} { + puts stderr "Too few args: $lx (need $naPass)" + incr ::iShuffleErrors + break + } else { + lappend args [subst [dict get $::dispCfg "DC_ARG${na}_DEFAULT"]] + } + incr na + } set body {} while {![eof $hFile]} { set lb [gets $hFile] @@ -143,41 +186,72 @@ proc do_shuffle {hFile lx ostrm} { lappend body $lb if {[regexp "^\x7D\\s*\$" $lb]} { break } } - foreach {cmd cmdLen naMin naMax azA nA pSS} $args { - if {$cmdLen eq "?"} { - set cmdLen [string length $cmd] + for {set aix 1} {$aix < $na} {incr aix} { + set av [lindex $args $aix] + if {$av eq "?"} { + set ai [expr {$aix + 1}] + set av [subst [dict get $::dispCfg "DC_ARG${ai}_DEFAULT"]] } - set func "$cmd$::commandFuncSuffix" - set dispEntry " \x7B \"$cmd\", $func, $cmdLen, $naMin, $naMax \x7D," - set funcOpen "static int ${func}(char *$azA\[\], int $nA, ShellState *$pSS)\x7B" + set "arg$aix" $av + } + if {$cmd ne "?"} { + set rsct [dict get $::dispCfg STORAGE_CLASS] + set rsct "$rsct [dict get $::dispCfg RETURN_TYPE]" + set argexp [subst [dict get $::dispCfg ARGS_SIGNATURE]] + set fname [subst [dict get $::dispCfg DISPATCHEE_NAME]] + set funcOpen "$rsct $fname\($argexp\)\x7B" + set dispEntry [subst [dict get $::dispCfg DISPATCH_ENTRY]] emit_conditionally $cmd [linsert $body 0 $funcOpen] $ostrm dict set ::cmd_dispatch $cmd [list $dispEntry] } } - } elseif {[regexp {^\s*EMIT_HELP_TEXT\(\)} $lx]} { + } elseif {[regexp {^\s*EMIT_HELP_TEXT\((\d*)\)} $lx ma indent]} { incr iAte foreach htc [lsort [dict keys $::cmd_help]] { emit_conditionally $htc [dict get $::cmd_help $htc] $ostrm } - } elseif {[regexp {^COLLECT_DISPATCH\(\s*(\w+)\s*\)\[} $lx ma cmd]} { + } elseif {[regexp {^COLLECT_DISPATCH\(\s*([\w\*]+)\s*\)\[} $lx ma cmd]} { incr iAte - set disp_frag {} set lx [gets $hFile] while {![eof $hFile] && ![regexp {^\s*\];} $lx]} { lappend disp_frag $lx + set grabCmd [dict get $::dispCfg CMD_CAPTURE_RE] + if {![regexp $grabCmd $lx ma dcmd]} { + puts stderr "malformed dispatch element:\n $lx" + incr ::iShuffleErrors + } elseif {$cmd ne "*" && $dcmd ne $cmd} { + puts stderr "misdeclared dispatch element:\n $lx" + incr ::iShuffleErrors + } else { + dict set ::cmd_dispatch $dcmd [list $lx] + } set lx [gets $hFile] incr iAte } incr iAte - dict set ::cmd_dispatch $cmd $disp_frag - } elseif {[regexp {^\s*EMIT_DISPATCH\(\)} $lx]} { + } elseif {[regexp {^\s*EMIT_DISPATCH\((\d*)\)} $lx ma indent]} { incr iAte foreach cmd [lsort [dict keys $::cmd_dispatch]] { - emit_conditionally $cmd [dict get $::cmd_dispatch $cmd] $ostrm + emit_conditionally $cmd [dict get $::cmd_dispatch $cmd] $ostrm $indent } } elseif {[regexp {^CONDITION_COMMAND\(\s*(\w+)\s+([^;]+)\);} $lx ma cmd pp_expr]} { incr iAte condition_command $cmd [string trim $pp_expr] + } elseif {[regexp {^DISPATCH_CONFIG\[} $lx]} { + incr iAte + set def_disp {} + set lx [gets $hFile] + while {![eof $hFile] && ![regexp {^\s*\];} $lx]} { + lappend def_disp $lx + set lx [gets $hFile] + incr iAte + } + incr iAte + foreach line $def_disp { + if {[regexp {^\s*(\w+)=(.+)$} $line ma k v]} { + dict set ::dispCfg $k $v + } + } } else { puts $ostrm $lx } -- 2.47.3