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

View File

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

View File

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

View File

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

View File

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

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 ); 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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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