1
0
forked from Alepha/Alepha

Add a simple commandline parser example.

Also cleaned up a few files.  Since these were pulled from various
scratch repos from informal ISO discussions over the years, they don't
quite line up.  This at least gets everything building again.
This commit is contained in:
2023-10-10 02:24:58 -04:00
parent 83f693de53
commit 76606fca97
15 changed files with 352 additions and 146 deletions

View File

@ -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 >;
}

View File

@ -7,6 +7,7 @@ static_assert( __cplusplus > 2020'00 );
#include <iosfwd>
#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;
}

View File

@ -1,13 +1,25 @@
static_assert( __cplusplus > 2020'00 );
#include "console.h"
#include "Console.h"
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stack>
#include <vector>
#include <utility>
#include <string>
#include <sstream>
#include <iostream>
#include <ext/stdio_filebuf.h>
#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();
}
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]
@ -237,20 +280,6 @@ 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 ) )
@ -262,18 +291,11 @@ 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;
}
}

View File

@ -2,6 +2,11 @@ static_assert( __cplusplus > 2020'00 );
#pragma once
#include <string>
#include <memory>
#include <Alepha/TotalOrder.h>
// 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;
}

View File

@ -11,7 +11,7 @@ static_assert( __cplusplus > 2020'00 );
#include <Alepha/Alepha.h>
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{};

View File

@ -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

View File

@ -1,10 +1,14 @@
static_assert( __cplusplus > 2020'00 );
#include "Options.h"
#include "ProgramOptions.h"
#include <set>
#include <exception>
#include "algorithm.h"
#include <Alepha/Console.h>
#include <Alepha/word_wrap.h>
#include <Alepha/StaticValue.h>
#include <Alepha/error.h>
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;

View File

@ -81,6 +81,17 @@ static_assert( __cplusplus > 2020'00 );
#include <string>
#include <typeindex>
#include <exception>
#include <stdexcept>
#include <optional>
#include <vector>
#include <boost/lexical_cast.hpp>
#include <Alepha/Alepha.h>
#include <Alepha/Concepts.h>
#include <Alepha/string_algorithms.h>
#include <Alepha/evaluation_helpers.h>
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 );

View File

@ -4,7 +4,7 @@ static_assert( __cplusplus > 2020'00 );
#include <tuple>
#include <boost/noncoyable.hpp>
#include <boost/noncopyable.hpp>
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();

35
example.cc Normal file
View File

@ -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;
}

View File

@ -5,7 +5,7 @@ static_assert( __cplusplus > 2020'00 );
#include <algorithm>
#include <exception>
#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;
}
}

View File

@ -6,10 +6,16 @@ static_assert( __cplusplus > 2020'00 );
#include <iostream>
#include <functional>
#include <algorithm>
#include <numeric>
#include <vector>
#include <string>
#include <map>
#include <boost/lexical_cast.hpp>
#include <Alepha/Concepts.h>
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.
@ -64,8 +73,9 @@ namespace Alepha::inline Cavorite ::detail:: string_algorithms
return rv;
}
}
}
namespace Alepha::Cavorite::inline exports::inline string_algorithms
{
using namespace detail::exports::string_algorithms;
using namespace detail::string_algorithms::exports;
}

View File

@ -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 >

View File

@ -2,6 +2,13 @@ static_assert( __cplusplus > 2020 );
#include "word_wrap.h"
#include <cassert>
#include <tuple>
#include <iostream>
#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++;
}
}

View File

@ -4,6 +4,8 @@ static_assert( __cplusplus > 2020'00 );
#include <cstddef>
#include <string>
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;
}