The Battle for Wesnoth  1.15.5+dev
string_utils.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 by David White <dave@whitevine.net>
3  Copyright (C) 2005 by Guillaume Melquiond <guillaume.melquiond@gmail.com>
4  Copyright (C) 2005 - 2018 by Philippe Plantier <ayin@anathas.org>
5  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6 
7  This program is free software; you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY.
13 
14  See the COPYING file for more details.
15 */
16 
17 /**
18  * @file
19  * Various string-routines.
20  */
21 
22 #include "gettext.hpp"
23 #include "log.hpp"
26 #include "utils/general.hpp"
27 #include <cassert>
28 #include <array>
29 #include <limits>
30 #include <stdexcept>
31 
32 #include <boost/algorithm/string.hpp>
33 
34 static lg::log_domain log_engine("engine");
35 #define ERR_GENERAL LOG_STREAM(err, lg::general())
36 #define ERR_NG LOG_STREAM(err, log_engine)
37 
38 namespace utils {
39 
40 bool isnewline(const char c)
41 {
42  return c == '\r' || c == '\n';
43 }
44 
45 // Make sure that we can use Mac, DOS, or Unix style text files on any system
46 // and they will work, by making sure the definition of whitespace is consistent
47 bool portable_isspace(const char c)
48 {
49  // returns true only on ASCII spaces
50  if (static_cast<unsigned char>(c) >= 128)
51  return false;
52  return isnewline(c) || isspace(static_cast<unsigned char>(c));
53 }
54 
55 // Make sure we regard '\r' and '\n' as a space, since Mac, Unix, and DOS
56 // all consider these differently.
57 bool notspace(const char c)
58 {
59  return !portable_isspace(c);
60 }
61 
63 {
64  s.remove_prefix(std::min(s.find_first_not_of(" \t\r\n"), s.size()));
65  if(s.empty()) {
66  return;
67  }
68  //find_last_not_of never returns npos because !s.empty()
69  size_t first_to_trim = s.find_last_not_of(" \t\r\n") + 1;
70  s = s.substr(0, first_to_trim);
71 }
72 
73 /**
74  * Splits a (comma-)separated string into a vector of pieces.
75  * @param[in] val A (comma-)separated string.
76  * @param[in] c The separator character (usually a comma).
77  * @param[in] flags Flags controlling how the split is done.
78  * This is a bit field with two settings (both on by default):
79  * REMOVE_EMPTY causes empty pieces to be skipped/removed.
80  * STRIP_SPACES causes the leading and trailing spaces of each piece to be ignored/stripped.
81  */
82 std::vector<std::string> split(string_view s, const char sep, const int flags)
83 {
84  std::vector<std::string> res;
85  split_foreach(s, sep, flags, [&](string_view item) {
86  res.emplace_back(item);
87  });
88  return res;
89 }
90 
91 std::set<std::string> split_set(string_view s, char sep, const int flags)
92 {
93  std::set<std::string> res;
94  split_foreach(s, sep, flags, [&](string_view item) {
95  res.emplace(item);
96  });
97  return res;
98 }
99 
100 std::vector<std::string> square_parenthetical_split(const std::string& val,
101  const char separator, const std::string& left,
102  const std::string& right,const int flags)
103 {
104  std::vector< std::string > res;
105  std::vector<char> part;
106  bool in_parenthesis = false;
107  std::vector<std::string::const_iterator> square_left;
108  std::vector<std::string::const_iterator> square_right;
109  std::vector< std::string > square_expansion;
110 
111  std::string lp=left;
112  std::string rp=right;
113 
114  std::string::const_iterator i1 = val.begin();
115  std::string::const_iterator i2;
116  std::string::const_iterator j1;
117  if (flags & STRIP_SPACES) {
118  while (i1 != val.end() && portable_isspace(*i1))
119  ++i1;
120  }
121  i2=i1;
122  j1=i1;
123 
124  if (i1 == val.end()) return res;
125 
126  if (!separator) {
127  ERR_GENERAL << "Separator must be specified for square bracket split function." << std::endl;
128  return res;
129  }
130 
131  if(left.size()!=right.size()){
132  ERR_GENERAL << "Left and Right Parenthesis lists not same length" << std::endl;
133  return res;
134  }
135 
136  while (true) {
137  if(i2 == val.end() || (!in_parenthesis && *i2 == separator)) {
138  //push back square contents
139  std::size_t size_square_exp = 0;
140  for (std::size_t i=0; i < square_left.size(); i++) {
141  std::string tmp_val(square_left[i]+1,square_right[i]);
142  std::vector< std::string > tmp = split(tmp_val);
143  for(const std::string& piece : tmp) {
144  std::size_t found_tilde = piece.find_first_of('~');
145  if (found_tilde == std::string::npos) {
146  std::size_t found_asterisk = piece.find_first_of('*');
147  if (found_asterisk == std::string::npos) {
148  std::string tmp2(piece);
149  boost::trim(tmp2);
150  square_expansion.push_back(tmp2);
151  }
152  else { //'*' multiple expansion
153  std::string s_begin = piece.substr(0,found_asterisk);
154  boost::trim(s_begin);
155  std::string s_end = piece.substr(found_asterisk+1);
156  boost::trim(s_end);
157  for (int ast=std::stoi(s_end); ast>0; --ast)
158  square_expansion.push_back(s_begin);
159  }
160  }
161  else { //expand number range
162  std::string s_begin = piece.substr(0,found_tilde);
163  boost::trim(s_begin);
164  int begin = std::stoi(s_begin);
165  std::size_t padding = 0, padding_end = 0;
166  while (padding<s_begin.size() && s_begin[padding]=='0') {
167  padding++;
168  }
169  std::string s_end = piece.substr(found_tilde+1);
170  boost::trim(s_end);
171  int end = std::stoi(s_end);
172  while (padding_end<s_end.size() && s_end[padding_end]=='0') {
173  padding_end++;
174  }
175  if (padding*padding_end > 0 && s_begin.size() != s_end.size()) {
176  ERR_GENERAL << "Square bracket padding sizes not matching: "
177  << s_begin << " and " << s_end <<".\n";
178  }
179  if (padding_end > padding) padding = padding_end;
180 
181  int increment = (end >= begin ? 1 : -1);
182  end+=increment; //include end in expansion
183  for (int k=begin; k!=end; k+=increment) {
184  std::string pb = std::to_string(k);
185  for (std::size_t p=pb.size(); p<=padding; p++)
186  pb = std::string("0") + pb;
187  square_expansion.push_back(pb);
188  }
189  }
190  }
191  if (i*square_expansion.size() != (i+1)*size_square_exp ) {
192  std::string tmp2(i1, i2);
193  ERR_GENERAL << "Square bracket lengths do not match up: " << tmp2 << std::endl;
194  return res;
195  }
196  size_square_exp = square_expansion.size();
197  }
198 
199  //combine square contents and rest of string for comma zone block
200  std::size_t j = 0;
201  std::size_t j_max = 0;
202  if (!square_left.empty())
203  j_max = square_expansion.size() / square_left.size();
204  do {
205  j1 = i1;
206  std::string new_val;
207  for (std::size_t i=0; i < square_left.size(); i++) {
208  std::string tmp_val(j1, square_left[i]);
209  new_val.append(tmp_val);
210  std::size_t k = j+i*j_max;
211  if (k < square_expansion.size())
212  new_val.append(square_expansion[k]);
213  j1 = square_right[i]+1;
214  }
215  std::string tmp_val(j1, i2);
216  new_val.append(tmp_val);
217  if (flags & STRIP_SPACES)
218  boost::trim_right(new_val);
219  if (!(flags & REMOVE_EMPTY) || !new_val.empty())
220  res.push_back(new_val);
221  j++;
222  } while (j<j_max);
223 
224  if (i2 == val.end()) //escape loop
225  break;
226  ++i2;
227  if (flags & STRIP_SPACES) { //strip leading spaces
228  while (i2 != val.end() && portable_isspace(*i2))
229  ++i2;
230  }
231  i1=i2;
232  square_left.clear();
233  square_right.clear();
234  square_expansion.clear();
235  continue;
236  }
237  if(!part.empty() && *i2 == part.back()) {
238  part.pop_back();
239  if (*i2 == ']') square_right.push_back(i2);
240  if (part.empty())
241  in_parenthesis = false;
242  ++i2;
243  continue;
244  }
245  bool found=false;
246  for(std::size_t i=0; i < lp.size(); i++) {
247  if (*i2 == lp[i]){
248  if (*i2 == '[')
249  square_left.push_back(i2);
250  ++i2;
251  part.push_back(rp[i]);
252  found=true;
253  break;
254  }
255  }
256  if(!found){
257  ++i2;
258  } else
259  in_parenthesis = true;
260  }
261 
262  if(!part.empty()){
263  ERR_GENERAL << "Mismatched parenthesis:\n"<<val<< std::endl;
264  }
265 
266  return res;
267 }
268 
269 std::map<std::string, std::string> map_split(
270  const std::string& val
271  , char major
272  , char minor
273  , int flags
274  , const std::string& default_value)
275 {
276  //first split by major so that we get a vector with the key-value pairs
277  std::vector< std::string > v = split(val, major, flags);
278 
279  //now split by minor to extract keys and values
280  std::map< std::string, std::string > res;
281 
282  for( std::vector< std::string >::iterator i = v.begin(); i != v.end(); ++i) {
283  std::size_t pos = i->find_first_of(minor);
284  std::string key, value;
285 
286  if(pos == std::string::npos) {
287  key = (*i);
288  value = default_value;
289  } else {
290  key = i->substr(0, pos);
291  value = i->substr(pos + 1);
292  }
293 
294  res[key] = value;
295  }
296 
297  return res;
298 }
299 
300 std::vector<std::string> parenthetical_split(const std::string& val,
301  const char separator, const std::string& left,
302  const std::string& right,const int flags)
303 {
304  std::vector< std::string > res;
305  std::vector<char> part;
306  bool in_parenthesis = false;
307 
308  std::string lp=left;
309  std::string rp=right;
310 
311  std::string::const_iterator i1 = val.begin();
312  std::string::const_iterator i2;
313  if (flags & STRIP_SPACES) {
314  while (i1 != val.end() && portable_isspace(*i1))
315  ++i1;
316  }
317  i2=i1;
318 
319  if(left.size()!=right.size()){
320  ERR_GENERAL << "Left and Right Parenthesis lists not same length" << std::endl;
321  return res;
322  }
323 
324  while (i2 != val.end()) {
325  if(!in_parenthesis && separator && *i2 == separator){
326  std::string new_val(i1, i2);
327  if (flags & STRIP_SPACES)
328  boost::trim_right(new_val);
329  if (!(flags & REMOVE_EMPTY) || !new_val.empty())
330  res.push_back(new_val);
331  ++i2;
332  if (flags & STRIP_SPACES) {
333  while (i2 != val.end() && portable_isspace(*i2))
334  ++i2;
335  }
336  i1=i2;
337  continue;
338  }
339  if(!part.empty() && *i2 == part.back()){
340  part.pop_back();
341  if(!separator && part.empty()){
342  std::string new_val(i1, i2);
343  if (flags & STRIP_SPACES)
344  boost::trim(new_val);
345  res.push_back(new_val);
346  ++i2;
347  i1=i2;
348  }else{
349  if (part.empty())
350  in_parenthesis = false;
351  ++i2;
352  }
353  continue;
354  }
355  bool found=false;
356  for(std::size_t i=0; i < lp.size(); i++){
357  if (*i2 == lp[i]){
358  if (!separator && part.empty()){
359  std::string new_val(i1, i2);
360  if (flags & STRIP_SPACES)
361  boost::trim(new_val);
362  res.push_back(new_val);
363  ++i2;
364  i1=i2;
365  }else{
366  ++i2;
367  }
368  part.push_back(rp[i]);
369  found=true;
370  break;
371  }
372  }
373  if(!found){
374  ++i2;
375  } else
376  in_parenthesis = true;
377  }
378 
379  std::string new_val(i1, i2);
380  if (flags & STRIP_SPACES)
381  boost::trim(new_val);
382  if (!(flags & REMOVE_EMPTY) || !new_val.empty())
383  res.push_back(std::move(new_val));
384 
385  if(!part.empty()){
386  ERR_GENERAL << "Mismatched parenthesis:\n"<<val<< std::endl;
387  }
388 
389  return res;
390 }
391 
392 // Modify a number by string representing integer difference, or optionally %
393 int apply_modifier( const int number, const std::string &amount, const int minimum ) {
394  // wassert( amount.empty() == false );
395  int value = 0;
396  try {
397  value = std::stoi(amount);
398  } catch(const std::invalid_argument&) {}
399  if(amount[amount.size()-1] == '%') {
400  value = div100rounded(number * value);
401  }
402  value += number;
403  if (( minimum > 0 ) && ( value < minimum ))
404  value = minimum;
405  return value;
406 }
407 
408 std::string escape(const std::string &str, const char *special_chars)
409 {
410  std::string::size_type pos = str.find_first_of(special_chars);
411  if (pos == std::string::npos) {
412  // Fast path, possibly involving only reference counting.
413  return str;
414  }
415  std::string res = str;
416  do {
417  res.insert(pos, 1, '\\');
418  pos = res.find_first_of(special_chars, pos + 2);
419  } while (pos != std::string::npos);
420  return res;
421 }
422 
424 {
425  std::string::size_type pos = str.find('\\');
426  if (pos == std::string::npos) {
427  // Fast path, possibly involving only reference counting.
428  return str;
429  }
430  std::string res = str;
431  do {
432  res.erase(pos, 1);
433  pos = res.find('\\', pos + 1);
434  } while (pos != std::string::npos);
435  return str;
436 }
437 
439 {
440  static const std::string nonresv_str =
441  "-."
442  "0123456789"
443  "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
444  "_"
445  "abcdefghijklmnopqrstuvwxyz"
446  "~";
447  static const std::set<char> nonresv(nonresv_str.begin(), nonresv_str.end());
448 
449  std::ostringstream res;
450  res << std::hex;
451  res.fill('0');
452 
453  for(char c : str) {
454  if(nonresv.count(c) != 0) {
455  res << c;
456  continue;
457  }
458 
459  res << '%';
460  res.width(2);
461  res << static_cast<int>(c);
462  }
463 
464  return res.str();
465 }
466 
467 bool string_bool(const std::string& str, bool def) {
468  if (str.empty()) return def;
469 
470  // yes/no is the standard, test it first
471  if (str == "yes") return true;
472  if (str == "no"|| str == "false" || str == "off" || str == "0" || str == "0.0")
473  return false;
474 
475  // all other non-empty string are considered as true
476  return true;
477 }
478 
479 std::string bool_string(const bool value)
480 {
481  std::ostringstream ss;
482  ss << std::boolalpha << value;
483 
484  return ss.str();
485 }
486 
488 {
489  std::ostringstream oss;
490  oss << (val >= 0 ? "+" : font::unicode_minus) << std::abs(val);
491  return oss.str();
492 }
493 
495 {
496  std::ostringstream oss;
497  if (val < 0)
498  oss << font::unicode_minus;
499  oss << std::abs(val);
500  return oss.str();
501 }
502 
503 static void si_string_impl_stream_write(std::stringstream &ss, double input) {
504  std::streamsize oldprec = ss.precision();
505 #ifdef _MSC_VER
506  // For MSVC, default mode misbehaves, so we use fixed instead.
507  ss.precision(1);
508  ss << std::fixed
509  << input;
510 #else
511  // In default mode, precision sets the number of significant figures.
512 
513  // 999.5 and above will render as 1000+, however, only numbers above 1000 will use 4 digits
514  // Rounding everything from 100 up (at which point we stop using decimals anyway) avoids this.
515  if (input >= 100) {
516  input = std::round(input);
517  }
518 
519  // When in binary mode, numbers of up to 1023.9999 can be passed
520  // We should render those with 4 digits, instead of as 1e+3.
521  // Input should be an integer number now, but doubles can do strange things, so check the halfway point instead.
522  if (input >= 999.5) {
523  ss.precision(4);
524  } else {
525  ss.precision(3);
526  }
527  ss << input;
528 #endif
529  ss.precision(oldprec);
530 }
531 
532 std::string si_string(double input, bool base2, const std::string& unit) {
533  const double multiplier = base2 ? 1024 : 1000;
534 
535  typedef std::array<std::string, 9> strings9;
536 
537  if(input < 0){
538  return font::unicode_minus + si_string(std::abs(input), base2, unit);
539  }
540 
541  strings9 prefixes;
542  strings9::const_iterator prefix;
543  if (input == 0.0) {
544  strings9 tmp { { "","","","","","","","","" } };
545  prefixes = tmp;
546  prefix = prefixes.begin();
547  } else if (input < 1.0) {
548  strings9 tmp { {
549  "",
550  _("prefix_milli^m"),
551  _("prefix_micro^µ"),
552  _("prefix_nano^n"),
553  _("prefix_pico^p"),
554  _("prefix_femto^f"),
555  _("prefix_atto^a"),
556  _("prefix_zepto^z"),
557  _("prefix_yocto^y")
558  } };
559  prefixes = tmp;
560  prefix = prefixes.begin();
561  while (input < 1.0 && *prefix != prefixes.back()) {
562  input *= multiplier;
563  ++prefix;
564  }
565  } else {
566  strings9 tmp { {
567  "",
568  (base2 ?
569  // TRANSLATORS: Translate the K in KiB only
570  _("prefix_kibi^K") :
571  _("prefix_kilo^k")
572  ),
573  _("prefix_mega^M"),
574  _("prefix_giga^G"),
575  _("prefix_tera^T"),
576  _("prefix_peta^P"),
577  _("prefix_exa^E"),
578  _("prefix_zetta^Z"),
579  _("prefix_yotta^Y")
580  } };
581  prefixes = tmp;
582  prefix = prefixes.begin();
583  while (input > multiplier && *prefix != prefixes.back()) {
584  input /= multiplier;
585  ++prefix;
586  }
587  }
588 
589  std::stringstream ss;
590  si_string_impl_stream_write(ss, input);
591  ss << ' '
592  << *prefix
593  // TRANSLATORS: Translate the i in (for example) KiB only
594  << (base2 && (!(*prefix).empty()) ? _("infix_binary^i") : "")
595  << unit;
596  return ss.str();
597 }
598 
599 static bool is_username_char(char c) {
600  return ((c == '_') || (c == '-'));
601 }
602 
603 static bool is_wildcard_char(char c) {
604  return ((c == '?') || (c == '*'));
605 }
606 
607 bool isvalid_username(const std::string& username) {
608  const std::size_t alnum = std::count_if(username.begin(), username.end(), isalnum);
609  const std::size_t valid_char =
610  std::count_if(username.begin(), username.end(), is_username_char);
611  if ((alnum + valid_char != username.size())
612  || valid_char == username.size() || username.empty() )
613  {
614  return false;
615  }
616  return true;
617 }
618 
619 bool isvalid_wildcard(const std::string& username) {
620  const std::size_t alnum = std::count_if(username.begin(), username.end(), isalnum);
621  const std::size_t valid_char =
622  std::count_if(username.begin(), username.end(), is_username_char);
623  const std::size_t wild_char =
624  std::count_if(username.begin(), username.end(), is_wildcard_char);
625  if ((alnum + valid_char + wild_char != username.size())
626  || valid_char == username.size() || username.empty() )
627  {
628  return false;
629  }
630  return true;
631 }
632 
633 
634 bool word_completion(std::string& text, std::vector<std::string>& wordlist) {
635  std::vector<std::string> matches;
636  const std::size_t last_space = text.rfind(" ");
637  // If last character is a space return.
638  if (last_space == text.size() -1) {
639  wordlist = matches;
640  return false;
641  }
642 
643  bool text_start;
644  std::string semiword;
645  if (last_space == std::string::npos) {
646  text_start = true;
647  semiword = text;
648  } else {
649  text_start = false;
650  semiword.assign(text, last_space + 1, text.size());
651  }
652 
653  std::string best_match = semiword;
654  for (std::vector<std::string>::const_iterator word = wordlist.begin();
655  word != wordlist.end(); ++word)
656  {
657  if (word->size() < semiword.size()
658  || !std::equal(semiword.begin(), semiword.end(), word->begin(),
660  {
661  continue;
662  }
663  if (matches.empty()) {
664  best_match = *word;
665  } else {
666  int j = 0;
667  while (toupper(best_match[j]) == toupper((*word)[j])) j++;
668  if (best_match.begin() + j < best_match.end()) {
669  best_match.erase(best_match.begin() + j, best_match.end());
670  }
671  }
672  matches.push_back(*word);
673  }
674  if(!matches.empty()) {
675  text.replace(last_space + 1, best_match.size(), best_match);
676  }
677  wordlist = matches;
678  return text_start;
679 }
680 
681 static bool is_word_boundary(char c) {
682  return (c == ' ' || c == ',' || c == ':' || c == '\'' || c == '"' || c == '-');
683 }
684 
685 bool word_match(const std::string& message, const std::string& word) {
686  std::size_t first = message.find(word);
687  if (first == std::string::npos) return false;
688  if (first == 0 || is_word_boundary(message[first - 1])) {
689  std::size_t next = first + word.size();
690  if (next == message.size() || is_word_boundary(message[next])) {
691  return true;
692  }
693  }
694  return false;
695 }
696 
698  const bool wild_matching = (!match.empty() && (match[0] == '*' || match[0] == '+'));
699  const std::string::size_type solid_begin = match.find_first_not_of("*+");
700  const bool have_solids = (solid_begin != std::string::npos);
701  // Check the simple cases first
702  if(!have_solids) {
703  const std::string::size_type plus_count = std::count(match.begin(), match.end(), '+');
704  return match.empty() ? str.empty() : str.length() >= plus_count;
705  } else if(str.empty()) {
706  return false;
707  }
708 
709  const std::string::size_type solid_end = match.find_first_of("*+", solid_begin);
710  const std::string::size_type solid_len = (solid_end == std::string::npos)
711  ? match.length() - solid_begin : solid_end - solid_begin;
712  // Since + always consumes at least one character, increment current if the match
713  // begins with one
714  std::string::size_type current = match[0] == '+' ? 1 : 0;
715  bool matches;
716  do {
717  matches = true;
718  // Now try to place the str into the solid space
719  const std::string::size_type test_len = str.length() - current;
720  for(std::string::size_type i=0; i < solid_len && matches; ++i) {
721  char solid_c = match[solid_begin + i];
722  if(i > test_len || !(solid_c == '?' || solid_c == str[current+i])) {
723  matches = false;
724  }
725  }
726  if(matches) {
727  // The solid space matched, now consume it and attempt to find more
728  const std::string consumed_match = (solid_begin+solid_len < match.length())
729  ? match.substr(solid_end) : "";
730  const std::string consumed_str = (solid_len < test_len)
731  ? str.substr(current+solid_len) : "";
732  matches = wildcard_string_match(consumed_str, consumed_match);
733  }
734  } while(wild_matching && !matches && ++current < str.length());
735  return matches;
736 }
737 
738 std::string indent(const std::string& string, std::size_t indent_size)
739 {
740  if(indent_size == 0) {
741  return string;
742  }
743 
744  const std::string indent(indent_size, ' ');
745 
746  if(string.empty()) {
747  return indent;
748  }
749 
750  const std::vector<std::string>& lines = split(string, '\x0A', 0);
751  std::string res;
752 
753  for(std::size_t lno = 0; lno < lines.size();) {
754  const std::string& line = lines[lno];
755 
756  // Lines containing only a carriage return count as empty.
757  if(!line.empty() && line != "\x0D") {
758  res += indent;
759  }
760 
761  res += line;
762 
763  if(++lno < lines.size()) {
764  res += '\x0A';
765  }
766  }
767 
768  return res;
769 }
770 
771 std::vector<std::string> quoted_split(const std::string& val, char c, int flags, char quote)
772 {
773  std::vector<std::string> res;
774 
775  std::string::const_iterator i1 = val.begin();
776  std::string::const_iterator i2 = val.begin();
777 
778  while (i2 != val.end()) {
779  if (*i2 == quote) {
780  // Ignore quoted character
781  ++i2;
782  if (i2 != val.end()) ++i2;
783  } else if (*i2 == c) {
784  std::string new_val(i1, i2);
785  if (flags & STRIP_SPACES)
786  boost::trim(new_val);
787  if (!(flags & REMOVE_EMPTY) || !new_val.empty())
788  res.push_back(std::move(new_val));
789  ++i2;
790  if (flags & STRIP_SPACES) {
791  while(i2 != val.end() && *i2 == ' ')
792  ++i2;
793  }
794 
795  i1 = i2;
796  } else {
797  ++i2;
798  }
799  }
800 
801  std::string new_val(i1, i2);
802  if (flags & STRIP_SPACES)
803  boost::trim(new_val);
804  if (!(flags & REMOVE_EMPTY) || !new_val.empty())
805  res.push_back(new_val);
806 
807  return res;
808 }
809 
810 std::pair<int, int> parse_range(const std::string& str)
811 {
812  const std::string::const_iterator dash = std::find(str.begin(), str.end(), '-');
813  const std::string a(str.begin(), dash);
814  const std::string b = dash != str.end() ? std::string(dash + 1, str.end()) : a;
815  std::pair<int,int> res {0,0};
816  try {
817  if (b == "infinity") {
818  res = std::make_pair(std::stoi(a), std::numeric_limits<int>::max());
819  } else {
820  res = std::make_pair(std::stoi(a), std::stoi(b));
821  }
822 
823  if (res.second < res.first) {
824  res.second = res.first;
825  }
826  } catch(const std::invalid_argument&) {
827  ERR_GENERAL << "Invalid range: "<< str << std::endl;
828  }
829 
830  return res;
831 }
832 
833 std::vector<std::pair<int, int>> parse_ranges(const std::string& str)
834 {
835  std::vector<std::pair<int, int>> to_return;
836  for(const std::string& r : utils::split(str)) {
837  to_return.push_back(parse_range(r));
838  }
839 
840  return to_return;
841 }
842 
843 void ellipsis_truncate(std::string& str, const std::size_t size)
844 {
845  const std::size_t prev_size = str.length();
846 
847  utf8::truncate(str, size);
848 
849  if(str.length() != prev_size) {
850  str += font::ellipsis;
851  }
852 }
853 
854 } // end namespace utils
bool isvalid_wildcard(const std::string &username)
Check if the username pattern contains only valid characters.
BOOST_CXX14_CONSTEXPR size_type find_last_not_of(basic_string_view s, size_type pos=npos) const BOOST_NOEXCEPT
std::pair< int, int > parse_range(const std::string &str)
std::string urlencode(const std::string &str)
Percent-escape characters in a UTF-8 string intended to be part of a URL.
bool isvalid_username(const std::string &username)
Check if the username contains only valid characters.
This class represents a single unit of a specific type.
Definition: unit.hpp:129
#define a
BOOST_CXX14_CONSTEXPR void remove_prefix(size_type n)
REMOVE_EMPTY: remove empty elements.
std::string unescape(const std::string &str)
Remove all escape characters (backslash)
bool wildcard_string_match(const std::string &str, const std::string &match)
Match using &#39;*&#39; as any number of characters (including none), &#39;+&#39; as one or more characters, and &#39;?&#39; as any one character.
void ellipsis_truncate(std::string &str, const std::size_t size)
Truncates a string to a given utf-8 character count and then appends an ellipsis. ...
BOOST_CXX14_CONSTEXPR basic_string_view substr(size_type pos, size_type n=npos) const
#define ERR_GENERAL
bool notspace(const char c)
static bool is_username_char(char c)
bool chars_equal_insensitive(char a, char b)
Definition: general.hpp:21
std::map< std::string, std::string > map_split(const std::string &val, char major, char minor, int flags, const std::string &default_value)
Splits a string based on two separators into a map.
std::string str
Definition: statement.cpp:110
std::string quote(const std::string &str)
Surround the string &#39;str&#39; with double quotes.
void split_foreach(string_view s, char sep, const int flags, const F &f)
#define b
std::string half_signed_value(int val)
Sign with Unicode "−" if negative.
int div100rounded(int num)
Guarantees portable results for division by 100; round half up, to the nearest integer.
Definition: math.hpp:38
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:100
const std::string unicode_minus
Definition: constants.cpp:38
std::vector< std::pair< int, int > > parse_ranges(const std::string &str)
const std::string ellipsis
Definition: constants.cpp:35
std::string si_string(double input, bool base2, const std::string &unit)
Convert into a string with an SI-postfix.
void trim(string_view &s)
static bool is_wildcard_char(char c)
BOOST_CXX14_CONSTEXPR size_type find_first_not_of(basic_string_view s, size_type pos=0) const BOOST_NOEXCEPT
std::string & truncate(std::string &str, const std::size_t size)
Truncates a UTF-8 string to the specified number of characters.
Definition: unicode.cpp:117
std::set< std::string > split_set(string_view s, char sep, const int flags)
BOOST_CONSTEXPR bool empty() const BOOST_NOEXCEPT
lu_byte right
Definition: lparser.cpp:1027
BOOST_CONSTEXPR size_type size() const BOOST_NOEXCEPT
std::string escape(const std::string &str, const char *special_chars)
Prepends a configurable set of characters with a backslash.
std::size_t i
Definition: function.cpp:933
u64 size
Definition: statement.cpp:80
mock_party p
static map_location::DIRECTION s
std::vector< std::string > quoted_split(const std::string &val, char c, int flags, char quote)
This function is identical to split(), except it does not split when it otherwise would if the previo...
std::string bool_string(const bool value)
Converts a bool value to &#39;true&#39; or &#39;false&#39;.
bool string_bool(const std::string &str, bool def)
Convert no, false, off, 0, 0.0 to false, empty to def, and others to true.
bool isnewline(const char c)
#define next(ls)
Definition: llex.cpp:32
static lg::log_domain log_engine("engine")
std::vector< std::string > split(const config_attribute_value &val)
bool find(E event, F functor)
Tests whether an event handler is available.
int apply_modifier(const int number, const std::string &amount, const int minimum)
lu_byte left
Definition: lparser.cpp:1026
static bool is_word_boundary(char c)
std::string indent(const std::string &string, std::size_t indent_size)
Indent a block of text.
Standard logging facilities (interface).
static const char * match(MatchState *ms, const char *s, const char *p)
Definition: lstrlib.cpp:425
bool portable_isspace(const char c)
std::string signed_value(int val)
Convert into a signed value (using the Unicode "−" and +0 convention.
mock_char c
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
bool word_completion(std::string &text, std::vector< std::string > &wordlist)
Try to complete the last word of &#39;text&#39; with the &#39;wordlist&#39;.
std::vector< std::string > square_parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Similar to parenthetical_split, but also expands embedded square brackets.
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:384
static void si_string_impl_stream_write(std::stringstream &ss, double input)
bool word_match(const std::string &message, const std::string &word)
Check if a message contains a word.
std::vector< std::string > parenthetical_split(const std::string &val, const char separator, const std::string &left, const std::string &right, const int flags)
Splits a string based either on a separator, except then the text appears within specified parenthesi...
auto * ss
Definition: result_set.cpp:281