Improve formatting of help descriptions (#213)

* new function: cxxopts::Option::set_width(size_t width)
  Set the size of a helpline.
* new function: cxxopts::Option::set_tab_expansion()
  Expand the tabs in descriptions.
  The tabsize 8 chars, base is start of description.
  The descriptions are not disturbed by adding additional options.
* Allow newlines \n nad tabs \t in descriptions.

Other changes (last commit/new commit):
* 1453/1471: size_t for OPTION_LONGEST and OPTION_DESC_GAP.
  This prevents the static cast in 2086/2140.
* 2088/2142: in case of small width the value of
  "width - longest - OPTION_DEC_GAP" becomes negative.
  Because size_t is unsigned the result is a big number, and
  the width of the column of the descriptions is not shortened.
* new 2143: When the given width is too small, it is set to
  longest + OPTION_DESC_GAP + 10
* new 1570: A long description is broken into multiple lines, and
  the iterator lastSpace remembers the begin of the last word.
  But when the iterator current reaches the end of line, the whole
  string from iterator is printed, which in soome cases is too
  long. Thats why one blank is added to the description to trigger
  the handling of lastSpace.
  Accordingly in 1574/1627 the line is shortened by one char.
This commit is contained in:
Wolfgang Gahr 2019-11-25 17:04:43 +01:00
parent 073dd3e645
commit 2e3d9a756e

View File

@ -1297,6 +1297,8 @@ namespace cxxopts
, m_positional_help("positional parameters") , m_positional_help("positional parameters")
, m_show_positional(false) , m_show_positional(false)
, m_allow_unrecognised(false) , m_allow_unrecognised(false)
, m_width(76)
, m_tab_expansion(false)
, m_options(std::make_shared<OptionMap>()) , m_options(std::make_shared<OptionMap>())
, m_next_positional(m_positional.end()) , m_next_positional(m_positional.end())
{ {
@ -1330,6 +1332,20 @@ namespace cxxopts
return *this; return *this;
} }
Options&
set_width(size_t width)
{
m_width = width;
return *this;
}
Options&
set_tab_expansion(bool expansion=true)
{
m_tab_expansion = expansion;
return *this;
}
ParseResult ParseResult
parse(int& argc, char**& argv); parse(int& argc, char**& argv);
@ -1414,6 +1430,8 @@ namespace cxxopts
std::string m_positional_help; std::string m_positional_help;
bool m_show_positional; bool m_show_positional;
bool m_allow_unrecognised; bool m_allow_unrecognised;
size_t m_width;
bool m_tab_expansion;
std::shared_ptr<OptionMap> m_options; std::shared_ptr<OptionMap> m_options;
std::vector<std::string> m_positional; std::vector<std::string> m_positional;
@ -1450,8 +1468,8 @@ namespace cxxopts
namespace namespace
{ {
constexpr int OPTION_LONGEST = 30; constexpr size_t OPTION_LONGEST = 30;
constexpr int OPTION_DESC_GAP = 2; constexpr size_t OPTION_DESC_GAP = 2;
std::basic_regex<char> option_matcher std::basic_regex<char> option_matcher
("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)"); ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)");
@ -1506,7 +1524,8 @@ namespace cxxopts
( (
const HelpOptionDetails& o, const HelpOptionDetails& o,
size_t start, size_t start,
size_t width size_t width,
bool m_tab_expansion
) )
{ {
auto desc = o.desc; auto desc = o.desc;
@ -1525,53 +1544,85 @@ namespace cxxopts
String result; String result;
if (m_tab_expansion)
{
String desc2;
auto size = size_t{ 0 };
for (auto c = std::begin(desc); c != std::end(desc); ++c)
{
if (*c == '\n')
{
desc2 += *c;
size = 0;
}
else if (*c == '\t')
{
auto skip = 8 - size % 8;
desc2.append(skip, ' ');
size += skip;
}
else
{
desc2 += *c;
++size;
}
}
desc = desc2;
}
desc += " ";
auto current = std::begin(desc); auto current = std::begin(desc);
auto startLine = current; auto startLine = current;
auto lastSpace = current; auto lastSpace = current;
auto size = size_t{};
while (current != std::end(desc)) while (current != std::end(desc))
{ {
if (*current == ' ') size_t addNewLine = 0;
while (*current == '\n')
{ {
*current = ' ';
++current;
++addNewLine;
}
if (addNewLine == 0 && current - startLine >= width)
{
if (lastSpace != startLine)
{
current = lastSpace;
}
if (current + 1 != std::end(desc))
{
++addNewLine;
}
}
if (addNewLine > 0)
{
stringAppend(result, startLine, current);
startLine = current;
lastSpace = current; lastSpace = current;
while (addNewLine-- > 0)
{
stringAppend(result, "\n");
}
if (current + 1 != std::end(desc))
{
stringAppend(result, start, ' ');
}
} }
if (*current == '\n') if (std::isblank(*current))
{ {
startLine = current + 1; lastSpace = current + 1;
lastSpace = startLine;
}
else if (size > width)
{
if (lastSpace == startLine)
{
stringAppend(result, startLine, current + 1);
stringAppend(result, "\n");
stringAppend(result, start, ' ');
startLine = current + 1;
lastSpace = startLine;
}
else
{
stringAppend(result, startLine, lastSpace);
stringAppend(result, "\n");
stringAppend(result, start, ' ');
startLine = lastSpace + 1;
}
size = 0;
}
else
{
++size;
} }
++current; ++current;
} }
//append whatever is left //append whatever is left
stringAppend(result, startLine, current); stringAppend(result, startLine, std::end(desc) - 1);
return result; return result;
} }
@ -2082,11 +2133,14 @@ Options::help_one_group(const std::string& g) const
longest = (std::max)(longest, stringLength(s)); longest = (std::max)(longest, stringLength(s));
format.push_back(std::make_pair(s, String())); format.push_back(std::make_pair(s, String()));
} }
longest = (std::min)(longest, OPTION_LONGEST);
longest = (std::min)(longest, static_cast<size_t>(OPTION_LONGEST)); //widest allowed description -- min 10 chars for helptext/line
size_t allowed = 10;
//widest allowed description if (m_width > allowed + longest + OPTION_DESC_GAP)
auto allowed = size_t{76} - longest - OPTION_DESC_GAP; {
allowed = m_width - longest - OPTION_DESC_GAP;
}
auto fiter = format.begin(); auto fiter = format.begin();
for (const auto& o : group->second.options) for (const auto& o : group->second.options)
@ -2097,7 +2151,7 @@ Options::help_one_group(const std::string& g) const
continue; continue;
} }
auto d = format_description(o, longest + OPTION_DESC_GAP, allowed); auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion);
result += fiter->first; result += fiter->first;
if (stringLength(fiter->first) > longest) if (stringLength(fiter->first) > longest)