aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPavel Belikov <pavel.fuchs.belikov@gmail.com>2017-12-02 14:08:05 +0300
committerPavel Belikov <pavel.fuchs.belikov@gmail.com>2017-12-02 14:08:05 +0300
commit6ac2747212532f5296b08b37516badb5a79e89a9 (patch)
tree179fb89c33a635bf66f410b6fcb2e878d97f04fa
parentMerge pull request #48 from pavel-belikov/fix-multiple-inclusion (diff)
downloadargs.hxx-6ac2747212532f5296b08b37516badb5a79e89a9.tar.xz
fix usage line wrapping
-rw-r--r--Makefile4
-rw-r--r--args.hxx154
-rw-r--r--test.cxx46
3 files changed, 159 insertions, 45 deletions
diff --git a/Makefile b/Makefile
index 4c71e96..5cb8feb 100644
--- a/Makefile
+++ b/Makefile
@@ -54,8 +54,8 @@ doc/man:
doxygen Doxyfile
bzip2 doc/man/man3/*.3
-runtests: test
- ./test
+runtests: ${EXECUTABLE}
+ ./${EXECUTABLE}
%.o: %.cxx
$(CXX) $< -o $@ $(CFLAGS)
diff --git a/args.hxx b/args.hxx
index 03faade..a8818ab 100644
--- a/args.hxx
+++ b/args.hxx
@@ -30,6 +30,7 @@
#define ARGS_HXX
#include <algorithm>
+#include <iterator>
#include <exception>
#include <functional>
#include <sstream>
@@ -83,54 +84,56 @@ namespace args
return length;
}
- /** (INTERNAL) Wrap a string into a vector of lines
+ /** (INTERNAL) Wrap a vector of words into a vector of lines
*
- * This is quick and hacky, but works well enough. You can specify a
- * different width for the first line
+ * Empty words are skipped. Word "\n" forces wrapping.
*
+ * \param begin The begin iterator
+ * \param end The end iterator
* \param width The width of the body
- * \param the width of the first line, defaults to the width of the body
+ * \param firstlinewidth the width of the first line, defaults to the width of the body
+ * \param firstlineindent the indent of the first line, defaults to 0
* \return the vector of lines
*/
- inline std::vector<std::string> Wrap(const std::string &in, const std::string::size_type width, std::string::size_type firstlinewidth = 0)
+ template <typename It>
+ inline std::vector<std::string> Wrap(It begin,
+ It end,
+ const std::string::size_type width,
+ std::string::size_type firstlinewidth = 0,
+ std::string::size_type firstlineindent = 0)
{
- // Preserve existing line breaks
- const auto newlineloc = in.find('\n');
- if (newlineloc != in.npos)
- {
- auto first = Wrap(std::string(in, 0, newlineloc), width);
- auto second = Wrap(std::string(in, newlineloc + 1), width);
- first.insert(
- std::end(first),
- std::make_move_iterator(std::begin(second)),
- std::make_move_iterator(std::end(second)));
- return first;
- }
+ std::vector<std::string> output;
+ std::string line(firstlineindent, ' ');
+ bool empty = true;
+
if (firstlinewidth == 0)
{
firstlinewidth = width;
}
- auto currentwidth = firstlinewidth;
- std::istringstream stream(in);
- std::vector<std::string> output;
- std::string line;
- bool empty = true;
+ auto currentwidth = firstlinewidth;
- for (char c : in)
+ for (auto it = begin; it != end; ++it)
{
- if (!isspace(c))
+ if (it->empty())
{
- break;
+ continue;
}
- line += c;
- }
- while (stream)
- {
- std::string item;
- stream >> item;
- auto itemsize = Glyphs(item);
+ if (*it == "\n")
+ {
+ if (!empty)
+ {
+ output.push_back(line);
+ line.clear();
+ empty = true;
+ currentwidth = width;
+ }
+
+ continue;
+ }
+
+ auto itemsize = Glyphs(*it);
if ((line.length() + 1 + itemsize) > currentwidth)
{
if (!empty)
@@ -141,6 +144,7 @@ namespace args
currentwidth = width;
}
}
+
if (itemsize > 0)
{
if (!empty)
@@ -148,7 +152,7 @@ namespace args
line += ' ';
}
- line += item;
+ line += *it;
empty = false;
}
}
@@ -157,9 +161,50 @@ namespace args
{
output.push_back(line);
}
+
return output;
}
+ /** (INTERNAL) Wrap a string into a vector of lines
+ *
+ * This is quick and hacky, but works well enough. You can specify a
+ * different width for the first line
+ *
+ * \param width The width of the body
+ * \param firstlinewid the width of the first line, defaults to the width of the body
+ * \return the vector of lines
+ */
+ inline std::vector<std::string> Wrap(const std::string &in, const std::string::size_type width, std::string::size_type firstlinewidth = 0)
+ {
+ // Preserve existing line breaks
+ const auto newlineloc = in.find('\n');
+ if (newlineloc != in.npos)
+ {
+ auto first = Wrap(std::string(in, 0, newlineloc), width);
+ auto second = Wrap(std::string(in, newlineloc + 1), width);
+ first.insert(
+ std::end(first),
+ std::make_move_iterator(std::begin(second)),
+ std::make_move_iterator(std::end(second)));
+ return first;
+ }
+
+ std::istringstream stream(in);
+ std::string::size_type indent = 0;
+
+ for (char c : in)
+ {
+ if (!isspace(c))
+ {
+ break;
+ }
+ ++indent;
+ }
+
+ return Wrap(std::istream_iterator<std::string>(stream), std::istream_iterator<std::string>(),
+ width, firstlinewidth, indent);
+ }
+
#ifdef ARGS_NOEXCEPT
/// Error class, for when ARGS_NOEXCEPT is defined
enum class Error
@@ -1748,7 +1793,32 @@ namespace args
if (!ProglinePostfix().empty())
{
- res.push_back(ProglinePostfix());
+ std::string line;
+ for (char c : ProglinePostfix())
+ {
+ if (isspace(c))
+ {
+ if (!line.empty())
+ {
+ res.push_back(line);
+ line.clear();
+ }
+
+ if (c == '\n')
+ {
+ res.push_back("\n");
+ }
+ }
+ else
+ {
+ line += c;
+ }
+ }
+
+ if (!line.empty())
+ {
+ res.push_back(line);
+ }
}
return res;
@@ -2387,15 +2457,15 @@ namespace args
const bool hasoptions = command.HasFlag();
const bool hasarguments = command.HasPositional();
- std::ostringstream prognameline;
- prognameline << helpParams.usageString << Prog();
-
- for (const std::string &posname: command.GetProgramLine(helpParams))
- {
- prognameline << ' ' << posname;
- }
+ std::vector<std::string> prognameline;
+ prognameline.push_back(helpParams.usageString);
+ prognameline.push_back(Prog());
+ auto commandProgLine = command.GetProgramLine(helpParams);
+ prognameline.insert(prognameline.end(), commandProgLine.begin(), commandProgLine.end());
- const auto proglines = Wrap(prognameline.str(), helpParams.width - (helpParams.progindent + 4), helpParams.width - helpParams.progindent);
+ const auto proglines = Wrap(prognameline.begin(), prognameline.end(),
+ helpParams.width - (helpParams.progindent + helpParams.progtailindent),
+ helpParams.width - helpParams.progindent);
auto progit = std::begin(proglines);
if (progit != std::end(proglines))
{
diff --git a/test.cxx b/test.cxx
index 897dcf3..911779d 100644
--- a/test.cxx
+++ b/test.cxx
@@ -958,6 +958,50 @@ TEST_CASE("GetProgramLine works as expected", "[args]")
REQUIRE(line(b) == "b -f <STRING> [positional]");
}
+TEST_CASE("Program line wrapping works as expected", "[args]")
+{
+ args::ArgumentParser p("parser");
+ args::ValueFlag<std::string> f(p, "foo_name", "f", {"foo"});
+ args::ValueFlag<std::string> g(p, "bar_name", "b", {"bar"});
+ args::ValueFlag<std::string> z(p, "baz_name", "z", {"baz"});
+
+ p.helpParams.proglineShowFlags = true;
+ p.helpParams.width = 42;
+ p.Prog("parser");
+ p.ProglinePostfix("\na\nliiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiine line2 line2tail");
+
+ REQUIRE((p.GetProgramLine(p.helpParams) == std::vector<std::string>{
+ "[--foo <foo_name>]",
+ "[--bar <bar_name>]",
+ "[--baz <baz_name>]",
+ "\n",
+ "a",
+ "\n",
+ "liiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiine",
+ "line2",
+ "line2tail",
+ }));
+
+ std::ostringstream s;
+ s << p;
+ REQUIRE(s.str() == R"( parser [--foo <foo_name>]
+ [--bar <bar_name>]
+ [--baz <baz_name>]
+ a
+ liiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiine
+ line2 line2tail
+
+ parser
+
+ OPTIONS:
+
+ --foo=[foo_name] f
+ --bar=[bar_name] b
+ --baz=[baz_name] z
+
+)");
+}
+
TEST_CASE("Matcher validation works as expected", "[args]")
{
args::ArgumentParser parser("Test command");
@@ -984,7 +1028,7 @@ TEST_CASE("HelpParams work as expected", "[args]")
)");
- p.helpParams.usageString = "usage: ";
+ p.helpParams.usageString = "usage:";
p.helpParams.optionsString = "Options";
p.helpParams.useValueNameOnce = true;
REQUIRE(p.Help() == R"( usage: prog {OPTIONS}