From 557297ae771aeffb3dbc2ce2e2cdb69f13304003 Mon Sep 17 00:00:00 2001 From: larrybr Date: Sun, 5 Nov 2023 01:21:14 +0000 Subject: [PATCH] Setup, takedown, mode set and output working. No input yet. (WIP) FossilOrigin-Name: dfea85be1fb927ea446c9d98bae42ba1197bdab098aa6d95aa512a37d07a1e52 --- manifest | 16 ++- manifest.uuid | 2 +- src/console_io.c | 258 +++++++++++++++++++++++++++++++++++++++++++++++ src/console_io.h | 59 +++++++---- 4 files changed, 307 insertions(+), 28 deletions(-) create mode 100755 src/console_io.c diff --git a/manifest b/manifest index ea6e754aed..914526c556 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Define\sinterface\sbetween\sproject\scommand-line\sapps\sand\sa\sconsole\sI/O\s"library". -D 2023-11-04T02:22:04.015 +C Setup,\stakedown,\smode\sset\sand\soutput\sworking.\sNo\sinput\syet.\s(WIP) +D 2023-11-05T01:21:14.743 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -669,7 +669,8 @@ F src/btreeInt.h ef12a72b708677e48d6bc8dcd66fed25434740568b89e2cfa368093cfc5b9d1 F src/build.c 189e4517d67f09f0a3e0d8e1faa6e2ef0c2e95f6ac82e33c912cb7efa2a359cc F src/callback.c db3a45e376deff6a16c0058163fe0ae2b73a2945f3f408ca32cf74960b28d490 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e -F src/console_io.h 14057195f16cace1f669f723510190f90300ee6c1ef0a6f417a54344bc57bdd0 +F src/console_io.c 484a4ad56c18ddc62b845494f8ac6e06df9f9b0bc2fa60ac2dcba74635bdf8da x +F src/console_io.h 6b7c86a5778445e507a089b9808803e4523e2989f33c04692e008eae3f0f4d8a F src/ctime.c 23331529e654be40ca97d171cbbffe9b3d4c71cc53b78fe5501230675952da8b F src/date.c eebc54a00e888d3c56147779e9f361b77d62fd69ff2008c5373946aa1ba1d574 F src/dbpage.c 80e46e1df623ec40486da7a5086cb723b0275a6e2a7b01d9f9b5da0f04ba2782 @@ -2143,11 +2144,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P 570635575cc5fbffe910ed992b58393e214117ef3b5370a66f115cd0ee202913 -R 44e2680e2cc755f86742bfd0211316ba -T *branch * console-io-lib -T *sym-console-io-lib * -T -sym-trunk * +P 64abef8314b8544fdc7b71317d61a4641dc607a1ae42b8ff21543226fd338ba2 +R 9aec452b903f28bc0cbf638b7c11370e U larrybr -Z e3b2358cc83000c61d02936c7cb34f1b +Z 37ecbe5d111f6f842b9b1e97c1e0e0d1 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 66b2569455..5e27b835d6 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -64abef8314b8544fdc7b71317d61a4641dc607a1ae42b8ff21543226fd338ba2 \ No newline at end of file +dfea85be1fb927ea446c9d98bae42ba1197bdab098aa6d95aa512a37d07a1e52 \ No newline at end of file diff --git a/src/console_io.c b/src/console_io.c new file mode 100755 index 0000000000..8c4db84396 --- /dev/null +++ b/src/console_io.c @@ -0,0 +1,258 @@ +/* +** 2023 November 4 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file implements various interfaces used for console I/O by the +** SQLite project command-line tools, as explained in console_io.h . +*/ + +#ifndef SQLITE_CDECL +# define SQLITE_CDECL +#endif + +#include +#include +#include "console_io.h" +#include "sqlite3.h" + +#if defined(_WIN32) || defined(WIN32) +# include +# include +# ifdef SHELL_LEGACY_CONSOLE_IO +# define SHELL_CON_TRANSLATE 2 /* Use UTF-8/MBCS translation for console I/O */ +# else +# define SHELL_CON_TRANSLATE 1 /* Use wchar APIs for console I/O */ +# endif +#else +# include +# define SHELL_CON_TRANSLATE 0 /* Use plain C library stream I/O at console */ +#endif + +#if SHELL_CON_TRANSLATE +static HANDLE handleOfFile(FILE *pf){ + int fileDesc = _fileno(pf); + union { intptr_t osfh; HANDLE fh; } fid = { + (fileDesc!=-2)? _get_osfhandle(fileDesc) : (intptr_t)INVALID_HANDLE_VALUE + }; + return fid.fh; +} +#endif + +static short fileOfConsole(FILE *pf){ +#if SHELL_CON_TRANSLATE + DWORD dwj; + HANDLE fh = handleOfFile(pf); + if( INVALID_HANDLE_VALUE != fh ){ + return (GetFileType(fh) == FILE_TYPE_CHAR && GetConsoleMode(fh,&dwj)); + }else return 0; +#else + return (short)isatty(fileno(pf)); +#endif +} + +#define SHELL_INVALID_FILE_PTR ((FILE *)sizeof(FILE*)) + +typedef struct ConsoleInfo { + /* int iDefaultFmode; */ + ConsoleStdConsStreams cscs; +#if SHELL_CON_TRANSLATE + HANDLE hIn; HANDLE hOut; HANDLE hErr; + HANDLE hLowest; +#endif + FILE *pfIn; FILE *pfOut; FILE *pfErr; +} ConsoleInfo; + +static ConsoleInfo consoleInfo = { + /* 0, iDefaultFmode */ + CSCS_NoConsole, +#if SHELL_CON_TRANSLATE + INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE, + INVALID_HANDLE_VALUE, +#endif + SHELL_INVALID_FILE_PTR, SHELL_INVALID_FILE_PTR, SHELL_INVALID_FILE_PTR +}; +#undef SHELL_INVALID_FILE_PTR + +#if SHELL_CON_TRANSLATE == 1 +# define SHELL_CON_MODE_CSZ _O_U16TEXT +#elif SHELL_CON_TRANSLATE == 2 +# define SHELL_CON_MODE_CSZ _O_U8TEXT +#endif + +INT_LINKAGE ConsoleStdConsStreams +consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ){ + ConsoleStdConsStreams rv = CSCS_NoConsole; + if( fileOfConsole(pfErr) ){ + rv |= CSCS_ErrConsole; + consoleInfo.pfErr = pfErr; +#if SHELL_CON_TRANSLATE + fflush(pfErr); +# if SHELL_CON_TRANSLATE == 1 + _setmode(_fileno(pfErr), _O_U16TEXT); + _setmode(_fileno(pfErr), _O_BINARY); +# elif SHELL_CON_TRANSLATE == 2 + _setmode(_fileno(pfErr), _O_U8TEXT); + _setmode(_fileno(pfErr), _O_TEXT); +# endif + consoleInfo.hLowest = consoleInfo.hErr = handleOfFile(pfErr); +#endif + } + if( fileOfConsole(pfOut) ){ + rv |= CSCS_OutConsole; + consoleInfo.pfOut = pfOut; +#if SHELL_CON_TRANSLATE + fflush(pfOut); +# if SHELL_CON_TRANSLATE == 1 + _setmode(_fileno(pfOut), _O_U16TEXT); + _setmode(_fileno(pfOut), _O_BINARY); +# elif SHELL_CON_TRANSLATE == 2 + _setmode(_fileno(pfOut), _O_U8TEXT); + _setmode(_fileno(pfOut), _O_TEXT); +# endif + consoleInfo.hLowest = consoleInfo.hOut = handleOfFile(pfOut); +#endif + } + if( fileOfConsole(pfIn) ){ + rv |= CSCS_InConsole; + consoleInfo.pfIn = pfIn; +#if SHELL_CON_TRANSLATE == 1 + _setmode(_fileno(pfIn), _O_U16TEXT); + _setmode(_fileno(pfIn), _O_BINARY); + consoleInfo.hLowest = consoleInfo.hIn = handleOfFile(pfIn); +#elif SHELL_CON_TRANSLATE == 2 + _setmode(_fileno(pfIn), _O_U8TEXT); + _setmode(_fileno(pfIn), _O_TEXT); + consoleInfo.hLowest = consoleInfo.hIn = handleOfFile(pfIn); +#endif + } + consoleInfo.cscs = rv; + return rv; +} + +INT_LINKAGE void SQLITE_CDECL consoleRestore( void ){ +#if SHELL_CON_TRANSLATE + if( consoleInfo.cscs ){ + /* ToDo: Read these modes in consoleClassifySetup somehow. + ** A _get_fmode() call almost works. But not with gcc, yet. + ** This has to be done to make the CLI a callable function. + */ + int tmode = _O_TEXT, xmode = _O_U8TEXT; + if( consoleInfo.cscs & CSCS_InConsole ){ + _setmode(_fileno(consoleInfo.pfIn), tmode); + _setmode(_fileno(consoleInfo.pfIn), xmode); + } + if( consoleInfo.cscs & CSCS_OutConsole ){ + _setmode(_fileno(consoleInfo.pfOut), tmode); + _setmode(_fileno(consoleInfo.pfOut), xmode); + } + if( consoleInfo.cscs & CSCS_ErrConsole ){ + _setmode(_fileno(consoleInfo.pfErr), tmode); + _setmode(_fileno(consoleInfo.pfErr), xmode); + } + } +#endif +#ifdef TEST_CIO +#endif +} + +static short isConOut(FILE *pf){ + if( pf==consoleInfo.pfOut ) return 1; + else if( pf==consoleInfo.pfErr ) return 2; + else return 0; +} + +INT_LINKAGE void setBinaryMode(FILE *pf, short bFlush){ + short ico = isConOut(pf); + if( ico || bFlush ) fflush(pf); +#if SHELL_CON_TRANSLATE == 2 + _setmode(_fileno(pf), _O_BINARY); +#elif SHELL_CON_TRANSLATE == 1 + /* Never change between text/binary on UTF-16 console streamss. */ + if( !ico && !(consoleInfo.pfIn==pf)) _setmode(_fileno(pf), _O_BINARY); +#endif +} +INT_LINKAGE void setTextMode(FILE *pf, short bFlush){ + short ico = isConOut(pf); + if( ico || bFlush ) fflush(pf); +#if SHELL_CON_TRANSLATE == 2 + _setmode(_fileno(pf), _O_TEXT); +#elif SHELL_CON_TRANSLATE == 1 + /* Never change between text/binary on UTF-16 console streamss. */ + if( !ico && !(consoleInfo.pfIn==pf)) _setmode(_fileno(pf), _O_TEXT); +#endif +} +/* Later: Factor common code out of above 2 procs. */ + +INT_LINKAGE int fprintfUtf8(FILE *pfO, const char *zFormat, ...){ + va_list ap; + int rv = 0; + short on = isConOut(pfO); + va_start(ap, zFormat); + if( on > 0 ){ +#if SHELL_CON_TRANSLATE + char *z1 = sqlite3_vmprintf(zFormat, ap); +# if SHELL_CON_TRANSLATE == 2 + /* Legacy translation to active code page, then MBCS chars out. */ + char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); + if( z2!=NULL ){ + rv = strlen(z2); + vfprintf(pfO, "%s", z2); + sqlite3_free(z2); + } +# else + /* Translation from UTF-8 to UTF-16, then WCHAR characters out. */ + if( z1!=NULL ){ + int nwc; + WCHAR *zw2 = 0; + rv = strlen(z1); + nwc = MultiByteToWideChar(CP_UTF8,0,z1,rv,0,0); + if( nwc>0 ){ + zw2 = sqlite3_malloc64((nwc+1)*sizeof(WCHAR)); + if( zw2!=NULL ){ + HANDLE ho = (on==1)? consoleInfo.hOut : consoleInfo.hErr; + nwc = MultiByteToWideChar(CP_UTF8,0,z1,rv,zw2,nwc); + zw2[nwc] = 0; + WriteConsoleW(ho, zw2, nwc, 0, NULL); + sqlite3_free(zw2); + }else rv = 0; + } + } +# endif + sqlite3_free(z1); +#else +#endif + }else{ + rv = vfprintf(pfO, zFormat, ap); + } + va_end(ap); + return rv; +} + +INT_LINKAGE int fgetsUtf8(char *buf, int ncMax, FILE *pfIn){ + return 0; +} + +#ifdef TEST_CIO +// cl -Zi -I. -DWIN32 -DTEST_CIO sqlite3.c src/console_io.c -Fecio.exe +// gcc -I. -DWIN32 -DTEST_CIO -o cio sqlite3.c src/console_io.c -o cio.exe +const char *prompts[] = { "main", "cont" }; +Prompts goofy = { 2, prompts }; + +int main(int na, char *av[]){ + ConsoleStdConsStreams cc = consoleClassifySetup(stdin, stdout, stderr); + setTextMode(stdout, 1); + setTextMode(stderr, 1); + fprintfUtf8(stderr, "%d\n", cc); + fprintfUtf8(stdout, "%s=%d\n", "∑(1st 7 primes)", 42); + fprintfUtf8(stderr, "%s\n", "∫ (1/x) dx ≡ ln(x)"); + consoleRestore(); + return 0; +} +#endif /* defined(TEST_CIO) */ diff --git a/src/console_io.h b/src/console_io.h index b8c1ea982d..444c3e0e65 100644 --- a/src/console_io.h +++ b/src/console_io.h @@ -24,19 +24,33 @@ ** This code may change in tandem with other project code as needed. */ +#ifndef INT_LINKAGE +# define INT_LINKAGE /* Linkage will be external to translation unit. */ +# include +# include +# include +# if defined(_WIN32) || defined(WIN32) +# undef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +# endif +#endif + /* Define enum for use with following function. */ -enum ConsoleStdStreams { - CSC_NoConsole = 0, - CSC_InConsole = 1, CSC_OutConsole = 2, CSC_ErrConsole = 4, - CSC_AnyConsole = 0x7 -}; +typedef enum ConsoleStdConsStreams { + CSCS_NoConsole = 0, + CSCS_InConsole = 1, CSCS_OutConsole = 2, CSCS_ErrConsole = 4, + CSCS_AnyConsole = 0x7 +} ConsoleStdConsStreams; /* ** Classify the three standard I/O streams according to whether ** they are connected to a console attached to the process. ** -** Returns the bit-wise OR of CSC_{In,Out,Err}Console values, -** or CSC_NoConsole if none of the streams reaches a console. +** Returns the bit-wise OR of CSCS_{In,Out,Err}Console values, +** or CSCS_NoConsole if none of the streams reaches a console. ** ** This function should be called before any I/O is done with ** the given streams. As a side-effect, the given inputs are @@ -47,8 +61,8 @@ enum ConsoleStdStreams { ** On some platforms, stream or console mode alteration (aka ** "Setup") may be made which is undone by consoleRestore(). */ -INT_LINKAGE ConsoleStdStreams -consoleClassifySetup( FILE *pfIn, FILE *pfOut,FILE *pfErr ); +INT_LINKAGE ConsoleStdConsStreams +consoleClassifySetup( FILE *pfIn, FILE *pfOut, FILE *pfErr ); /* ** Undo any side-effects left by consoleClassifySetup(...). @@ -64,10 +78,10 @@ INT_LINKAGE void SQLITE_CDECL consoleRestore( void ); ** Render output like fprintf(). If the output is going to the ** console and translation from UTF-8 is necessary, perform ** the needed translation. Otherwise, write formatted output -** to the provided stream almost as-is, with possibly with -** newline translation as set by set{Binary,Text}Mode(). +** to the provided stream almost as-is, possibly with newline +** translation as specified by set{Binary,Text}Mode(). */ -INT_LINKAGE int fprintfUtf8(FILE *, const char *zFmt, ...); +INT_LINKAGE int fprintfUtf8(FILE *pfO, const char *zFormat, ...); /* ** Collect input like fgets(...) with special provisions for input @@ -79,14 +93,23 @@ INT_LINKAGE int fgetsUtf8(char *buf, int ncMax, FILE *pfIn); /* ** Set given stream for binary mode, where newline translation is -** not done, or to text mode where, for some platforms, newlines +** not done, or for text mode where, for some platforms, newlines ** are translated to the platform's conventional char sequence. +** If bFlush true, flush the stream. ** ** An additional side-effect is that if the stream is one passed -** to consoleClassifySetup() as an output, it is flushed. +** to consoleClassifySetup() as an output, it is flushed first. +** +** Note that binary/text mode has no effect on console output +** translation. Newline chars start a new line on all platforms. */ -INT_LINKAGE void setBinaryMode(File *); -INT_LINKAGE void setTextMode(File *); +INT_LINKAGE void setBinaryMode(FILE *, short bFlush); +INT_LINKAGE void setTextMode(FILE *, short bFlush); + +typedef struct Prompts { + int numPrompts; + const char **azPrompts; +} Prompts; /* ** Macros for use of a line editor. @@ -128,8 +151,8 @@ INT_LINKAGE void setTextMode(File *); ** library to interactively collect line edited input. */ INT_LINKAGE char * -shellGetLine(File *pfIn, char *zBufPrior, int nLen, - short isContinuation, const char *azPrompt[2]); +shellGetLine(FILE *pfIn, char *zBufPrior, int nLen, + short isContinuation, Prompts azPrompt); /* ** TBD: Define an interface for application(s) to generate