otsdaq  v2_03_00
StringMacros.cc
1 #include "otsdaq-core/Macros/StringMacros.h"
2 
3 using namespace ots;
4 
5 //==============================================================================
6 // wildCardMatch
7 // find needle in haystack
8 // allow needle to have leading and/or trailing wildcard '*'
9 // consider priority in matching, no matter the order in the haystack:
10 // - 0: no match!
11 // - 1: highest priority is exact match
12 // - 2: next highest is partial TRAILING-wildcard match
13 // - 3: next highest is partial LEADING-wildcard match
14 // - 4: lowest priority is partial full-wildcard match
15 // return priority found by reference
16 bool StringMacros::wildCardMatch(const std::string& needle,
17  const std::string& haystack,
18  unsigned int* priorityIndex) try
19 {
20  // __COUT__ << "\t\t wildCardMatch: " << needle <<
21  // " =in= " << haystack << " ??? " <<
22  // std::endl;
23 
24  // empty needle
25  if(needle.size() == 0)
26  {
27  if(priorityIndex)
28  *priorityIndex = 1; // consider an exact match, to stop higher level loops
29  return true; // if empty needle, always "found"
30  }
31 
32  // only wildcard
33  if(needle == "*")
34  {
35  if(priorityIndex)
36  *priorityIndex = 5; // only wildcard, is lowest priority
37  return true; // if empty needle, always "found"
38  }
39 
40  // no wildcards
41  if(needle == haystack)
42  {
43  if(priorityIndex)
44  *priorityIndex = 1; // an exact match
45  return true;
46  }
47 
48  // trailing wildcard
49  if(needle[needle.size() - 1] == '*' &&
50  needle.substr(0, needle.size() - 1) == haystack.substr(0, needle.size() - 1))
51  {
52  if(priorityIndex)
53  *priorityIndex = 2; // trailing wildcard match
54  return true;
55  }
56 
57  // leading wildcard
58  if(needle[0] == '*' &&
59  needle.substr(1) == haystack.substr(haystack.size() - (needle.size() - 1)))
60  {
61  if(priorityIndex)
62  *priorityIndex = 3; // leading wildcard match
63  return true;
64  }
65 
66  // leading wildcard and trailing wildcard
67  if(needle[0] == '*' && needle[needle.size() - 1] == '*' &&
68  std::string::npos != haystack.find(needle.substr(1, needle.size() - 2)))
69  {
70  if(priorityIndex)
71  *priorityIndex = 4; // leading and trailing wildcard match
72  return true;
73  }
74 
75  // else no match
76  if(priorityIndex)
77  *priorityIndex = 0; // no match
78  return false;
79 }
80 catch(...)
81 {
82  if(priorityIndex)
83  *priorityIndex = 0; // no match
84  return false; // if out of range
85 }
86 
87 //========================================================================================================================
88 // inWildCardSet ~
89 // returns true if needle is in haystack (considering wildcards)
90 bool StringMacros::inWildCardSet(const std::string& needle,
91  const std::set<std::string>& haystack)
92 {
93  for(const auto& haystackString : haystack)
94  // use wildcard match, flip needle parameter.. because we want haystack to have
95  // the wildcards
96  if(StringMacros::wildCardMatch(haystackString, needle))
97  return true;
98  return false;
99 }
100 
101 //==============================================================================
102 // decodeURIComponent
103 // converts all %## to the ascii character
104 std::string StringMacros::decodeURIComponent(const std::string& data)
105 {
106  std::string decodeURIString(data.size(), 0); // init to same size
107  unsigned int j = 0;
108  for(unsigned int i = 0; i < data.size(); ++i, ++j)
109  {
110  if(data[i] == '%')
111  {
112  // high order hex nibble digit
113  if(data[i + 1] > '9') // then ABCDEF
114  decodeURIString[j] += (data[i + 1] - 55) * 16;
115  else
116  decodeURIString[j] += (data[i + 1] - 48) * 16;
117 
118  // low order hex nibble digit
119  if(data[i + 2] > '9') // then ABCDEF
120  decodeURIString[j] += (data[i + 2] - 55);
121  else
122  decodeURIString[j] += (data[i + 2] - 48);
123 
124  i += 2; // skip to next char
125  }
126  else
127  decodeURIString[j] = data[i];
128  }
129  decodeURIString.resize(j);
130  return decodeURIString;
131 }
132 
133 //==============================================================================
134 // convertEnvironmentVariables ~
135 // static recursive function
136 //
137 // allows environment variables entered as $NAME or ${NAME}
138 std::string StringMacros::convertEnvironmentVariables(const std::string& data)
139 {
140  size_t begin = data.find("$");
141  if(begin != std::string::npos)
142  {
143  size_t end;
144  std::string envVariable;
145  std::string converted = data; // make copy to modify
146 
147  if(data[begin + 1] == '{') // check if using ${NAME} syntax
148  {
149  end = data.find("}", begin + 2);
150  envVariable = data.substr(begin + 2, end - begin - 2);
151  ++end; // replace the closing } too!
152  }
153  else // else using $NAME syntax
154  {
155  // end is first non environment variable character
156  for(end = begin + 1; end < data.size(); ++end)
157  if(!((data[end] >= '0' && data[end] <= '9') ||
158  (data[end] >= 'A' && data[end] <= 'Z') ||
159  (data[end] >= 'a' && data[end] <= 'z') || data[end] == '-' ||
160  data[end] == '_' || data[end] == '.' || data[end] == ':'))
161  break; // found end
162  envVariable = data.substr(begin + 1, end - begin - 1);
163  }
164  //__COUTV__(data);
165  //__COUTV__(envVariable);
166  char* envResult = getenv(envVariable.c_str());
167 
168  if(envResult)
169  {
170  // proceed recursively
171  return convertEnvironmentVariables(
172  converted.replace(begin, end - begin, envResult));
173  }
174  else
175  {
176  __SS__ << ("The environmental variable '" + envVariable +
177  "' is not set! Please make sure you set it before continuing!")
178  << std::endl;
179  __SS_THROW__;
180  }
181  }
182  // else no environment variables found in string
183  //__COUT__ << "Result: " << data << __E__;
184  return data;
185 }
186 
187 //==============================================================================
188 // isNumber ~~
189 // returns true if one or many numbers separated by operations (+,-,/,*) is
190 // present in the string.
191 // Numbers can be hex ("0x.."), binary("b..."), or base10.
192 bool StringMacros::isNumber(const std::string& s)
193 {
194  // extract set of potential numbers and operators
195  std::vector<std::string> numbers;
196  std::vector<char> ops;
197 
198  StringMacros::getVectorFromString(
199  s,
200  numbers,
201  /*delimiter*/ std::set<char>({'+', '-', '*', '/'}),
202  /*whitespace*/ std::set<char>({' ', '\t', '\n', '\r'}),
203  &ops);
204 
205  //__COUTV__(StringMacros::vectorToString(numbers));
206  //__COUTV__(StringMacros::vectorToString(ops));
207 
208  for(const auto& number : numbers)
209  {
210  if(number.size() == 0)
211  continue; // skip empty numbers
212 
213  if(number.find("0x") == 0) // indicates hex
214  {
215  //__COUT__ << "0x found" << std::endl;
216  for(unsigned int i = 2; i < number.size(); ++i)
217  {
218  if(!((number[i] >= '0' && number[i] <= '9') ||
219  (number[i] >= 'A' && number[i] <= 'F') ||
220  (number[i] >= 'a' && number[i] <= 'f')))
221  {
222  //__COUT__ << "prob " << number[i] << std::endl;
223  return false;
224  }
225  }
226  // return std::regex_match(number.substr(2), std::regex("^[0-90-9a-fA-F]+"));
227  }
228  else if(number[0] == 'b') // indicates binary
229  {
230  //__COUT__ << "b found" << std::endl;
231 
232  for(unsigned int i = 1; i < number.size(); ++i)
233  {
234  if(!((number[i] >= '0' && number[i] <= '1')))
235  {
236  //__COUT__ << "prob " << number[i] << std::endl;
237  return false;
238  }
239  }
240  }
241  else
242  {
243  //__COUT__ << "base 10 " << std::endl;
244  for(unsigned int i = 0; i < number.size(); ++i)
245  if(!((number[i] >= '0' && number[i] <= '9') || number[i] == '.' ||
246  number[i] == '+' || number[i] == '-'))
247  return false;
248  // Note: std::regex crashes in unresolvable ways (says Ryan.. also, stop using
249  // libraries) return std::regex_match(s,
250  // std::regex("^(\\-|\\+)?[0-9]*(\\.[0-9]+)?"));
251  }
252  }
253 
254  //__COUT__ << "yes " << std::endl;
255 
256  // all numbers are numbers
257  return true;
258 } // end isNumber()
259 
260 //==============================================================================
261 // getTimestampString ~~
262 // returns ots style timestamp string
263 // of known fixed size: Thu Aug 23 14:55:02 2001 CST
264 std::string StringMacros::getTimestampString(const std::string& linuxTimeInSeconds)
265 {
266  time_t timestamp(strtol(linuxTimeInSeconds.c_str(), 0, 10));
267  return getTimestampString(timestamp);
268 } // end getTimestampString()
269 
270 //==============================================================================
271 // getTimestampString ~~
272 // returns ots style timestamp string
273 // of known fixed size: Thu Aug 23 14:55:02 2001 CST
274 std::string StringMacros::getTimestampString(const time_t& linuxTimeInSeconds)
275 {
276  std::string retValue(30, '\0'); // known fixed size: Thu Aug 23 14:55:02 2001 CST
277 
278  struct tm tmstruct;
279  ::localtime_r(&linuxTimeInSeconds, &tmstruct);
280  ::strftime(&retValue[0], 30, "%c %Z", &tmstruct);
281  retValue.resize(strlen(retValue.c_str()));
282 
283  return retValue;
284 } // end getTimestampString()
285 
286 //==============================================================================
287 // validateValueForDefaultStringDataType
288 //
289 std::string StringMacros::validateValueForDefaultStringDataType(
290  const std::string& value, bool doConvertEnvironmentVariables) try
291 {
292  return doConvertEnvironmentVariables
293  ? StringMacros::convertEnvironmentVariables(value)
294  : value;
295 }
296 catch(const std::runtime_error& e)
297 {
298  __SS__ << "Failed to validate value for default string data type. " << __E__
299  << e.what() << __E__;
300  __SS_THROW__;
301 }
302 
303 //==============================================================================
304 // getSetFromString
305 // extracts the set of elements from string that uses a delimiter
306 // ignoring whitespace
307 void StringMacros::getSetFromString(const std::string& inputString,
308  std::set<std::string>& setToReturn,
309  const std::set<char>& delimiter,
310  const std::set<char>& whitespace)
311 {
312  unsigned int i = 0;
313  unsigned int j = 0;
314 
315  // go through the full string extracting elements
316  // add each found element to set
317  for(; j < inputString.size(); ++j)
318  if((whitespace.find(inputString[j]) !=
319  whitespace.end() || // ignore leading white space or delimiter
320  delimiter.find(inputString[j]) != delimiter.end()) &&
321  i == j)
322  ++i;
323  else if((whitespace.find(inputString[j]) !=
324  whitespace
325  .end() || // trailing white space or delimiter indicates end
326  delimiter.find(inputString[j]) != delimiter.end()) &&
327  i != j) // assume end of element
328  {
329  //__COUT__ << "Set element found: " <<
330  // inputString.substr(i,j-i) << std::endl;
331 
332  setToReturn.emplace(inputString.substr(i, j - i));
333 
334  // setup i and j for next find
335  i = j + 1;
336  }
337 
338  if(i != j) // last element check (for case when no concluding ' ' or delimiter)
339  setToReturn.emplace(inputString.substr(i, j - i));
340 } // end getSetFromString()
341 
342 //==============================================================================
343 // getVectorFromString
344 // extracts the list of elements from string that uses a delimiter
345 // ignoring whitespace
346 // optionally returns the list of delimiters encountered, which may be useful
347 // for extracting which operator was used.
348 //
349 //
350 // Note: lists are returned as vectors
351 // Note: the size() of delimiters will be one less than the size() of the returned values
352 // unless there is a leading delimiter, in which case vectors will have the same
353 // size.
354 void StringMacros::getVectorFromString(const std::string& inputString,
355  std::vector<std::string>& listToReturn,
356  const std::set<char>& delimiter,
357  const std::set<char>& whitespace,
358  std::vector<char>* listOfDelimiters)
359 {
360  unsigned int i = 0;
361  unsigned int j = 0;
362  unsigned int c = 0;
363  std::set<char>::iterator delimeterSearchIt;
364  char lastDelimiter;
365  bool isDelimiter;
366  // bool foundLeadingDelimiter = false;
367 
368  //__COUT__ << inputString << __E__;
369  //__COUTV__(inputString.length());
370 
371  // go through the full string extracting elements
372  // add each found element to set
373  for(; c < inputString.size(); ++c)
374  {
375  //__COUT__ << (char)inputString[c] << __E__;
376 
377  delimeterSearchIt = delimiter.find(inputString[c]);
378  isDelimiter = delimeterSearchIt != delimiter.end();
379 
380  //__COUT__ << (char)inputString[c] << " " << isDelimiter <<
381  //__E__;//char)lastDelimiter << __E__;
382 
383  if(whitespace.find(inputString[c]) !=
384  whitespace.end() // ignore leading white space
385  && i == j)
386  {
387  ++i;
388  ++j;
389  // if(isDelimiter)
390  // foundLeadingDelimiter = true;
391  }
392  else if(whitespace.find(inputString[c]) != whitespace.end() &&
393  i != j) // trailing white space, assume possible end of element
394  {
395  // do not change j or i
396  }
397  else if(isDelimiter) // delimiter is end of element
398  {
399  //__COUT__ << "Set element found: " <<
400  // inputString.substr(i,j-i) << std::endl;
401 
402  if(listOfDelimiters && listToReturn.size()) // || foundLeadingDelimiter))
403  // //accept leading delimiter
404  // (especially for case of
405  // leading negative in math
406  // parsing)
407  {
408  //__COUTV__(lastDelimiter);
409  listOfDelimiters->push_back(lastDelimiter);
410  }
411  listToReturn.push_back(inputString.substr(i, j - i));
412 
413  // setup i and j for next find
414  i = c + 1;
415  j = c + 1;
416  }
417  else // part of element, so move j, not i
418  j = c + 1;
419 
420  if(isDelimiter)
421  lastDelimiter = *delimeterSearchIt;
422  //__COUTV__(lastDelimiter);
423  }
424 
425  if(1) // i != j) //last element check (for case when no concluding ' ' or delimiter)
426  {
427  //__COUT__ << "Last element found: " <<
428  // inputString.substr(i,j-i) << std::endl;
429 
430  if(listOfDelimiters && listToReturn.size()) // || foundLeadingDelimiter))
431  // //accept leading delimiter
432  // (especially for case of leading
433  // negative in math parsing)
434  {
435  //__COUTV__(lastDelimiter);
436  listOfDelimiters->push_back(lastDelimiter);
437  }
438  listToReturn.push_back(inputString.substr(i, j - i));
439  }
440 
441  // assert that there is one less delimiter than values
442  if(listOfDelimiters && listToReturn.size() - 1 != listOfDelimiters->size() &&
443  listToReturn.size() != listOfDelimiters->size())
444  {
445  __SS__ << "There is a mismatch in delimiters to entries (should be equal or one "
446  "less delimiter): "
447  << listOfDelimiters->size() << " vs " << listToReturn.size() << __E__
448  << "Entries: " << StringMacros::vectorToString(listToReturn) << __E__
449  << "Delimiters: " << StringMacros::vectorToString(*listOfDelimiters)
450  << __E__;
451  __SS_THROW__;
452  }
453 
454 } // end getVectorFromString()
455 
456 //==============================================================================
457 // getMapFromString
458 // extracts the map of name-value pairs from string that uses two s
459 // ignoring whitespace
460 void StringMacros::getMapFromString(const std::string& inputString,
461  std::map<std::string, std::string>& mapToReturn,
462  const std::set<char>& pairPairDelimiter,
463  const std::set<char>& nameValueDelimiter,
464  const std::set<char>& whitespace) try
465 {
466  unsigned int i = 0;
467  unsigned int j = 0;
468  std::string name;
469  bool needValue = false;
470 
471  // go through the full string extracting map pairs
472  // add each found pair to map
473  for(; j < inputString.size(); ++j)
474  if(!needValue) // finding name
475  {
476  if((whitespace.find(inputString[j]) !=
477  whitespace.end() || // ignore leading white space or delimiter
478  pairPairDelimiter.find(inputString[j]) != pairPairDelimiter.end()) &&
479  i == j)
480  ++i;
481  else if((whitespace.find(inputString[j]) !=
482  whitespace
483  .end() || // trailing white space or delimiter indicates end
484  nameValueDelimiter.find(inputString[j]) !=
485  nameValueDelimiter.end()) &&
486  i != j) // assume end of map name
487  {
488  //__COUT__ << "Map name found: " <<
489  // inputString.substr(i,j-i) << std::endl;
490 
491  name = inputString.substr(i, j - i); // save name, for concluding pair
492 
493  needValue = true; // need value now
494 
495  // setup i and j for next find
496  i = j + 1;
497  }
498  }
499  else // finding value
500  {
501  if((whitespace.find(inputString[j]) !=
502  whitespace.end() || // ignore leading white space or delimiter
503  nameValueDelimiter.find(inputString[j]) != nameValueDelimiter.end()) &&
504  i == j)
505  ++i;
506  else if((whitespace.find(inputString[j]) !=
507  whitespace
508  .end() || // trailing white space or delimiter indicates end
509  pairPairDelimiter.find(inputString[j]) != pairPairDelimiter.end()) &&
510  i != j) // assume end of value name
511  {
512  //__COUT__ << "Map value found: " <<
513  // inputString.substr(i,j-i) << std::endl;
514 
515  auto /*pair<it,success>*/ emplaceReturn =
516  mapToReturn.emplace(std::pair<std::string, std::string>(
517  name,
518  validateValueForDefaultStringDataType(
519  inputString.substr(i, j - i)) // value
520  ));
521 
522  if(!emplaceReturn.second)
523  {
524  __COUT__ << "Ignoring repetitive value ('"
525  << inputString.substr(i, j - i)
526  << "') and keeping current value ('"
527  << emplaceReturn.first->second << "'). " << __E__;
528  }
529 
530  needValue = false; // need name now
531 
532  // setup i and j for next find
533  i = j + 1;
534  }
535  }
536 
537  if(i != j) // last value (for case when no concluding ' ' or delimiter)
538  {
539  auto /*pair<it,success>*/ emplaceReturn =
540  mapToReturn.emplace(std::pair<std::string, std::string>(
541  name,
542  validateValueForDefaultStringDataType(
543  inputString.substr(i, j - i)) // value
544  ));
545 
546  if(!emplaceReturn.second)
547  {
548  __COUT__ << "Ignoring repetitive value ('" << inputString.substr(i, j - i)
549  << "') and keeping current value ('" << emplaceReturn.first->second
550  << "'). " << __E__;
551  }
552  }
553 }
554 catch(const std::runtime_error& e)
555 {
556  __SS__ << "Error while extracting a map from the string '" << inputString
557  << "'... is it a valid map?" << __E__ << e.what() << __E__;
558  __SS_THROW__;
559 }
560 
561 //==============================================================================
562 // mapToString
563 std::string StringMacros::mapToString(const std::map<std::string, uint8_t>& mapToReturn,
564  const std::string& primaryDelimeter,
565  const std::string& secondaryDelimeter)
566 {
567  std::stringstream ss;
568  bool first = true;
569  for(auto& mapPair : mapToReturn)
570  {
571  if(first)
572  first = false;
573  else
574  ss << primaryDelimeter;
575  ss << mapPair.first << secondaryDelimeter << (unsigned int)mapPair.second;
576  }
577  return ss.str();
578 }
579 
580 //==============================================================================
581 // setToString
582 std::string StringMacros::setToString(const std::set<uint8_t>& setToReturn,
583  const std::string& delimeter)
584 {
585  std::stringstream ss;
586  bool first = true;
587  for(auto& setValue : setToReturn)
588  {
589  if(first)
590  first = false;
591  else
592  ss << delimeter;
593  ss << (unsigned int)setValue;
594  }
595  return ss.str();
596 }
597 //==============================================================================
598 // vectorToString
599 std::string StringMacros::vectorToString(const std::vector<uint8_t>& setToReturn,
600  const std::string& delimeter)
601 {
602  std::stringstream ss;
603  bool first = true;
604  for(auto& setValue : setToReturn)
605  {
606  if(first)
607  first = false;
608  else
609  ss << delimeter;
610  ss << (unsigned int)setValue;
611  }
612  return ss.str();
613 }
614 
615 //========================================================================================================================
616 // exec
617 // run linux command and get result back in string
618 std::string StringMacros::exec(const char* cmd)
619 {
620  __COUTV__(cmd);
621 
622  std::array<char, 128> buffer;
623  std::string result;
624  std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
625  if(!pipe)
626  __THROW__("popen() failed!");
627  while(!feof(pipe.get()))
628  {
629  if(fgets(buffer.data(), 128, pipe.get()) != nullptr)
630  result += buffer.data();
631  }
632  return result;
633 } // end exec
634 
635 //==============================================================================
636 // stackTrace
637 // static function
638 // https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
639 #include <cxxabi.h> //for abi::__cxa_demangle
640 #include <execinfo.h> //for back trace of stack
641 std::string StringMacros::stackTrace()
642 {
643  __SS__ << "ots::stackTrace:\n";
644 
645  void* array[10];
646  size_t size;
647 
648  // get void*'s for all entries on the stack
649  size = backtrace(array, 10);
650  // backtrace_symbols_fd(array, size, STDERR_FILENO);
651 
652  // https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes
653  char** messages = backtrace_symbols(array, size);
654 
655  // skip first stack frame (points here)
656  char syscom[256];
657  for(unsigned int i = 1; i < size && messages != NULL; ++i)
658  {
659  // mangled name needs to be converted to get nice name and line number
660  // line number not working... FIXME
661 
662  // sprintf(syscom,"addr2line %p -e %s",
663  // array[i],
664  // messages[i]); //last parameter is the name of this app
665  // ss << StringMacros::exec(syscom) << __E__;
666  // system(syscom);
667 
668  // continue;
669 
670  char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;
671 
672  // find parentheses and +address offset surrounding mangled name
673  for(char* p = messages[i]; *p; ++p)
674  {
675  if(*p == '(')
676  {
677  mangled_name = p;
678  }
679  else if(*p == '+')
680  {
681  offset_begin = p;
682  }
683  else if(*p == ')')
684  {
685  offset_end = p;
686  break;
687  }
688  }
689 
690  // if the line could be processed, attempt to demangle the symbol
691  if(mangled_name && offset_begin && offset_end && mangled_name < offset_begin)
692  {
693  *mangled_name++ = '\0';
694  *offset_begin++ = '\0';
695  *offset_end++ = '\0';
696 
697  int status;
698  char* real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
699 
700  // if demangling is successful, output the demangled function name
701  if(status == 0)
702  {
703  ss << "[" << i << "] " << messages[i] << " : " << real_name << "+"
704  << offset_begin << offset_end << std::endl;
705  }
706  // otherwise, output the mangled function name
707  else
708  {
709  ss << "[" << i << "] " << messages[i] << " : " << mangled_name << "+"
710  << offset_begin << offset_end << std::endl;
711  }
712  free(real_name);
713  }
714  // otherwise, print the whole line
715  else
716  {
717  ss << "[" << i << "] " << messages[i] << std::endl;
718  }
719  }
720  ss << std::endl;
721 
722  free(messages);
723 
724  return ss.str();
725 } // end stackTrace
726 
727 #ifdef __GNUG__
728 #include <cxxabi.h>
729 #include <cstdlib>
730 #include <memory>
731 
732 //==============================================================================
733 // demangleTypeName
734 std::string StringMacros::demangleTypeName(const char* name)
735 {
736  int status = -4; // some arbitrary value to eliminate the compiler warning
737 
738  // enable c++11 by passing the flag -std=c++11 to g++
739  std::unique_ptr<char, void (*)(void*)> res{
740  abi::__cxa_demangle(name, NULL, NULL, &status), std::free};
741 
742  return (status == 0) ? res.get() : name;
743 } // end demangleTypeName()
744 
745 #else // does nothing if not g++
746 //==============================================================================
747 // demangleTypeName
748 //
749 std::string StringMacros::demangleTypeName(const char* name) { return name; }
750 #endif