From a9136b45fa736aeb3b085686b507ef2fab98beed Mon Sep 17 00:00:00 2001 From: drh <> Date: Wed, 12 Nov 2025 18:54:09 +0000 Subject: [PATCH] Continuing code cleanup. Various minor bug fixes. All legacy tests now pass. All planned features for ".mode" are implemented, at least at the interface level, though some features are still no-ops. FossilOrigin-Name: e5a81711d0076b447e5bd3206bc04d755a6229b9f4926f42260fcd01ecf3e5a2 --- manifest | 20 +-- manifest.uuid | 2 +- src/shell.c.in | 433 ++++++++++++++++++++++++++++++++++++--------- test/modeA.clitest | 12 +- test/shell1.test | 18 +- test/shellA.test | 8 +- test/shellB.test | 8 +- 7 files changed, 396 insertions(+), 105 deletions(-) diff --git a/manifest b/manifest index ebd49ac4a9..e59807f3fb 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\sa\snew\sdirect\sCLI\stesting\sscript\sin\stest/modeA.clitest\sto\sdemonstration\nthat\scan\sbe\sdone.\s\sRig\sthis\sthis\sto\sbe\srun\sby\stest/shellB.test\sduring\snormal\ntesting. -D 2025-11-12T13:46:11.794 +C Continuing\scode\scleanup.\s\sVarious\sminor\sbug\sfixes.\s\sAll\slegacy\stests\snow\npass.\s\sAll\splanned\sfeatures\sfor\s".mode"\sare\simplemented,\sat\sleast\sat\sthe\ninterface\slevel,\sthough\ssome\sfeatures\sare\sstill\sno-ops. +D 2025-11-12T18:54:09.684 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -735,7 +735,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c F src/resolve.c 5616fbcf3b833c7c705b24371828215ad0925d0c0073216c4f153348d5753f0a F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97 F src/select.c ba9cd07ffa3277883c1986085f6ddc4320f4d35d5f212ab58df79a7ecc1a576a -F src/shell.c.in 68396182b0d542eb223fb93e41fcfdb98ffa2caac8ce06868ec1e1da962badba +F src/shell.c.in 7fda2605823e01dbd3bde96d3189eb38bd3ed838070be73aeeb0ccfe89af8db8 F src/sqlite.h.in 7403a952a8f1239de7525b73c4e3a0f9540ec0607ed24fec887f5832642d44b8 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479 F src/sqlite3ext.h 7f236ca1b175ffe03316d974ef57df79b3938466c28d2f95caef5e08c57f3a52 @@ -1437,7 +1437,7 @@ F test/mmap4.test 2e2b4e32555b58da15176e6fe750f17c9dcf7f93 F test/mmapcorrupt.test 470fb44fe92e99c1d23701d156f8c17865f5b027063c9119dcfdb842791f4465 F test/mmapfault.test d4c9eff9cd8c2dc14bc43e71e042f175b0a26fe3 F test/mmapwarm.test 2272005969cd17a910077bd5082f70bc1fefad9a875afec7fc9af483898ecaf3 -F test/modeA.clitest 7d6a81c5fe60ba07b5b68c9fb624fd7cb7c75be3fc364e17c75c47d8efaaa54d +F test/modeA.clitest 73d091c785671f629af0900f0654a28468bbba5685a545c1513049c57f1b8cbc F test/multiplex.test d74c034e52805f6de8cc5432cef8c9eb774bb64ec29b83a22effc8ca4dac1f08 F test/multiplex2.test 580ca5817c7edbe4cc68fa150609c9473393003a F test/multiplex3.test fac575e0b1b852025575a6a8357701d80933e98b5d2fe6d35ddaa68f92f6a1f7 @@ -1602,7 +1602,7 @@ F test/sharedA.test 64bdd21216dda2c6a3bd3475348ccdc108160f34682c97f2f51c19fc0e21 F test/sharedB.test 1a84863d7a2204e0d42f2e1606577c5e92e4473fa37ea0f5bdf829e4bf8ee707 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304 -F test/shell1.test 4d44fb7d08a62b6c6424185e519d59fff9c93d9f9cbf4c773f30908bf60cb3de +F test/shell1.test cdc63d6badd34aef37cd31d92a30b3d3c38fe1386a9c98ac047a7ecec5bfc6a3 F test/shell2.test d8da6a06dcce1d8f04f776f918d4d57c28ddc28c54f3a44f95429794892e3a91 F test/shell3.test 603b448e917537cf77be0f265c05c6f63bc677c63a533c8e96aae923b56f4a0e F test/shell4.test 03593fa7908a55f255916ffeda707cdf55680c777736e3da62b1d78cde0d684d @@ -1611,8 +1611,8 @@ F test/shell6.test e3b883b61d4916b6906678a35f9d19054861123ad91b856461e0a456273bd F test/shell7.test 43fd8e511c533bab5232e95c7b4be93b243451709e89582600d4b6e67693d5c3 F test/shell8.test 641cf21a99c59404c24e3062923734951c4099a6b6b6520de00cf7a1249ee871 F test/shell9.test 8742a5b390cdcef6369f5aa223e415aa4255a4129ef249b177887dc635a87209 -F test/shellA.test 1de183849ddb6b49d9f21a10a2f6f22bc66f962c0bfaed5baa5ecc3dc6d8f2d5 -F test/shellB.test d2b2f6256560145b8c027dbfc41cd2ac50b2ef25d5853f3ae80c9407f441c9d2 +F test/shellA.test 3a3ccbac90367d51f12c3d762c90e84723492e3bc5d228cfefd4c6ebfc72394d +F test/shellB.test 714c13ba4d964277ab236fa210d8c8e05b5f224ce342981548ef4929794d3768 F test/shmlock.test 9f1f729a7fe2c46c88b156af819ac9b72c0714ac6f7246638a73c5752b5fd13c F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5 @@ -2175,8 +2175,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 4c8109682e3a224fd8e015e73a9c1c7b6d56ad747d7ca7bc387bb92e1c98f731 -R e7fc6b191c336123ef02d62bdd856b8f +P 3107ebb9e1602fe5bed644c6f69426bebf307772e581b17a8fa3e50bd522e566 +R 8dad54c9d16986934ba268d5971ec78d U drh -Z 90d2468da1196b69530a9ff9957b86e3 +Z 3adf1517a92d999df985d59d4ee4e85f # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 3e790b0dac..d2ff45567a 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -3107ebb9e1602fe5bed644c6f69426bebf307772e581b17a8fa3e50bd522e566 +e5a81711d0076b447e5bd3206bc04d755a6229b9f4926f42260fcd01ecf3e5a2 diff --git a/src/shell.c.in b/src/shell.c.in index b7d02ca1f7..0e9f0d8ead 100644 --- a/src/shell.c.in +++ b/src/shell.c.in @@ -1415,8 +1415,8 @@ static const char *qrfQuoteNames[] = { "off","off","sql","hex", "tcl", "json"}; #define MODE_QBox 15 /* BOX with SQL-quoted content */ #define MODE_Quote 16 /* Quote values as for SQL */ #define MODE_Table 17 /* MySQL-style table formatting */ -#define MODE_Tcl 18 /* Space-separated list of TCL strings */ -#define MODE_Tsv 19 /* Tab-separated values */ +#define MODE_Tabs 18 /* Tab-separated values */ +#define MODE_Tcl 19 /* Space-separated list of TCL strings */ #define MODE_Www 20 /* Full web-page output */ /* @@ -1439,7 +1439,7 @@ static const char *aModeStr[] = { 0, "\n", "|", " ", ",", "\r\n", "\036", "\037", "\t", "", "NULL", "null" }; static const ModeInfo aModeInfo[] = { /* zName eCSep eRSep eNull eText eBlob bHdr eTitle eStyle eCx */ - { "ascii", 7, 8, 9, 3, 1, 1, 3, 12, 0 }, + { "ascii", 7, 6, 9, 1, 1, 1, 1, 12, 0 }, { "box", 0, 0, 9, 1, 1, 2, 1, 1, 2 }, { "c", 4, 1, 2, 5, 4, 1, 5, 12, 0 }, { "column", 0, 0, 9, 1, 1, 2, 1, 2, 2 }, @@ -1457,8 +1457,8 @@ static const ModeInfo aModeInfo[] = { { "qbox", 0, 0, 9, 2, 2, 2, 1, 1, 2 }, { "quote", 4, 1, 10, 2, 2, 1, 2, 12, 0 }, { "table", 0, 0, 9, 1, 1, 2, 1, 19, 2 }, + { "tabs", 8, 1, 9, 3, 1, 1, 3, 12, 0 }, { "tcl", 3, 1, 9, 5, 4, 1, 5, 12, 0 }, - { "tsv", 8, 5, 9, 3, 1, 1, 3, 12, 0 }, { "www", 0, 0, 9, 4, 1, 2, 4, 7, 0 }, }; /* | / / \ \ | / / ** | / / \ \ | / / @@ -1975,6 +1975,51 @@ static void output_c_string(FILE *out, const char *z){ cli_puts(zq, out); } +/* Encode input string z[] as a C-language string literal and +** append it to the sqlite3_str. If z is NULL render and empty string. +*/ +static void append_c_string(sqlite3_str *out, const char *z){ + char c; + static const char *zq = "\""; + static long ctrlMask = ~0L; + static const char *zDQBSRO = "\"\\\x7f"; /* double-quote, backslash, rubout */ + char ace[3] = "\\?"; + char cbsSay; + if( z==0 ) z = ""; + sqlite3_str_appendall(out,zq); + while( *z!=0 ){ + const char *pcDQBSRO = anyOfInStr(z, zDQBSRO, ~(size_t)0); + const char *pcPast = zSkipValidUtf8(z, INT_MAX, ctrlMask); + const char *pcEnd = (pcDQBSRO && pcDQBSRO < pcPast)? pcDQBSRO : pcPast; + if( pcEnd > z ){ + sqlite3_str_appendf(out, "%.*s", (int)(pcEnd-z), z); + } + if( (c = *pcEnd)==0 ) break; + ++pcEnd; + switch( c ){ + case '\\': case '"': + cbsSay = (char)c; + break; + case '\t': cbsSay = 't'; break; + case '\n': cbsSay = 'n'; break; + case '\r': cbsSay = 'r'; break; + case '\f': cbsSay = 'f'; break; + default: cbsSay = 0; break; + } + if( cbsSay ){ + ace[1] = cbsSay; + sqlite3_str_appendall(out,ace); + }else if( !isprint(c&0xff) ){ + sqlite3_str_appendf(out, "\\%03o", c&0xff); + }else{ + ace[1] = (char)c; + sqlite3_str_appendall(out, ace+1); + } + z = pcEnd; + } + sqlite3_str_appendall(out, zq); +} + /* ** This routine runs when the user presses Ctrl-C */ @@ -7144,9 +7189,68 @@ static int pickStr(const char *zArg, char **pzErr, ...){ ** ** USAGE: .mode [MODE] [OPTIONS] ** -** Change the output mode to MODE. And/or apply OPTIONS to the -** output mode. If no arguments, show the current output mode and -** relevant options. +** Change the output mode to MODE and/or apply OPTIONS to the +** output mode. If no arguments, show the current output mode +** and relevant options. +** +** Options: +** +** --align STRING Set the alignment of text in columnar modes +** String consists of characters 'L', 'C', 'R' +** meaning "left", "centered", and "right", with +** one letter per column starting from the left. +** Unspecified alignment defaults to 'L'. +** +** --charlimit N Set the maximum number of output characters to +** show for any single SQL value to N. Longer values +** truncated. Zero means "no limit". +** +** --colsep STRING Use STRING as the column separator +** +** --escape ESC Enable/disable escaping of control characters +** in output. ESC can be "off", "ascii", or +** "symbol". +** +** --linelimit N Set the maximum number of output lines to show for +** any single SQL value to N. Longer values are +** truncated. Zero means "no limit". Only works +** in "line" mode and in columnar modes. +** +** --null STRING Render SQL NULL values as the given string +** +** --quote ARG Enable/disable quoting of text. ARG can be +** "off", "on", "sql", "csv", "html", "tcl", +** or "json". "off" means show the text as-is. +** "on and "sql" are synonyms. +** +** --reset Changes all mode settings back to their default. +** +** --rowsep STRING Use STRING as the row separator +** +** --screenwidth N Declare the screen width of the output device +** to be N characters. An attempt may be made to +** wrap output text to fit within this limit. Zero +** means "no limit". Or N can be "auto" to set the +** width automatically. +** +** --tablename NAME Set the name of the table for "insert" mode. +** +** --textjsonb BOOLEAN If enabled, JSONB text is displayed as text JSON. +** +** -v|--verbose Verbose output +** +** --widths LIST Set the columns widths for columnar modes. The +** argument is a list of integers, one for each +** column. A "0" width means use a dynamic width +** based on the actual width of data. If there are +** fewer entries in LIST than columns, "0" is used +** for the unspecified widths. +** +** --wordwrap BOOLEAN Enable/disable word wrapping +** +** --wrap N Wrap columns wider than N characters +** +** --ww Shorthand for "--wordwrap on" */ static int dotCmdMode(ShellState *p){ int nArg = p->dot.nArg; /* Number of arguments */ @@ -7156,24 +7260,100 @@ static int dotCmdMode(ShellState *p){ int i; /* Loop counter */ int k; /* Misc index variable */ int chng = 0; /* True if anything has changed */ + int bAll = 0; /* Show all details of the mode */ for(i=1; iQRF_MAX_WIDTH ) w = QRF_MAX_WIDTH; - p->mode.spec.nWrap = w; + if( z[0]=='-' && z[1]=='-' ) z++; + if( z[0]!='-' + && iMode<0 + && (eMode = modeFind(azArg[i]))>=0 + && eMode!=MODE_Www + ){ + iMode = i; + modeChange(&p->mode, eMode); + /* (Legacy) If the mode is MODE_Insert and the next argument + ** is not an option, then the next argument must be the table + ** name. + */ + if( i+1mode.spec.zTableName, azArg[i]); + } chng = 1; - }else if( optionMatch(z,"ww") ){ - p->mode.spec.bWordWrap = QRF_Yes; + }else if( optionMatch(z,"align") ){ + char *zAlign; + int nAlign; + int nErr = 0; + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + i++; + zAlign = azArg[i]; + nAlign = 0x3fff & strlen(zAlign); + free(p->mode.spec.aAlign); + p->mode.spec.aAlign = malloc(nAlign); + shell_check_oom(p->mode.spec.aAlign); + for(k=0; kmode.spec.aAlign[k] = c; + } + p->mode.spec.nAlign = nAlign; chng = 1; - }else if( optionMatch(z,"wordwrap") ){ + if( nErr ){ + dotCmdError(p, i, "bad alignment string", + "Should contain only characters L, C, and R."); + return 1; + } + }else if( 0<=(k=pickStr(z,0,"-charlimit","-linelimit","")) ){ + int w; /* 0 1 */ if( i+1>=nArg ){ dotCmdError(p, i, "missing argument", 0); return 1; } - p->mode.spec.bWordWrap = (u8)booleanValue(azArg[++i]) ? QRF_Yes : QRF_No; + w = integerValue(azArg[++i]); + if( k==0 ){ + p->mode.spec.nCharLimit = w; + }else{ + p->mode.spec.nLineLimit = 2; + } + chng = 1; + }else if( 0<=(k=pickStr(z,0,"-tablename","-rowsep","-colsep","-null","")) ){ + /* 0 1 2 3 */ + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + i++; + switch( k ){ + case 0: modeSetStr(&p->mode.spec.zTableName, azArg[i]); break; + case 1: modeSetStr(&p->mode.spec.zRowSep, azArg[i]); break; + case 2: modeSetStr(&p->mode.spec.zColumnSep, azArg[i]); break; + case 3: modeSetStr(&p->mode.spec.zNull, azArg[i]); break; + } + chng = 1; + }else if( optionMatch(z,"escape") ){ + /* See similar code at tag-20250224-1 */ + char *zErr = 0; + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + i++; /* 0 1 2 <-- One less than QRF_ESC_ */ + k = pickStr(azArg[i],&zErr,"off","ascii","symbol",""); + if( k<0 ){ + dotCmdError(p, i, "unknown escape type", "%s", zErr); + sqlite3_free(zErr); + return 1; + } + p->mode.spec.eEsc = k+1; chng = 1; }else if( optionMatch(z,"quote") ){ if( i+1mode.spec.eText = QRF_TEXT_Plain; p->mode.spec.eBlob = QRF_BLOB_Text; chng = 1; + }else if( optionMatch(z,"reset") ){ + int eMode = p->mode.eMode; + modeFree(&p->mode); + modeChange(&p->mode, eMode); }else if( optionMatch(z,"textjsonb") ){ if( i+1>=nArg ){ dotCmdError(p, i, "missing argument", 0); return 1; } - p->mode.spec.bTextJsonb = (u8)booleanValue(azArg[++i]) ? QRF_Yes : QRF_No; + p->mode.spec.bTextJsonb = booleanValue(azArg[++i]) ? QRF_Yes : QRF_No; chng = 1; - }else if( optionMatch(z,"escape") ){ - /* See similar code at tag-20250224-1 */ - char *zErr = 0; + }else if( optionMatch(z,"widths") ){ + int nWidth = 0; + short int *aWidth; + const char *z; if( i+1>=nArg ){ dotCmdError(p, i, "missing argument", 0); return 1; } - i++; /* 0 1 2 <-- One less than QRF_ESC_ */ - k = pickStr(azArg[i],&zErr,"off","ascii","symbol",""); - if( k<0 ){ - dotCmdError(p, i, "unknown escape type", "%s", zErr); - sqlite3_free(zErr); - return 1; - } - p->mode.spec.eEsc = k+1; + z = azArg[++i]; + /* Every width value takes at least 2 bytes in the input string to + ** specify, so strlen(z) bytes should be plenty of space to hold the + ** result. */ + aWidth = malloc( strlen(z) ); + while( isspace(z[0]) ) z++; + while( z[0] ){ + int w = 0; + k = z[0]=='-' && isdigit(z[1]); + while( isdigit(z[k]) ){ + w = w*10 + z[k] - '0'; + if( w>QRF_MAX_WIDTH ){ + dotCmdError(p,i+1,"width too big", + "Maximum column width is %d", QRF_MAX_WIDTH); + free(aWidth); + return 1; + } + k++; + } + if( z[0]=='-' ) w = -w; + aWidth[nWidth++] = w; + z += k; + if( z[0]==',' ) z++; + while( isspace(z[0]) ) z++; + } + free(p->mode.spec.aWidth); + p->mode.spec.aWidth = aWidth; + p->mode.spec.nWidth = nWidth; chng = 1; - }else if( 0<=(k=pickStr(z,0,"-tablename","-rowsep","-colsep","-null","")) ){ - /* 0 1 2 3 */ + }else if( optionMatch(z,"wrap") ){ + int w; if( i+1>=nArg ){ dotCmdError(p, i, "missing argument", 0); return 1; } - i++; - switch( k ){ - case 0: modeSetStr(&p->mode.spec.zTableName, azArg[i]); break; - case 1: modeSetStr(&p->mode.spec.zRowSep, azArg[i]); break; - case 2: modeSetStr(&p->mode.spec.zColumnSep, azArg[i]); break; - case 3: modeSetStr(&p->mode.spec.zNull, azArg[i]); break; - } + w = integerValue(azArg[++i]); + if( w<(-QRF_MAX_WIDTH) ) w = -QRF_MAX_WIDTH; + if( w>QRF_MAX_WIDTH ) w = QRF_MAX_WIDTH; + p->mode.spec.nWrap = w; chng = 1; - }else if( iMode<0 && (eMode = modeFind(azArg[i]))>=0 && eMode!=MODE_Www ){ - iMode = i; - modeChange(&p->mode, eMode); - /* (Legacy) If the mode is MODE_Insert and the next argument - ** is not an option, then the next argument must be the table - ** name. - */ - if( i+1mode.spec.zTableName, azArg[i]); - } + }else if( optionMatch(z,"ww") ){ + p->mode.spec.bWordWrap = QRF_Yes; chng = 1; - }else if( optionMatch(z,"error-prefix") ){ + }else if( optionMatch(z,"wordwrap") ){ if( i+1>=nArg ){ dotCmdError(p, i, "missing argument", 0); return 1; } - free(p->zErrPrefix); - p->zErrPrefix = strdup(azArg[++i]); + p->mode.spec.bWordWrap = (u8)booleanValue(azArg[++i]) ? QRF_Yes : QRF_No; chng = 1; + }else if( optionMatch(z,"v") || optionMatch(z,"verbose") ){ + bAll = 1; }else if( z[0]=='-' ){ dotCmdError(p, i, "bad option", "Use \".help .mode\" for more info"); return 1; @@ -7298,22 +7492,87 @@ static int dotCmdMode(ShellState *p){ return 1; } } - if( !chng ){ + if( !chng || bAll ){ const ModeInfo *pI = aModeInfo + p->mode.eMode; sqlite3_str *pDesc = sqlite3_str_new(p->db); char *zDesc; + const char *zSetting; sqlite3_str_appendall(pDesc,pI->zName); - if( pI->eCx>0 && p->mode.spec.bWordWrap ){ - if( p->mode.spec.nWrap ){ - sqlite3_str_appendf(pDesc, " --wrap %d", p->mode.spec.nWrap); + if( bAll || (p->mode.spec.nAlign && pI->eCx==2) ){ + int i; + sqlite3_str_appendall(pDesc, " --align \""); + for(i=0; imode.spec.nAlign; i++){ + unsigned char a = p->mode.spec.aAlign[i]; + sqlite3_str_append(pDesc, "LLCR"+(a&3), 1); } - sqlite3_str_append(pDesc, " --ww", 5); + sqlite3_str_append(pDesc, "\"", 1); + } + if( bAll || p->mode.spec.nCharLimit>0 ){ + sqlite3_str_appendf(pDesc, " --charlimit %d",p->mode.spec.nCharLimit); + } + zSetting = aModeStr[pI->eCSep]; + if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zColumnSep)!=0) ){ + sqlite3_str_appendf(pDesc, " --colsep "); + append_c_string(pDesc, p->mode.spec.zColumnSep); + } + if( bAll || p->mode.spec.eEsc!=QRF_Auto ){ + sqlite3_str_appendf(pDesc, " --escape %s",qrfEscNames[p->mode.spec.eEsc]); + } + if( bAll || (p->mode.spec.nLineLimit>0 && pI->eCx>0) ){ + sqlite3_str_appendf(pDesc, " --linelimit %d",p->mode.spec.nLineLimit); } - if( pI->eText!=p->mode.spec.eText ){ + zSetting = aModeStr[pI->eNull]; + if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zNull)!=0) ){ + sqlite3_str_appendf(pDesc, " --null "); + append_c_string(pDesc, p->mode.spec.zNull); + } + if( bAll + || (pI->eText!=p->mode.spec.eText && (pI->eText>1 || p->mode.spec.eText>1)) + ){ sqlite3_str_appendf(pDesc," --quote %s",qrfQuoteNames[p->mode.spec.eText]); } - if( p->mode.spec.eEsc!=QRF_Auto ){ - sqlite3_str_appendf(pDesc, " --escape %s", qrfEscNames[p->mode.spec.eEsc]); + zSetting = aModeStr[pI->eRSep]; + if( bAll || (zSetting && cli_strcmp(zSetting,p->mode.spec.zRowSep)!=0) ){ + sqlite3_str_appendf(pDesc, " --rowsep "); + append_c_string(pDesc, p->mode.spec.zRowSep); + } + if( bAll + || (pI->eCx && (p->mode.spec.nLineLimit>0 || p->mode.bAutoScreenWidth)) + ){ + if( p->mode.bAutoScreenWidth ){ + sqlite3_str_appendall(pDesc, " --screenwidth auto"); + }else{ + sqlite3_str_appendf(pDesc," --screenwidth %d", + p->mode.spec.nScreenWidth); + } + } + if( bAll || p->mode.eMode==MODE_Insert ){ + sqlite3_str_appendf(pDesc," --tablename "); + append_c_string(pDesc, p->mode.spec.zTableName); + } + if( bAll || p->mode.spec.bTextJsonb ){ + sqlite3_str_appendf(pDesc," --textjsonb %s", + p->mode.spec.bTextJsonb==QRF_Yes ? "on" : "off"); + } + if( p->mode.spec.nWidth>0 && (bAll || pI->eCx==2) ){ + int i; + const char *zSep = " --widths "; + for(i=0; imode.spec.nWidth; i++){ + sqlite3_str_appendf(pDesc, "%s%d", zSep, (int)p->mode.spec.aWidth[i]); + zSep = ","; + } + }else if( bAll ){ + sqlite3_str_appendall(pDesc, " --widths \"\""); + } + if( bAll || (pI->eCx>0 && p->mode.spec.bWordWrap) ){ + if( bAll ){ + sqlite3_str_appendf(pDesc, " --wordwrap %s", + p->mode.spec.bWordWrap==QRF_Yes ? "on" : "off"); + } + if( p->mode.spec.nWrap ){ + sqlite3_str_appendf(pDesc, " --wrap %d", p->mode.spec.nWrap); + } + if( !bAll ) sqlite3_str_append(pDesc, " --ww", 5); } zDesc = sqlite3_str_finish(pDesc); cli_printf(p->out, "current output mode: %s\n", zDesc); @@ -7340,6 +7599,9 @@ static int dotCmdMode(ShellState *p){ ** -e Accumulate output in a temporary text file then ** launch a text editor when the redirection ends. ** +** --error-prefix X Use X as the left-margin prefix for error messages. +** Set to an empty string to restore the default. +** ** --glob GLOB Raise an error if the memory buffer does not match ** the GLOB pattern. ** @@ -7463,6 +7725,14 @@ static int dotCmdOutput(ShellState *p){ } zCheck = azArg[++i]; eCheck = z[1]=='g' ? 1 : z[1]=='n' ? 2 : 3; + }else if( optionMatch(z,"error-prefix") ){ + if( i+1>=nArg ){ + dotCmdError(p, i, "missing argument", 0); + return 1; + } + free(p->zErrPrefix); + i++; + p->zErrPrefix = azArg[i][0]==0 ? 0 : strdup(azArg[i]); }else{ dotCmdError(p, i, "unknown option", 0); sqlite3_free(zFile); @@ -7517,6 +7787,7 @@ static int dotCmdOutput(ShellState *p){ int nCheck = strlen30(zCheck); sqlite3_str *pPattern = sqlite3_str_new(p->db); char *zPattern; + sqlite3_int64 iStart = p->lineno; char zLine[2000]; while( sqlite3_fgets(zLine,sizeof(zLine),p->in) ){ if( strchr(zLine,'\n') ) p->lineno++; @@ -7527,7 +7798,7 @@ static int dotCmdOutput(ShellState *p){ if( cli_strcmp(zPattern,zTest)!=0 ){ sqlite3_fprintf(stderr, "%s:%lld: --verify does matches prior output\n", - p->zInFile, p->lineno); + p->zInFile, iStart); p->nTestErr++; } sqlite3_free(zPattern); @@ -11870,6 +12141,7 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ int nOptsEnd = argc; int bEnableVfstrace = 0; char **azCmd = 0; + int *aiCmd = 0; const char *zVfs = 0; /* Value of -vfs command-line option */ #if !SQLITE_SHELL_IS_UTF8 char **argvToFree = 0; @@ -11995,7 +12267,10 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ nCmd++; azCmd = realloc(azCmd, sizeof(azCmd[0])*nCmd); shell_check_oom(azCmd); + aiCmd = realloc(aiCmd, sizeof(aiCmd[0])*nCmd); + shell_check_oom(azCmd); azCmd[nCmd-1] = z; + aiCmd[nCmd-1] = i; } continue; } @@ -12216,28 +12491,25 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){ if( cli_strcmp(z,"-init")==0 ){ i++; }else if( cli_strcmp(z,"-html")==0 ){ - data.mode.eMode = MODE_Html; + modeChange(&data.mode, MODE_Html); }else if( cli_strcmp(z,"-list")==0 ){ - data.mode.eMode = MODE_List; + modeChange(&data.mode, MODE_List); }else if( cli_strcmp(z,"-quote")==0 ){ - data.mode.eMode = MODE_Quote; - modeSetStr(&data.mode.spec.zColumnSep, SEP_Comma); - modeSetStr(&data.mode.spec.zRowSep, SEP_Row); + modeChange(&data.mode, MODE_Quote); }else if( cli_strcmp(z,"-line")==0 ){ - data.mode.eMode = MODE_Line; + modeChange(&data.mode, MODE_Line); }else if( cli_strcmp(z,"-column")==0 ){ - data.mode.eMode = MODE_Column; + modeChange(&data.mode, MODE_Column); }else if( cli_strcmp(z,"-json")==0 ){ - data.mode.eMode = MODE_Json; + modeChange(&data.mode, MODE_Json); }else if( cli_strcmp(z,"-markdown")==0 ){ - data.mode.eMode = MODE_Markdown; + modeChange(&data.mode, MODE_Markdown); }else if( cli_strcmp(z,"-table")==0 ){ - data.mode.eMode = MODE_Table; + modeChange(&data.mode, MODE_Table); }else if( cli_strcmp(z,"-box")==0 ){ - data.mode.eMode = MODE_Box; + modeChange(&data.mode, MODE_Box); }else if( cli_strcmp(z,"-csv")==0 ){ - data.mode.eMode = MODE_Csv; - modeSetStr(&data.mode.spec.zColumnSep, SEP_Comma); + modeChange(&data.mode, MODE_Csv); }else if( cli_strcmp(z,"-escape")==0 && i+1" 2>@stdout} res set ntest 0 - set nerr 0 - regexp {(\d+) tests run with (\d+) errors} $res all ntest nerr + set nerr 999 + regexp {.*(\d+) tests? run with (\d+) errors?} $res all ntest nerr set_test_counter count [expr {[set_test_counter count]+$ntest-1}] set_test_counter errors [expr {[set_test_counter errors]+$nerr}] - set answer "$nerr errors" + set answer "error count: $nerr" }] # puts $script - do_test shellB-$name $script {0 errors} + do_test shellB-$name $script {error count: 0} } do_clitest modeA.clitest -- 2.47.3