]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Add support for "box" mode in the CLI: Like "table" except that it uses
authordrh <drh@noemail.net>
Thu, 4 Jun 2020 18:05:39 +0000 (18:05 +0000)
committerdrh <drh@noemail.net>
Thu, 4 Jun 2020 18:05:39 +0000 (18:05 +0000)
unicode box-drawing characters instead of ascii-art.

FossilOrigin-Name: 6da784c9e174744d6deeb76c553b515b96c1fcb80c55a281e476959ec680fb72

manifest
manifest.uuid
src/shell.c.in
test/shell1.test

index d3d3f40fed2028a5f4f19ad612e3ff04667d5cb4..e3723f551c1bf1e112e4a1041b2d9ea3ccc452c9 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Improved\sdisplay\sof\s".mode\stable"\soutput\sfor\sempty\sresult\ssets.
-D 2020-06-04T16:54:10.263
+C Add\ssupport\sfor\s"box"\smode\sin\sthe\sCLI:\s\sLike\s"table"\sexcept\sthat\sit\suses\nunicode\sbox-drawing\scharacters\sinstead\sof\sascii-art.
+D 2020-06-04T18:05:39.066
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -534,7 +534,7 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
 F src/resolve.c c2008519a0654f1e7490e9281ed0397d0f14bb840d81f0b96946248afcbdb25d
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
 F src/select.c 39a00a8bc89596dfb37c16afcbb1d33de5085b9963564b58aafe1566d08c0881
-F src/shell.c.in b8fd54e80021c9aed59c4f8ef7d8a68167aea3b2c5f8cc80e6ca373fb146cdba
+F src/shell.c.in 6f7ea57d3f15e7e6a1f7049b6b7e39589dd5fe114e8de034ae9a68bf7722fd40
 F src/sqlite.h.in 74342b41e9d68ff9e56b192009046f8dd0aa2bd76ce1a588f330de614ba61de7
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h 2d1af80082edffd71c6f96f70ad1ce6a4fb46615ad10291fc77fe0dea9ff0197
@@ -1338,7 +1338,7 @@ F test/sharedA.test 49d87ec54ab640fbbc3786ee3c01de94aaa482a3a9f834ad3fe92770eb69
 F test/sharedB.test 16cc7178e20965d75278f410943109b77b2e645e
 F test/shared_err.test 32634e404a3317eeb94abc7a099c556a346fdb8fb3858dbe222a4cbb8926a939
 F test/sharedlock.test 5ede3c37439067c43b0198f580fd374ebf15d304
-F test/shell1.test 1c4713ccec468f9300100d5e1419b414b8dcccc742978ad8942e8bd31d2adc9c
+F test/shell1.test fabf21eea2c6bb04dd86dfc7441c7c14841b25e2540c1fffeae815e718625bcc
 F test/shell2.test e242a9912f44f4c23c3d1d802a83e934e84c853b
 F test/shell3.test ac8c2b744014c3e9a0e26bfd829ab65f00923dc1a91ffd044863e9423cc91494
 F test/shell4.test 1c6aef11daaa2d6830acaba3ac9cbec93fbc1c3d5530743a637f39b3987d08ce
@@ -1866,7 +1866,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 362255791f8801e0d9869e36239b8b2cb29c38bf0b86894bd2d159ce46d8447e
-R e25e15e7d406e657ad2786c9bbcb9942
+P 7efabd683b79743b407ad71dda56db00fb0d668828bdc342145816b4f1c3bf3a
+R 3dac6f750121e41150b03b7c089ed222
 U drh
-Z acf5bed5cb288b39151d44a020264134
+Z e00f940b0330d0b11d881b13cbeefb56
index ba2429e47fc2e5d8d227d6e720a0e8fe30f94991..93e7eee6752fa2e23827ec19e7ed5c19b479fd52 100644 (file)
@@ -1 +1 @@
-7efabd683b79743b407ad71dda56db00fb0d668828bdc342145816b4f1c3bf3a
\ No newline at end of file
+6da784c9e174744d6deeb76c553b515b96c1fcb80c55a281e476959ec680fb72
\ No newline at end of file
index bd78165e6bbc655210f653303c2bbffe1f81127b..e46188e4ecffe83e49d81c0817497cacd9262c05 100644 (file)
@@ -1194,6 +1194,7 @@ struct ShellState {
 #define MODE_Json    13  /* Output JSON */
 #define MODE_Markdown 14 /* Markdown formatting */
 #define MODE_Table   15  /* MySQL-style table formatting */
+#define MODE_Box     16  /* Unicode box-drawing characters */
 
 static const char *modeDescr[] = {
   "line",
@@ -1211,7 +1212,8 @@ static const char *modeDescr[] = {
   "eqp",
   "json",
   "markdown",
-  "table"
+  "table",
+  "box"
 };
 
 /*
@@ -1936,7 +1938,7 @@ static void print_dashes(FILE *out, int N){
 }
 
 /*
-** Print a markdown or table-style row separator
+** Print a markdown or table-style row separator using ascii-art
 */
 static void print_row_separator(
   ShellState *p,
@@ -1944,11 +1946,15 @@ static void print_row_separator(
   const char *zSep
 ){
   int i;
-  for(i=0; i<nArg; i++){
+  if( nArg>0 ){
+    fputs(zSep, p->out);
+    print_dashes(p->out, p->actualWidth[0]+2);
+    for(i=1; i<nArg; i++){
+      fputs(zSep, p->out);
+      print_dashes(p->out, p->actualWidth[i]+2);
+    }
     fputs(zSep, p->out);
-    print_dashes(p->out, p->actualWidth[i]+2);
   }
-  fputs(zSep, p->out);
   fputs("\n", p->out);
 }
 
@@ -2947,9 +2953,76 @@ static void bind_prepared_stmt(ShellState *pArg, sqlite3_stmt *pStmt){
   sqlite3_finalize(pQ);
 }
 
+/*
+** UTF8 box-drawing characters.  Imagine box lines like this:
+**
+**           1
+**           |
+**       4 --+-- 2
+**           |
+**           3
+**
+** Each box characters has between 2 and 4 of the lines leading from
+** the center.  The characters are here identified by the numbers of
+** their corresponding lines.
+*/
+#define BOX_24   "\342\224\200"  /* U+2500 --- */
+#define BOX_13   "\342\224\202"  /* U+2502  |  */
+#define BOX_23   "\342\224\214"  /* U+250c  ,- */
+#define BOX_34   "\342\224\220"  /* U+2510 -,  */
+#define BOX_12   "\342\224\224"  /* U+2514  '- */
+#define BOX_14   "\342\224\230"  /* U+2518 -'  */
+#define BOX_123  "\342\224\234"  /* U+251c  |- */
+#define BOX_134  "\342\224\244"  /* U+2524 -|  */
+#define BOX_234  "\342\224\254"  /* U+252c -,- */
+#define BOX_124  "\342\224\264"  /* U+2534 -'- */
+#define BOX_1234 "\342\224\274"  /* U+253c -|- */
+
+/* Draw horizontal line N characters long using unicode box
+** characters
+*/
+static void print_box_line(FILE *out, int N){
+  const char zDash[] = 
+      BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24
+      BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24;
+  const int nDash = sizeof(zDash) - 1;
+  N *= 3;
+  while( N>nDash ){
+    utf8_printf(out, zDash);
+    N -= nDash;
+  }
+  utf8_printf(out, "%.*s", N, zDash);
+}
+
+/*
+** Draw a horizontal separator for a MODE_Box table.
+*/
+static void print_box_row_separator(
+  ShellState *p,
+  int nArg,
+  const char *zSep1,
+  const char *zSep2,
+  const char *zSep3
+){
+  int i;
+  if( nArg>0 ){
+    utf8_printf(p->out, "%s", zSep1);
+    print_box_line(p->out, p->actualWidth[0]+2);
+    for(i=1; i<nArg; i++){
+      utf8_printf(p->out, "%s", zSep2);
+      print_box_line(p->out, p->actualWidth[i]+2);
+    }
+    utf8_printf(p->out, "%s", zSep3);
+  }
+  fputs("\n", p->out);
+}
+
+
+
 /*
 ** Run a prepared statement and output the result in one of the
-** table-oriented formats: MODE_Column, MODE_Markdown, or MODE_Table.
+** table-oriented formats: MODE_Column, MODE_Markdown, MODE_Table,
+** or MODE_Box.
 **
 ** This is different from ordinary exec_prepared_stmt() in that
 ** it has to run the entire query and gather the results into memory
@@ -2967,8 +3040,8 @@ static void exec_prepared_stmt_columnar(
   const char *z;
   int rc;
   int i, j, nTotal, w, n;
-  const char *colSep;
-  const char *rowSep;
+  const char *colSep = 0;
+  const char *rowSep = 0;
 
   rc = sqlite3_get_table(p->db, sqlite3_sql(pStmt),
                          &azData, &nRow, &nColumn, &zMsg);
@@ -3003,50 +3076,87 @@ static void exec_prepared_stmt_columnar(
     j = i%nColumn;
     if( n>p->actualWidth[j] ) p->actualWidth[j] = n;
   }
-  if( p->cMode==MODE_Column ){
-    colSep = "  ";
-    rowSep = "\n";
-    if( p->showHeader ){
+  switch( p->cMode ){
+    case MODE_Column: {
+      colSep = "  ";
+      rowSep = "\n";
+      if( p->showHeader ){
+        for(i=0; i<nColumn; i++){
+          w = p->actualWidth[i];
+          if( p->colWidth[i]<0 ) w = -w;
+          utf8_width_print(p->out, w, azData[i]);
+          fputs(i==nColumn-1?"\n":"  ", p->out);
+        }
+        for(i=0; i<nColumn; i++){
+          print_dashes(p->out, p->actualWidth[i]);
+          fputs(i==nColumn-1?"\n":"  ", p->out);
+        }
+      }
+      break;
+    }
+    case MODE_Table: {
+      colSep = " | ";
+      rowSep = " |\n";
+      print_row_separator(p, nColumn, "+");
+      fputs("| ", p->out);
       for(i=0; i<nColumn; i++){
         w = p->actualWidth[i];
-        if( p->colWidth[i]<0 ) w = -w;
-        utf8_width_print(p->out, w, azData[i]);
-        fputs(i==nColumn-1?"\n":"  ", p->out);
+        n = strlenChar(azData[i]);
+        utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, "");
+        fputs(i==nColumn-1?" |\n":" | ", p->out);
       }
+      print_row_separator(p, nColumn, "+");
+      break;
+    }
+    case MODE_Markdown: {
+      colSep = " | ";
+      rowSep = " |\n";
+      fputs("| ", p->out);
       for(i=0; i<nColumn; i++){
-        print_dashes(p->out, p->actualWidth[i]);
-        fputs(i==nColumn-1?"\n":"  ", p->out);
+        w = p->actualWidth[i];
+        n = strlenChar(azData[i]);
+        utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, "");
+        fputs(i==nColumn-1?" |\n":" | ", p->out);
       }
+      print_row_separator(p, nColumn, "|");
+      break;
+    }
+    case MODE_Box: {
+      colSep = " " BOX_13 " ";
+      rowSep = " " BOX_13 "\n";
+      print_box_row_separator(p, nColumn, BOX_23, BOX_234, BOX_34);
+      utf8_printf(p->out, BOX_13 " ");
+      for(i=0; i<nColumn; i++){
+        w = p->actualWidth[i];
+        n = strlenChar(azData[i]);
+        utf8_printf(p->out, "%*s%s%*s%s",
+            (w-n)/2, "", azData[i], (w-n+1)/2, "",
+            i==nColumn-1?" "BOX_13"\n":" "BOX_13" ");
+      }
+      print_box_row_separator(p, nColumn, BOX_123, BOX_1234, BOX_134);
+      break;
     }
-  }else{
-    colSep = " | ";
-    rowSep = " |\n";
-    if( p->cMode==MODE_Table ) print_row_separator(p, nColumn, "+");
-    fputs("| ", p->out);
-    for(i=0; i<nColumn; i++){
-      w = p->actualWidth[i];
-      n = strlenChar(azData[i]);
-      utf8_printf(p->out, "%*s%s%*s", (w-n)/2, "", azData[i], (w-n+1)/2, "");
-      fputs(i==nColumn-1?" |\n":" | ", p->out);
-    }
-    print_row_separator(p, nColumn, p->cMode==MODE_Table ? "+" : "|");
   }
   for(i=nColumn, j=0; i<nTotal; i++, j++){
-    if( j==0 && p->cMode!=MODE_Column ) fputs("| ", p->out);
+    if( j==0 && p->cMode!=MODE_Column ){
+      utf8_printf(p->out, "%s", p->cMode==MODE_Box?BOX_13" ":"| ");
+    }
     z = azData[i];
     if( z==0 ) z = p->nullValue;
     w = p->actualWidth[j];
     if( p->colWidth[j]<0 ) w = -w;
     utf8_width_print(p->out, w, z);
     if( j==nColumn-1 ){
-      fputs(rowSep, p->out);
+      utf8_printf(p->out, "%s", rowSep);
       j = -1;
     }else{
-      fputs(colSep, p->out);
+      utf8_printf(p->out, "%s", colSep);
     }
   }
   if( p->cMode==MODE_Table ){
     print_row_separator(p, nColumn, "+");
+  }else if( p->cMode==MODE_Box ){
+    print_box_row_separator(p, nColumn, BOX_12, BOX_124, BOX_14);
   }
   sqlite3_free_table(azData);
 }
@@ -3062,6 +3172,7 @@ static void exec_prepared_stmt(
 
   if( pArg->cMode==MODE_Column
    || pArg->cMode==MODE_Table
+   || pArg->cMode==MODE_Box
    || pArg->cMode==MODE_Markdown
   ){
     exec_prepared_stmt_columnar(pArg, pStmt);
@@ -3115,9 +3226,7 @@ static void exec_prepared_stmt(
         }
       } while( SQLITE_ROW == rc );
       sqlite3_free(pData);
-      if( pArg->cMode==MODE_Table ){
-        print_row_separator(pArg, nCol, "+");
-      }else if( pArg->cMode==MODE_Json ){
+      if( pArg->cMode==MODE_Json ){
         fputs("]\n", pArg->out);
       }
     }
@@ -3801,6 +3910,7 @@ static const char *(azHelp[]) = {
   ".mode MODE ?TABLE?       Set output mode",
   "   MODE is one of:",
   "     ascii     Columns/rows delimited by 0x1F and 0x1E",
+  "     box       Tables using unicode box-drawing characters",
   "     csv       Comma-separated values",
   "     column    Output in columns.  (See .width)",
   "     html      HTML <table> code",
@@ -8414,13 +8524,15 @@ static int do_meta_command(char *zLine, ShellState *p){
       p->mode = MODE_Markdown;
     }else if( c2=='t' && strncmp(azArg[1],"table",n2)==0 ){
       p->mode = MODE_Table;
+    }else if( c2=='b' && strncmp(azArg[1],"box",n2)==0 ){
+      p->mode = MODE_Box;
     }else if( c2=='j' && strncmp(azArg[1],"json",n2)==0 ){
       p->mode = MODE_Json;
     }else if( nArg==1 ){
       raw_printf(p->out, "current output mode: %s\n", modeDescr[p->mode]);
     }else{
       raw_printf(stderr, "Error: mode should be one of: "
-         "ascii column csv html insert json line list markdown "
+         "ascii box column csv html insert json line list markdown "
          "quote table tabs tcl\n");
       rc = 1;
     }
@@ -10420,6 +10532,7 @@ static const char zOptions[] =
   "   -ascii               set output mode to 'ascii'\n"
   "   -bail                stop after hitting an error\n"
   "   -batch               force batch I/O\n"
+  "   -box                 set output mode to 'box'\n"
   "   -column              set output mode to 'column'\n"
   "   -cmd COMMAND         run \"COMMAND\" before reading stdin\n"
   "   -csv                 set output mode to 'csv'\n"
@@ -10867,6 +10980,8 @@ int SQLITE_CDECL wmain(int argc, wchar_t **wargv){
       data.mode = MODE_Markdown;
     }else if( strcmp(z,"-table")==0 ){
       data.mode = MODE_Table;
+    }else if( strcmp(z,"-box")==0 ){
+      data.mode = MODE_Box;
     }else if( strcmp(z,"-csv")==0 ){
       data.mode = MODE_Csv;
       memcpy(data.colSeparator,",",2);
index a900dc3e1db17b1078c2107073d6bc1d9e436baa..b08e1118043ac9c85c9db47ba019cb06944a8999 100644 (file)
@@ -199,10 +199,10 @@ do_test shell1-2.2.4 {
 } {0 {}}
 do_test shell1-2.2.5 {
   catchcmd "test.db" ".mode \"insert FOO"
-} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
+} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
 do_test shell1-2.2.6 {
   catchcmd "test.db" ".mode \'insert FOO"
-} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
+} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
 
 # check multiple tokens, and quoted tokens
 do_test shell1-2.3.1 {
@@ -230,7 +230,7 @@ do_test shell1-2.3.7 {
 # check quoted args are unquoted
 do_test shell1-2.4.1 {
   catchcmd "test.db" ".mode FOO"
-} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
+} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
 do_test shell1-2.4.2 {
   catchcmd "test.db" ".mode csv"
 } {0 {}}
@@ -430,7 +430,7 @@ do_test shell1-3.13.1 {
 } {0 {current output mode: list}}
 do_test shell1-3.13.2 {
   catchcmd "test.db" ".mode FOO"
-} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
+} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
 do_test shell1-3.13.3 {
   catchcmd "test.db" ".mode csv"
 } {0 {}}
@@ -463,10 +463,10 @@ do_test shell1-3.13.11 {
 # don't allow partial mode type matches
 do_test shell1-3.13.12 {
   catchcmd "test.db" ".mode l"
-} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
+} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
 do_test shell1-3.13.13 {
   catchcmd "test.db" ".mode li"
-} {1 {Error: mode should be one of: ascii column csv html insert json line list markdown quote table tabs tcl}}
+} {1 {Error: mode should be one of: ascii box column csv html insert json line list markdown quote table tabs tcl}}
 do_test shell1-3.13.14 {
   catchcmd "test.db" ".mode lin"
 } {0 {}}