CLI11 2.2.0
Loading...
Searching...
No Matches
StringTools.hpp
Go to the documentation of this file.
1// Copyright (c) 2017-2022, University of Cincinnati, developed by Henry Schreiner
2// under NSF AWARD 1414736 and by the respective contributors.
3// All rights reserved.
4//
5// SPDX-License-Identifier: BSD-3-Clause
6
7#pragma once
8
9// [CLI11:public_includes:set]
10#include <algorithm>
11#include <iomanip>
12#include <locale>
13#include <sstream>
14#include <stdexcept>
15#include <string>
16#include <type_traits>
17#include <vector>
18// [CLI11:public_includes:end]
19
20namespace CLI {
21
22// [CLI11:string_tools_hpp:verbatim]
23
26namespace enums {
27
29template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
30std::ostream &operator<<(std::ostream &in, const T &item) {
31 // make sure this is out of the detail namespace otherwise it won't be found when needed
32 return in << static_cast<typename std::underlying_type<T>::type>(item);
33}
34
35} // namespace enums
36
38using enums::operator<<;
39
40namespace detail {
43constexpr int expected_max_vector_size{1 << 29};
44// Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
46inline std::vector<std::string> split(const std::string &s, char delim) {
47 std::vector<std::string> elems;
48 // Check to see if empty string, give consistent result
49 if(s.empty()) {
50 elems.emplace_back();
51 } else {
52 std::stringstream ss;
53 ss.str(s);
54 std::string item;
55 while(std::getline(ss, item, delim)) {
56 elems.push_back(item);
57 }
58 }
59 return elems;
60}
61
63template <typename T> std::string join(const T &v, std::string delim = ",") {
64 std::ostringstream s;
65 auto beg = std::begin(v);
66 auto end = std::end(v);
67 if(beg != end)
68 s << *beg++;
69 while(beg != end) {
70 s << delim << *beg++;
71 }
72 return s.str();
73}
74
76template <typename T,
77 typename Callable,
78 typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
79std::string join(const T &v, Callable func, std::string delim = ",") {
80 std::ostringstream s;
81 auto beg = std::begin(v);
82 auto end = std::end(v);
83 auto loc = s.tellp();
84 while(beg != end) {
85 auto nloc = s.tellp();
86 if(nloc > loc) {
87 s << delim;
88 loc = nloc;
89 }
90 s << func(*beg++);
91 }
92 return s.str();
93}
94
96template <typename T> std::string rjoin(const T &v, std::string delim = ",") {
97 std::ostringstream s;
98 for(std::size_t start = 0; start < v.size(); start++) {
99 if(start > 0)
100 s << delim;
101 s << v[v.size() - start - 1];
102 }
103 return s.str();
104}
105
106// Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string
107
109inline std::string &ltrim(std::string &str) {
110 auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
111 str.erase(str.begin(), it);
112 return str;
113}
114
116inline std::string &ltrim(std::string &str, const std::string &filter) {
117 auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
118 str.erase(str.begin(), it);
119 return str;
120}
121
123inline std::string &rtrim(std::string &str) {
124 auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
125 str.erase(it.base(), str.end());
126 return str;
127}
128
130inline std::string &rtrim(std::string &str, const std::string &filter) {
131 auto it =
132 std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
133 str.erase(it.base(), str.end());
134 return str;
135}
136
138inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); }
139
141inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); }
142
144inline std::string trim_copy(const std::string &str) {
145 std::string s = str;
146 return trim(s);
147}
148
150inline std::string &remove_quotes(std::string &str) {
151 if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
152 if(str.front() == str.back()) {
153 str.pop_back();
154 str.erase(str.begin(), str.begin() + 1);
155 }
156 }
157 return str;
158}
159
164inline std::string fix_newlines(const std::string &leader, std::string input) {
165 std::string::size_type n = 0;
166 while(n != std::string::npos && n < input.size()) {
167 n = input.find('\n', n);
168 if(n != std::string::npos) {
169 input = input.substr(0, n + 1) + leader + input.substr(n + 1);
170 n += leader.size();
171 }
172 }
173 return input;
174}
175
177inline std::string trim_copy(const std::string &str, const std::string &filter) {
178 std::string s = str;
179 return trim(s, filter);
180}
182inline std::ostream &format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid) {
183 name = " " + name;
184 out << std::setw(static_cast<int>(wid)) << std::left << name;
185 if(!description.empty()) {
186 if(name.length() >= wid)
187 out << "\n" << std::setw(static_cast<int>(wid)) << "";
188 for(const char c : description) {
189 out.put(c);
190 if(c == '\n') {
191 out << std::setw(static_cast<int>(wid)) << "";
192 }
193 }
194 }
195 out << "\n";
196 return out;
197}
198
200inline std::ostream &format_aliases(std::ostream &out, const std::vector<std::string> &aliases, std::size_t wid) {
201 if(!aliases.empty()) {
202 out << std::setw(static_cast<int>(wid)) << " aliases: ";
203 bool front = true;
204 for(const auto &alias : aliases) {
205 if(!front) {
206 out << ", ";
207 } else {
208 front = false;
209 }
210 out << detail::fix_newlines(" ", alias);
211 }
212 out << "\n";
213 }
214 return out;
215}
216
219template <typename T> bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); }
220
222template <typename T> bool valid_later_char(T c) {
223 // = and : are value separators, { has special meaning for option defaults,
224 // and \n would just be annoying to deal with in many places allowing space here has too much potential for
225 // inadvertent entry errors and bugs
226 return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n');
227}
228
230inline bool valid_name_string(const std::string &str) {
231 if(str.empty() || !valid_first_char(str[0])) {
232 return false;
233 }
234 auto e = str.end();
235 for(auto c = str.begin() + 1; c != e; ++c)
236 if(!valid_later_char(*c))
237 return false;
238 return true;
239}
240
242inline bool valid_alias_name_string(const std::string &str) {
243 static const std::string badChars(std::string("\n") + '\0');
244 return (str.find_first_of(badChars) == std::string::npos);
245}
246
248inline bool is_separator(const std::string &str) {
249 static const std::string sep("%%");
250 return (str.empty() || str == sep);
251}
252
254inline bool isalpha(const std::string &str) {
255 return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });
256}
257
259inline std::string to_lower(std::string str) {
260 std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) {
261 return std::tolower(x, std::locale());
262 });
263 return str;
264}
265
267inline std::string remove_underscore(std::string str) {
268 str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));
269 return str;
270}
271
273inline std::string find_and_replace(std::string str, std::string from, std::string to) {
274
275 std::size_t start_pos = 0;
276
277 while((start_pos = str.find(from, start_pos)) != std::string::npos) {
278 str.replace(start_pos, from.length(), to);
279 start_pos += to.length();
280 }
281
282 return str;
283}
284
286inline bool has_default_flag_values(const std::string &flags) {
287 return (flags.find_first_of("{!") != std::string::npos);
288}
289
290inline void remove_default_flag_values(std::string &flags) {
291 auto loc = flags.find_first_of('{', 2);
292 while(loc != std::string::npos) {
293 auto finish = flags.find_first_of("},", loc + 1);
294 if((finish != std::string::npos) && (flags[finish] == '}')) {
295 flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
296 flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
297 }
298 loc = flags.find_first_of('{', loc + 1);
299 }
300 flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
301}
302
304inline std::ptrdiff_t find_member(std::string name,
305 const std::vector<std::string> names,
306 bool ignore_case = false,
307 bool ignore_underscore = false) {
308 auto it = std::end(names);
309 if(ignore_case) {
312 it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
313 return detail::to_lower(detail::remove_underscore(local_name)) == name;
314 });
315 } else {
316 name = detail::to_lower(name);
317 it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
318 return detail::to_lower(local_name) == name;
319 });
320 }
321
322 } else if(ignore_underscore) {
323 name = detail::remove_underscore(name);
324 it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
325 return detail::remove_underscore(local_name) == name;
326 });
327 } else {
328 it = std::find(std::begin(names), std::end(names), name);
329 }
330
331 return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
332}
333
336template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) {
337 std::size_t start_pos = 0;
338 while((start_pos = str.find(trigger, start_pos)) != std::string::npos) {
339 start_pos = modify(str, start_pos);
340 }
341 return str;
342}
343
346inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') {
347
348 const std::string delims("\'\"`");
349 auto find_ws = [delimiter](char ch) {
350 return (delimiter == '\0') ? (std::isspace<char>(ch, std::locale()) != 0) : (ch == delimiter);
351 };
352 trim(str);
353
354 std::vector<std::string> output;
355 bool embeddedQuote = false;
356 char keyChar = ' ';
357 while(!str.empty()) {
358 if(delims.find_first_of(str[0]) != std::string::npos) {
359 keyChar = str[0];
360 auto end = str.find_first_of(keyChar, 1);
361 while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes
362 end = str.find_first_of(keyChar, end + 1);
363 embeddedQuote = true;
364 }
365 if(end != std::string::npos) {
366 output.push_back(str.substr(1, end - 1));
367 if(end + 2 < str.size()) {
368 str = str.substr(end + 2);
369 } else {
370 str.clear();
371 }
372
373 } else {
374 output.push_back(str.substr(1));
375 str = "";
376 }
377 } else {
378 auto it = std::find_if(std::begin(str), std::end(str), find_ws);
379 if(it != std::end(str)) {
380 std::string value = std::string(str.begin(), it);
381 output.push_back(value);
382 str = std::string(it + 1, str.end());
383 } else {
384 output.push_back(str);
385 str = "";
386 }
387 }
388 // transform any embedded quotes into the regular character
389 if(embeddedQuote) {
390 output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
391 embeddedQuote = false;
392 }
393 trim(str);
394 }
395 return output;
396}
397
402inline std::size_t escape_detect(std::string &str, std::size_t offset) {
403 auto next = str[offset + 1];
404 if((next == '\"') || (next == '\'') || (next == '`')) {
405 auto astart = str.find_last_of("-/ \"\'`", offset - 1);
406 if(astart != std::string::npos) {
407 if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
408 str[offset] = ' '; // interpret this as a space so the split_up works properly
409 }
410 }
411 return offset + 1;
412}
413
415inline std::string &add_quotes_if_needed(std::string &str) {
416 if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
417 char quote = str.find('"') < str.find('\'') ? '\'' : '"';
418 if(str.find(' ') != std::string::npos) {
419 str.insert(0, 1, quote);
420 str.append(1, quote);
421 }
422 }
423 return str;
424}
425
426} // namespace detail
427
428// [CLI11:string_tools_hpp:end]
429
430} // namespace CLI
std::string & remove_quotes(std::string &str)
remove quotes at the front and back of a string either '"' or '\''
Definition StringTools.hpp:150
std::vector< std::string > output
Definition StringTools.hpp:354
std::string & rtrim(std::string &str)
Trim whitespace from right of string.
Definition StringTools.hpp:123
std::ptrdiff_t find_member(std::string name, const std::vector< std::string > names, bool ignore_case=false, bool ignore_underscore=false)
Check if a string is a member of a list of strings and optionally ignore case or ignore underscores.
Definition StringTools.hpp:304
bool valid_first_char(T c)
Definition StringTools.hpp:219
bool valid_name_string(const std::string &str)
Verify an option/subcommand name.
Definition StringTools.hpp:230
std::ostream & format_help(std::ostream &out, std::string name, const std::string &description, std::size_t wid)
Print a two part "help" string.
Definition StringTools.hpp:182
std::string remove_underscore(std::string str)
remove underscores from a string
Definition StringTools.hpp:267
bool is_separator(const std::string &str)
check if a string is a container segment separator (empty or "%%")
Definition StringTools.hpp:248
std::string find_and_modify(std::string str, std::string trigger, Callable modify)
Definition StringTools.hpp:336
std::string trim_copy(const std::string &str)
Make a copy of the string and then trim it.
Definition StringTools.hpp:144
std::string & trim(std::string &str)
Trim whitespace from string.
Definition StringTools.hpp:138
auto find_ws
Definition StringTools.hpp:349
std::string fix_newlines(const std::string &leader, std::string input)
Definition StringTools.hpp:164
void remove_default_flag_values(std::string &flags)
Definition StringTools.hpp:290
std::ostream & format_aliases(std::ostream &out, const std::vector< std::string > &aliases, std::size_t wid)
Print subcommand aliases.
Definition StringTools.hpp:200
bool valid_later_char(T c)
Verify following characters of an option.
Definition StringTools.hpp:222
constexpr int expected_max_vector_size
Definition StringTools.hpp:43
std::string & ltrim(std::string &str)
Trim whitespace from left of string.
Definition StringTools.hpp:109
std::string join(const T &v, std::string delim=",")
Simple function to join a string.
Definition StringTools.hpp:63
std::string find_and_replace(std::string str, std::string from, std::string to)
Find and replace a substring with another substring.
Definition StringTools.hpp:273
std::vector< std::string > split(const std::string &s, char delim)
Split a string by a delim.
Definition StringTools.hpp:46
bool valid_alias_name_string(const std::string &str)
Verify an app name.
Definition StringTools.hpp:242
bool isalpha(const std::string &str)
Verify that str consists of letters only.
Definition StringTools.hpp:254
bool embeddedQuote
Definition StringTools.hpp:355
std::string to_lower(std::string str)
Return a lower case version of a string.
Definition StringTools.hpp:259
bool has_default_flag_values(const std::string &flags)
check if the flag definitions has possible false flags
Definition StringTools.hpp:286
char keyChar
Definition StringTools.hpp:356
std::string rjoin(const T &v, std::string delim=",")
Join a string in reverse order.
Definition StringTools.hpp:96
std::ostream & operator<<(std::ostream &in, const T &item)
output streaming for enumerations
Definition StringTools.hpp:30
Definition App.hpp:34
std::string ignore_case(std::string item)
Helper function to allow ignore_case to be passed to IsMember or Transform.
Definition Validators.hpp:909
std::string ignore_underscore(std::string item)
Helper function to allow ignore_underscore to be passed to IsMember or Transform.
Definition Validators.hpp:912
std::string & add_quotes_if_needed(std::string &str)
Add quotes if the string contains spaces.
Definition StringTools.hpp:415
std::size_t escape_detect(std::string &str, std::size_t offset)
Definition StringTools.hpp:402