CLI11  2.1.0
StringTools.hpp
Go to the documentation of this file.
1 // Copyright (c) 2017-2021, 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 
20 namespace CLI {
21 
22 // [CLI11:string_tools_hpp:verbatim]
23 
26 namespace enums {
27 
29 template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
30 std::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 
38 using enums::operator<<;
39 
40 namespace detail {
43 constexpr int expected_max_vector_size{1 << 29};
44 // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
46 inline 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 
63 template <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 
76 template <typename T,
77  typename Callable,
78  typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
79 std::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 
96 template <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 
109 inline 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 
116 inline 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 
123 inline 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 
130 inline 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 
138 inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); }
139 
141 inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); }
142 
144 inline std::string trim_copy(const std::string &str) {
145  std::string s = str;
146  return trim(s);
147 }
148 
150 inline 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 
164 inline 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 
177 inline std::string trim_copy(const std::string &str, const std::string &filter) {
178  std::string s = str;
179  return trim(s, filter);
180 }
182 inline 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 
200 inline 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 
219 template <typename T> bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); }
220 
222 template <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 
230 inline 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 
242 inline 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 
248 inline bool is_separator(const std::string &str) {
249  static const std::string sep("%%");
250  return (str.empty() || str == sep);
251 }
252 
254 inline 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 
259 inline 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 
267 inline 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 
273 inline 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 
286 inline bool has_default_flag_values(const std::string &flags) {
287  return (flags.find_first_of("{!") != std::string::npos);
288 }
289 
290 inline 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 
304 inline 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) {
310  if(ignore_underscore) {
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 
336 template <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 
346 inline 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 
402 inline 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 
415 inline 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::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::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::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 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 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
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::vector< std::string > split(const std::string &s, char delim)
Split a string by a delim.
Definition: StringTools.hpp:46
std::vector< std::string > split_up(std::string str, char delimiter='\0')
Definition: StringTools.hpp:346
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::size_t escape_detect(std::string &str, std::size_t offset)
Definition: StringTools.hpp:402
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
std::string & ltrim(std::string &str)
Trim whitespace from left of string.
Definition: StringTools.hpp:109
std::string & trim(std::string &str)
Trim whitespace from string.
Definition: StringTools.hpp:138
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
std::string & rtrim(std::string &str)
Trim whitespace from right of string.
Definition: StringTools.hpp:123
std::ostream & format_aliases(std::ostream &out, const std::vector< std::string > &aliases, std::size_t wid)
Print subcommand aliases.
Definition: StringTools.hpp:200
std::string & add_quotes_if_needed(std::string &str)
Add quotes if the string contains spaces.
Definition: StringTools.hpp:415
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:876
std::string ignore_underscore(std::string item)
Helper function to allow ignore_underscore to be passed to IsMember or Transform.
Definition: Validators.hpp:879