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 |
|
} |