E-MailRelay
goptionsusage.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file goptionsusage.cpp
19///
20
21#include "gdef.h"
22#include "goptionsusage.h"
23#include "genvironment.h"
24#include "ggettext.h"
25#include "gstr.h"
26#include "gstringfield.h"
27#include "gtest.h"
28#include "gstringwrap.h"
29#include "gassert.h"
30#include <algorithm>
31
32G::OptionsUsage::OptionsUsage( const std::vector<Option> & options , std::function<bool(const Option&,const Option&)> sort_fn ) :
33 m_options(options)
34{
35 if( sort_fn )
36 std::sort( m_options.begin() , m_options.end() , sort_fn ) ;
37
38 if( G::Test::enabled("options-usage-debug") )
39 {
40 m_space_margin = 'M' ;
41 m_space_separator = 'S' ;
42 m_space_indent = 'I' ;
43 m_space_padding = 'P' ;
44 m_space_overflow = '_' ;
45 m_space_syntax = '.' ;
46 }
47}
48
49std::string G::OptionsUsage::summary( const Config & config_in ,
50 const std::string & exe , const std::string & args ) const
51{
52 Config config = config_in ;
53 config.setDefaults() ;
54
55 const char * usage = txt( "usage: " ) ;
56 const char * alt_usage = txt( "abbreviated usage: " ) ;
57 std::string s = std::string()
58 .append(config.alt_usage?alt_usage:usage)
59 .append(exe)
60 .append(" ")
61 .append(summaryPartOne(config))
62 .append(summaryPartTwo(config))
63 .append(args.empty()||args.at(0U)==' '?"":" ")
64 .append(args) ;
65 std::string indent( 2U , ' ' ) ;
66 return config.width == 0U ? s : StringWrap::wrap( s , "" , indent , config.width , 0U , true ) ;
67}
68
69std::string G::OptionsUsage::help( const Config & config_in , bool * overflow_p ) const
70{
71 Config config = config_in ;
72 config.setDefaults() ;
73 config.setWidthsWrtMargin() ;
74
75 if( overflow_p == nullptr )
76 {
77 bool overflow = false ;
78 std::string s = helpImp( config , false , overflow ) ;
79 if( overflow )
80 s = helpImp( config , true , overflow ) ;
81 return s ;
82 }
83 else
84 {
85 bool overflow = *overflow_p ;
86 return helpImp( config , overflow , *overflow_p ) ;
87 }
88}
89
90void G::OptionsUsage::output( const Config & config , std::ostream & stream ,
91 const std::string & exe , const std::string & args ) const
92{
93 stream
94 << summary(config,exe,args) << std::endl
95 << std::endl
96 << help(config) ;
97}
98
99std::string G::OptionsUsage::summaryPartOne( const Config & config ) const
100{
101 // summarise the single-character switches, excluding those which take a value
102 std::ostringstream ss ;
103 bool first = true ;
104 for( const auto & option : m_options )
105 {
106 if( option.c != '\0' && !option.valued() && option.visible({config.level_min,config.level_max},config.main_tag,config.tag_bits) )
107 {
108 if( first )
109 ss << "[-" ;
110 first = false ;
111 ss << option.c ;
112 }
113 }
114
115 std::string s = ss.str() ;
116 if( !s.empty() ) s.append( "] " ) ;
117 return s ;
118}
119
120std::string G::OptionsUsage::summaryPartTwo( const Config & config ) const
121{
122 std::ostringstream ss ;
123 const char * sep = "" ;
124 for( const auto & option : m_options )
125 {
126 if( option.visible({config.level_min,config.level_max},config.main_tag,config.tag_bits) )
127 {
128 ss << sep << "[" ;
129 if( !option.name.empty() )
130 {
131 ss << "--" << option.name ;
132 }
133 else
134 {
135 G_ASSERT( option.c != '\0' ) ;
136 ss << "-" << option.c ;
137 }
138 if( option.valued() )
139 {
140 std::string vd = option.value_description ;
141 if( vd.empty() ) vd = "value" ;
142 ss << "=<" << vd << ">" ;
143 }
144 ss << "]" ;
145 sep = " " ;
146 }
147 }
148 return ss.str() ;
149}
150
151std::string G::OptionsUsage::helpImp( const Config & config , bool overflow , bool & overflow_out ) const
152{
153 std::string result ;
154 for( const auto & option : m_options )
155 {
156 if( option.visible({config.level_min,config.level_max},config.main_tag,config.tag_bits) )
157 {
158 result.append( optionHelp(config,option,overflow,overflow_out) ).append( 1U , '\n' ) ;
159 }
160 }
161 return result ;
162}
163
164std::string G::OptionsUsage::optionHelp( const Config & config , const Option & option ,
165 bool overflow , bool & overflow_out ) const
166{
167 char syntax_non_space = '\x01' ;
168 std::string syntax_simple = helpSyntax( option ) ;
169 std::string syntax_aligned = helpSyntax( option , true , syntax_non_space ) ;
170 std::string description = helpDescription( option , config.extra ) ;
171 std::string separator = helpSeparator( config , syntax_aligned.length() ) ;
172
173 // concatentate and wrap
174 std::string line = helpWrap( config , syntax_simple , syntax_aligned , separator , description , overflow , overflow_out ) ;
175
176 // add a margin
177 if( config.margin )
178 {
179 std::string margin = std::string( config.margin , m_space_margin ) ;
180 G::Str::replaceAll( line , "\n" , std::string(1U,'\n').append(margin) ) ;
181 line = margin + line ;
182 }
183
184 // fix up the placeholders in the syntax part
185 G::Str::replace( line , syntax_non_space , m_space_syntax ) ;
186
187 return line ;
188}
189
190std::string G::OptionsUsage::helpWrap( const Config & config , const std::string & syntax_simple ,
191 const std::string & syntax_aligned , const std::string & separator , const std::string & description ,
192 bool overflow_in , bool & overflow_out ) const
193{
194 std::string text = std::string(syntax_aligned).append(separator).append(description) ;
195 if( config.width == 0 )
196 {
197 // no wrapping
198 return text ;
199 }
200 else if( config.separator == "\t" )
201 {
202 // wrapped lines are indented with a tab
203 return StringWrap::wrap( text , "" , "\t" , config.width , config.width2 , true ) ;
204 }
205 else if( overflow_in )
206 {
207 // overflow mode -- first line is syntax, word-wrapped description from line two
208 std::string indent( config.overflow_spaces , m_space_overflow ) ;
209 return syntax_simple + "\n" + StringWrap::wrap( description , indent , indent , config.width2 , config.width2 , true ) ;
210 }
211 else if( config.separator.empty() )
212 {
213 // no separator so wrapped lines are indented to the required column
214 std::string s = StringWrap::wrap( text , "" , helpPadding(config) ,
215 config.width , config.width2 , true ) ;
216
217 // suggest overflow mode if width has been squished down too close to column
218 if( !overflow_out && std::min(config.width,config.width2) <= config.overflow )
219 overflow_out = true ;
220
221 return s ;
222 }
223 else
224 {
225 // separator defined -- no column for the wrapped lines to indent to --
226 // just add (typically) one leading space to wrapped lines
227 return StringWrap::wrap( text , "" , std::string(config.separator_spaces,m_space_separator) ,
228 config.width , config.width2 , true ) ;
229 }
230}
231
232std::string G::OptionsUsage::helpSyntax( const Option & option , bool with_non_space , char non_space ) const
233{
234 std::string syntax ;
235 if( option.c != '\0' )
236 {
237 syntax.append( 1U , '-' ) ;
238 syntax.append( 1U , option.c ) ;
239 if( !option.name.empty() )
240 syntax.append( ", " ) ;
241 }
242 else if( with_non_space )
243 {
244 syntax.append( 4U , non_space ) ; // (new)
245 }
246 if( !option.name.empty() )
247 {
248 syntax.append( "--" ) ;
249 syntax.append( option.name ) ;
250 }
251 if( option.valued() )
252 {
253 std::string vd = option.value_description ;
254 if( vd.empty() ) vd = "value" ;
255 if( option.defaulting() ) syntax.append( "[" ) ;
256 syntax.append( "=<" ) ;
257 syntax.append( vd ) ;
258 syntax.append( ">" ) ;
259 if( option.defaulting() ) syntax.append( "]" ) ;
260 }
261 return syntax ;
262}
263
264std::string G::OptionsUsage::helpDescription( const Option & option , bool config_extra ) const
265{
266 std::string description = option.description ;
267 if( config_extra )
268 description.append( option.description_extra ) ;
269 return description ;
270}
271
272std::string G::OptionsUsage::helpSeparator( const Config & config , std::size_t syntax_length ) const
273{
274 if( !config.separator.empty() )
275 return config.separator ;
276 else if( (config.margin+syntax_length) >= config.column )
277 return std::string( 1U , m_space_separator ) ; // NOLINT not return {...}
278 else
279 return std::string( config.column-syntax_length-config.margin , m_space_separator ) ; // NOLINT not return {...}
280}
281
282std::string G::OptionsUsage::helpPadding( const Config & config ) const
283{
284 std::size_t n = std::max( std::size_t(1U) , config.column - std::min(config.column,config.margin) ) ;
285 return std::string( n , m_space_padding ) ; // NOLINT not return {...}
286}
287
288G::OptionsUsage::SortFn G::OptionsUsage::sort()
289{
290 return []( const Option & a , const Option & b ){
291 // sort by level, then by name
292 return a.level == b.level ? G::Str::iless(a.name,b.name) : ( a.level < b.level ) ;
293 } ;
294}
295
296// ==
297
298G::OptionsUsage::Config & G::OptionsUsage::Config::setDefaults()
299{
300 if( width == Config::default_ )
301 width = Str::toUInt( Environment::get("COLUMNS",{}) , "79" ) ;
302 if( width2 == 0U )
303 width2 = width ;
304 return *this ;
305}
306
307G::OptionsUsage::Config & G::OptionsUsage::Config::setWidthsWrtMargin()
308{
309 // adjust the widths wrt. the margin to pass to StringWrap::wrap()
310 width -= std::min( width , margin ) ;
311 width = std::max( width , std::size_t(1U) ) ;
312 width2 -= std::min( width2 , margin ) ;
313 width2 = std::max( width2 , std::size_t(1U) ) ;
314 return *this ;
315}
316
static std::string get(const std::string &name, const std::string &default_)
Returns the environment variable value or the given default.
static SortFn sort()
Returns the default sort function that sorts by level first and then by name.
std::string summary(const Config &, const std::string &exe, const std::string &args={}) const
Returns a one-line (or line-wrapped) usage summary, as "usage: <exe> <options> <args>".
void output(const Config &, std::ostream &stream, const std::string &exe, const std::string &args={}) const
Streams out multi-line usage text using summary() and help().
std::string help(const Config &, bool *overflow_p=nullptr) const
Returns a multi-line string giving help on each option.
OptionsUsage(const std::vector< Option > &, SortFn=sort())
Constructor.
static unsigned int toUInt(std::string_view s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:648
static unsigned int replaceAll(std::string &s, std::string_view from, std::string_view to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
Definition: gstr.cpp:247
static bool replace(std::string &s, std::string_view from, std::string_view to, std::size_t *pos_p=nullptr)
A std::string_view overload.
Definition: gstr.cpp:226
static bool iless(std::string_view, std::string_view) noexcept
Returns true if the first string is lexicographically less than the first, after seven-bit lower-case...
Definition: gstr.cpp:1404
static std::string wrap(const std::string &text, const std::string &prefix_first, const std::string &prefix_other, std::size_t width_first=70U, std::size_t width_other=0U, bool preserve_spaces=false)
Does word-wrapping of UTF-8 text.
static bool enabled() noexcept
Returns true if test features are enabled.
Definition: gtest.cpp:79
const char * txt(const char *p) noexcept
A briefer alternative to G::gettext().
Definition: ggettext.h:74
A structure representing a G::Options command-line option.
Definition: goption.h:38
A configuration structure for G::OptionsUsage.
Definition: goptionsusage.h:68
bool alt_usage
use alternate "usage:" string
Definition: goptionsusage.h:81
std::size_t width
overall width for wrapping, or zero for none, defaults to $COLUMNS
Definition: goptionsusage.h:74