Wrap help text shown by wxCmdLineParser at terminal width

By default, wrap the options descriptions to make output prettier.

It is also possible to disable this by explicitly passing
wxCMD_LINE_WRAP_NONE to Parse() and related functions or always wrap at
80 columns, for example.
This commit is contained in:
Vadim Zeitlin
2024-11-22 23:29:53 +01:00
parent 4897743c58
commit 0eda5ed3ec
4 changed files with 150 additions and 16 deletions
+10 -4
View File
@@ -75,6 +75,10 @@ enum wxCmdLineSwitchState
wxCMD_SWITCH_ON // Found in normal state.
};
// Constants determining how (and if) to wrap the usage message
constexpr int wxCMD_LINE_WRAP_AUTO = -1;
constexpr int wxCMD_LINE_WRAP_NONE = 0;
// ----------------------------------------------------------------------------
// wxCmdLineEntryDesc is a description of one command line
// switch/option/parameter
@@ -295,14 +299,16 @@ public:
// syntax error occurred
//
// if showUsage is true, Usage() is called in case of syntax error or if
// help was requested
int Parse(bool showUsage = true);
// help was requested and if wrapColumn is not 0, the usage message is
// wrapped at the specified column, which will be the terminal width for
// its default value
int Parse(bool showUsage = true, int wrapColumn = wxCMD_LINE_WRAP_AUTO);
// give the usage message describing all program options
void Usage() const;
void Usage(int wrapColumn = wxCMD_LINE_WRAP_AUTO) const;
// return the usage string, call Usage() to directly show it to the user
wxString GetUsageString() const;
wxString GetUsageString(int wrapColumn = wxCMD_LINE_WRAP_AUTO) const;
// get the command line arguments
// ------------------------------
+47
View File
@@ -0,0 +1,47 @@
///////////////////////////////////////////////////////////////////////////////
// Name: wx/private/terminal.h
// Purpose: Helpers for working with terminal output
// Author: Vadim Zeitlin
// Created: 2024-11-22
// Copyright: (c) 2024 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_PRIVATE_TERMINAL_H_
#define _WX_PRIVATE_TERMINAL_H_
#ifdef __WINDOWS__
#include "wx/utils.h"
#include "wx/msw/wrapwin.h"
#endif
#ifdef __UNIX__
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#endif
namespace wxTerminal
{
// Return the current terminal width or 0 if we couldn't find it.
inline int GetWidth()
{
#ifdef __WINDOWS__
CONSOLE_SCREEN_BUFFER_INFO csbi;
if ( ::GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi) )
return csbi.srWindow.Right - csbi.srWindow.Left + 1;
#elif defined TIOCGWINSZ
winsize w;
int fd = fileno(stdout);
if ( fd != -1 && ioctl(fd, TIOCGWINSZ, &w) == 0 )
return w.ws_col;
#endif
return 0;
}
} // namespace wxTerminal
#endif // _WX_PRIVATE_TERMINAL_H_
+42 -3
View File
@@ -98,6 +98,29 @@ enum wxCmdLineSwitchState
wxCMD_SWITCH_ON
};
/**
Value indicating that wxCmdLineParser::Parse() should determine the wrap
column automatically.
This is the default value for the @c wrapColumn parameter of
wxCmdLineParser::Parse() and means that the text will be wrapped at the
window boundary if it is possible to find it (which may not be the case if
the output is redirected and doesn't appear on a terminal at all, for
example).
@since 3.3.0
*/
constexpr int wxCMD_LINE_WRAP_AUTO = -1;
/**
Value indicating that wxCmdLineParser::Parse() should not wrap the output.
Passing this value as @c wrapColumn parameter of wxCmdLineParser::Parse()
means that the text should not be wrapped at all.
@since 3.3.0
*/
constexpr int wxCMD_LINE_WRAP_NONE = 0;
/**
Flags determining wxCmdLineParser::ConvertStringToArgs() behaviour.
@@ -682,8 +705,16 @@ public:
requested. If @false, only error messages about possible syntax
errors are given, use Usage to show the usage message from the
caller if needed.
@param wrapColumn
When @a giveUsage is @true, this parameter specifies the column at
which the help text should be wrapped if given. When left as its
default value of wxCMD_LINE_WRAP_AUTO, the text will be wrapped at
the window boundary, if it was possible to detect it, or not at all
otherwise. If the value of this parameter is wxCMD_LINE_WRAP_NONE,
the text is never wrapped. Any other value specifies the column at
which to wrap, e.g. 80. This parameter is new since wxWidgets 3.3.0.
*/
int Parse(bool giveUsage = true);
int Parse(bool giveUsage = true, int wrapColumn = wxCMD_LINE_WRAP_AUTO);
///@{
/**
@@ -760,15 +791,23 @@ public:
resulting message will not be helpful to the user unless the
descriptions were indeed specified.
@param wrapColumn
See Parse() for the description of this parameter. This parameter
is new since wxWidgets 3.3.0.
@see SetLogo(), SetUsageSynopsis()
*/
void Usage() const;
void Usage(int wrapColumn = wxCMD_LINE_WRAP_AUTO) const;
/**
Return the string containing the program usage description.
Call Usage() to directly show this string to the user.
@param wrapColumn
See Parse() for the description of this parameter. This parameter
is new since wxWidgets 3.3.0.
*/
wxString GetUsageString() const;
wxString GetUsageString(int wrapColumn = wxCMD_LINE_WRAP_AUTO) const;
};
+51 -9
View File
@@ -40,6 +40,9 @@
#include "wx/apptrait.h"
#include "wx/scopeguard.h"
#include "wx/private/terminal.h"
#include "wx/private/wordwrap.h"
// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------
@@ -784,7 +787,7 @@ void wxCmdLineParser::Reset()
// the real work is done here
// ----------------------------------------------------------------------------
int wxCmdLineParser::Parse(bool showUsage)
int wxCmdLineParser::Parse(bool showUsage, int wrapColumn)
{
bool maybeOption = true; // can the following arg be an option?
bool ok = true; // true until an error is detected
@@ -1237,7 +1240,7 @@ int wxCmdLineParser::Parse(bool showUsage)
if ( !ok && (!errorMsg.empty() || (helpRequested && showUsage)) )
{
if ( showUsage )
Usage();
Usage(wrapColumn);
wxSafeMessageOutput(errorMsg);
}
@@ -1249,12 +1252,12 @@ int wxCmdLineParser::Parse(bool showUsage)
// give the usage message
// ----------------------------------------------------------------------------
void wxCmdLineParser::Usage() const
void wxCmdLineParser::Usage(int wrapColumn) const
{
wxSafeMessageOutput(GetUsageString(wrapColumn));
}
wxString wxCmdLineParser::GetUsageString() const
wxString wxCmdLineParser::GetUsageString(int wrapColumn) const
{
wxString appname;
if ( m_data->m_arguments.empty() )
@@ -1416,22 +1419,61 @@ wxString wxCmdLineParser::GetUsageString() const
lenMax = len;
}
// Leave some space between the options and their descriptions.
constexpr int MARGIN = 3;
const int widthName = lenMax + MARGIN;
// Determine the column to wrap at, if necessary.
if ( wrapColumn == wxCMD_LINE_WRAP_AUTO )
{
// Note that GetWidth() returns 0 if the terminal size couldn't be
// determined, which happens to be exactly wxCMD_LINE_WRAP_NONE.
wrapColumn = wxTerminal::GetWidth();
}
// Check that we end up with reasonable layout, where descriptions column
// is at least as wide as the names one.
if ( wrapColumn != wxCMD_LINE_WRAP_NONE && wrapColumn < 2*widthName )
{
// It doesn't make sense to wrap anything if there is so little space.
wrapColumn = wxCMD_LINE_WRAP_NONE;
}
// This is only used when wrapColumn is not wxCMD_LINE_WRAP_NONE.
const int widthDesc = wrapColumn - widthName;
for ( size_t n = 0; n < namesOptions.size(); n++ )
{
if ( n == count )
usage << wxT('\n') << stdDesc;
const auto& desc = descOptions[n];
// desc contains text if name is empty
if ( namesOptions[n].empty() )
{
usage << descOptions[n] << wxT('\n');
usage << desc << wxT('\n');
}
else
{
usage << wxString::Format("%-*s\t%s\n",
static_cast<int>(lenMax),
namesOptions[n],
descOptions[n]);
// Check if the description fits on the same line if we wrap it.
if ( wrapColumn == wxCMD_LINE_WRAP_NONE ||
static_cast<int>(desc.length()) <= widthDesc )
{
// Note that we use TAB as separator here for compatibility.
usage << wxString::Format("%-*s%*s%s\n",
static_cast<int>(lenMax),
namesOptions[n],
MARGIN, "",
desc);
}
else // We need to wrap it.
{
usage << wxString::Format("%s\n", namesOptions[n]);
for ( const auto& s : wxWordWrap(desc, widthDesc) )
usage << wxString::Format("%*s%s\n", widthName, "", s);
}
}
}