CLI11  1.9.1
StringTools.hpp
Go to the documentation of this file.
1 // Copyright (c) 2017-2020, 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 #include <algorithm>
10 #include <iomanip>
11 #include <locale>
12 #include <sstream>
13 #include <stdexcept>
14 #include <string>
15 #include <type_traits>
16 #include <vector>
17 
18 namespace CLI {
19 
22 namespace enums {
23 
25 template <typename T, typename = typename std::enable_if<std::is_enum<T>::value>::type>
26 std::ostream &operator<<(std::ostream &in, const T &item) {
27  // make sure this is out of the detail namespace otherwise it won't be found when needed
28  return in << static_cast<typename std::underlying_type<T>::type>(item);
29 }
30 
31 } // namespace enums
32 
34 using enums::operator<<;
35 
36 namespace detail {
39 constexpr int expected_max_vector_size{1 << 29};
40 // Based on http://stackoverflow.com/questions/236129/split-a-string-in-c
42 inline std::vector<std::string> split(const std::string &s, char delim) {
43  std::vector<std::string> elems;
44  // Check to see if empty string, give consistent result
45  if(s.empty()) {
46  elems.emplace_back();
47  } else {
48  std::stringstream ss;
49  ss.str(s);
50  std::string item;
51  while(std::getline(ss, item, delim)) {
52  elems.push_back(item);
53  }
54  }
55  return elems;
56 }
57 
59 template <typename T> std::string join(const T &v, std::string delim = ",") {
60  std::ostringstream s;
61  auto beg = std::begin(v);
62  auto end = std::end(v);
63  if(beg != end)
64  s << *beg++;
65  while(beg != end) {
66  s << delim << *beg++;
67  }
68  return s.str();
69 }
70 
72 template <typename T,
73  typename Callable,
74  typename = typename std::enable_if<!std::is_constructible<std::string, Callable>::value>::type>
75 std::string join(const T &v, Callable func, std::string delim = ",") {
76  std::ostringstream s;
77  auto beg = std::begin(v);
78  auto end = std::end(v);
79  if(beg != end)
80  s << func(*beg++);
81  while(beg != end) {
82  s << delim << func(*beg++);
83  }
84  return s.str();
85 }
86 
88 template <typename T> std::string rjoin(const T &v, std::string delim = ",") {
89  std::ostringstream s;
90  for(std::size_t start = 0; start < v.size(); start++) {
91  if(start > 0)
92  s << delim;
93  s << v[v.size() - start - 1];
94  }
95  return s.str();
96 }
97 
98 // Based roughly on http://stackoverflow.com/questions/25829143/c-trim-whitespace-from-a-string
99 
101 inline std::string &ltrim(std::string &str) {
102  auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
103  str.erase(str.begin(), it);
104  return str;
105 }
106 
108 inline std::string &ltrim(std::string &str, const std::string &filter) {
109  auto it = std::find_if(str.begin(), str.end(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
110  str.erase(str.begin(), it);
111  return str;
112 }
113 
115 inline std::string &rtrim(std::string &str) {
116  auto it = std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace<char>(ch, std::locale()); });
117  str.erase(it.base(), str.end());
118  return str;
119 }
120 
122 inline std::string &rtrim(std::string &str, const std::string &filter) {
123  auto it =
124  std::find_if(str.rbegin(), str.rend(), [&filter](char ch) { return filter.find(ch) == std::string::npos; });
125  str.erase(it.base(), str.end());
126  return str;
127 }
128 
130 inline std::string &trim(std::string &str) { return ltrim(rtrim(str)); }
131 
133 inline std::string &trim(std::string &str, const std::string filter) { return ltrim(rtrim(str, filter), filter); }
134 
136 inline std::string trim_copy(const std::string &str) {
137  std::string s = str;
138  return trim(s);
139 }
140 
142 inline std::string &remove_quotes(std::string &str) {
143  if(str.length() > 1 && (str.front() == '"' || str.front() == '\'')) {
144  if(str.front() == str.back()) {
145  str.pop_back();
146  str.erase(str.begin(), str.begin() + 1);
147  }
148  }
149  return str;
150 }
151 
153 inline std::string trim_copy(const std::string &str, const std::string &filter) {
154  std::string s = str;
155  return trim(s, filter);
156 }
158 inline std::ostream &format_help(std::ostream &out, std::string name, std::string description, std::size_t wid) {
159  name = " " + name;
160  out << std::setw(static_cast<int>(wid)) << std::left << name;
161  if(!description.empty()) {
162  if(name.length() >= wid)
163  out << "\n" << std::setw(static_cast<int>(wid)) << "";
164  for(const char c : description) {
165  out.put(c);
166  if(c == '\n') {
167  out << std::setw(static_cast<int>(wid)) << "";
168  }
169  }
170  }
171  out << "\n";
172  return out;
173 }
174 
176 template <typename T> bool valid_first_char(T c) {
177  return std::isalnum(c, std::locale()) || c == '_' || c == '?' || c == '@';
178 }
179 
181 template <typename T> bool valid_later_char(T c) { return valid_first_char(c) || c == '.' || c == '-'; }
182 
184 inline bool valid_name_string(const std::string &str) {
185  if(str.empty() || !valid_first_char(str[0]))
186  return false;
187  for(auto c : str.substr(1))
188  if(!valid_later_char(c))
189  return false;
190  return true;
191 }
192 
194 inline bool isalpha(const std::string &str) {
195  return std::all_of(str.begin(), str.end(), [](char c) { return std::isalpha(c, std::locale()); });
196 }
197 
199 inline std::string to_lower(std::string str) {
200  std::transform(std::begin(str), std::end(str), std::begin(str), [](const std::string::value_type &x) {
201  return std::tolower(x, std::locale());
202  });
203  return str;
204 }
205 
207 inline std::string remove_underscore(std::string str) {
208  str.erase(std::remove(std::begin(str), std::end(str), '_'), std::end(str));
209  return str;
210 }
211 
213 inline std::string find_and_replace(std::string str, std::string from, std::string to) {
214 
215  std::size_t start_pos = 0;
216 
217  while((start_pos = str.find(from, start_pos)) != std::string::npos) {
218  str.replace(start_pos, from.length(), to);
219  start_pos += to.length();
220  }
221 
222  return str;
223 }
224 
226 inline bool has_default_flag_values(const std::string &flags) {
227  return (flags.find_first_of("{!") != std::string::npos);
228 }
229 
230 inline void remove_default_flag_values(std::string &flags) {
231  auto loc = flags.find_first_of('{');
232  while(loc != std::string::npos) {
233  auto finish = flags.find_first_of("},", loc + 1);
234  if((finish != std::string::npos) && (flags[finish] == '}')) {
235  flags.erase(flags.begin() + static_cast<std::ptrdiff_t>(loc),
236  flags.begin() + static_cast<std::ptrdiff_t>(finish) + 1);
237  }
238  loc = flags.find_first_of('{', loc + 1);
239  }
240  flags.erase(std::remove(flags.begin(), flags.end(), '!'), flags.end());
241 }
242 
244 inline std::ptrdiff_t find_member(std::string name,
245  const std::vector<std::string> names,
246  bool ignore_case = false,
247  bool ignore_underscore = false) {
248  auto it = std::end(names);
249  if(ignore_case) {
250  if(ignore_underscore) {
252  it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
253  return detail::to_lower(detail::remove_underscore(local_name)) == name;
254  });
255  } else {
256  name = detail::to_lower(name);
257  it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
258  return detail::to_lower(local_name) == name;
259  });
260  }
261 
262  } else if(ignore_underscore) {
263  name = detail::remove_underscore(name);
264  it = std::find_if(std::begin(names), std::end(names), [&name](std::string local_name) {
265  return detail::remove_underscore(local_name) == name;
266  });
267  } else {
268  it = std::find(std::begin(names), std::end(names), name);
269  }
270 
271  return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
272 }
273 
276 template <typename Callable> inline std::string find_and_modify(std::string str, std::string trigger, Callable modify) {
277  std::size_t start_pos = 0;
278  while((start_pos = str.find(trigger, start_pos)) != std::string::npos) {
279  start_pos = modify(str, start_pos);
280  }
281  return str;
282 }
283 
286 inline std::vector<std::string> split_up(std::string str, char delimiter = '\0') {
287 
288  const std::string delims("\'\"`");
289  auto find_ws = [delimiter](char ch) {
290  return (delimiter == '\0') ? (std::isspace<char>(ch, std::locale()) != 0) : (ch == delimiter);
291  };
292  trim(str);
293 
294  std::vector<std::string> output;
295  bool embeddedQuote = false;
296  char keyChar = ' ';
297  while(!str.empty()) {
298  if(delims.find_first_of(str[0]) != std::string::npos) {
299  keyChar = str[0];
300  auto end = str.find_first_of(keyChar, 1);
301  while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes
302  end = str.find_first_of(keyChar, end + 1);
303  embeddedQuote = true;
304  }
305  if(end != std::string::npos) {
306  output.push_back(str.substr(1, end - 1));
307  str = str.substr(end + 1);
308  } else {
309  output.push_back(str.substr(1));
310  str = "";
311  }
312  } else {
313  auto it = std::find_if(std::begin(str), std::end(str), find_ws);
314  if(it != std::end(str)) {
315  std::string value = std::string(str.begin(), it);
316  output.push_back(value);
317  str = std::string(it + 1, str.end());
318  } else {
319  output.push_back(str);
320  str = "";
321  }
322  }
323  // transform any embedded quotes into the regular character
324  if(embeddedQuote) {
325  output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
326  embeddedQuote = false;
327  }
328  trim(str);
329  }
330  return output;
331 }
332 
337 inline std::string fix_newlines(const std::string &leader, std::string input) {
338  std::string::size_type n = 0;
339  while(n != std::string::npos && n < input.size()) {
340  n = input.find('\n', n);
341  if(n != std::string::npos) {
342  input = input.substr(0, n + 1) + leader + input.substr(n + 1);
343  n += leader.size();
344  }
345  }
346  return input;
347 }
348 
353 inline std::size_t escape_detect(std::string &str, std::size_t offset) {
354  auto next = str[offset + 1];
355  if((next == '\"') || (next == '\'') || (next == '`')) {
356  auto astart = str.find_last_of("-/ \"\'`", offset - 1);
357  if(astart != std::string::npos) {
358  if(str[astart] == ((str[offset] == '=') ? '-' : '/'))
359  str[offset] = ' '; // interpret this as a space so the split_up works properly
360  }
361  }
362  return offset + 1;
363 }
364 
366 inline std::string &add_quotes_if_needed(std::string &str) {
367  if((str.front() != '"' && str.front() != '\'') || str.front() != str.back()) {
368  char quote = str.find('"') < str.find('\'') ? '\'' : '"';
369  if(str.find(' ') != std::string::npos) {
370  str.insert(0, 1, quote);
371  str.append(1, quote);
372  }
373  }
374  return str;
375 }
376 
377 } // namespace detail
378 
379 } // namespace CLI
CLI::detail::find_and_replace
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:213
CLI::detail::valid_first_char
bool valid_first_char(T c)
Verify the first character of an option.
Definition: StringTools.hpp:176
CLI::detail::expected_max_vector_size
constexpr int expected_max_vector_size
Definition: StringTools.hpp:39
CLI::detail::rjoin
std::string rjoin(const T &v, std::string delim=",")
Join a string in reverse order.
Definition: StringTools.hpp:88
CLI::detail::split
std::vector< std::string > split(const std::string &s, char delim)
Split a string by a delim.
Definition: StringTools.hpp:42
CLI::detail::add_quotes_if_needed
std::string & add_quotes_if_needed(std::string &str)
Add quotes if the string contains spaces.
Definition: StringTools.hpp:366
CLI::detail::trim_copy
std::string trim_copy(const std::string &str)
Make a copy of the string and then trim it.
Definition: StringTools.hpp:136
CLI::detail::rtrim
std::string & rtrim(std::string &str)
Trim whitespace from right of string.
Definition: StringTools.hpp:115
CLI::detail::join
std::string join(const T &v, std::string delim=",")
Simple function to join a string.
Definition: StringTools.hpp:59
CLI::detail::valid_later_char
bool valid_later_char(T c)
Verify following characters of an option.
Definition: StringTools.hpp:181
CLI
Definition: App.hpp:32
CLI::detail::valid_name_string
bool valid_name_string(const std::string &str)
Verify an option name.
Definition: StringTools.hpp:184
CLI::detail::split_up
std::vector< std::string > split_up(std::string str, char delimiter='\0')
Definition: StringTools.hpp:286
CLI::detail::ltrim
std::string & ltrim(std::string &str)
Trim whitespace from left of string.
Definition: StringTools.hpp:101
CLI::ignore_case
std::string ignore_case(std::string item)
Helper function to allow ignore_case to be passed to IsMember or Transform.
Definition: Validators.hpp:902
CLI::enums::operator<<
std::ostream & operator<<(std::ostream &in, const T &item)
output streaming for enumerations
Definition: StringTools.hpp:26
CLI::detail::find_member
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:244
CLI::ignore_underscore
std::string ignore_underscore(std::string item)
Helper function to allow ignore_underscore to be passed to IsMember or Transform.
Definition: Validators.hpp:905
CLI::detail::find_and_modify
std::string find_and_modify(std::string str, std::string trigger, Callable modify)
Definition: StringTools.hpp:276
CLI::detail::remove_default_flag_values
void remove_default_flag_values(std::string &flags)
Definition: StringTools.hpp:230
CLI::detail::format_help
std::ostream & format_help(std::ostream &out, std::string name, std::string description, std::size_t wid)
Print a two part "help" string.
Definition: StringTools.hpp:158
CLI::detail::to_lower
std::string to_lower(std::string str)
Return a lower case version of a string.
Definition: StringTools.hpp:199
CLI::detail::fix_newlines
std::string fix_newlines(const std::string &leader, std::string input)
Definition: StringTools.hpp:337
CLI::detail::remove_underscore
std::string remove_underscore(std::string str)
remove underscores from a string
Definition: StringTools.hpp:207
CLI::detail::remove_quotes
std::string & remove_quotes(std::string &str)
remove quotes at the front and back of a string either '"' or '\''
Definition: StringTools.hpp:142
CLI::detail::trim
std::string & trim(std::string &str)
Trim whitespace from string.
Definition: StringTools.hpp:130
CLI::detail::escape_detect
std::size_t escape_detect(std::string &str, std::size_t offset)
Definition: StringTools.hpp:353
CLI::detail::isalpha
bool isalpha(const std::string &str)
Verify that str consists of letters only.
Definition: StringTools.hpp:194
CLI::detail::has_default_flag_values
bool has_default_flag_values(const std::string &flags)
check if the flag definitions has possible false flags
Definition: StringTools.hpp:226