diff --git a/CMakeLists.txt b/CMakeLists.txt index 25517f6..3f70595 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,6 +147,18 @@ if(FORCE_UTF8_CODEPAGE) list(APPEND META_PRIVATE_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_FORCE_UTF8_CODEPAGE) endif() +# configure whether escape codes should be enabled by default +option(ENABLE_ESCAPE_CODES_BY_DEAULT "enables usage of escape codes by default" ON) +if(ENABLE_ESCAPE_CODES_BY_DEAULT) + set_source_files_properties( + application/argumentparser.cpp + io/ansiescapecodes.cpp + PROPERTIES COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_ESCAPE_CODES_ENABLED_BY_DEFAULT + ) +else() + message(STATUS "Disabling use of escape codes by default.") +endif() + # include modules to apply configuration include(BasicConfig) include(WindowsResources) diff --git a/application/argumentparser.cpp b/application/argumentparser.cpp index d0b50ba..7cf5bd1 100644 --- a/application/argumentparser.cpp +++ b/application/argumentparser.cpp @@ -782,7 +782,9 @@ void ArgumentParser::readArgs(int argc, const char *const *argv) completionMode); try { reader.read(); + NoColorArgument::apply(); } catch (const Failure &) { + NoColorArgument::apply(); if (!completionMode) { throw; } @@ -1375,4 +1377,91 @@ HelpArgument::HelpArgument(ArgumentParser &parser) * \brief The ConfigValueArgument class is an Argument where setCombinable() is true by default. * \sa ConfigValueArgument::ConfigValueArgument() */ + +/*! + * \class NoColorArgument + * \brief The NoColorArgument class allows to specify whether use of escape codes or similar technique to provide formatted output + * on the terminal should be enabled/disabled. + * + * This argument will either prevent or explicitely allow the use of escape codes or similar technique to provide formatted output + * on the terminal. More explicitly, the argument will always allow to negate the default value of EscapeCodes::enabled which can be + * configured at build time by setting the CMake variable ENABLE_ESCAPE_CODES_BY_DEFAULT. + * + * \remarks + * - Only the first instance is considered for actually altering the value of EscapeCodes::enabled so it makes no sense to + * instantiate this class multiple times. + * - It is ensure that EscapeCodes::enabled will be set before any callback functions are invoked and even in the error case (if + * the error doesn't prevent the argument from being detected). Hence this feature is implemented via NoColorArgument::apply() + * rather than the usual callback mechanism. + * + * \sa NoColorArgument::NoColorArgument(), EscapeCodes::enabled + */ + +NoColorArgument *NoColorArgument::s_instance = nullptr; + +/*! + * \brief Constructs a new NoColorArgument argument. + * \remarks This will also set EscapeCodes::enabled to the value of the environment variable ENABLE_ESCAPE_CODES. + */ +NoColorArgument::NoColorArgument() +#ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT + : Argument("no-color", '\0', "disables formatted/colorized output") +#else + : Argument("enable-color", '\0', "enables formatted/colorized output") +#endif +{ + setCombinable(true); + + if (s_instance) { + return; + } + s_instance = this; + + // set the environmentvariable: note that this is not directly used and just assigned for printing help + setEnvironmentVariable("ENABLE_ESCAPE_CODES"); + + // default-initialize EscapeCodes::enabled from environment variable + const char *envValue = getenv(environmentVariable()); + if (!envValue) { + return; + } + for (; *envValue; ++envValue) { + switch (*envValue) { + case '0': + case ' ': + break; + default: + // enable escape codes if ENABLE_ESCAPE_CODES contains anything else than spaces or zeros + EscapeCodes::enabled = true; + return; + } + } + // disable escape codes if ENABLE_ESCAPE_CODES is empty or only contains spaces and zeros + EscapeCodes::enabled = false; +} + +/*! + * \brief Destroys the object. + */ +NoColorArgument::~NoColorArgument() +{ + if (s_instance == this) { + s_instance = nullptr; + } +} + +/*! + * \brief Sets EscapeCodes::enabled according to the presense of the first instantiation of NoColorArgument. + */ +void NoColorArgument::apply() +{ + if (NoColorArgument::s_instance && NoColorArgument::s_instance->isPresent()) { +#ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT + EscapeCodes::enabled = false; +#else + EscapeCodes::enabled = true; +#endif + } +} + } // namespace ApplicationUtilities diff --git a/application/argumentparser.h b/application/argumentparser.h index 00a8275..19f482c 100644 --- a/application/argumentparser.h +++ b/application/argumentparser.h @@ -873,6 +873,17 @@ inline ConfigValueArgument::ConfigValueArgument( setRequiredValueCount(valueNames.size()); setValueNames(valueNames); } + +class CPP_UTILITIES_EXPORT NoColorArgument : public Argument { +public: + NoColorArgument(); + ~NoColorArgument(); + static void apply(); + +private: + static NoColorArgument *s_instance; +}; + } // namespace ApplicationUtilities #endif // APPLICATION_UTILITIES_ARGUMENTPARSER_H diff --git a/doc/buildvariables.md b/doc/buildvariables.md index 9adb939..4eb474d 100644 --- a/doc/buildvariables.md +++ b/doc/buildvariables.md @@ -70,6 +70,12 @@ None of these are enabled or set by default, unless stated otherwise. * coverage report is stored in build directory * `ENABLE_INSTALL_TARGETS=ON/OFF`: enables creation of install targets (enabled by default) +* `ENABLE_ESCAPE_CODES_BY_DEAULT`: enables use of escape codes for formatted + output by default + * enabled by default + * see ApplicationUtilities::NoColorArgument and EscapeCodes::enabled + * has to be set when building `c++utilities`; projects using that build of + `c++utilities` will then use this default #### Variables for specifying location of 3rd party dependencies The build script tries to find the required dependencies at standard loctions diff --git a/io/ansiescapecodes.cpp b/io/ansiescapecodes.cpp index 8d7e93a..5f96abb 100644 --- a/io/ansiescapecodes.cpp +++ b/io/ansiescapecodes.cpp @@ -1,7 +1,30 @@ #include "./ansiescapecodes.h" +/*! + * \brief Encapsulates functions for formatted terminal output using ANSI escape codes. + */ namespace EscapeCodes { +/*! + * \brief Controls whether the functions inside the EscapeCodes namespace actually make use of escape codes. + * + * This allows to disable use of escape codes when not appropriate. + * + * The default value can be configured at build time by setting the CMake variable ENABLE_ESCAPE_CODES_BY_DEFAULT. + * The "default for the default" is true. + * However, the default is overridden with the value of the environment variable ENABLE_ESCAPE_CODES when instantiating + * an ApplicationUtilities::NoColorArgument (if ENABLE_ESCAPE_CODES is present). + * + * \sa ApplicationUtilities::NoColorArgument + */ +bool enabled = +#ifdef CPP_UTILITIES_ESCAPE_CODES_ENABLED_BY_DEFAULT + true +#else + false +#endif + ; + /*! * \brief Prints the specified \a phrase. */ diff --git a/io/ansiescapecodes.h b/io/ansiescapecodes.h index d5d68f0..eb84966 100644 --- a/io/ansiescapecodes.h +++ b/io/ansiescapecodes.h @@ -6,12 +6,10 @@ #include #include -/*! - * \brief Encapsulates functions for formatted terminal output using ANSI escape codes. - * \remarks The functions haven't been tested yet and are still experimental. API/ABI might change in next minor release. - */ namespace EscapeCodes { +extern CPP_UTILITIES_EXPORT bool enabled; + enum class Color : char { Black = '0', Red, Green, Yellow, Blue, Purple, Cyan, White }; enum class ColorContext : char { Foreground = '3', Background = '4' }; @@ -31,54 +29,75 @@ enum class Direction : char { Up = 'A', Down = 'B', Forward = 'C', Backward = 'D inline void setStyle(std::ostream &stream, TextAttribute displayAttribute = TextAttribute::Reset) { - stream << '\e' << '[' << static_cast(displayAttribute) << 'm'; + if (enabled) { + stream << '\e' << '[' << static_cast(displayAttribute) << 'm'; + } } inline void setStyle( std::ostream &stream, Color color, ColorContext context = ColorContext::Foreground, TextAttribute displayAttribute = TextAttribute::Reset) { - stream << '\e' << '[' << static_cast(displayAttribute) << ';' << static_cast(context) << static_cast(color) << 'm'; + if (enabled) { + stream << '\e' << '[' << static_cast(displayAttribute) << ';' << static_cast(context) << static_cast(color) << 'm'; + } } inline void setStyle(std::ostream &stream, Color foregroundColor, Color backgroundColor, TextAttribute displayAttribute = TextAttribute::Reset) { - stream << '\e' << '[' << static_cast(displayAttribute) << ';' << static_cast(ColorContext::Foreground) - << static_cast(foregroundColor) << ';' << static_cast(ColorContext::Foreground) << static_cast(backgroundColor) << 'm'; + if (enabled) { + stream << '\e' << '[' << static_cast(displayAttribute) << ';' << static_cast(ColorContext::Foreground) + << static_cast(foregroundColor) << ';' << static_cast(ColorContext::Foreground) << static_cast(backgroundColor) + << 'm'; + } } inline void resetStyle(std::ostream &stream) { - stream << '\e' << '[' << static_cast(TextAttribute::Reset) << 'm'; + if (enabled) { + stream << '\e' << '[' << static_cast(TextAttribute::Reset) << 'm'; + } } inline void setCursor(std::ostream &stream, unsigned int row = 0, unsigned int col = 0) { - stream << '\e' << '[' << row << ';' << col << 'H'; + if (enabled) { + stream << '\e' << '[' << row << ';' << col << 'H'; + } } inline void moveCursor(std::ostream &stream, unsigned int cells, Direction direction) { - stream << '\e' << '[' << cells << static_cast(direction); + if (enabled) { + stream << '\e' << '[' << cells << static_cast(direction); + } } inline void saveCursor(std::ostream &stream) { - stream << "\e[s"; + if (enabled) { + stream << "\e[s"; + } } inline void restoreCursor(std::ostream &stream) { - stream << "\e[u"; + if (enabled) { + stream << "\e[u"; + } } inline void eraseDisplay(std::ostream &stream) { - stream << "\e[2J"; + if (enabled) { + stream << "\e[2J"; + } } inline void eraseLine(std::ostream &stream) { - stream << "\33[2K"; + if (enabled) { + stream << "\33[2K"; + } } inline std::ostream &operator<<(std::ostream &stream, TextAttribute displayAttribute)