diff options
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | args.hxx | 154 | ||||
| -rw-r--r-- | test.cxx | 46 | 
3 files changed, 159 insertions, 45 deletions
| @@ -54,8 +54,8 @@ doc/man:  	doxygen Doxyfile  	bzip2 doc/man/man3/*.3 -runtests: test -	./test +runtests: ${EXECUTABLE} +	./${EXECUTABLE}  %.o: %.cxx  	$(CXX) $< -o $@ $(CFLAGS) @@ -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))                  { @@ -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} | 
