Advertisement
Delfigamer

console

Sep 30th, 2015
393
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 12.26 KB | None | 0 0
  1. #ifndef UTILS_CONSOLE_HPP__
  2. #define UTILS_CONSOLE_HPP__ 1
  3.  
  4. #include "singleton.hpp"
  5. #if defined( _WIN32 ) || defined( _WIN64 )
  6. #include "winapi.hpp"
  7. #endif
  8. #include <cstdio>
  9. #include <cstdarg>
  10. #include <mutex>
  11.  
  12. namespace utils
  13. {
  14.     class ConsoleClass
  15.     {
  16.     private:
  17.         typedef std::recursive_mutex mutex_t;
  18.         typedef std::lock_guard< mutex_t > lock_t;
  19.  
  20.     private:
  21.         mutex_t m_mutex;
  22.         FILE* m_file;
  23.         bool m_newline;
  24. #if defined( _WIN32 ) || defined( _WIN64 )
  25.         HANDLE m_inputhandle;
  26.         bool m_inputisconsole;
  27.         HANDLE m_outputhandle;
  28.         bool m_outputisconsole;
  29. #endif
  30.  
  31.         // Sends the string to stdout, possibly converting it from UTF-8 to something other
  32.         void writeconsole( char const* str, int length );
  33.         // Sends the string to the global log file
  34.         void writefile( char const* str, int length );
  35.         void linestart();
  36.  
  37.     public:
  38.         ConsoleClass();
  39.         ~ConsoleClass();
  40.         ConsoleClass( ConsoleClass const& ) = delete;
  41.         ConsoleClass( ConsoleClass&& ) = delete;
  42.         ConsoleClass& operator=( ConsoleClass const& ) = delete;
  43.         ConsoleClass& operator=( ConsoleClass&& ) = delete;
  44.  
  45.         // Accepts a UTF-8 string and sends it to both stdout and log file, by sending it to writeconsole and writefile
  46.         // Additionally, in the log file, each newline is prepended with a time returned by clock()
  47.         void writeraw( char const* str );
  48.         // Construct the string according to the format and send it to writeraw
  49.         __attribute__(( __format__( gnu_printf, 2, 0 ) ))
  50.             void vwrite( char const* format, va_list args );
  51.         __attribute__(( __format__( gnu_printf, 2, 3 ) ))
  52.             void write( char const* format, ... );
  53.         // Construct the string, send it, append a newline at the end and flush both streams
  54.         __attribute__(( __format__( gnu_printf, 2, 0 ) ))
  55.             void vwriteln( char const* format, va_list args );
  56.         __attribute__(( __format__( gnu_printf, 2, 3 ) ))
  57.             void writeln( char const* format, ... );
  58.         // Sends a newline and flushes both streams
  59.         void writeln();
  60.         void flush();
  61.         void lock();
  62.         void unlock();
  63.         // Attempts to read a single character from stdin and writes it as a zero-terminated UTF-8 string in the given buffer
  64.         // str must be able to safely hold at least 5 bytes
  65.         // On failure, str[ 0 ] == 0
  66.         void getchar( char* str );
  67.     };
  68.  
  69.     extern utils::Singleton< ConsoleClass > Console;
  70. }
  71.  
  72. // My debugging tool #1
  73. #define LOG( format, ... ) \
  74.     utils::Console()->writeln( \
  75.         "[%48s:%24s@%4i]\t" format, \
  76.         __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__ )
  77.  
  78. #endif
  79.  
  80. ////////////////////////////////////////////////////////////////////////////////
  81.  
  82. #include "console.hpp"
  83. #if defined( _WIN32 ) || defined( _WIN64 )
  84. // hello again, http://pastebin.com/iXMm3B7t
  85. #include "encoding.hpp"
  86. #endif
  87. #include <cstring>
  88. #include <ctime>
  89.  
  90. namespace utils
  91. {
  92.     void ConsoleClass::writeconsole( char const* str, int length )
  93.     {
  94. // Windows plz
  95. #if defined( _WIN32 ) || defined( _WIN64 )
  96.         if( m_outputhandle == 0 )
  97.         {
  98.             return;
  99.         }
  100.         if( m_outputisconsole )
  101.         {
  102.             while( length > 0 )
  103.             {
  104.                 // cmd.exe can work with legacy codepages and UTF-16
  105.                 // Supporting all the pages is even more pain, so we stick to *W versions of WinAPI functions
  106.                 wchar_t buffer[ 256 ];
  107.                 translation_t trstruct =
  108.                 {
  109.                     &encoding::utf8, // source encoding
  110.                     &encoding::utf16, // target encoding
  111.                     str, // source buffer
  112.                     buffer, // dest buffer
  113.                     size_t( length ), // source size
  114.                     sizeof( buffer ), // dest size
  115.                     0xfffd, // 'REPLACEMENT CHARACTER', this little thing: �
  116.                 };
  117.                 int trresult = utils::translatestr( &trstruct );
  118.                 DWORD wcresult;
  119.                 if( !WriteConsoleW(
  120.                     m_outputhandle, buffer, trstruct.destresult / 2, &wcresult, 0 ) )
  121.                 {
  122.                     fprintf( stderr, "%i\n", __LINE__ );
  123.                     winerror();
  124.                 }
  125.                 str += trstruct.sourceresult;
  126.                 length -= trstruct.sourceresult;
  127.                 switch( trresult )
  128.                 {
  129.                 case translate_success:
  130.                 case translate_failure_source_overrun:
  131.                 // We have finished our work with the given chunk
  132.                     return;
  133.  
  134.                 case translate_failure_dest_unsupported:
  135.                 // Something unbelievable: UTF-8 decoder retrieved something UTF-16 encoder refuses to understand
  136.                 // But in case it happens anyway, skip the problematic character and go on
  137.                 {
  138.                     size_t pointlength;
  139.                     encoding::utf8.decode( str, 0, length, &pointlength );
  140.                     str += pointlength;
  141.                     length -= pointlength;
  142.                     break;
  143.                 }
  144.  
  145.                 case translate_failure_dest_overrun:
  146.                 // This just means we got the buffer filled, this is normal operation
  147.                     break;
  148.                 }
  149.             }
  150.         }
  151.         else
  152.         {
  153.             // If stdout is redirected to a file, we don't apply any conversions, therefore the file becomes the same encoding as the rest of the engine, which is UTF-8
  154.             DWORD result;
  155.             if( !WriteFile( m_outputhandle, str, length, &result, 0 ) )
  156.             {
  157.                 fprintf( stderr, "%i\n", __LINE__ );
  158.                 winerror();
  159.             }
  160.         }
  161. #else
  162.         printf( "%.*s", str, length );
  163. #endif
  164.     }
  165.  
  166.     void ConsoleClass::writefile( char const* str, int length )
  167.     {
  168.         fprintf( m_file, "%.*s", length, str );
  169.     }
  170.  
  171.     ConsoleClass::ConsoleClass()
  172.         : m_newline( true )
  173.     {
  174. #ifdef __ANDROID__
  175.         // I used CCTools to compile the engine for Android, and its run_na placed CWD in some abyss that is not even accessible with a file manager, so I use a full path here
  176.         m_file = fopen( "/storage/sdcard0/projects/output/last.log", "w" );
  177. #else
  178.         m_file = fopen( "last.log", "wb" );
  179. #endif
  180. #if defined( _WIN32 ) || defined( _WIN64 )
  181. #if defined( DISABLE_CONSOLE )
  182.         m_inputhandle = 0;
  183.         m_outputhandle = 0;
  184. #else
  185.         m_inputhandle = GetStdHandle( STD_INPUT_HANDLE );
  186.         if( m_inputhandle == INVALID_HANDLE_VALUE )
  187.         {
  188.             fprintf( stderr, "%i\n", __LINE__ );
  189.             winerror();
  190.         }
  191.         DWORD imode;
  192.         if( m_inputhandle == 0 )
  193.         {
  194.             // There is no stdin
  195.         }
  196.         else if( GetConsoleMode( m_inputhandle, &imode ) )
  197.         {
  198.             // stdin is provied by cmd.exe
  199.             m_inputisconsole = true;
  200.         }
  201.         else
  202.         {
  203.             BY_HANDLE_FILE_INFORMATION fi;
  204.             // This will probably break if stdin is actually a pipe, but that's the best test I can think of for now
  205.             if( GetFileInformationByHandle( m_inputhandle, &fi ) )
  206.             {
  207.                 m_inputisconsole = false;
  208.             }
  209.             else
  210.             {
  211.                 fprintf( stderr, "%i\n", __LINE__ );
  212.                 winerror();
  213.             }
  214.         }
  215.         m_outputhandle = GetStdHandle( STD_OUTPUT_HANDLE );
  216.         if( m_outputhandle == INVALID_HANDLE_VALUE )
  217.         {
  218.             fprintf( stderr, "%i\n", __LINE__ );
  219.             winerror();
  220.         }
  221.         // The same set of tests
  222.         if( m_outputhandle == 0 )
  223.         {
  224.         }
  225.         else if( GetConsoleMode( m_outputhandle, &imode ) )
  226.         {
  227.             m_outputisconsole = true;
  228.         }
  229.         else
  230.         {
  231.             BY_HANDLE_FILE_INFORMATION fi;
  232.             if( GetFileInformationByHandle( m_outputhandle, &fi ) )
  233.             {
  234.                 m_outputisconsole = false;
  235.             }
  236.             else
  237.             {
  238.                 fprintf( stderr, "%i\n", __LINE__ );
  239.                 winerror();
  240.             }
  241.         }
  242. #endif
  243. #endif
  244.     }
  245.  
  246.     ConsoleClass::~ConsoleClass()
  247.     {
  248.         writeraw( "\nLog finished\n" );
  249.         fclose( m_file );
  250.     }
  251.  
  252.     void ConsoleClass::linestart()
  253.     {
  254.         if( m_newline )
  255.         {
  256.             m_newline = false;
  257.             char buffer[ 32 ];
  258.             int length = snprintf( buffer, sizeof( buffer ), "[%10u] ", uint32_t( clock() * 1000 / CLOCKS_PER_SEC ) );
  259.             writefile( buffer, length - 1 );
  260.         }
  261.     }
  262.  
  263.     void ConsoleClass::writeraw( char const* str )
  264.     {
  265.         // The point of m_newline flag is that the times that will appear before every log line will be measured when that line is started. If it was output immediately after '\n', then it will show the time the previous line was finished, which can happen much earlier, and the result would be misleading
  266.         lock_t lock( m_mutex );
  267.         while( str[ 0 ] )
  268.         {
  269.             linestart();
  270.             char const* npos = strchr( str, '\n' );
  271.             if( npos )
  272.             {
  273.                 writefile( str, npos - str + 1 );
  274.                 writeconsole( str, npos - str + 1 );
  275.                 m_newline = true;
  276.                 str = npos + 1;
  277.             }
  278.             else
  279.             {
  280.                 int len = strlen( str );
  281.                 writefile( str, len );
  282.                 writeconsole( str, len );
  283.                 break;
  284.             }
  285.         }
  286.     }
  287.  
  288.     void ConsoleClass::vwrite( char const* format, va_list args )
  289.     {
  290.         lock_t lock( m_mutex );
  291.         va_list args2;
  292.         va_copy( args2, args );
  293.         int length = vsnprintf( 0, 0, format, args2 );
  294.         if( length < 1024 )
  295.         {
  296.             char buffer[ 1024 ];
  297.             vsnprintf( buffer, sizeof( buffer ), format, args );
  298.             writeraw( buffer );
  299.         }
  300.         else
  301.         {
  302.             char* buffer = new char[ length ];
  303.             vsnprintf( buffer, length, format, args );
  304.             writeraw( buffer );
  305.             delete[] buffer;
  306.         }
  307.         va_end( args2 );
  308.     }
  309.  
  310.     void ConsoleClass::write( char const* format, ... )
  311.     {
  312.         va_list args;
  313.         va_start( args, format );
  314.         vwrite( format, args );
  315.         va_end( args );
  316.     }
  317.  
  318.     void ConsoleClass::vwriteln( char const* format, va_list args )
  319.     {
  320.         lock_t lock( m_mutex );
  321.         vwrite( format, args );
  322.         writeraw( "\n" );
  323.         flush();
  324.     }
  325.  
  326.     void ConsoleClass::writeln( char const* format, ... )
  327.     {
  328.         va_list args;
  329.         va_start( args, format );
  330.         vwriteln( format, args );
  331.         va_end( args );
  332.     }
  333.  
  334.     void ConsoleClass::writeln()
  335.     {
  336.         lock_t lock( m_mutex );
  337.         writeraw( "\n" );
  338.         flush();
  339.     }
  340.  
  341.     void ConsoleClass::flush()
  342.     {
  343.         lock_t lock( m_mutex );
  344.         fflush( m_file );
  345. #if defined( _WIN32 ) || defined( _WIN64 )
  346.         if( m_outputhandle != 0 && !m_outputisconsole )
  347.         {
  348.             // There is no need to flush a console screen
  349.             if( !FlushFileBuffers( m_outputhandle ) )
  350.             {
  351.                 fprintf( stderr, "%i\n", __LINE__ );
  352.                 winerror();
  353.             }
  354.         }
  355. #endif
  356.     }
  357.  
  358.     void ConsoleClass::lock()
  359.     {
  360.         m_mutex.lock();
  361.     }
  362.  
  363.     void ConsoleClass::unlock()
  364.     {
  365.         m_mutex.unlock();
  366.     }
  367.  
  368.     void ConsoleClass::getchar( char* str )
  369.     {
  370.         size_t pointlength;
  371. // Windows plz
  372. #if defined( _WIN32 ) || defined( _WIN64 )
  373.         if( m_inputhandle == 0 )
  374.         {
  375.             str[ 0 ] = 0;
  376.             return;
  377.         }
  378.         // Windows likes to think that "newline" is "newline and carriage return"
  379.         // I like to think it is not
  380.         static uint32_t newline_consumed = 0;
  381.         uint32_t charcode;
  382.         if( m_inputisconsole )
  383.         {
  384.             // Remember that a single wchar_t is not a char yet
  385.             wchar_t buffer[ 2 ] = { 0 };
  386.             int length = 0;
  387.             while( true )
  388.             {
  389.                 DWORD result;
  390.                 if( !ReadConsoleW( m_inputhandle, buffer + length, 1, &result, 0 ) )
  391.                 {
  392.                     fprintf( stderr, "%i\n", __LINE__ );
  393.                     winerror();
  394.                 }
  395.                 length += 1;
  396.                 size_t pointlength;
  397.                 encoding::utf16.decode(
  398.                     buffer, &charcode, length * 2, &pointlength );
  399.                 if( pointlength != 0 )
  400.                 {
  401.                     break;
  402.                 }
  403.             }
  404.         }
  405.         else
  406.         {
  407.             str[ 0 ] = 0;
  408.             int length = 0;
  409.             while( true )
  410.             {
  411.                 DWORD result;
  412.                 if( !ReadFile( m_inputhandle, str + length, 1, &result, 0 ) )
  413.                 {
  414.                     fprintf( stderr, "%i\n", __LINE__ );
  415.                     winerror();
  416.                 }
  417.                 length += 1;
  418.                 size_t pointlength;
  419.                 encoding::utf8.decode(
  420.                     str, &charcode, length, &pointlength );
  421.                 if( pointlength != 0 )
  422.                 {
  423.                     // If this code stumbles upon invalid UTF-8 sequence, depending on how exactly it is invalid, it may decide to eat the next byte as well
  424.                     // No, I am not handling this. This engine is neither web-browser nor Windows to encourage such bad habits as making some bullshit and thinking it will be handled consistently.
  425.                     break;
  426.                 }
  427.             }
  428.         }
  429.         if( charcode == '\n' || charcode == '\r' )
  430.         {
  431.             if( newline_consumed && newline_consumed != charcode )
  432.             {
  433.                 newline_consumed = 0;
  434.                 getchar( str );
  435.                 return;
  436.             }
  437.             else
  438.             {
  439.                 newline_consumed = charcode;
  440.                 charcode = '\n';
  441.             }
  442.         }
  443.         else
  444.         {
  445.             newline_consumed = 0;
  446.         }
  447.         if( !encoding::utf8.encode( str, charcode, 4, &pointlength ) )
  448.         {
  449.             str[ 0 ] = 0;
  450.         }
  451.         else
  452.         {
  453.             // As "str" is actually a string of bytes, it makes sense to zero-terminate it
  454.             str[ pointlength ] = 0;
  455.         }
  456. #else
  457.         int length = 0;
  458.         while( true )
  459.         {
  460.             scanf( "%c", str + length );
  461.             length += 1;
  462.             if( !encoding::utf8.decode( str, 0, length, &pointlength ) )
  463.             {
  464.                 if( pointlength != 0 )
  465.                 {
  466.                     // The same issue: non-conformant UTF-8 makes the engine to tell you to go home and cry in a corner
  467.                     str[ 0 ] = 0;
  468.                     break;
  469.                 }
  470.             }
  471.             else
  472.             {
  473.                 str[ pointlength ] = 0;
  474.                 break;
  475.             }
  476.         }
  477. #endif
  478.         // All the characters read are echoed to the log file
  479.         linestart();
  480.         writefile( str, pointlength );
  481.         if( str[ 0 ] == '\n' )
  482.         {
  483.             m_newline = true;
  484.         }
  485.     }
  486.  
  487.     utils::Singleton< ConsoleClass > Console;
  488. }
  489.  
  490. // Conclusion:
  491. // #ifdef _WIN32
  492. // a couple of screens of code
  493. // #else
  494. // a single POSIX/stdc call
  495. // #endif
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement