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:
20
AutoRAII.h
20
AutoRAII.h
@ -79,6 +79,26 @@ namespace Alepha::Hydrogen
|
|||||||
decltype( auto ) operator->() const { return value; }
|
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 >
|
template< typename Ctor, typename Dtor >
|
||||||
explicit AutoRAII( Ctor ctor, Dtor ) -> AutoRAII< decltype( ctor() ), Dtor >;
|
explicit AutoRAII( Ctor ctor, Dtor ) -> AutoRAII< decltype( ctor() ), Dtor >;
|
||||||
}
|
}
|
||||||
|
17
Concepts.h
17
Concepts.h
@ -7,6 +7,7 @@ static_assert( __cplusplus > 2020'00 );
|
|||||||
#include <iosfwd>
|
#include <iosfwd>
|
||||||
|
|
||||||
#include "meta.h"
|
#include "meta.h"
|
||||||
|
#include "function_traits.h"
|
||||||
|
|
||||||
namespace Alepha::inline Cavorite ::detail:: core_concepts
|
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 >;
|
concept ConvertibleTo= std::convertible_to< T, U >;
|
||||||
|
|
||||||
template< typename T, typename Base >
|
template< typename T, typename Base >
|
||||||
concept DerivedFrom= std::derived_from< T, Base >
|
concept DerivedFrom= std::derived_from< T, Base >;
|
||||||
|
|
||||||
template< typename T >
|
template< typename T >
|
||||||
concept FloatingPoint= std::floating_point< T >;
|
concept FloatingPoint= std::floating_point< T >;
|
||||||
@ -50,7 +51,7 @@ namespace Alepha::inline Cavorite ::detail:: core_concepts
|
|||||||
template< typename T, typename Target >
|
template< typename T, typename Target >
|
||||||
concept ConvertibleToButNotSameAs= true
|
concept ConvertibleToButNotSameAs= true
|
||||||
and not SameAs< T, Target >
|
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 >
|
template< typename T >
|
||||||
concept IStreamable=
|
concept IStreamable=
|
||||||
requires( const T &t, std::istream &os )
|
requires( const T &t, std::istream &is )
|
||||||
{
|
{
|
||||||
{ is >> t } -> SameAs< std::istream & >;
|
{ is >> t } -> SameAs< std::istream & >;
|
||||||
};
|
};
|
||||||
@ -188,7 +189,7 @@ namespace Alepha::inline Cavorite ::detail:: core_concepts
|
|||||||
;
|
;
|
||||||
|
|
||||||
template< typename T >
|
template< typename T >
|
||||||
concept Primitive= Floatish< T > or Intish< T >;
|
concept Primitive= FloatingPoint< T > or Integral< T >;
|
||||||
|
|
||||||
template< typename T >
|
template< typename T >
|
||||||
concept Aggregate= std::is_aggregate_v< T >;
|
concept Aggregate= std::is_aggregate_v< T >;
|
||||||
@ -203,6 +204,9 @@ namespace Alepha::inline Cavorite ::detail:: core_concepts
|
|||||||
template< typename T >
|
template< typename T >
|
||||||
concept NotFunctional= not Functional< T >;
|
concept NotFunctional= not Functional< T >;
|
||||||
|
|
||||||
|
template< typename T >
|
||||||
|
concept UnaryFunction= Functional< T > and function_traits< T >::args_size == 1;
|
||||||
|
|
||||||
template< typename T >
|
template< typename T >
|
||||||
concept StandardLayout= std::is_standard_layout_v< 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 >
|
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 >
|
template< typename Member, typename Seq >
|
||||||
concept SequenceOf= Sequence< Seq > and SpecializedOn< Seq, Member >;
|
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
|
namespace Alepha::Cavorite::inline exports::inline core_concepts
|
||||||
{
|
{
|
||||||
|
using namespace detail::core_concepts::exports;
|
||||||
|
}
|
||||||
|
197
Console.cpp
197
Console.cpp
@ -1,13 +1,25 @@
|
|||||||
static_assert( __cplusplus > 2020'00 );
|
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 <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 "Enum.h"
|
||||||
|
#include "ProgramOptions.h"
|
||||||
#include "StaticValue.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).
|
* 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";
|
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 screenWidthEnv() { return applicationName() + "_SCREEN_WIDTH"; }
|
||||||
auto screenWidthEnvLimit() { return applicationName() + "_SCREEN_WIDTH_LIMIT"; }
|
auto screenWidthEnvLimit() { return applicationName() + "_SCREEN_WIDTH_LIMIT"; }
|
||||||
auto disableColorsEnv() { return applicationName() + "_DISABLE_COLOR_TEXT"; }
|
auto disableColorsEnv() { return applicationName() + "_DISABLE_COLOR_TEXT"; }
|
||||||
@ -74,7 +91,7 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
if( getenv( env.c_str() ) )
|
if( getenv( env.c_str() ) )
|
||||||
try
|
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 & ) {}
|
catch( const boost::bad_lexical_cast & ) {}
|
||||||
return d;
|
return d;
|
||||||
@ -82,7 +99,7 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
|
|
||||||
int cachedScreenWidth= evaluate <=[]
|
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 ) );
|
return std::min( underlying, getEnvOrDefault( screenWidthEnvLimit(), C::defaultScreenWidthLimit ) );
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,11 +109,11 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
bool
|
bool
|
||||||
colorEnabled()
|
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.value() == "never"_value ) return false;
|
||||||
if( colorState == "always"_value ) return true;
|
if( colorState.value() == "always"_value ) return true;
|
||||||
assert( colorState == "auto"_value );
|
assert( colorState.value() == "auto"_value );
|
||||||
|
|
||||||
return ::isatty( 1 ); // Auto means only do this for TTYs.
|
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() )
|
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()
|
<< "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 << []
|
--"dump-color-env-var"_option << []
|
||||||
{
|
{
|
||||||
std::cout << "export " << colorsEnv() << "-\"";
|
std::cout << "export " << colorsEnv() << "-\"";
|
||||||
|
bool first= true;
|
||||||
for( const auto &[ name, sgr ]: colorVariables() )
|
for( const auto &[ name, sgr ]: colorVariables() )
|
||||||
{
|
{
|
||||||
if( not first ) std::cout << ":";
|
if( not first ) std::cout << ":";
|
||||||
@ -135,11 +153,11 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
<< "application.";
|
<< "application.";
|
||||||
|
|
||||||
parse_environment_variable_for_color:
|
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, '=' );
|
const auto parsed= split( var, '=' );
|
||||||
if( parsed.size() != 2 )
|
if( parsed.size() != 2 )
|
||||||
@ -199,11 +217,36 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum exports::Console::Mode
|
|
||||||
|
enum ConsoleMode
|
||||||
{
|
{
|
||||||
cooked, raw, noblock,
|
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
|
namespace
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -212,15 +255,15 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
BadScreenStateError() : std::runtime_error( "Error in getting terminal dimensions." ) {}
|
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
|
auto
|
||||||
rawModeGuard( Console console )
|
rawModeGuard( Console console )
|
||||||
{
|
{
|
||||||
const bool skip= console.getMode() == Console::raw;
|
const bool skip= console.getMode() == ConsoleMode::raw;
|
||||||
return AutoRAII
|
return AutoRAII
|
||||||
{
|
{
|
||||||
[skip, &console]
|
[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 )
|
Console::Console( const int fd )
|
||||||
: impl( std::make_unique< Impl >( fd ) )
|
: impl( std::make_unique< Impl >( fd ) )
|
||||||
@ -261,19 +290,12 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
{
|
{
|
||||||
return pimpl().stream;
|
return pimpl().stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Console::Mode
|
|
||||||
Console::getMode() const
|
|
||||||
{
|
|
||||||
return pimpl().mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
Console::popTermMode()
|
Console::popTermMode()
|
||||||
{
|
{
|
||||||
tcsetattr( pimpl().fd, TCSAFLUSH, &pimpl().modeStack.top().first );
|
tcsetattr( pimpl().fd, TCSAFLUSH, &pimpl().modeStack.top().first );
|
||||||
mode= pimpl().modeStack.top().second;
|
pimpl().mode= pimpl().modeStack.top().second;
|
||||||
pimpl().modeStack.pop();
|
pimpl().modeStack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,12 +311,12 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
|
|
||||||
next.c_iflag&= ~( BRKINT | ICRNL | INPCK | ISTRIP | IXON );
|
next.c_iflag&= ~( BRKINT | ICRNL | INPCK | ISTRIP | IXON );
|
||||||
next.c_oflag&= ~( OPOST );
|
next.c_oflag&= ~( OPOST );
|
||||||
next.c_flag|= CS8;
|
next.c_cflag|= CS8;
|
||||||
next.c_lflag&= !( ECHO | ICANNON | IEXTEN | ISIG );
|
next.c_lflag&= !( ECHO | ICANON | IEXTEN | ISIG );
|
||||||
next.c_cc[ VMIN ]= min;
|
next.c_cc[ VMIN ]= min;
|
||||||
next.c_cc[ VTIME ]= 0;
|
next.c_cc[ VTIME ]= 0;
|
||||||
|
|
||||||
if( tcsetattr( pimpl().fd, TCSAFLUSH, &next ) ) throw UnknownScreenException{};
|
if( tcsetattr( fd, TCSAFLUSH, &next ) ) throw UnknownScreenError{};
|
||||||
|
|
||||||
return now;
|
return now;
|
||||||
}
|
}
|
||||||
@ -303,17 +325,17 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
void
|
void
|
||||||
Console::setRaw()
|
Console::setRaw()
|
||||||
{
|
{
|
||||||
setRawModeWithMin( pimpl().fd, 1 );
|
const auto old= setRawModeWithMin( pimpl().fd, 1 );
|
||||||
orig.emplace_back( now, mode );
|
pimpl().modeStack.emplace( old, pimpl().mode );
|
||||||
mode= raw;
|
pimpl().mode= raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Console::setNoblock()
|
Console::setNoblock()
|
||||||
{
|
{
|
||||||
setRawModeWithMin( pimpl().fd, 0 );
|
const auto old= setRawModeWithMin( pimpl().fd, 0 );
|
||||||
orig.emplace_back( now, mode );
|
pimpl().modeStack.emplace( old, pimpl().mode );
|
||||||
mode= raw;
|
pimpl().mode= raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Console::killLineTail() { csi() << 'K'; }
|
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::gotoX( const int x ) { csi() << x << 'G'; }
|
||||||
|
|
||||||
void
|
void
|
||||||
Console::gotoY( const int x )
|
Console::gotoY( const int y )
|
||||||
{
|
{
|
||||||
cursorUp( 1'000'000 );
|
cursorUp( 1'000'000 );
|
||||||
cursorDown( y );
|
cursorDown( y );
|
||||||
@ -349,7 +371,7 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
SGR_String exports::setBlink() { return { "5" }; }
|
SGR_String exports::setBlink() { return { "5" }; }
|
||||||
|
|
||||||
SGR_String
|
SGR_String
|
||||||
exports::setFGColor( const BasicTextColor c )
|
exports::setFgColor( const BasicTextColor c )
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << '3' << int( c );
|
oss << '3' << int( c );
|
||||||
@ -357,7 +379,7 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
}
|
}
|
||||||
|
|
||||||
SGR_String
|
SGR_String
|
||||||
exports::setBGColor( const BasicTextColor c )
|
exports::setBgColor( const BasicTextColor c )
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << '4' << int( c );
|
oss << '4' << int( c );
|
||||||
@ -368,7 +390,7 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
exports::setColor( const BasicTextColor fg, const BasicTextColor bg )
|
exports::setColor( const BasicTextColor fg, const BasicTextColor bg )
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss << '3' << fg << ";4" << int( bg );
|
oss << '3' << int( fg ) << ";4" << int( bg );
|
||||||
return { std::move( oss ).str() };
|
return { std::move( oss ).str() };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,7 +415,54 @@ namespace Alepha::Cavorite ::detail:: console
|
|||||||
exports::setExtColor( const TextColor fg, const TextColor bg )
|
exports::setExtColor( const TextColor fg, const TextColor bg )
|
||||||
{
|
{
|
||||||
std::ostringstream oss;
|
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() };
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
34
Console.h
34
Console.h
@ -2,6 +2,11 @@ static_assert( __cplusplus > 2020'00 );
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <Alepha/TotalOrder.h>
|
||||||
|
|
||||||
// These are some terminal/console control primitives.
|
// These are some terminal/console control primitives.
|
||||||
// There are several "modern" terminal assumptions built
|
// There are several "modern" terminal assumptions built
|
||||||
// into this library.
|
// into this library.
|
||||||
@ -20,8 +25,6 @@ namespace Alepha::inline Cavorite ::detail:: console
|
|||||||
|
|
||||||
class Console;
|
class Console;
|
||||||
|
|
||||||
Console &console() noexcept;
|
|
||||||
|
|
||||||
struct SGR_String
|
struct SGR_String
|
||||||
{
|
{
|
||||||
std::string code;
|
std::string code;
|
||||||
@ -52,7 +55,7 @@ namespace Alepha::inline Cavorite ::detail:: console
|
|||||||
|
|
||||||
// TODO: Move this to its own library.
|
// TODO: Move this to its own library.
|
||||||
const std::string &applicationName();
|
const std::string &applicationName();
|
||||||
void setApplication( std::string name );
|
void setApplicationName( std::string name );
|
||||||
}
|
}
|
||||||
|
|
||||||
struct exports::ScreenSize
|
struct exports::ScreenSize
|
||||||
@ -77,13 +80,13 @@ namespace Alepha::inline Cavorite ::detail:: console
|
|||||||
|
|
||||||
std::ostream &csi();
|
std::ostream &csi();
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// A console object can only be constructed on a raw UNIX file descriptor.
|
// A console object can only be constructed on a raw UNIX file descriptor.
|
||||||
explicit Console( int fd );
|
explicit Console( int fd );
|
||||||
|
|
||||||
enum Mode;
|
static Console &main();
|
||||||
|
auto getMode() const;
|
||||||
Mode getMode() const;
|
|
||||||
|
|
||||||
int getScreenWidth();
|
int getScreenWidth();
|
||||||
int getScreenHeight();
|
int getScreenHeight();
|
||||||
@ -142,12 +145,12 @@ namespace Alepha::inline Cavorite ::detail:: console
|
|||||||
|
|
||||||
[[nodiscard]] SGR_String setBlink();
|
[[nodiscard]] SGR_String setBlink();
|
||||||
|
|
||||||
[[nodiscard]] SGR_String setFGColor( BasicTextColor fg );
|
[[nodiscard]] SGR_String setFgColor( BasicTextColor fg );
|
||||||
[[nodiscard]] SGR_String setBGColor( BasicTextColor bg );
|
[[nodiscard]] SGR_String setBgColor( BasicTextColor bg );
|
||||||
[[nodiscard]] SGR_String setColor( BasicTextColor fg, BasicTextColor bg );
|
[[nodiscard]] SGR_String setColor( BasicTextColor fg, BasicTextColor bg );
|
||||||
|
|
||||||
[[nodiscard]] SGR_String setExtFGColor( TextColor fg );
|
[[nodiscard]] SGR_String setExtFgColor( TextColor fg );
|
||||||
[[nodiscard]] SGR_String setExtBGColor( TextColor fg );
|
[[nodiscard]] SGR_String setExtBgColor( TextColor fg );
|
||||||
[[nodiscard]] SGR_String setExtColor( TextColor fg, TextColor bg );
|
[[nodiscard]] SGR_String setExtColor( TextColor fg, TextColor bg );
|
||||||
|
|
||||||
// Basic color wrapping aliases:
|
// 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]] 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 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 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 );
|
void sendSGR( std::ostream &os, SGR_String );
|
||||||
|
|
||||||
|
int getConsoleWidth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Alepha::Cavorite::inline exports::inline console
|
||||||
|
{
|
||||||
|
using namespace detail::console::exports;
|
||||||
|
}
|
||||||
|
@ -11,7 +11,7 @@ static_assert( __cplusplus > 2020'00 );
|
|||||||
|
|
||||||
#include <Alepha/Alepha.h>
|
#include <Alepha/Alepha.h>
|
||||||
|
|
||||||
namespace Alepha::Hydrogen ::detail:: constexpr_string
|
namespace Alepha::Cavorite ::detail:: constexpr_string
|
||||||
{
|
{
|
||||||
namespace C
|
namespace C
|
||||||
{
|
{
|
||||||
@ -63,7 +63,7 @@ namespace Alepha::Hydrogen ::detail:: constexpr_string
|
|||||||
constexpr ConstexprString()= default;
|
constexpr ConstexprString()= default;
|
||||||
|
|
||||||
constexpr
|
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{};
|
if( len >= C::maxSize ) throw BadConstantStringAllocationError{};
|
||||||
|
|
||||||
|
6
Makefile
6
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
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
static_assert( __cplusplus > 2020'00 );
|
static_assert( __cplusplus > 2020'00 );
|
||||||
|
|
||||||
#include "Options.h"
|
#include "ProgramOptions.h"
|
||||||
|
|
||||||
#include <set>
|
#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
|
namespace Alepha::Cavorite ::detail:: program_options
|
||||||
{
|
{
|
||||||
@ -30,7 +34,7 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
void
|
void
|
||||||
printString( const std::string &s, const std::size_t indent )
|
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;
|
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 )
|
impl::checkArgument( const std::optional< std::string > &argument, const std::string &name )
|
||||||
{
|
{
|
||||||
if( argument.has_value() ) return;
|
if( argument.has_value() ) return;
|
||||||
throw OptionMissingArgumentError( '`' + name "` requires an argument." );
|
throw OptionMissingArgumentError( '`' + name + "` requires an argument." );
|
||||||
}
|
}
|
||||||
|
|
||||||
const OptionBinding &
|
const OptionBinding &
|
||||||
@ -105,7 +109,7 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::ostream &
|
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,
|
// So that users do not have to implement their own checking for argument present,
|
||||||
// we do it for them.
|
// we do it for them.
|
||||||
@ -133,7 +137,7 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
std::string
|
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 ) )
|
if( not domains.contains( typeid( ExclusivityDomain ) )
|
||||||
or domains.at( typeid( ExclusivityDomain ) ).empty() )
|
or domains.at( typeid( ExclusivityDomain ) ).empty() )
|
||||||
@ -144,8 +148,8 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
std::set< std::string > incompatibles;
|
std::set< std::string > incompatibles;
|
||||||
for( const auto &domain: domains.at( typeid( ExclusivityDomain ) ) )
|
for( const auto &domain: domains.at( typeid( ExclusivityDomain ) ) )
|
||||||
{
|
{
|
||||||
std::transform( mutuallyExclusiveOptions.lower_bound( domain ),
|
std::transform( exclusivityMembers.lower_bound( domain ),
|
||||||
mutuallyExclusiveOptions.upper_bound( domain ),
|
exclusivityMembers.upper_bound( domain ),
|
||||||
std::inserter( incompatibles, end( incompatibles ) ),
|
std::inserter( incompatibles, end( incompatibles ) ),
|
||||||
[]( const auto &item ) { return item.second; } );
|
[]( const auto &item ) { return item.second; } );
|
||||||
}
|
}
|
||||||
@ -167,13 +171,13 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
void
|
void
|
||||||
printAllOptionsHelp( const std::optional< std::string > canonicalProgramName )
|
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 )
|
[]( const auto &lhs, const auto &rhs )
|
||||||
{
|
{
|
||||||
return lhs.first.size() < rhs.first.size();
|
return lhs.first.size() < rhs.first.size();
|
||||||
} );
|
} );
|
||||||
// Account for the `:` and the ` ` in the output table format.
|
// 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;
|
std::multimap< const DomainBase *, std::string > exclusivityMembers;
|
||||||
@ -193,14 +197,14 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
{
|
{
|
||||||
const auto &[ _, helpText, defaultBuilder, domains ]= def;
|
const auto &[ _, helpText, defaultBuilder, domains ]= def;
|
||||||
// How much unused of the max width there will be
|
// 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=
|
VariableMap substitutions=
|
||||||
{
|
{
|
||||||
// This uses a GNU extension, but it's fine. We can always make this
|
// This uses a GNU extension, but it's fine. We can always make this
|
||||||
// portable, later.
|
// portable, later.
|
||||||
{ "program-name"s, lambaste<=::program_invocation_short_name },
|
{ "program-name"s, lambaste<=::program_invocation_short_name },
|
||||||
{ "option-name"s, lambaste<=optionName },
|
{ "option-name"s, lambaste<=name },
|
||||||
{ "default"s, [&defaultBuilder= defaultBuilder, &name= name]
|
{ "default"s, [&defaultBuilder= defaultBuilder, &name= name]
|
||||||
{
|
{
|
||||||
return "Default is `" + name + defaultBuilder() + "`";
|
return "Default is `" + name + defaultBuilder() + "`";
|
||||||
@ -208,23 +212,24 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
};
|
};
|
||||||
if( canonicalProgramName.has_value() )
|
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, ' ' )
|
std::string substitutionTemplate= name + ": " + std::string( padding, ' ' )
|
||||||
+ helpText.str() + "\n";
|
+ helpText.str() + "\n";
|
||||||
|
|
||||||
// Append the incompatibility text, when we see mutually-exclusive options.
|
// 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, '!' );
|
const std::string helpString= expandVariables( substitutionTemplate, substitutions, '!' );
|
||||||
printString( helpString, alignmentWidth );
|
printString( helpString, alignmentWidth );
|
||||||
|
std::cout << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for required options, and print a summary of those:
|
// 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;
|
std::ostringstream oss;
|
||||||
oss << "At least one of the options in this group are required: ";
|
oss << "At least one of the options in this group are required: ";
|
||||||
bool first= true;
|
bool first= true;
|
||||||
@ -232,7 +237,7 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
{
|
{
|
||||||
if( not first ) oss << ", ";
|
if( not first ) oss << ", ";
|
||||||
first= false;
|
first= false;
|
||||||
oss << '`' << required << '`':
|
oss << '`' << required << '`';
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << wordWrap( oss.str(), width ) << std::endl;
|
std::cout << wordWrap( oss.str(), width ) << std::endl;
|
||||||
@ -244,7 +249,7 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
std::ostream &
|
std::ostream &
|
||||||
OptionBinding::operator << ( bool &flag ) const
|
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.";
|
<< [&flag] { flag= false; } << "Disable `" + name + "`. See that option for more details.";
|
||||||
return self() << [&flag] { flag= true; };
|
return self() << [&flag] { flag= true; };
|
||||||
}
|
}
|
||||||
@ -267,7 +272,7 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[noreturn]] void
|
[[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() )
|
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();
|
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;
|
<< std::endl << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
printOptionsHelp( canonicalName );
|
printAllOptionsHelp( canonicalName );
|
||||||
::exit( EXIT_SUCCESS );
|
::exit( EXIT_SUCCESS );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,12 +336,12 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
// doing a map lookup.
|
// doing a map lookup.
|
||||||
for( const auto &[ name, def ]: opts )
|
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;
|
const auto &handler= def.handler;
|
||||||
std::optional< std::string > argument;
|
std::optional< std::string > argument;
|
||||||
if( param == name ) argument= std::nullopt;
|
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 );
|
argument= param.substr( name.size() + 1 );
|
||||||
}
|
}
|
||||||
@ -352,12 +357,12 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
if( C::debugExclusions )
|
if( C::debugExclusions )
|
||||||
{
|
{
|
||||||
error() << "I see " << exclusions.size() << " mutual exclusions against `"
|
error() << "I see " << exclusions.size() << " mutual exclusions against `"
|
||||||
<< name << "`" std::endl;
|
<< name << "`" << std::endl;
|
||||||
}
|
}
|
||||||
for( const auto &exclusion: exclusions )
|
for( const auto &exclusion: exclusions )
|
||||||
{
|
{
|
||||||
// Look up this domain, and see if something from it was used.
|
// 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 )
|
if( other.has_value() and other != name )
|
||||||
{
|
{
|
||||||
throw std::runtime_error{ "Options `" + other.value() + "` and `"
|
throw std::runtime_error{ "Options `" + other.value() + "` and `"
|
||||||
@ -380,8 +385,8 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
if( C::debugMatching and not found ) error() << "No match for `" << param << "` was found." << std::endl;
|
if( C::debugMatching and not matched ) error() << "No match for `" << param << "` was found." << std::endl;
|
||||||
if( found ) continue;
|
if( matched ) continue;
|
||||||
rv.push_back( param );
|
rv.push_back( param );
|
||||||
|
|
||||||
if( param.starts_with( "--" ) )
|
if( param.starts_with( "--" ) )
|
||||||
@ -392,8 +397,8 @@ namespace Alepha::Cavorite ::detail:: program_options
|
|||||||
}
|
}
|
||||||
catch( const OptionMissingArgumentError &e )
|
catch( const OptionMissingArgumentError &e )
|
||||||
{
|
{
|
||||||
if( next == end( argsForProcessing ) or next->startsWith( "--" ) ) throw;
|
if( next == end( argsToProcess ) or next->starts_with( "--" ) ) throw;
|
||||||
throw std::runtime_error( ex.what() + " did you mean: `"s + param + "=" + *next + "`?" );
|
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 ) );
|
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
|
// If we're not doing a help-run, then we need to validate the required
|
||||||
// options were all passed.
|
// options were all passed.
|
||||||
if( requiredOptions.size() != requiredOptionsSeen.size() )
|
if( requiredOptions().size() != requiredOptionsSeen.size() )
|
||||||
{
|
{
|
||||||
for( auto [ required, opts ]: requiredOptions() )
|
for( auto [ required, opts ]: requiredOptions() )
|
||||||
{
|
{
|
||||||
if( requiredOptionsSeen.contains( required ) ) continue;
|
if( requiredOptionsSeen.contains( required ) ) continue;
|
||||||
|
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
oss <<< "Required option missing. At least one of ";
|
oss << "Required option missing. At least one of ";
|
||||||
bool first= true;
|
bool first= true;
|
||||||
for( const auto &name: opts )
|
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( 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;
|
return rv;
|
||||||
|
@ -81,6 +81,17 @@ static_assert( __cplusplus > 2020'00 );
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <typeindex>
|
#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
|
namespace Alepha::inline Cavorite ::detail:: program_options
|
||||||
{
|
{
|
||||||
@ -142,7 +153,7 @@ namespace Alepha::inline Cavorite ::detail:: program_options
|
|||||||
|
|
||||||
using RequirementDomain= Domain< requirement_tag >;
|
using RequirementDomain= Domain< requirement_tag >;
|
||||||
|
|
||||||
using PreHelpDoimain= Domain< pre_help_tag >;
|
using PreHelpDomain= Domain< pre_help_tag >;
|
||||||
inline const PreHelpDomain affectsHelp;
|
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 + "`)" );
|
throw std::runtime_error( "Error parsing option `" + argName + "`, with parameter string: `" + s + "` (full option: `" + fullOption + "`)" );
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace impl
|
inline namespace impl
|
||||||
{
|
{
|
||||||
struct ProgramOption;
|
struct ProgramOption;
|
||||||
|
|
||||||
@ -211,7 +222,7 @@ namespace Alepha::inline Cavorite ::detail:: program_options
|
|||||||
{
|
{
|
||||||
return self() << [&list, name= name]( const std::string param )
|
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 > )
|
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.
|
// Boolean flag options are a special case of the value-binding system.
|
||||||
// They generate `--no-` forms of the option as well.
|
// They generate `--no-` forms of the option as well.
|
||||||
OptionBinding operator << ( bool &flag ) const;
|
std::ostream &operator << ( bool &flag ) const;
|
||||||
|
|
||||||
template< NotFunctional T >
|
template< NotFunctional T >
|
||||||
[[nodiscard]] std::ostream &
|
[[nodiscard]] std::ostream &
|
||||||
@ -269,12 +280,12 @@ namespace Alepha::inline Cavorite ::detail:: program_options
|
|||||||
operator << ( UnaryFunction auto handler ) const
|
operator << ( UnaryFunction auto handler ) const
|
||||||
{
|
{
|
||||||
using arg_type= get_arg_t< std::decay_t< decltype( handler ) >, 0 >;
|
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.
|
// 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;
|
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 );
|
impl::checkArgument( argument, name );
|
||||||
|
|
||||||
@ -293,28 +304,26 @@ namespace Alepha::inline Cavorite ::detail:: program_options
|
|||||||
}
|
}
|
||||||
return rv;
|
return rv;
|
||||||
};
|
};
|
||||||
core( parsed );
|
handler( parsed );
|
||||||
};
|
};
|
||||||
return registerHandler( handler );
|
return registerHandler( handler );
|
||||||
}
|
}
|
||||||
else
|
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 );
|
impl::checkArgument( argument, name );
|
||||||
|
|
||||||
const auto value= argumentFromString< arg_type >( argument.value(), name, name + "=" + argument.value() );
|
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 printString( const std::string &s, const std::size_t indent );
|
||||||
|
|
||||||
void printOptionsHelp();
|
|
||||||
|
|
||||||
struct OptionString { std::string name; };
|
struct OptionString { std::string name; };
|
||||||
|
|
||||||
namespace exports::inline literals
|
namespace exports::inline literals
|
||||||
@ -324,7 +333,7 @@ namespace Alepha::inline Cavorite ::detail:: program_options
|
|||||||
|
|
||||||
inline namespace impl
|
inline namespace impl
|
||||||
{
|
{
|
||||||
[[nodiscard]] OptoinBinding operator --( OptionString option );
|
[[nodiscard]] OptionBinding operator --( OptionString option );
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ProgramDescription
|
struct ProgramDescription
|
||||||
@ -354,23 +363,23 @@ namespace Alepha::inline Cavorite ::detail:: program_options
|
|||||||
auto
|
auto
|
||||||
handleOptions( const std::vector< std::string > &args )
|
handleOptions( const std::vector< std::string > &args )
|
||||||
{
|
{
|
||||||
return impl::handleOptions( args, usageWrap< T > );
|
return impl::handleOptions( args, usageWrap< Supplement > );
|
||||||
}
|
}
|
||||||
|
|
||||||
template< typename Supplement >
|
template< typename Supplement >
|
||||||
auto
|
auto
|
||||||
handleOptions( const int argcnt, const char *const *const argvec )
|
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 )
|
handleOptions( const std::vector< std::string > &args )
|
||||||
{
|
{
|
||||||
return handleOptions< ProgramDescription >( args );
|
return handleOptions< ProgramDescription >( args );
|
||||||
}
|
}
|
||||||
|
|
||||||
auto
|
inline auto
|
||||||
handleOptions( const int argcnt, const char *const *const argvec )
|
handleOptions( const int argcnt, const char *const *const argvec )
|
||||||
{
|
{
|
||||||
return handleOptions< ProgramDescription >( argcnt, argvec );
|
return handleOptions< ProgramDescription >( argcnt, argvec );
|
||||||
|
@ -4,7 +4,7 @@ static_assert( __cplusplus > 2020'00 );
|
|||||||
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
#include <boost/noncoyable.hpp>
|
#include <boost/noncopyable.hpp>
|
||||||
|
|
||||||
namespace Alepha::inline Cavorite ::detail:: static_value
|
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
|
class exports::StaticValue : boost::noncopyable
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
@ -141,7 +141,7 @@ namespace Alepha::inline Cavorite ::detail:: static_value
|
|||||||
return *storage;
|
return *storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr auto
|
constexpr decltype( auto )
|
||||||
operator() () noexcept
|
operator() () noexcept
|
||||||
{
|
{
|
||||||
return get();
|
return get();
|
||||||
|
35
example.cc
Normal file
35
example.cc
Normal 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;
|
||||||
|
}
|
@ -5,7 +5,7 @@ static_assert( __cplusplus > 2020'00 );
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
||||||
#include "error.hpp"
|
#include "error.h"
|
||||||
|
|
||||||
namespace Alepha::Cavorite ::detail:: string_algorithms
|
namespace Alepha::Cavorite ::detail:: string_algorithms
|
||||||
{
|
{
|
||||||
@ -31,7 +31,7 @@ namespace Alepha::Cavorite ::detail:: string_algorithms
|
|||||||
|
|
||||||
for( const char ch: text )
|
for( const char ch: text )
|
||||||
{
|
{
|
||||||
if( mode == normal and ch == sigil )
|
if( mode == Normal and ch == sigil )
|
||||||
{
|
{
|
||||||
mode= Symbol;
|
mode= Symbol;
|
||||||
varName.clear();
|
varName.clear();
|
||||||
@ -46,7 +46,7 @@ namespace Alepha::Cavorite ::detail:: string_algorithms
|
|||||||
{
|
{
|
||||||
throw std::runtime_error( "No such variable: `" + varName + "`" );
|
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 )();
|
rv+= vars.at( varName )();
|
||||||
}
|
}
|
||||||
else rv+= sigil;
|
else rv+= sigil;
|
||||||
@ -95,4 +95,22 @@ namespace Alepha::Cavorite ::detail:: string_algorithms
|
|||||||
|
|
||||||
return rv;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,16 @@ static_assert( __cplusplus > 2020'00 );
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <numeric>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
|
||||||
|
#include <Alepha/Concepts.h>
|
||||||
|
|
||||||
namespace Alepha::inline Cavorite ::detail:: string_algorithms
|
namespace Alepha::inline Cavorite ::detail:: string_algorithms
|
||||||
{
|
{
|
||||||
inline namespace exports {}
|
inline namespace exports {}
|
||||||
@ -18,6 +24,8 @@ namespace Alepha::inline Cavorite ::detail:: string_algorithms
|
|||||||
|
|
||||||
inline namespace exports
|
inline namespace exports
|
||||||
{
|
{
|
||||||
|
using VariableMap= VarMap;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Returns a new string with text-replacement variables expanded.
|
* 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 > 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.
|
* 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 );
|
std::iota( begin( rv ), end( rv ), low );
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Alepha::Cavorite::inline exports::inline string_algorithms
|
namespace Alepha::Cavorite::inline exports::inline string_algorithms
|
||||||
{
|
{
|
||||||
using namespace detail::exports::string_algorithms;
|
using namespace detail::string_algorithms::exports;
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace Alepha::inline Cavorite ::detail:: type_lisp
|
|||||||
namespace exports
|
namespace exports
|
||||||
{
|
{
|
||||||
template< typename Type >
|
template< typename Type >
|
||||||
concept TypeListType= is_type_list_v< Type >:
|
concept TypeListType= is_type_list_v< Type >;
|
||||||
}
|
}
|
||||||
|
|
||||||
template< typename T >
|
template< typename T >
|
||||||
|
@ -2,6 +2,13 @@ static_assert( __cplusplus > 2020 );
|
|||||||
|
|
||||||
#include "word_wrap.h"
|
#include "word_wrap.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "evaluation_helpers.h"
|
||||||
|
|
||||||
namespace Alepha::Cavorite ::detail:: word_wrap
|
namespace Alepha::Cavorite ::detail:: word_wrap
|
||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
@ -16,8 +23,14 @@ namespace Alepha::Cavorite ::detail:: word_wrap
|
|||||||
{
|
{
|
||||||
if( currentLineWidth + word.size() > maximumWidth )
|
if( currentLineWidth + word.size() > maximumWidth )
|
||||||
{
|
{
|
||||||
|
//std::cerr << "Going to newline on word: " << word << "(currentLineWidth= " << currentLineWidth << ")" << std::endl;
|
||||||
result+= '\n';
|
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;
|
return nextLineOffset;
|
||||||
}
|
}
|
||||||
else return currentLineWidth;
|
else return currentLineWidth;
|
||||||
@ -32,9 +45,10 @@ namespace Alepha::Cavorite ::detail:: word_wrap
|
|||||||
std::string
|
std::string
|
||||||
exports::wordWrap( const std::string &text, const std::size_t width, const std::size_t nextLineOffset )
|
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;
|
std::string result;
|
||||||
@ -54,7 +68,7 @@ namespace Alepha::Cavorite ::detail:: word_wrap
|
|||||||
word.clear();
|
word.clear();
|
||||||
if( lineLength < width )
|
if( lineLength < width )
|
||||||
{
|
{
|
||||||
line+= ' ';
|
result+= ' ';
|
||||||
lineLength++;
|
lineLength++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ static_assert( __cplusplus > 2020'00 );
|
|||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace Alepha::inline Cavorite ::detail:: word_wrap
|
namespace Alepha::inline Cavorite ::detail:: word_wrap
|
||||||
{
|
{
|
||||||
inline namespace exports
|
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 );
|
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;
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user