xaizek / dit (License: GPLv3) (since 2018-12-07)
Command-line task keeper that remembers all old values and is meant to combine several orthogonal features to be rather flexible in managing items.
Commit 00559113f9ea547529d816afdb752c0ee2ae3da2

Improved argument parsing code
This version treats consecutive separators as single one and thus
doesn't produce useless empty tokens (including trailing ones).
Unmatched quotation results in exception.
Author: xaizek
Author date (UTC): 2016-05-04 20:41
Committer name: xaizek
Committer date (UTC): 2016-05-05 12:04
Parent(s): 67cce0cca6385335fcf6f0619a76fbac25d1bb1e
Signing key:
Tree: dc2a42c046229066d9b762969f1982b63039eed3
File Lines added Lines deleted
src/utils/args.hpp 151 2
tests/utils/args.cpp 32 0
File src/utils/args.hpp changed (mode: 100644) (index 4524a64..e51e8f2)
15 15 // You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
16 16 // along with dit. If not, see <http://www.gnu.org/licenses/>. // along with dit. If not, see <http://www.gnu.org/licenses/>.
17 17
18 // Boost Software License - Version 1.0 - August 17th, 2003
19 //
20 // Permission is hereby granted, free of charge, to any person or organization
21 // obtaining a copy of the software and accompanying documentation covered by
22 // this license (the "Software") to use, reproduce, display, distribute,
23 // execute, and transmit the Software, and to prepare derivative works of the
24 // Software, and to permit third-parties to whom the Software is furnished to
25 // do so, all subject to the following:
26 //
27 // The copyright notices in the Software and this entire statement, including
28 // the above license grant, this restriction and the following disclaimer,
29 // must be included in all copies of the Software, in whole or in part, and
30 // all derivative works of the Software, unless such copies or derivative
31 // works are solely in the form of machine-executable object code generated by
32 // a source language processor.
33 //
34 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
35 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
36 // FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
37 // SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
38 // FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
39 // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
40 // DEALINGS IN THE SOFTWARE.
41
18 42 #ifndef DIT__UTILS__ARGS_HPP__ #ifndef DIT__UTILS__ARGS_HPP__
19 43 #define DIT__UTILS__ARGS_HPP__ #define DIT__UTILS__ARGS_HPP__
20 44
 
23 47
24 48 #include <boost/tokenizer.hpp> #include <boost/tokenizer.hpp>
25 49
50 namespace detail {
51
52 // arg_list_separator is based on boost::escaped_list_separator, hence the
53 // license text above. This version treats consecutive separators as single one
54 // and thus doesn't produce useless empty tokens (including trailing ones).
55 // Unmatched quotation results in exception.
56 template <class Char,
57 class Traits = typename std::basic_string<Char>::traits_type>
58 class arg_list_separator
59 {
60 typedef std::basic_string<Char, Traits> string_type;
61
62 struct char_eq
63 {
64 Char e;
65 char_eq(Char e) : e(e) { }
66 bool operator()(Char c)
67 {
68 return Traits::eq(e, c);
69 }
70 };
71
72 public:
73 explicit arg_list_separator(Char e = '\\', Char c = ',', Char q = '\"')
74 : escape(1, e), c(1, c), quote(1, q) { }
75
76 arg_list_separator(string_type e, string_type c, string_type q)
77 : escape(e), c(c), quote(q) { }
78
79 void reset() { }
80
81 template <typename InputIterator, typename Token>
82 bool operator()(InputIterator &next, InputIterator end, Token &tok)
83 {
84 bool bInQuote = false;
85 tok = Token();
86
87 if (next == end) {
88 return false;
89 }
90 while (next != end) {
91 if (is_escape(*next)) {
92 do_escape(next, end, tok);
93 } else if (is_c(*next)) {
94 if (!bInQuote) {
95 do {
96 ++next;
97 } while (next != end && is_c(*next));
98
99 if (tok.empty()) {
100 if (next == end) {
101 return false;
102 }
103 continue;
104 }
105
106 return true;
107 } else {
108 tok += *next;
109 }
110 } else if (is_quote(*next)) {
111 bInQuote = !bInQuote;
112 } else {
113 tok += *next;
114 }
115
116 ++next;
117 }
118 if (bInQuote) {
119 throw boost::escaped_list_error(
120 std::string("incomplete quoted argument")
121 );
122 }
123 return true;
124 }
125
126 private:
127 bool is_escape(Char e)
128 {
129 char_eq f(e);
130 return std::find_if(escape.cbegin(), escape.cend(), f) != escape.cend();
131 }
132
133 bool is_c(Char e)
134 {
135 char_eq f(e);
136 return std::find_if(c.cbegin(), c.cend(), f) != c.cend();
137 }
138
139 bool is_quote(Char e)
140 {
141 char_eq f(e);
142 return std::find_if(quote.cbegin(), quote.cend(), f) != quote.cend();
143 }
144
145 template <typename Iterator, typename Token>
146 void do_escape(Iterator &next, Iterator end, Token &tok)
147 {
148 if (++next == end) {
149 throw boost::escaped_list_error(
150 std::string("cannot end with escape")
151 );
152 } else if (Traits::eq(*next, 'n')) {
153 tok += '\n';
154 } else if (is_quote(*next)) {
155 tok += *next;
156 } else if (is_c(*next)) {
157 tok += *next;
158 } else if (is_escape(*next)) {
159 tok += *next;
160 } else {
161 throw boost::escaped_list_error(
162 std::string("unknown escape sequence")
163 );
164 }
165 }
166
167 private:
168 string_type escape;
169 string_type c;
170 string_type quote;
171 };
172
173 }
174
26 175 /** /**
27 176 * @brief Tokenize the command line, respecting escapes and quotes. * @brief Tokenize the command line, respecting escapes and quotes.
28 177 * *
 
33 182 inline std::vector<std::string> inline std::vector<std::string>
34 183 breakIntoArgs(const std::string &line) breakIntoArgs(const std::string &line)
35 184 { {
36 boost::escaped_list_separator<char> sep("\\", " ", "\"'");
37 boost::tokenizer<boost::escaped_list_separator<char>> tok(line, sep);
185 detail::arg_list_separator<char> sep("\\", " ", "\"'");
186 boost::tokenizer<decltype(sep)> tok(line, sep);
38 187 return { tok.begin(), tok.end() }; return { tok.begin(), tok.end() };
39 188 } }
40 189
File tests/utils/args.cpp changed (mode: 100644) (index 05d9a59..d26f6e2)
... ... TEST_CASE("Benign input", "[utils][args]")
32 32 std::vector<std::string> args = breakIntoArgs("a b c"); std::vector<std::string> args = breakIntoArgs("a b c");
33 33 REQUIRE(args == (std::vector<std::string>{ "a", "b", "c" })); REQUIRE(args == (std::vector<std::string>{ "a", "b", "c" }));
34 34 } }
35
36 TEST_CASE("Leading whitespace", "[utils][args]")
37 {
38 std::vector<std::string> args = breakIntoArgs(" a b c");
39 REQUIRE(args == (std::vector<std::string>{ "a", "b", "c" }));
40 }
41
42 TEST_CASE("Extra whitespace", "[utils][args]")
43 {
44 std::vector<std::string> args = breakIntoArgs("a b c");
45 REQUIRE(args == (std::vector<std::string>{ "a", "b", "c" }));
46 }
47
48 TEST_CASE("Trailing whitespace", "[utils][args]")
49 {
50 std::vector<std::string> args = breakIntoArgs("a b c ");
51 REQUIRE(args == (std::vector<std::string>{ "a", "b", "c" }));
52 }
53
54 TEST_CASE("Whitespace everywhere", "[utils][args]")
55 {
56 std::vector<std::string> args = breakIntoArgs(" a b c ");
57 REQUIRE(args == (std::vector<std::string>{ "a", "b", "c" }));
58 }
59
60 TEST_CASE("Throws exception on unclosed bracket", "[utils][args]")
61 {
62 REQUIRE_THROWS_AS(breakIntoArgs("'broken"), boost::escaped_list_error);
63 REQUIRE_THROWS_AS(breakIntoArgs("\"broken"), boost::escaped_list_error);
64 REQUIRE_THROWS_AS(breakIntoArgs("broken'"), boost::escaped_list_error);
65 REQUIRE_THROWS_AS(breakIntoArgs("broken\""), boost::escaped_list_error);
66 }
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/dit

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/dit

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master