diff --git a/AutoRAII.h b/AutoRAII.h index 5ee1557..84d4273 100644 --- a/AutoRAII.h +++ b/AutoRAII.h @@ -79,6 +79,26 @@ namespace Alepha::Hydrogen decltype( auto ) operator->() const { return value; } }; + template< typename Dtor > + class AutoRAII< void, Dtor > : boost::noncopyable + { + private: + Dtor dtor; + + public: + ~AutoRAII() + { + if constexpr( std::is_same_v< Dtor, std::function< void () > > ) + { + if( dtor == nullptr ) return; + } + dtor(); + } + + template< typename Ctor > + explicit AutoRAII( Ctor ctor, Dtor dtor ) : dtor( std::move( dtor ) ) { ctor(); } + }; + template< typename Ctor, typename Dtor > explicit AutoRAII( Ctor ctor, Dtor ) -> AutoRAII< decltype( ctor() ), Dtor >; } diff --git a/Concepts.h b/Concepts.h index 1513e24..ef48216 100644 --- a/Concepts.h +++ b/Concepts.h @@ -7,6 +7,7 @@ static_assert( __cplusplus > 2020'00 ); #include #include "meta.h" +#include "function_traits.h" namespace Alepha::inline Cavorite ::detail:: core_concepts { @@ -25,7 +26,7 @@ namespace Alepha::inline Cavorite ::detail:: core_concepts concept ConvertibleTo= std::convertible_to< T, U >; template< typename T, typename Base > - concept DerivedFrom= std::derived_from< T, Base > + concept DerivedFrom= std::derived_from< T, Base >; template< typename T > concept FloatingPoint= std::floating_point< T >; @@ -50,7 +51,7 @@ namespace Alepha::inline Cavorite ::detail:: core_concepts template< typename T, typename Target > concept ConvertibleToButNotSameAs= true and not SameAs< T, Target > - and ConveritbleTo< T, Target > + and ConvertibleTo< T, Target > ; @@ -64,7 +65,7 @@ namespace Alepha::inline Cavorite ::detail:: core_concepts template< typename T > concept IStreamable= - requires( const T &t, std::istream &os ) + requires( const T &t, std::istream &is ) { { is >> t } -> SameAs< std::istream & >; }; @@ -188,7 +189,7 @@ namespace Alepha::inline Cavorite ::detail:: core_concepts ; template< typename T > - concept Primitive= Floatish< T > or Intish< T >; + concept Primitive= FloatingPoint< T > or Integral< T >; template< typename T > concept Aggregate= std::is_aggregate_v< T >; @@ -203,6 +204,9 @@ namespace Alepha::inline Cavorite ::detail:: core_concepts template< typename T > concept NotFunctional= not Functional< T >; + template< typename T > + concept UnaryFunction= Functional< T > and function_traits< T >::args_size == 1; + template< typename T > concept StandardLayout= std::is_standard_layout_v< T >; @@ -237,7 +241,7 @@ namespace Alepha::inline Cavorite ::detail:: core_concepts ; template< typename T, typename Member > - concept SpecializedOn= is_specialized_on< T, Member >; + concept SpecializedOn= is_specialized_on_v< T, Member >; template< typename Member, typename Seq > concept SequenceOf= Sequence< Seq > and SpecializedOn< Seq, Member >; @@ -276,4 +280,5 @@ namespace Alepha::inline Cavorite ::detail:: core_concepts namespace Alepha::Cavorite::inline exports::inline core_concepts { - + using namespace detail::core_concepts::exports; +} diff --git a/Console.cpp b/Console.cpp index 8f201f4..5a1600c 100644 --- a/Console.cpp +++ b/Console.cpp @@ -1,13 +1,25 @@ static_assert( __cplusplus > 2020'00 ); -#include "console.h" +#include "Console.h" +#include +#include +#include + +#include #include +#include +#include +#include +#include + +#include -#include "ProgramOptions.h" -#include "file_help.h" #include "Enum.h" +#include "ProgramOptions.h" #include "StaticValue.h" +#include "string_algorithms.h" +#include "AutoRAII.h" /* * All of the terminal control code in this library uses ANSI escape sequences (https://en.wikipedia.org/wiki/ANSI_escape_code). @@ -51,17 +63,22 @@ namespace Alepha::Cavorite ::detail:: console if( applicationName().empty() ) applicationName()= "ALEPHA"; }; } - void - exports::setApplicationName( std::string name ) - { - storage::applicationName()= std::move( name ); - } - const std::string & - exports::applicationName() - { - return storage::applicationName(); - } + } + void + exports::setApplicationName( std::string name ) + { + storage::applicationName()= std::move( name ); + } + + const std::string & + exports::applicationName() + { + return storage::applicationName(); + } + + namespace + { auto screenWidthEnv() { return applicationName() + "_SCREEN_WIDTH"; } auto screenWidthEnvLimit() { return applicationName() + "_SCREEN_WIDTH_LIMIT"; } auto disableColorsEnv() { return applicationName() + "_DISABLE_COLOR_TEXT"; } @@ -74,7 +91,7 @@ namespace Alepha::Cavorite ::detail:: console if( getenv( env.c_str() ) ) try { - return boost::lexical_cast< int >( getenv( env.v_str() ) ); + return boost::lexical_cast< int >( getenv( env.c_str() ) ); } catch( const boost::bad_lexical_cast & ) {} return d; @@ -82,7 +99,7 @@ namespace Alepha::Cavorite ::detail:: console int cachedScreenWidth= evaluate <=[] { - const int underlying getEnvOrDefault( screenWidthEnv(), getScreenSize().columns ); + const int underlying= getEnvOrDefault( screenWidthEnv(), Console::main().getScreenSize().columns ); return std::min( underlying, getEnvOrDefault( screenWidthEnvLimit(), C::defaultScreenWidthLimit ) ); }; @@ -92,11 +109,11 @@ namespace Alepha::Cavorite ::detail:: console bool colorEnabled() { - if( not colorState.has_value() ) return getenv( disableColorsEnv() ); + if( not colorState.has_value() ) return getenv( disableColorsEnv().c_str() ); - if( colorState == "never"_value ) return false; - if( colorState == "always"_value ) return true; - assert( colorState == "auto"_value ); + if( colorState.value() == "never"_value ) return false; + if( colorState.value() == "always"_value ) return true; + assert( colorState.value() == "auto"_value ); return ::isatty( 1 ); // Auto means only do this for TTYs. } @@ -113,7 +130,7 @@ namespace Alepha::Cavorite ::detail:: console { for( const auto [ name, sgr ]: colorVariables() ) { - std::cout << name.name << ": ^[[": << sgr.code << "m" << std::endl; + std::cout << name.name << ": ^[[" << sgr.code << "m" << std::endl; } } << "Emit a list with the color variables supported by this application. For use with the `" << colorsEnv() @@ -122,6 +139,7 @@ namespace Alepha::Cavorite ::detail:: console --"dump-color-env-var"_option << [] { std::cout << "export " << colorsEnv() << "-\""; + bool first= true; for( const auto &[ name, sgr ]: colorVariables() ) { if( not first ) std::cout << ":"; @@ -135,11 +153,11 @@ namespace Alepha::Cavorite ::detail:: console << "application."; parse_environment_variable_for_color: - if( getenv( colorsEnv() ) ) + if( getenv( colorsEnv().c_str() ) ) { - const std::string contents= getenv( colorsEnv() ); + const std::string contents= getenv( colorsEnv().c_str() ); - for( const auto var: split( varString, ':' ) ) + for( const auto var: split( contents, ':' ) ) { const auto parsed= split( var, '=' ); if( parsed.size() != 2 ) @@ -199,11 +217,36 @@ namespace Alepha::Cavorite ::detail:: console return os; } - enum exports::Console::Mode + + enum ConsoleMode { cooked, raw, noblock, }; + + struct Console::Impl + { + int fd; + // TODO: Do we want to make this not gnu libstdc++ specific? + __gnu_cxx::stdio_filebuf< char > filebuf; + std::ostream stream; + std::stack< std::pair< struct termios, ConsoleMode > > modeStack; + ConsoleMode mode= cooked; + std::optional< int > cachedScreenWidth; + + explicit + Impl( const int fd ) + : fd( fd ), filebuf( fd, std::ios::out ), stream( &filebuf ) + {} + }; + + auto + Console::getMode() const + { + return pimpl().mode; + } + + namespace { @@ -212,15 +255,15 @@ namespace Alepha::Cavorite ::detail:: console BadScreenStateError() : std::runtime_error( "Error in getting terminal dimensions." ) {} }; - struct UnknowScreenError : std::runtime_error + struct UnknownScreenError : std::runtime_error { - UnknowScreenError() : std::runtime_error( "Terminal is unrecognized. Using defaults." ) {} + UnknownScreenError() : std::runtime_error( "Terminal is unrecognized. Using defaults." ) {} }; auto rawModeGuard( Console console ) { - const bool skip= console.getMode() == Console::raw; + const bool skip= console.getMode() == ConsoleMode::raw; return AutoRAII { [skip, &console] @@ -236,21 +279,7 @@ namespace Alepha::Cavorite ::detail:: console }; } } - - struct Console::Impl - { - int fd; - // TODO: Do we want to make this not gnu libstdc++ specific? - __gnu_cxx::stdio_filebuf< char > filebuf; - std::ostream stream; - std::stack< std::pair< struct termios, decltype( mode ) > > modeStack; - ConsoleMode mode= cooked; - - explicit - Impl( const int fd ) - : fd( fd ), filebuf( fd, std::ios::out ), stream( &filebuf ) - {} - }; + Console::Console( const int fd ) : impl( std::make_unique< Impl >( fd ) ) @@ -261,19 +290,12 @@ namespace Alepha::Cavorite ::detail:: console { return pimpl().stream; } - - - Console::Mode - Console::getMode() const - { - return pimpl().mode; - } void Console::popTermMode() { tcsetattr( pimpl().fd, TCSAFLUSH, &pimpl().modeStack.top().first ); - mode= pimpl().modeStack.top().second; + pimpl().mode= pimpl().modeStack.top().second; pimpl().modeStack.pop(); } @@ -289,12 +311,12 @@ namespace Alepha::Cavorite ::detail:: console next.c_iflag&= ~( BRKINT | ICRNL | INPCK | ISTRIP | IXON ); next.c_oflag&= ~( OPOST ); - next.c_flag|= CS8; - next.c_lflag&= !( ECHO | ICANNON | IEXTEN | ISIG ); + next.c_cflag|= CS8; + next.c_lflag&= !( ECHO | ICANON | IEXTEN | ISIG ); next.c_cc[ VMIN ]= min; next.c_cc[ VTIME ]= 0; - if( tcsetattr( pimpl().fd, TCSAFLUSH, &next ) ) throw UnknownScreenException{}; + if( tcsetattr( fd, TCSAFLUSH, &next ) ) throw UnknownScreenError{}; return now; } @@ -303,17 +325,17 @@ namespace Alepha::Cavorite ::detail:: console void Console::setRaw() { - setRawModeWithMin( pimpl().fd, 1 ); - orig.emplace_back( now, mode ); - mode= raw; + const auto old= setRawModeWithMin( pimpl().fd, 1 ); + pimpl().modeStack.emplace( old, pimpl().mode ); + pimpl().mode= raw; } void Console::setNoblock() { - setRawModeWithMin( pimpl().fd, 0 ); - orig.emplace_back( now, mode ); - mode= raw; + const auto old= setRawModeWithMin( pimpl().fd, 0 ); + pimpl().modeStack.emplace( old, pimpl().mode ); + pimpl().mode= raw; } void Console::killLineTail() { csi() << 'K'; } @@ -329,7 +351,7 @@ namespace Alepha::Cavorite ::detail:: console void Console::gotoX( const int x ) { csi() << x << 'G'; } void - Console::gotoY( const int x ) + Console::gotoY( const int y ) { cursorUp( 1'000'000 ); cursorDown( y ); @@ -349,7 +371,7 @@ namespace Alepha::Cavorite ::detail:: console SGR_String exports::setBlink() { return { "5" }; } SGR_String - exports::setFGColor( const BasicTextColor c ) + exports::setFgColor( const BasicTextColor c ) { std::ostringstream oss; oss << '3' << int( c ); @@ -357,7 +379,7 @@ namespace Alepha::Cavorite ::detail:: console } SGR_String - exports::setBGColor( const BasicTextColor c ) + exports::setBgColor( const BasicTextColor c ) { std::ostringstream oss; oss << '4' << int( c ); @@ -368,7 +390,7 @@ namespace Alepha::Cavorite ::detail:: console exports::setColor( const BasicTextColor fg, const BasicTextColor bg ) { std::ostringstream oss; - oss << '3' << fg << ";4" << int( bg ); + oss << '3' << int( fg ) << ";4" << int( bg ); return { std::move( oss ).str() }; } @@ -393,7 +415,54 @@ namespace Alepha::Cavorite ::detail:: console exports::setExtColor( const TextColor fg, const TextColor bg ) { std::ostringstream oss; - oss << "38;2" << fg << "48;2" << int( bg ); + oss << "38;2" << int( fg ) << "48;2" << int( bg ); return { std::move( oss ).str() }; } + + int + exports::getConsoleWidth() + { + return cachedScreenWidth; + } + + int + Console::getScreenWidth() + { + if( not pimpl().cachedScreenWidth.has_value() ) + { + pimpl().cachedScreenWidth= getScreenSize().columns; + } + + return pimpl().cachedScreenWidth.value(); + } + + ScreenSize + Console::getScreenSize() + try + { + if( not isatty( pimpl().fd ) ) throw UnknownScreenError{}; + + // Use the `ioctl( TIOCGWINSZ )`, but we'll just defer to 24x80 if we fail that... + struct winsize ws; + const int ec= ioctl( pimpl().fd, TIOCGWINSZ, &ws ); + if( ec == -1 or ws.ws_col == 0 ) throw UnknownScreenError{}; + + return { ws.ws_row, ws.ws_col }; + } + catch( const UnknownScreenError & ) { return { 24, 80 }; } // Fallback position.... + + namespace + { + namespace storage + { + std::unique_ptr< Console > console; + } + } + + Console & + Console::main() + { + if( not storage::console ) storage::console= std::make_unique< Console >( 1 ); // stdout + return *storage::console; + } } diff --git a/Console.h b/Console.h index f922a1e..7e9ff15 100644 --- a/Console.h +++ b/Console.h @@ -2,6 +2,11 @@ static_assert( __cplusplus > 2020'00 ); #pragma once +#include +#include + +#include + // These are some terminal/console control primitives. // There are several "modern" terminal assumptions built // into this library. @@ -20,8 +25,6 @@ namespace Alepha::inline Cavorite ::detail:: console class Console; - Console &console() noexcept; - struct SGR_String { std::string code; @@ -52,7 +55,7 @@ namespace Alepha::inline Cavorite ::detail:: console // TODO: Move this to its own library. const std::string &applicationName(); - void setApplication( std::string name ); + void setApplicationName( std::string name ); } struct exports::ScreenSize @@ -77,13 +80,13 @@ namespace Alepha::inline Cavorite ::detail:: console std::ostream &csi(); + public: // A console object can only be constructed on a raw UNIX file descriptor. explicit Console( int fd ); - enum Mode; - - Mode getMode() const; + static Console &main(); + auto getMode() const; int getScreenWidth(); int getScreenHeight(); @@ -142,12 +145,12 @@ namespace Alepha::inline Cavorite ::detail:: console [[nodiscard]] SGR_String setBlink(); - [[nodiscard]] SGR_String setFGColor( BasicTextColor fg ); - [[nodiscard]] SGR_String setBGColor( BasicTextColor bg ); + [[nodiscard]] SGR_String setFgColor( BasicTextColor fg ); + [[nodiscard]] SGR_String setBgColor( BasicTextColor bg ); [[nodiscard]] SGR_String setColor( BasicTextColor fg, BasicTextColor bg ); - [[nodiscard]] SGR_String setExtFGColor( TextColor fg ); - [[nodiscard]] SGR_String setExtBGColor( TextColor fg ); + [[nodiscard]] SGR_String setExtFgColor( TextColor fg ); + [[nodiscard]] SGR_String setExtBgColor( TextColor fg ); [[nodiscard]] SGR_String setExtColor( TextColor fg, TextColor bg ); // Basic color wrapping aliases: @@ -159,11 +162,18 @@ namespace Alepha::inline Cavorite ::detail:: console [[nodiscard]] inline SGR_String setExtColor( const BasicTextColor fg, const BasicTextColor bg ) { return setExtColor( static_cast< TextColor >( fg ), static_cast< TextColor >( bg ) ); } [[nodiscard]] SGR_String setFgTrueColor( int rgb ); - [[nodiscard]] SGR_String setFgTrueColor( int r, int g, int b ) + [[nodiscard]] SGR_String setFgTrueColor( int r, int g, int b ); [[nodiscard]] SGR_String setBgTrueColor( int rgb ); - [[nodiscard]] SGR_String setBgTrueColor( int r, int g, int b ) + [[nodiscard]] SGR_String setBgTrueColor( int r, int g, int b ); void sendSGR( std::ostream &os, SGR_String ); + + int getConsoleWidth(); } } + +namespace Alepha::Cavorite::inline exports::inline console +{ + using namespace detail::console::exports; +} diff --git a/ConstexprString.h b/ConstexprString.h index 47c6765..4316937 100644 --- a/ConstexprString.h +++ b/ConstexprString.h @@ -11,7 +11,7 @@ static_assert( __cplusplus > 2020'00 ); #include -namespace Alepha::Hydrogen ::detail:: constexpr_string +namespace Alepha::Cavorite ::detail:: constexpr_string { namespace C { @@ -63,7 +63,7 @@ namespace Alepha::Hydrogen ::detail:: constexpr_string constexpr ConstexprString()= default; constexpr - CosntexprString( const char *const s, std::size_t len ) + ConstexprString( const char *const s, std::size_t len ) { if( len >= C::maxSize ) throw BadConstantStringAllocationError{}; diff --git a/Makefile b/Makefile index c58d433..1d210f2 100644 --- a/Makefile +++ b/Makefile @@ -1 +1,5 @@ -CXXFLAGS+= -std=c++17 -I . +CXXFLAGS+= -std=c++20 -I . + +all: example + +example: ProgramOptions.o string_algorithms.o Console.o word_wrap.o diff --git a/ProgramOptions.cpp b/ProgramOptions.cpp index 42d2606..c075ae1 100644 --- a/ProgramOptions.cpp +++ b/ProgramOptions.cpp @@ -1,10 +1,14 @@ static_assert( __cplusplus > 2020'00 ); -#include "Options.h" +#include "ProgramOptions.h" #include +#include -#include "algorithm.h" +#include +#include +#include +#include namespace Alepha::Cavorite ::detail:: program_options { @@ -30,7 +34,7 @@ namespace Alepha::Cavorite ::detail:: program_options void printString( const std::string &s, const std::size_t indent ) { - const std::size_t width= Console::main().getScreenWidth(); + const std::size_t width= getConsoleWidth(); std::cout << wordWrap( s, width, indent ) << std::endl; } } @@ -72,7 +76,7 @@ namespace Alepha::Cavorite ::detail:: program_options impl::checkArgument( const std::optional< std::string > &argument, const std::string &name ) { if( argument.has_value() ) return; - throw OptionMissingArgumentError( '`' + name "` requires an argument." ); + throw OptionMissingArgumentError( '`' + name + "` requires an argument." ); } const OptionBinding & @@ -105,7 +109,7 @@ namespace Alepha::Cavorite ::detail:: program_options } std::ostream & - OptionBinding::operator << ( std::function< void ( std::optional< std::string > ) core ) const + OptionBinding::operator << ( std::function< void ( std::string ) > core ) const { // So that users do not have to implement their own checking for argument present, // we do it for them. @@ -133,7 +137,7 @@ namespace Alepha::Cavorite ::detail:: program_options namespace { std::string - buildIncompatibleHelpText( const std::string &name, const auto &domains ) + buildIncompatibleHelpText( const std::string &name, const auto &domains, const auto &exclusivityMembers ) { if( not domains.contains( typeid( ExclusivityDomain ) ) or domains.at( typeid( ExclusivityDomain ) ).empty() ) @@ -144,8 +148,8 @@ namespace Alepha::Cavorite ::detail:: program_options std::set< std::string > incompatibles; for( const auto &domain: domains.at( typeid( ExclusivityDomain ) ) ) { - std::transform( mutuallyExclusiveOptions.lower_bound( domain ), - mutuallyExclusiveOptions.upper_bound( domain ), + std::transform( exclusivityMembers.lower_bound( domain ), + exclusivityMembers.upper_bound( domain ), std::inserter( incompatibles, end( incompatibles ) ), []( const auto &item ) { return item.second; } ); } @@ -167,13 +171,13 @@ namespace Alepha::Cavorite ::detail:: program_options void printAllOptionsHelp( const std::optional< std::string > canonicalProgramName ) { - const auto maxOptionLength= std::max_element( begin( programOptions(), end( programOptions ), + const auto longestOption= std::max_element( begin( programOptions() ), end( programOptions() ), []( const auto &lhs, const auto &rhs ) { return lhs.first.size() < rhs.first.size(); } ); // Account for the `:` and the ` ` in the output table format. - const std::size_t alignmentWidth= maxOptionLength + 2; + const std::size_t alignmentWidth= longestOption->first.size() + 2; // std::multimap< const DomainBase *, std::string > exclusivityMembers; @@ -193,14 +197,14 @@ namespace Alepha::Cavorite ::detail:: program_options { const auto &[ _, helpText, defaultBuilder, domains ]= def; // How much unused of the max width there will be - const std::size_t padding= alignmentWidth - optionName.size() - 2; + const std::size_t padding= alignmentWidth - name.size() - 2; VariableMap substitutions= { // This uses a GNU extension, but it's fine. We can always make this // portable, later. { "program-name"s, lambaste<=::program_invocation_short_name }, - { "option-name"s, lambaste<=optionName }, + { "option-name"s, lambaste<=name }, { "default"s, [&defaultBuilder= defaultBuilder, &name= name] { return "Default is `" + name + defaultBuilder() + "`"; @@ -208,23 +212,24 @@ namespace Alepha::Cavorite ::detail:: program_options }; if( canonicalProgramName.has_value() ) { - substitutions[ "canonical-name"s ]= lambaste<=canonicalName.value(); + substitutions[ "canonical-name"s ]= lambaste<=canonicalProgramName.value(); } std::string substitutionTemplate= name + ": " + std::string( padding, ' ' ) + helpText.str() + "\n"; // Append the incompatibility text, when we see mutually-exclusive options. - substitutionTemplate+= buildIncompatibleHelpText( name, domains ); + substitutionTemplate+= buildIncompatibleHelpText( name, domains, exclusivityMembers ); const std::string helpString= expandVariables( substitutionTemplate, substitutions, '!' ); printString( helpString, alignmentWidth ); + std::cout << std::endl; } // Check for required options, and print a summary of those: - if( not requiredOptions().empty() ) for( const auto &[ _, group ]: requiredOptions ) + if( not requiredOptions().empty() ) for( const auto &[ _, group ]: requiredOptions() ) { - const std::size_t width= Console::main().getScreenWidth(); + const std::size_t width= getConsoleWidth(); std::ostringstream oss; oss << "At least one of the options in this group are required: "; bool first= true; @@ -232,7 +237,7 @@ namespace Alepha::Cavorite ::detail:: program_options { if( not first ) oss << ", "; first= false; - oss << '`' << required << '`': + oss << '`' << required << '`'; } std::cout << wordWrap( oss.str(), width ) << std::endl; @@ -244,7 +249,7 @@ namespace Alepha::Cavorite ::detail:: program_options std::ostream & OptionBinding::operator << ( bool &flag ) const { - --OptionString{ "no-" + name.substr( 2 ) }; + --OptionString{ "no-" + name.substr( 2 ) } << [&flag] { flag= false; } << "Disable `" + name + "`. See that option for more details."; return self() << [&flag] { flag= true; }; } @@ -267,7 +272,7 @@ namespace Alepha::Cavorite ::detail:: program_options } [[noreturn]] void - impl::usage( const std::string &helpmessage, const std::optional< std::string > &canonicalName ) + impl::usage( const std::string &helpMessage, const std::optional< std::string > &canonicalName ) { if( not helpMessage.empty() ) { @@ -278,11 +283,11 @@ namespace Alepha::Cavorite ::detail:: program_options }; if( canonicalName.has_value() ) substitutions[ "canonical-name"s ]= lambaste<=canonicalName.value(); - std::cout << wordWrap( expandVariables( helpMessage, substitutions, '!' ), Console::main().getScreenWidth() ) + std::cout << wordWrap( expandVariables( helpMessage, substitutions, '!' ), getConsoleWidth() ) << std::endl << std::endl; } - printOptionsHelp( canonicalName ); + printAllOptionsHelp( canonicalName ); ::exit( EXIT_SUCCESS ); } @@ -331,12 +336,12 @@ namespace Alepha::Cavorite ::detail:: program_options // doing a map lookup. for( const auto &[ name, def ]: opts ) { - if( C::debugMatching ) error() << "Attempting to match `" << name << "` to `" << arg << "`" << std::endl; + if( C::debugMatching ) error() << "Attempting to match `" << name << "` to `" << param << "`" << std::endl; const auto &handler= def.handler; std::optional< std::string > argument; if( param == name ) argument= std::nullopt; - else if( param.starts_with( name ) and "=:"s.contains( param.at( name.size() ) ) ) + else if( param.starts_with( name ) and "=:"s.find( param.at( name.size() ) ) != std::string::npos ) { argument= param.substr( name.size() + 1 ); } @@ -352,12 +357,12 @@ namespace Alepha::Cavorite ::detail:: program_options if( C::debugExclusions ) { error() << "I see " << exclusions.size() << " mutual exclusions against `" - << name << "`" std::endl; + << name << "`" << std::endl; } for( const auto &exclusion: exclusions ) { // Look up this domain, and see if something from it was used. - auto &other= exclusiveOptions()[ exclusion ].previousOption; + auto &other= mutuallyExclusiveOptions()[ exclusion ].previous; if( other.has_value() and other != name ) { throw std::runtime_error{ "Options `" + other.value() + "` and `" @@ -380,8 +385,8 @@ namespace Alepha::Cavorite ::detail:: program_options } return false; }; - if( C::debugMatching and not found ) error() << "No match for `" << param << "` was found." << std::endl; - if( found ) continue; + if( C::debugMatching and not matched ) error() << "No match for `" << param << "` was found." << std::endl; + if( matched ) continue; rv.push_back( param ); if( param.starts_with( "--" ) ) @@ -392,8 +397,8 @@ namespace Alepha::Cavorite ::detail:: program_options } catch( const OptionMissingArgumentError &e ) { - if( next == end( argsForProcessing ) or next->startsWith( "--" ) ) throw; - throw std::runtime_error( ex.what() + " did you mean: `"s + param + "=" + *next + "`?" ); + if( next == end( argsToProcess ) or next->starts_with( "--" ) ) throw; + throw std::runtime_error( e.what() + " did you mean: `"s + param + "=" + *next + "`?" ); } if( endOfArgs != end( args ) ) std::copy( endOfArgs + 1, end( args ), back_inserter( rv ) ); @@ -402,14 +407,14 @@ namespace Alepha::Cavorite ::detail:: program_options // If we're not doing a help-run, then we need to validate the required // options were all passed. - if( requiredOptions.size() != requiredOptionsSeen.size() ) + if( requiredOptions().size() != requiredOptionsSeen.size() ) { for( auto [ required, opts ]: requiredOptions() ) { if( requiredOptionsSeen.contains( required ) ) continue; std::ostringstream oss; - oss <<< "Required option missing. At least one of "; + oss << "Required option missing. At least one of "; bool first= true; for( const auto &name: opts ) { @@ -422,7 +427,7 @@ namespace Alepha::Cavorite ::detail:: program_options throw std::runtime_error( oss.str() ); } - throw std::runtime_error{ "A required option was missing, and it couldn't be identified." ); + throw std::runtime_error{ "A required option was missing, and it couldn't be identified." }; } return rv; diff --git a/ProgramOptions.h b/ProgramOptions.h index a597e51..ecbf3a5 100644 --- a/ProgramOptions.h +++ b/ProgramOptions.h @@ -81,6 +81,17 @@ static_assert( __cplusplus > 2020'00 ); #include #include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include namespace Alepha::inline Cavorite ::detail:: program_options { @@ -142,7 +153,7 @@ namespace Alepha::inline Cavorite ::detail:: program_options using RequirementDomain= Domain< requirement_tag >; - using PreHelpDoimain= Domain< pre_help_tag >; + using PreHelpDomain= Domain< pre_help_tag >; inline const PreHelpDomain affectsHelp; } @@ -159,7 +170,7 @@ namespace Alepha::inline Cavorite ::detail:: program_options throw std::runtime_error( "Error parsing option `" + argName + "`, with parameter string: `" + s + "` (full option: `" + fullOption + "`)" ); } - namespace impl + inline namespace impl { struct ProgramOption; @@ -211,7 +222,7 @@ namespace Alepha::inline Cavorite ::detail:: program_options { return self() << [&list, name= name]( const std::string param ) { - for( const std:;string &datum: parseCommas( param ) ) + for( const std::string &datum: parseCommas( param ) ) { if constexpr( Integral< T > ) { @@ -240,7 +251,7 @@ namespace Alepha::inline Cavorite ::detail:: program_options // Boolean flag options are a special case of the value-binding system. // They generate `--no-` forms of the option as well. - OptionBinding operator << ( bool &flag ) const; + std::ostream &operator << ( bool &flag ) const; template< NotFunctional T > [[nodiscard]] std::ostream & @@ -269,12 +280,12 @@ namespace Alepha::inline Cavorite ::detail:: program_options operator << ( UnaryFunction auto handler ) const { using arg_type= get_arg_t< std::decay_t< decltype( handler ) >, 0 >; - if constexpr( is_vector_v< arg_type > ) + if constexpr( Vector< arg_type > ) { // TODO: This should steal the impl from the vector form, above, and that should defer to this. using parse_type= typename arg_type::value_type; - auto handler= [core, name= name]( std::optional< std::string > argument ) + auto handler= [handler, name= name]( std::optional< std::string > argument ) { impl::checkArgument( argument, name ); @@ -293,28 +304,26 @@ namespace Alepha::inline Cavorite ::detail:: program_options } return rv; }; - core( parsed ); + handler( parsed ); }; return registerHandler( handler ); } else { - auto handler= [core, name= name]( std::optional< std::string > argument ) + auto wrapped= [handler, name= name]( std::optional< std::string > argument ) { impl::checkArgument( argument, name ); const auto value= argumentFromString< arg_type >( argument.value(), name, name + "=" + argument.value() ); - return core( value ); + return handler( value ); }; - return registerHandler( handler ); + return registerHandler( wrapped ); } } }; void printString( const std::string &s, const std::size_t indent ); - void printOptionsHelp(); - struct OptionString { std::string name; }; namespace exports::inline literals @@ -324,7 +333,7 @@ namespace Alepha::inline Cavorite ::detail:: program_options inline namespace impl { - [[nodiscard]] OptoinBinding operator --( OptionString option ); + [[nodiscard]] OptionBinding operator --( OptionString option ); } struct ProgramDescription @@ -354,23 +363,23 @@ namespace Alepha::inline Cavorite ::detail:: program_options auto handleOptions( const std::vector< std::string > &args ) { - return impl::handleOptions( args, usageWrap< T > ); + return impl::handleOptions( args, usageWrap< Supplement > ); } template< typename Supplement > auto handleOptions( const int argcnt, const char *const *const argvec ) { - return handleOptions< T >( { argvec + 1, argvec + argcnt }, usageWrap< T > ); + return handleOptions< Supplement >( { argvec + 1, argvec + argcnt } ); } - auto + inline auto handleOptions( const std::vector< std::string > &args ) { return handleOptions< ProgramDescription >( args ); } - auto + inline auto handleOptions( const int argcnt, const char *const *const argvec ) { return handleOptions< ProgramDescription >( argcnt, argvec ); diff --git a/StaticValue.h b/StaticValue.h index 444189d..5cae363 100644 --- a/StaticValue.h +++ b/StaticValue.h @@ -4,7 +4,7 @@ static_assert( __cplusplus > 2020'00 ); #include -#include +#include namespace Alepha::inline Cavorite ::detail:: static_value { @@ -121,7 +121,7 @@ namespace Alepha::inline Cavorite ::detail:: static_value * } * ``` */ - template< typename T, typename init_helper= default_init > + template< typename T, typename init_helper= default_init< T > > class exports::StaticValue : boost::noncopyable { private: @@ -141,7 +141,7 @@ namespace Alepha::inline Cavorite ::detail:: static_value return *storage; } - constexpr auto + constexpr decltype( auto ) operator() () noexcept { return get(); diff --git a/example.cc b/example.cc new file mode 100644 index 0000000..07efc88 --- /dev/null +++ b/example.cc @@ -0,0 +1,35 @@ +static_assert( __cplusplus > 2020'00 ); + +#include "ProgramOptions.h" + +namespace +{ + using namespace Alepha::literals::option_literals; + using namespace std::literals::string_literals; + + int optionA= 42; + std::optional< std::string > optionB; + + auto init= Alepha::enroll <=[] + { + --"set-a"_option << optionA << "The option is an integer. !default!"; + --"set-b"_option << optionB << "The option is a string, no defaults."; + }; +} + +int +main( const int argcnt, const char *const *const argvec ) +try +{ + const auto args= Alepha::handleOptions( argcnt, argvec ); + + std::cout << "A is set to: " << optionA << std::endl; + std::cout << "B is set to: " << ( optionB.has_value() ? optionB.value() : "nullopt"s ) << std::endl; + + return EXIT_SUCCESS; +} +catch( const std::exception &ex ) +{ + std::cerr << "Error: " << ex.what() << std::endl; + return EXIT_FAILURE; +} diff --git a/string_algorithms.cpp b/string_algorithms.cpp index c6881b4..4fc147a 100644 --- a/string_algorithms.cpp +++ b/string_algorithms.cpp @@ -5,7 +5,7 @@ static_assert( __cplusplus > 2020'00 ); #include #include -#include "error.hpp" +#include "error.h" namespace Alepha::Cavorite ::detail:: string_algorithms { @@ -31,7 +31,7 @@ namespace Alepha::Cavorite ::detail:: string_algorithms for( const char ch: text ) { - if( mode == normal and ch == sigil ) + if( mode == Normal and ch == sigil ) { mode= Symbol; varName.clear(); @@ -46,7 +46,7 @@ namespace Alepha::Cavorite ::detail:: string_algorithms { throw std::runtime_error( "No such variable: `" + varName + "`" ); } - if( C::debugVariableExpansion ) error() << "Expanding variable with name `" << varName << "`" << std::endl; + if( C::debugExpansion ) error() << "Expanding variable with name `" << varName << "`" << std::endl; rv+= vars.at( varName )(); } else rv+= sigil; @@ -95,4 +95,22 @@ namespace Alepha::Cavorite ::detail:: string_algorithms return rv; } + + std::vector< std::string > + exports::split( const std::string &s, const char token ) + { + std::vector< std::string > rv; + std::string next; + for( const char ch: s ) + { + if( ch != token ) + { + next+= ch; + continue; + } + rv.push_back( std::move( next ) ); + next.clear(); + } + return rv; + } } diff --git a/string_algorithms.h b/string_algorithms.h index 3d86162..efd2852 100644 --- a/string_algorithms.h +++ b/string_algorithms.h @@ -6,10 +6,16 @@ static_assert( __cplusplus > 2020'00 ); #include #include +#include +#include #include #include #include +#include + +#include + namespace Alepha::inline Cavorite ::detail:: string_algorithms { inline namespace exports {} @@ -18,6 +24,8 @@ namespace Alepha::inline Cavorite ::detail:: string_algorithms inline namespace exports { + using VariableMap= VarMap; + /*! * Returns a new string with text-replacement variables expanded. * @@ -39,6 +47,7 @@ namespace Alepha::inline Cavorite ::detail:: string_algorithms */ std::vector< std::string > parseCommas( const std::string &text ); + std::vector< std::string > split( const std::string &s, char token ); /*! * Parses an integral range description into a vector of values. @@ -62,10 +71,11 @@ namespace Alepha::inline Cavorite ::detail:: string_algorithms std::iota( begin( rv ), end( rv ), low ); return rv; + } } } namespace Alepha::Cavorite::inline exports::inline string_algorithms { - using namespace detail::exports::string_algorithms; + using namespace detail::string_algorithms::exports; } diff --git a/type_lisp.h b/type_lisp.h index e5b1efc..bd0bfb1 100644 --- a/type_lisp.h +++ b/type_lisp.h @@ -27,7 +27,7 @@ namespace Alepha::inline Cavorite ::detail:: type_lisp namespace exports { template< typename Type > - concept TypeListType= is_type_list_v< Type >: + concept TypeListType= is_type_list_v< Type >; } template< typename T > diff --git a/word_wrap.cpp b/word_wrap.cpp index 945b47d..0a0f76f 100644 --- a/word_wrap.cpp +++ b/word_wrap.cpp @@ -2,6 +2,13 @@ static_assert( __cplusplus > 2020 ); #include "word_wrap.h" +#include + +#include +#include + +#include "evaluation_helpers.h" + namespace Alepha::Cavorite ::detail:: word_wrap { namespace @@ -16,8 +23,14 @@ namespace Alepha::Cavorite ::detail:: word_wrap { if( currentLineWidth + word.size() > maximumWidth ) { + //std::cerr << "Going to newline on word: " << word << "(currentLineWidth= " << currentLineWidth << ")" << std::endl; result+= '\n'; - std::fill_n( back_inserter( result ), ' ', nextLineOffset ); + //const auto orig= result.size(); + std::fill_n( back_inserter( result ), nextLineOffset, ' ' ); + //assert( orig + nextLineOffset == result.size() ); + //std::cerr << "orig: " << orig << " nextLineOffset: " << nextLineOffset << " result: " << result.size() << std::endl; + + //result+= '%'; return nextLineOffset; } else return currentLineWidth; @@ -32,9 +45,10 @@ namespace Alepha::Cavorite ::detail:: word_wrap std::string exports::wordWrap( const std::string &text, const std::size_t width, const std::size_t nextLineOffset ) { - auto putWord= [[nodiscard]] [width, nextLineOffset]( std::string &&word, std::string &line, const std::size_t lineLength ) + auto putWord= [width, nextLineOffset]( std::string &&word, std::string &line, const std::size_t lineLength ) { - return applyWordToLine( width, nextLineOffset, line.size(), std::move( word ), line ); + const auto rv= applyWordToLine( width, nextLineOffset, lineLength, std::move( word ), line ); + return rv; }; std::string result; @@ -54,7 +68,7 @@ namespace Alepha::Cavorite ::detail:: word_wrap word.clear(); if( lineLength < width ) { - line+= ' '; + result+= ' '; lineLength++; } } diff --git a/word_wrap.h b/word_wrap.h index 8353043..5f0ed97 100644 --- a/word_wrap.h +++ b/word_wrap.h @@ -4,6 +4,8 @@ static_assert( __cplusplus > 2020'00 ); #include +#include + namespace Alepha::inline Cavorite ::detail:: word_wrap { inline namespace exports @@ -11,3 +13,8 @@ namespace Alepha::inline Cavorite ::detail:: word_wrap std::string wordWrap( const std::string &text, std::size_t width, std::size_t nextLineOffset= 0 ); } } + +namespace Alepha::Cavorite::inline exports::inline word_wrap +{ + using namespace detail::word_wrap::exports; +}