Commit 43ce03fdbd850385b0461c8873771dc47be5c9b3

Authored by Wolfgang Gahr
Committed by GitHub
1 parent f34d6038

Improve formatting of help descriptions (#215)

* 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 and 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.

* repaired signed/unsigned issue

* changes for unicode
Showing 1 changed file with 105 additions and 30 deletions
include/cxxopts.hpp
... ... @@ -1403,6 +1403,8 @@ namespace cxxopts
1403 1403 , m_positional_help("positional parameters")
1404 1404 , m_show_positional(false)
1405 1405 , m_allow_unrecognised(false)
  1406 + , m_width(76)
  1407 + , m_tab_expansion(false)
1406 1408 , m_options(std::make_shared<OptionMap>())
1407 1409 {
1408 1410 }
... ... @@ -1435,6 +1437,20 @@ namespace cxxopts
1435 1437 return *this;
1436 1438 }
1437 1439  
  1440 + Options&
  1441 + set_width(size_t width)
  1442 + {
  1443 + m_width = width;
  1444 + return *this;
  1445 + }
  1446 +
  1447 + Options&
  1448 + set_tab_expansion(bool expansion=true)
  1449 + {
  1450 + m_tab_expansion = expansion;
  1451 + return *this;
  1452 + }
  1453 +
1438 1454 ParseResult
1439 1455 parse(int argc, const char* const* argv);
1440 1456  
... ... @@ -1519,6 +1535,8 @@ namespace cxxopts
1519 1535 std::string m_positional_help{};
1520 1536 bool m_show_positional;
1521 1537 bool m_allow_unrecognised;
  1538 + size_t m_width;
  1539 + bool m_tab_expansion;
1522 1540  
1523 1541 std::shared_ptr<OptionMap> m_options;
1524 1542 std::vector<std::string> m_positional{};
... ... @@ -1557,8 +1575,8 @@ namespace cxxopts
1557 1575  
1558 1576 namespace
1559 1577 {
1560   - constexpr int OPTION_LONGEST = 30;
1561   - constexpr int OPTION_DESC_GAP = 2;
  1578 + constexpr size_t OPTION_LONGEST = 30;
  1579 + constexpr size_t OPTION_DESC_GAP = 2;
1562 1580  
1563 1581 std::basic_regex<char> option_matcher
1564 1582 ("--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)");
... ... @@ -1617,7 +1635,8 @@ namespace cxxopts
1617 1635 (
1618 1636 const HelpOptionDetails& o,
1619 1637 size_t start,
1620   - size_t width
  1638 + size_t allowed,
  1639 + bool m_tab_expansion
1621 1640 )
1622 1641 {
1623 1642 auto desc = o.desc;
... ... @@ -1636,54 +1655,107 @@ namespace cxxopts
1636 1655  
1637 1656 String result;
1638 1657  
  1658 + if (m_tab_expansion)
  1659 + {
  1660 + String desc2;
  1661 + auto size = size_t{ 0 };
  1662 + for (auto c = std::begin(desc); c != std::end(desc); ++c)
  1663 + {
  1664 + if (*c == '\n')
  1665 + {
  1666 + desc2 += *c;
  1667 + size = 0;
  1668 + }
  1669 + else if (*c == '\t')
  1670 + {
  1671 + auto skip = 8 - size % 8;
  1672 + stringAppend(desc2, skip, ' ');
  1673 + size += skip;
  1674 + }
  1675 + else
  1676 + {
  1677 + desc2 += *c;
  1678 + ++size;
  1679 + }
  1680 + }
  1681 + desc = desc2;
  1682 + }
  1683 +
  1684 + desc += " ";
  1685 +
1639 1686 auto current = std::begin(desc);
  1687 + auto previous = current;
1640 1688 auto startLine = current;
1641 1689 auto lastSpace = current;
1642 1690  
1643 1691 auto size = size_t{};
1644 1692  
  1693 + bool appendNewLine;
  1694 + bool onlyWhiteSpace = true;
  1695 +
1645 1696 while (current != std::end(desc))
1646 1697 {
1647   - if (*current == ' ')
  1698 + appendNewLine = false;
  1699 +
  1700 + if (std::isblank(*previous))
1648 1701 {
1649 1702 lastSpace = current;
1650 1703 }
1651 1704  
1652   - if (*current == '\n')
  1705 + if (!std::isblank(*current))
1653 1706 {
1654   - startLine = current + 1;
1655   - lastSpace = startLine;
  1707 + onlyWhiteSpace = false;
1656 1708 }
1657   - else if (size > width)
  1709 +
  1710 + while (*current == '\n')
  1711 + {
  1712 + previous = current;
  1713 + ++current;
  1714 + appendNewLine = true;
  1715 + }
  1716 +
  1717 + if (!appendNewLine && size >= allowed)
1658 1718 {
1659   - if (lastSpace == startLine)
  1719 + if (lastSpace != startLine)
1660 1720 {
1661   - stringAppend(result, startLine, current + 1);
1662   - stringAppend(result, "\n");
1663   - stringAppend(result, start, ' ');
1664   - startLine = current + 1;
1665   - lastSpace = startLine;
  1721 + current = lastSpace;
  1722 + previous = current;
1666 1723 }
1667   - else
  1724 + appendNewLine = true;
  1725 + }
  1726 +
  1727 + if (appendNewLine)
  1728 + {
  1729 + stringAppend(result, startLine, current);
  1730 + startLine = current;
  1731 + lastSpace = current;
  1732 +
  1733 + if (*previous != '\n')
1668 1734 {
1669   - stringAppend(result, startLine, lastSpace);
1670 1735 stringAppend(result, "\n");
1671   - stringAppend(result, start, ' ');
1672   - startLine = lastSpace + 1;
1673   - lastSpace = startLine;
1674 1736 }
  1737 +
  1738 + stringAppend(result, start, ' ');
  1739 +
  1740 + if (*previous != '\n')
  1741 + {
  1742 + stringAppend(result, lastSpace, current);
  1743 + }
  1744 +
  1745 + onlyWhiteSpace = true;
1675 1746 size = 0;
1676 1747 }
1677   - else
1678   - {
1679   - ++size;
1680   - }
1681 1748  
  1749 + previous = current;
1682 1750 ++current;
  1751 + ++size;
1683 1752 }
1684 1753  
1685   - //append whatever is left
1686   - stringAppend(result, startLine, current);
  1754 + //append whatever is left but ignore whitespace
  1755 + if (!onlyWhiteSpace)
  1756 + {
  1757 + stringAppend(result, startLine, previous);
  1758 + }
1687 1759  
1688 1760 return result;
1689 1761 }
... ... @@ -2172,11 +2244,14 @@ Options::help_one_group(const std::string&amp; g) const
2172 2244 longest = (std::max)(longest, stringLength(s));
2173 2245 format.push_back(std::make_pair(s, String()));
2174 2246 }
  2247 + longest = (std::min)(longest, OPTION_LONGEST);
2175 2248  
2176   - longest = (std::min)(longest, static_cast<size_t>(OPTION_LONGEST));
2177   -
2178   - //widest allowed description
2179   - auto allowed = size_t{76} - longest - OPTION_DESC_GAP;
  2249 + //widest allowed description -- min 10 chars for helptext/line
  2250 + size_t allowed = 10;
  2251 + if (m_width > allowed + longest + OPTION_DESC_GAP)
  2252 + {
  2253 + allowed = m_width - longest - OPTION_DESC_GAP;
  2254 + }
2180 2255  
2181 2256 auto fiter = format.begin();
2182 2257 for (const auto& o : group->second.options)
... ... @@ -2187,7 +2262,7 @@ Options::help_one_group(const std::string&amp; g) const
2187 2262 continue;
2188 2263 }
2189 2264  
2190   - auto d = format_description(o, longest + OPTION_DESC_GAP, allowed);
  2265 + auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion);
2191 2266  
2192 2267 result += fiter->first;
2193 2268 if (stringLength(fiter->first) > longest)
... ...