otsdaq  v2_05_02_indev
StringMacros.cc
1 #include "otsdaq/Macros/StringMacros.h"
2 
3 #include <array>
4 
5 using namespace ots;
6 
7 //==============================================================================
8 // wildCardMatch
9 // find needle in haystack
10 // allow needle to have leading and/or trailing wildcard '*'
11 // consider priority in matching, no matter the order in the haystack:
12 // - 0: no match!
13 // - 1: highest priority is exact match
14 // - 2: next highest is partial TRAILING-wildcard match
15 // - 3: next highest is partial LEADING-wildcard match
16 // - 4: lowest priority is partial full-wildcard match
17 // return priority found by reference
18 bool StringMacros::wildCardMatch(const std::string& needle, const std::string& haystack, 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] == '*' && needle.substr(0, needle.size() - 1) == haystack.substr(0, needle.size() - 1))
50  {
51  if(priorityIndex)
52  *priorityIndex = 2; // trailing wildcard match
53  return true;
54  }
55 
56  // leading wildcard
57  if(needle[0] == '*' && needle.substr(1) == haystack.substr(haystack.size() - (needle.size() - 1)))
58  {
59  if(priorityIndex)
60  *priorityIndex = 3; // leading wildcard match
61  return true;
62  }
63 
64  // leading wildcard and trailing wildcard
65  if(needle[0] == '*' && needle[needle.size() - 1] == '*' && std::string::npos != haystack.find(needle.substr(1, needle.size() - 2)))
66  {
67  if(priorityIndex)
68  *priorityIndex = 4; // leading and trailing wildcard match
69  return true;
70  }
71 
72  // else no match
73  if(priorityIndex)
74  *priorityIndex = 0; // no match
75  return false;
76 }
77 catch(...)
78 {
79  if(priorityIndex)
80  *priorityIndex = 0; // no match
81  return false; // if out of range
82 }
83 
84 //==============================================================================
85 // inWildCardSet ~
86 // returns true if needle is in haystack (considering wildcards)
87 bool StringMacros::inWildCardSet(const std::string& needle, const std::set<std::string>& haystack)
88 {
89  for(const auto& haystackString : haystack)
90  // use wildcard match, flip needle parameter.. because we want haystack to have
91  // the wildcards
92  if(StringMacros::wildCardMatch(haystackString, needle))
93  return true;
94  return false;
95 }
96 
97 //==============================================================================
98 // decodeURIComponent
99 // converts all %## to the ascii character
100 std::string StringMacros::decodeURIComponent(const std::string& data)
101 {
102  std::string decodeURIString(data.size(), 0); // init to same size
103  unsigned int j = 0;
104  for(unsigned int i = 0; i < data.size(); ++i, ++j)
105  {
106  if(data[i] == '%')
107  {
108  // high order hex nibble digit
109  if(data[i + 1] > '9') // then ABCDEF
110  decodeURIString[j] += (data[i + 1] - 55) * 16;
111  else
112  decodeURIString[j] += (data[i + 1] - 48) * 16;
113 
114  // low order hex nibble digit
115  if(data[i + 2] > '9') // then ABCDEF
116  decodeURIString[j] += (data[i + 2] - 55);
117  else
118  decodeURIString[j] += (data[i + 2] - 48);
119 
120  i += 2; // skip to next char
121  }
122  else
123  decodeURIString[j] = data[i];
124  }
125  decodeURIString.resize(j);
126  return decodeURIString;
127 } // end decodeURIComponent()
128 
129 //==============================================================================
130 std::string StringMacros::encodeURIComponent(const std::string& sourceStr)
131 {
132  std::string retStr = "";
133  char encodeStr[4];
134  for(const auto& c : sourceStr)
135  if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'))
136  retStr += c;
137  else
138  {
139  sprintf(encodeStr, "%%%2.2X", c);
140  retStr += encodeStr;
141  }
142  return retStr;
143 } // end encodeURIComponent()
144 
145 //==============================================================================
146 // convertEnvironmentVariables ~
147 // static recursive function
148 //
149 // allows environment variables entered as $NAME or ${NAME}
150 std::string StringMacros::convertEnvironmentVariables(const std::string& data)
151 {
152  size_t begin = data.find("$");
153  if(begin != std::string::npos)
154  {
155  size_t end;
156  std::string envVariable;
157  std::string converted = data; // make copy to modify
158 
159  if(data[begin + 1] == '{') // check if using ${NAME} syntax
160  {
161  end = data.find("}", begin + 2);
162  envVariable = data.substr(begin + 2, end - begin - 2);
163  ++end; // replace the closing } too!
164  }
165  else // else using $NAME syntax
166  {
167  // end is first non environment variable character
168  for(end = begin + 1; end < data.size(); ++end)
169  if(!((data[end] >= '0' && data[end] <= '9') || (data[end] >= 'A' && data[end] <= 'Z') || (data[end] >= 'a' && data[end] <= 'z') ||
170  data[end] == '-' || data[end] == '_' || data[end] == '.' || data[end] == ':'))
171  break; // found end
172  envVariable = data.substr(begin + 1, end - begin - 1);
173  }
174  //__COUTV__(data);
175  //__COUTV__(envVariable);
176  char* envResult = __ENV__(envVariable.c_str());
177 
178  if(envResult)
179  {
180  // proceed recursively
181  return convertEnvironmentVariables(converted.replace(begin, end - begin, envResult));
182  }
183  else
184  {
185  __SS__ << ("The environmental variable '" + envVariable + "' is not set! Please make sure you set it before continuing!") << std::endl;
186  __SS_THROW__;
187  }
188  }
189  // else no environment variables found in string
190  //__COUT__ << "Result: " << data << __E__;
191  return data;
192 }
193 
194 //==============================================================================
195 // isNumber ~~
196 // returns true if one or many numbers separated by operations (+,-,/,*) is
197 // present in the string.
198 // Numbers can be hex ("0x.."), binary("b..."), or base10.
199 bool StringMacros::isNumber(const std::string& s)
200 {
201  // extract set of potential numbers and operators
202  std::vector<std::string> numbers;
203  std::vector<char> ops;
204 
205  StringMacros::getVectorFromString(s,
206  numbers,
207  /*delimiter*/ std::set<char>({'+', '-', '*', '/'}),
208  /*whitespace*/ std::set<char>({' ', '\t', '\n', '\r'}),
209  &ops);
210 
211  //__COUTV__(StringMacros::vectorToString(numbers));
212  //__COUTV__(StringMacros::vectorToString(ops));
213 
214  for(const auto& number : numbers)
215  {
216  if(number.size() == 0)
217  continue; // skip empty numbers
218 
219  if(number.find("0x") == 0) // indicates hex
220  {
221  //__COUT__ << "0x found" << std::endl;
222  for(unsigned int i = 2; i < number.size(); ++i)
223  {
224  if(!((number[i] >= '0' && number[i] <= '9') || (number[i] >= 'A' && number[i] <= 'F') || (number[i] >= 'a' && number[i] <= 'f')))
225  {
226  //__COUT__ << "prob " << number[i] << std::endl;
227  return false;
228  }
229  }
230  // return std::regex_match(number.substr(2), std::regex("^[0-90-9a-fA-F]+"));
231  }
232  else if(number[0] == 'b') // indicates binary
233  {
234  //__COUT__ << "b found" << std::endl;
235 
236  for(unsigned int i = 1; i < number.size(); ++i)
237  {
238  if(!((number[i] >= '0' && number[i] <= '1')))
239  {
240  //__COUT__ << "prob " << number[i] << std::endl;
241  return false;
242  }
243  }
244  }
245  else
246  {
247  //__COUT__ << "base 10 " << std::endl;
248  for(unsigned int i = 0; i < number.size(); ++i)
249  if(!((number[i] >= '0' && number[i] <= '9') || number[i] == '.' || number[i] == '+' || number[i] == '-'))
250  return false;
251  // Note: std::regex crashes in unresolvable ways (says Ryan.. also, stop using
252  // libraries) return std::regex_match(s,
253  // std::regex("^(\\-|\\+)?[0-9]*(\\.[0-9]+)?"));
254  }
255  }
256 
257  //__COUT__ << "yes " << std::endl;
258 
259  // all numbers are numbers
260  return true;
261 } // end isNumber()
262 
263 //==============================================================================
264 // getNumberType ~~
265 // returns string of number type: "unsigned long long", "double"
266 // or else "nan" for not-a-number
267 //
268 // Numbers can be hex ("0x.."), binary("b..."), or base10.
269 std::string StringMacros::getNumberType(const std::string& s)
270 {
271  // extract set of potential numbers and operators
272  std::vector<std::string> numbers;
273  std::vector<char> ops;
274 
275  bool hasDecimal = false;
276 
277  StringMacros::getVectorFromString(s,
278  numbers,
279  /*delimiter*/ std::set<char>({'+', '-', '*', '/'}),
280  /*whitespace*/ std::set<char>({' ', '\t', '\n', '\r'}),
281  &ops);
282 
283  //__COUTV__(StringMacros::vectorToString(numbers));
284  //__COUTV__(StringMacros::vectorToString(ops));
285 
286  for(const auto& number : numbers)
287  {
288  if(number.size() == 0)
289  continue; // skip empty numbers
290 
291  if(number.find("0x") == 0) // indicates hex
292  {
293  //__COUT__ << "0x found" << std::endl;
294  for(unsigned int i = 2; i < number.size(); ++i)
295  {
296  if(!((number[i] >= '0' && number[i] <= '9') || (number[i] >= 'A' && number[i] <= 'F') || (number[i] >= 'a' && number[i] <= 'f')))
297  {
298  //__COUT__ << "prob " << number[i] << std::endl;
299  return "nan";
300  }
301  }
302  // return std::regex_match(number.substr(2), std::regex("^[0-90-9a-fA-F]+"));
303  }
304  else if(number[0] == 'b') // indicates binary
305  {
306  //__COUT__ << "b found" << std::endl;
307 
308  for(unsigned int i = 1; i < number.size(); ++i)
309  {
310  if(!((number[i] >= '0' && number[i] <= '1')))
311  {
312  //__COUT__ << "prob " << number[i] << std::endl;
313  return "nan";
314  }
315  }
316  }
317  else
318  {
319  //__COUT__ << "base 10 " << std::endl;
320  for(unsigned int i = 0; i < number.size(); ++i)
321  if(!((number[i] >= '0' && number[i] <= '9') || number[i] == '.' || number[i] == '+' || number[i] == '-'))
322  return "nan";
323  else if(number[i] == '.')
324  hasDecimal = true;
325  // Note: std::regex crashes in unresolvable ways (says Ryan.. also, stop using
326  // libraries) return std::regex_match(s,
327  // std::regex("^(\\-|\\+)?[0-9]*(\\.[0-9]+)?"));
328  }
329  }
330 
331  //__COUT__ << "yes " << std::endl;
332 
333  // all numbers are numbers
334  if(hasDecimal)
335  return "double";
336  return "unsigned long long";
337 } // end getNumberType()
338 
339 //==============================================================================
340 // static template function
341 // for bool, but not all other number types
342 // return false if string is not a bool
343 // template<>
344 // inline bool StringMacros::getNumber<bool>(const std::string& s, bool& retValue)
345 bool StringMacros::getNumber(const std::string& s, bool& retValue)
346 {
347  if(s.size() < 1)
348  {
349  __COUT_ERR__ << "Invalid empty bool string " << s << __E__;
350  return false;
351  }
352 
353  // check true case
354  if(s.find("1") != std::string::npos || s == "true" || s == "True" || s == "TRUE")
355  {
356  retValue = true;
357  return true;
358  }
359 
360  // check false case
361  if(s.find("0") != std::string::npos || s == "false" || s == "False" || s == "FALSE")
362  {
363  retValue = false;
364  return true;
365  }
366 
367  __COUT_ERR__ << "Invalid bool string " << s << __E__;
368  return false;
369 
370 } // end static getNumber<bool>
371 
372 //==============================================================================
373 // getTimestampString ~~
374 // returns ots style timestamp string
375 // of known fixed size: Thu Aug 23 14:55:02 2001 CST
376 std::string StringMacros::getTimestampString(const std::string& linuxTimeInSeconds)
377 {
378  time_t timestamp(strtol(linuxTimeInSeconds.c_str(), 0, 10));
379  return getTimestampString(timestamp);
380 } // end getTimestampString()
381 
382 //==============================================================================
383 // getTimestampString ~~
384 // returns ots style timestamp string
385 // of known fixed size: Thu Aug 23 14:55:02 2001 CST
386 std::string StringMacros::getTimestampString(const time_t& linuxTimeInSeconds)
387 {
388  std::string retValue(30, '\0'); // known fixed size: Thu Aug 23 14:55:02 2001 CST
389 
390  struct tm tmstruct;
391  ::localtime_r(&linuxTimeInSeconds, &tmstruct);
392  ::strftime(&retValue[0], 30, "%c %Z", &tmstruct);
393  retValue.resize(strlen(retValue.c_str()));
394 
395  return retValue;
396 } // end getTimestampString()
397 
398 //==============================================================================
399 // validateValueForDefaultStringDataType
400 //
401 std::string StringMacros::validateValueForDefaultStringDataType(const std::string& value, bool doConvertEnvironmentVariables) try
402 {
403  return doConvertEnvironmentVariables ? StringMacros::convertEnvironmentVariables(value) : value;
404 }
405 catch(const std::runtime_error& e)
406 {
407  __SS__ << "Failed to validate value for default string data type. " << __E__ << e.what() << __E__;
408  __SS_THROW__;
409 }
410 
411 //==============================================================================
412 // getSetFromString
413 // extracts the set of elements from string that uses a delimiter
414 // ignoring whitespace
415 void StringMacros::getSetFromString(const std::string& inputString,
416  std::set<std::string>& setToReturn,
417  const std::set<char>& delimiter,
418  const std::set<char>& whitespace)
419 {
420  unsigned int i = 0;
421  unsigned int j = 0;
422 
423  // go through the full string extracting elements
424  // add each found element to set
425  for(; j < inputString.size(); ++j)
426  if((whitespace.find(inputString[j]) != whitespace.end() || // ignore leading white space or delimiter
427  delimiter.find(inputString[j]) != delimiter.end()) &&
428  i == j)
429  ++i;
430  else if((whitespace.find(inputString[j]) != whitespace.end() || // trailing white space or delimiter indicates end
431  delimiter.find(inputString[j]) != delimiter.end()) &&
432  i != j) // assume end of element
433  {
434  //__COUT__ << "Set element found: " <<
435  // inputString.substr(i,j-i) << std::endl;
436 
437  setToReturn.emplace(inputString.substr(i, j - i));
438 
439  // setup i and j for next find
440  i = j + 1;
441  }
442 
443  if(i != j) // last element check (for case when no concluding ' ' or delimiter)
444  setToReturn.emplace(inputString.substr(i, j - i));
445 } // end getSetFromString()
446 
447 //==============================================================================
448 // getVectorFromString
449 // extracts the list of elements from string that uses a delimiter
450 // ignoring whitespace
451 // optionally returns the list of delimiters encountered, which may be useful
452 // for extracting which operator was used.
453 //
454 //
455 // Note: lists are returned as vectors
456 // Note: the size() of delimiters will be one less than the size() of the returned values
457 // unless there is a leading delimiter, in which case vectors will have the same
458 // size.
459 void StringMacros::getVectorFromString(const std::string& inputString,
460  std::vector<std::string>& listToReturn,
461  const std::set<char>& delimiter,
462  const std::set<char>& whitespace,
463  std::vector<char>* listOfDelimiters)
464 {
465  unsigned int i = 0;
466  unsigned int j = 0;
467  unsigned int c = 0;
468  std::set<char>::iterator delimeterSearchIt;
469  char lastDelimiter = 0;
470  bool isDelimiter;
471  // bool foundLeadingDelimiter = false;
472 
473  //__COUT__ << inputString << __E__;
474  //__COUTV__(inputString.length());
475 
476  // go through the full string extracting elements
477  // add each found element to set
478  for(; c < inputString.size(); ++c)
479  {
480  //__COUT__ << (char)inputString[c] << __E__;
481 
482  delimeterSearchIt = delimiter.find(inputString[c]);
483  isDelimiter = delimeterSearchIt != delimiter.end();
484 
485  //__COUT__ << (char)inputString[c] << " " << isDelimiter <<
486  //__E__;//char)lastDelimiter << __E__;
487 
488  if(whitespace.find(inputString[c]) != whitespace.end() // ignore leading white space
489  && i == j)
490  {
491  ++i;
492  ++j;
493  // if(isDelimiter)
494  // foundLeadingDelimiter = true;
495  }
496  else if(whitespace.find(inputString[c]) != whitespace.end() && i != j) // trailing white space, assume possible end of element
497  {
498  // do not change j or i
499  }
500  else if(isDelimiter) // delimiter is end of element
501  {
502  //__COUT__ << "Set element found: " <<
503  // inputString.substr(i,j-i) << std::endl;
504 
505  if(listOfDelimiters && listToReturn.size()) // || foundLeadingDelimiter))
506  // //accept leading delimiter
507  // (especially for case of
508  // leading negative in math
509  // parsing)
510  {
511  //__COUTV__(lastDelimiter);
512  listOfDelimiters->push_back(lastDelimiter);
513  }
514  listToReturn.push_back(inputString.substr(i, j - i));
515 
516  // setup i and j for next find
517  i = c + 1;
518  j = c + 1;
519  }
520  else // part of element, so move j, not i
521  j = c + 1;
522 
523  if(isDelimiter)
524  lastDelimiter = *delimeterSearchIt;
525  //__COUTV__(lastDelimiter);
526  }
527 
528  if(1) // i != j) //last element check (for case when no concluding ' ' or delimiter)
529  {
530  //__COUT__ << "Last element found: " <<
531  // inputString.substr(i,j-i) << std::endl;
532 
533  if(listOfDelimiters && listToReturn.size()) // || foundLeadingDelimiter))
534  // //accept leading delimiter
535  // (especially for case of leading
536  // negative in math parsing)
537  {
538  //__COUTV__(lastDelimiter);
539  listOfDelimiters->push_back(lastDelimiter);
540  }
541  listToReturn.push_back(inputString.substr(i, j - i));
542  }
543 
544  // assert that there is one less delimiter than values
545  if(listOfDelimiters && listToReturn.size() - 1 != listOfDelimiters->size() && listToReturn.size() != listOfDelimiters->size())
546  {
547  __SS__ << "There is a mismatch in delimiters to entries (should be equal or one "
548  "less delimiter): "
549  << listOfDelimiters->size() << " vs " << listToReturn.size() << __E__ << "Entries: " << StringMacros::vectorToString(listToReturn) << __E__
550  << "Delimiters: " << StringMacros::vectorToString(*listOfDelimiters) << __E__;
551  __SS_THROW__;
552  }
553 
554 } // end getVectorFromString()
555 
556 //==============================================================================
557 // getVectorFromString
558 // extracts the list of elements from string that uses a delimiter
559 // ignoring whitespace
560 // optionally returns the list of delimiters encountered, which may be useful
561 // for extracting which operator was used.
562 //
563 //
564 // Note: lists are returned as vectors
565 // Note: the size() of delimiters will be one less than the size() of the returned values
566 // unless there is a leading delimiter, in which case vectors will have the same
567 // size.
568 std::vector<std::string> StringMacros::getVectorFromString(const std::string& inputString,
569  const std::set<char>& delimiter,
570  const std::set<char>& whitespace,
571  std::vector<char>* listOfDelimiters)
572 {
573  std::vector<std::string> listToReturn;
574 
575  StringMacros::getVectorFromString(inputString, listToReturn, delimiter, whitespace, listOfDelimiters);
576  return listToReturn;
577 } // end getVectorFromString()
578 
579 //==============================================================================
580 // getMapFromString
581 // extracts the map of name-value pairs from string that uses two delimiters
582 // ignoring whitespace
583 void StringMacros::getMapFromString(const std::string& inputString,
584  std::map<std::string, std::string>& mapToReturn,
585  const std::set<char>& pairPairDelimiter,
586  const std::set<char>& nameValueDelimiter,
587  const std::set<char>& whitespace) try
588 {
589  unsigned int i = 0;
590  unsigned int j = 0;
591  std::string name;
592  bool needValue = false;
593 
594  // go through the full string extracting map pairs
595  // add each found pair to map
596  for(; j < inputString.size(); ++j)
597  if(!needValue) // finding name
598  {
599  if((whitespace.find(inputString[j]) != whitespace.end() || // ignore leading white space or delimiter
600  pairPairDelimiter.find(inputString[j]) != pairPairDelimiter.end()) &&
601  i == j)
602  ++i;
603  else if((whitespace.find(inputString[j]) != whitespace.end() || // trailing white space or delimiter indicates end
604  nameValueDelimiter.find(inputString[j]) != nameValueDelimiter.end()) &&
605  i != j) // assume end of map name
606  {
607  //__COUT__ << "Map name found: " <<
608  // inputString.substr(i,j-i) << std::endl;
609 
610  name = inputString.substr(i, j - i); // save name, for concluding pair
611 
612  needValue = true; // need value now
613 
614  // setup i and j for next find
615  i = j + 1;
616  }
617  }
618  else // finding value
619  {
620  if((whitespace.find(inputString[j]) != whitespace.end() || // ignore leading white space or delimiter
621  nameValueDelimiter.find(inputString[j]) != nameValueDelimiter.end()) &&
622  i == j)
623  ++i;
624  else if(whitespace.find(inputString[j]) != whitespace.end() || // trailing white space or delimiter indicates end
625  pairPairDelimiter.find(inputString[j]) != pairPairDelimiter.end()) // &&
626  // i != j) // assume end of value name
627  {
628  //__COUT__ << "Map value found: " <<
629  // inputString.substr(i,j-i) << std::endl;
630 
631  auto /*pair<it,success>*/ emplaceReturn =
632  mapToReturn.emplace(std::pair<std::string, std::string>(name, validateValueForDefaultStringDataType(inputString.substr(i, j - i)) // value
633  ));
634 
635  if(!emplaceReturn.second)
636  {
637  __COUT__ << "Ignoring repetitive value ('" << inputString.substr(i, j - i) << "') and keeping current value ('"
638  << emplaceReturn.first->second << "'). " << __E__;
639  }
640 
641  needValue = false; // need name now
642 
643  // setup i and j for next find
644  i = j + 1;
645  }
646  }
647 
648  if(i != j) // last value (for case when no concluding ' ' or delimiter)
649  {
650  auto /*pair<it,success>*/ emplaceReturn =
651  mapToReturn.emplace(std::pair<std::string, std::string>(name, validateValueForDefaultStringDataType(inputString.substr(i, j - i)) // value
652  ));
653 
654  if(!emplaceReturn.second)
655  {
656  __COUT__ << "Ignoring repetitive value ('" << inputString.substr(i, j - i) << "') and keeping current value ('" << emplaceReturn.first->second
657  << "'). " << __E__;
658  }
659  }
660 } // end getMapFromString()
661 catch(const std::runtime_error& e)
662 {
663  __SS__ << "Error while extracting a map from the string '" << inputString << "'... is it a valid map?" << __E__ << e.what() << __E__;
664  __SS_THROW__;
665 }
666 
667 //==============================================================================
668 // mapToString
669 std::string StringMacros::mapToString(const std::map<std::string, uint8_t>& mapToReturn,
670  const std::string& primaryDelimeter,
671  const std::string& secondaryDelimeter)
672 {
673  std::stringstream ss;
674  bool first = true;
675  for(auto& mapPair : mapToReturn)
676  {
677  if(first)
678  first = false;
679  else
680  ss << primaryDelimeter;
681  ss << mapPair.first << secondaryDelimeter << (unsigned int)mapPair.second;
682  }
683  return ss.str();
684 } // end mapToString()
685 
686 //==============================================================================
687 // setToString
688 std::string StringMacros::setToString(const std::set<uint8_t>& setToReturn, const std::string& delimeter)
689 {
690  std::stringstream ss;
691  bool first = true;
692  for(auto& setValue : setToReturn)
693  {
694  if(first)
695  first = false;
696  else
697  ss << delimeter;
698  ss << (unsigned int)setValue;
699  }
700  return ss.str();
701 } // end setToString()
702 
703 //==============================================================================
704 // vectorToString
705 std::string StringMacros::vectorToString(const std::vector<uint8_t>& setToReturn, const std::string& delimeter)
706 {
707  std::stringstream ss;
708  bool first = true;
709  for(auto& setValue : setToReturn)
710  {
711  if(first)
712  first = false;
713  else
714  ss << delimeter;
715  ss << (unsigned int)setValue;
716  }
717  return ss.str();
718 } // end vectorToString()
719 
720 //==============================================================================
721 // extractCommonChunks
722 // return the common chunks from the vector of strings
723 // e.g. if the strings were created from a template
724 // string like reader*_east*, this function will return
725 // a vector of size 3 := {"reader","_east",""} and
726 // a vector of wildcards that would replace the *
727 //
728 // Returns true if common chunks and wildcards found,
729 // returns false if all inputs were the same (i.e. no wildcards needed)
730 bool StringMacros::extractCommonChunks(const std::vector<std::string>& haystack,
731  std::vector<std::string>& commonChunksToReturn,
732  std::vector<std::string>& wildcardStringsToReturn,
733  unsigned int& fixedWildcardLength)
734 {
735  fixedWildcardLength = 0; // default
736 
737  // Steps:
738  // - find start and end common chunks first in haystack strings
739  // - use start and end to determine if there is more than one *
740  // - decide if fixed width was specified (based on prepended 0s to numbers)
741  // - search for more instances of * value
742  //
743  //
744  // // Note: lambda recursive function to find chunks
745  // std::function<void(
746  // const std::vector<std::string>&,
747  // const std::string&,
748  // const unsigned int, const int)> localRecurse =
749  // [&specialFolders, &specialMapTypes, &retMap, &localRecurse](
750  // const std::vector<std::string>& haystack,
751  // const std::string& offsetPath,
752  // const unsigned int depth,
753  // const int specialIndex)
754  // {
755  //
756  // //__COUTV__(path);
757  // //__COUTV__(depth);
758  // }
759  std::pair<unsigned int /*lo*/, unsigned int /*hi*/> wildcardBounds(std::make_pair(-1, 0)); // initialize to illegal wildcard
760 
761  // look for starting matching segment
762  for(unsigned int n = 1; n < haystack.size(); ++n)
763  for(unsigned int i = 0, j = 0; i < haystack[0].length() && j < haystack[n].length(); ++i, ++j)
764  {
765  if(i < wildcardBounds.first)
766  {
767  if(haystack[0][i] != haystack[1][j])
768  {
769  wildcardBounds.first = i; // found lo side of wildcard
770  break;
771  }
772  }
773  else
774  break;
775  }
776  __COUT__ << "Low side = " << wildcardBounds.first << " " << haystack[0].substr(0, wildcardBounds.first) << __E__;
777 
778  // look for end matching segment
779  for(unsigned int n = 1; n < haystack.size(); ++n)
780  for(int i = haystack[0].length() - 1, j = haystack[n].length() - 1; i >= (int)wildcardBounds.first && j >= (int)wildcardBounds.first; --i, --j)
781  {
782  if(i > (int)wildcardBounds.second) // looking for hi side
783  {
784  if(haystack[0][i] != haystack[n][j])
785  {
786  wildcardBounds.second = i + 1; // found hi side of wildcard
787  break;
788  }
789  }
790  else
791  break;
792  }
793 
794  __COUT__ << "High side = " << wildcardBounds.second << " " << haystack[0].substr(wildcardBounds.second) << __E__;
795 
796  // add first common chunk
797  commonChunksToReturn.push_back(haystack[0].substr(0, wildcardBounds.first));
798 
799  if(wildcardBounds.first != (unsigned int)-1) // potentially more chunks if not end
800  {
801  // - use start and end to determine if there is more than one *
802  for(int i = (wildcardBounds.first + wildcardBounds.second) / 2 + 1; i < (int)wildcardBounds.second; ++i)
803  if(haystack[0][wildcardBounds.first] == haystack[0][i] &&
804  haystack[0].substr(wildcardBounds.first, wildcardBounds.second - i) == haystack[0].substr(i, wildcardBounds.second - i))
805  {
806  std::string multiWildcardString = haystack[0].substr(i, wildcardBounds.second - i);
807  __COUT__ << "Multi-wildcard found: " << multiWildcardString << __E__;
808 
809  std::vector<unsigned int /*lo index*/> wildCardInstances;
810  // add front one now, and back one later
811  wildCardInstances.push_back(wildcardBounds.first);
812 
813  unsigned int offset = wildCardInstances[0] + multiWildcardString.size() + 1;
814  std::string middleString = haystack[0].substr(offset, (i - 1) - offset);
815  __COUTV__(middleString);
816 
817  // search for more wildcard instances in new common area
818  size_t k;
819  while((k = middleString.find(multiWildcardString)) != std::string::npos)
820  {
821  __COUT__ << "Multi-wildcard found at " << k << __E__;
822 
823  wildCardInstances.push_back(offset + k);
824 
825  middleString = middleString.substr(k + multiWildcardString.size() + 1);
826  offset += k + multiWildcardString.size() + 1;
827  __COUTV__(middleString);
828  }
829 
830  // add back one last
831  wildCardInstances.push_back(i);
832 
833  for(unsigned int w = 0; w < wildCardInstances.size() - 1; ++w)
834  {
835  commonChunksToReturn.push_back(haystack[0].substr(wildCardInstances[w] + wildCardInstances.size(),
836  wildCardInstances[w + 1] - (wildCardInstances[w] + wildCardInstances.size())));
837  }
838  }
839 
840  // check if all common chunks end in 0 to add fixed length
841 
842  for(unsigned int i = 0; i < commonChunksToReturn[0].size(); ++i)
843  if(commonChunksToReturn[0][commonChunksToReturn[0].size() - 1 - i] == '0')
844  ++fixedWildcardLength;
845  else
846  break;
847 
848  //bool allHave0 = true;
849  for(unsigned int c = 0; c < commonChunksToReturn.size(); ++c)
850  {
851  unsigned int cnt = 0;
852  for(unsigned int i = 0; i < commonChunksToReturn[c].size(); ++i)
853  if(commonChunksToReturn[c][commonChunksToReturn[c].size() - 1 - i] == '0')
854  ++cnt;
855  else
856  break;
857 
858  if(fixedWildcardLength < cnt)
859  fixedWildcardLength = cnt;
860  else if(fixedWildcardLength > cnt)
861  {
862  __SS__ << "Invalid fixed length found, please simplify indexing between these common chunks: "
863  << StringMacros::vectorToString(commonChunksToReturn) << __E__;
864  __SS_THROW__;
865  }
866  }
867  __COUTV__(fixedWildcardLength);
868 
869  if(fixedWildcardLength) // take trailing 0s out of common chunks
870  for(unsigned int c = 0; c < commonChunksToReturn.size(); ++c)
871  commonChunksToReturn[c] = commonChunksToReturn[c].substr(0, commonChunksToReturn[c].size() - fixedWildcardLength);
872 
873  // add last common chunk
874  commonChunksToReturn.push_back(haystack[0].substr(wildcardBounds.second));
875  } // end handling more chunks
876 
877  // now determine wildcard strings
878  size_t k;
879  unsigned int i;
880  unsigned int ioff = fixedWildcardLength;
881  bool wildcardsNeeded = false;
882 
883  for(unsigned int n = 0; n < haystack.size(); ++n)
884  {
885  std::string wildcard = "";
886  k = 0;
887  i = ioff + commonChunksToReturn[0].size();
888 
889  if(commonChunksToReturn.size() == 1) // just get end
890  wildcard = haystack[n].substr(i);
891  else
892  for(unsigned int c = 1; c < commonChunksToReturn.size(); ++c)
893  {
894  if(c == commonChunksToReturn.size() - 1) // for last, do reverse find
895  k = haystack[n].rfind(commonChunksToReturn[c]);
896  else
897  k = haystack[n].find(commonChunksToReturn[c], i + 1);
898 
899  if(wildcard == "")
900  {
901  // set wildcard for first time
902  __COUTV__(i);
903  __COUTV__(k);
904  __COUTV__(k - i);
905 
906  wildcard = haystack[n].substr(i, k - i);
907  if(fixedWildcardLength && n == 0)
908  fixedWildcardLength += wildcard.size();
909 
910  __COUT__ << "name[" << n << "] = " << wildcard << " fixed @ " << fixedWildcardLength << __E__;
911 
912  break;
913  }
914  else if(0 /*skip validation in favor of speed*/ && wildcard != haystack[n].substr(i, k - i))
915  {
916  __SS__ << "Invalid wildcard! for name[" << n << "] = " << haystack[n]
917  << " - the extraction algorithm is confused, please simplify your naming convention." << __E__;
918  __SS_THROW__;
919  }
920 
921  i = k;
922  } // end commonChunksToReturn loop
923 
924  if(wildcard.size())
925  wildcardsNeeded = true;
926  wildcardStringsToReturn.push_back(wildcard);
927 
928  } // end name loop
929 
930  __COUTV__(StringMacros::vectorToString(commonChunksToReturn));
931  __COUTV__(StringMacros::vectorToString(wildcardStringsToReturn));
932 
933  if(wildcardStringsToReturn.size() != haystack.size())
934  {
935  __SS__ << "There was a problem during common chunk extraction!" << __E__;
936  __SS_THROW__;
937  }
938 
939  return wildcardsNeeded;
940 
941 } // end extractCommonChunks()
942 
943 //==============================================================================
944 // IgnoreCaseCompareStruct operator used to order
945 // std::set, etc ignoring letter case
946 // e.g. used here: void ConfigurationGUISupervisor::handleTablesXML
947 bool StringMacros::IgnoreCaseCompareStruct::operator()(const std::string& lhs, const std::string& rhs) const
948 {
949  //__COUTV__(lhs);
950  //__COUTV__(rhs);
951  // return true if lhs < rhs (lhs will be ordered first)
952 
953  for(unsigned int i = 0; i < lhs.size() && i < rhs.size(); ++i)
954  {
955  //__COUT__ << i << "\t" << lhs[i] << "\t" << rhs[i] << __E__;
956  if((lhs[i] >= 'A' && lhs[i] <= 'Z' && rhs[i] >= 'A' && rhs[i] <= 'Z') || (lhs[i] >= 'a' && lhs[i] <= 'z' && rhs[i] >= 'a' && rhs[i] <= 'z'))
957  { // same case
958  if(lhs[i] == rhs[i])
959  continue;
960  return (lhs[i] < rhs[i]);
961  //{ retVal = false; break;} //return false;
962  }
963  else if(lhs[i] >= 'A' && lhs[i] <= 'Z') // rhs lower case
964  {
965  if(lhs[i] + 32 == rhs[i]) // lower case is higher by 32
966  return false; // in tie return lower case first
967  return (lhs[i] + 32 < rhs[i]);
968  }
969  else if(rhs[i] >= 'A' && rhs[i] <= 'Z')
970  {
971  if(lhs[i] == rhs[i] + 32) // lower case is higher by 32
972  return true; // in tie return lower case first
973  return (lhs[i] < rhs[i] + 32);
974  }
975  else // not letters case (should only be for numbers)
976  {
977  if(lhs[i] == rhs[i])
978  continue;
979  return (lhs[i] < rhs[i]);
980  }
981  } // end case insensitive compare loop
982 
983  // lhs and rhs are equivalent to character[i], so return false if rhs.size() was the limit reached
984  return lhs.size() < rhs.size();
985 } // end IgnoreCaseCompareStruct::operator() comparison handler
986 
987 //==============================================================================
988 // exec
989 // run linux command and get result back in string
990 std::string StringMacros::exec(const char* cmd)
991 {
992  __COUTV__(cmd);
993 
994  std::array<char, 128> buffer;
995  std::string result;
996  std::shared_ptr<FILE> pipe(popen(cmd, "r"), pclose);
997  if(!pipe)
998  __THROW__("popen() failed!");
999  while(!feof(pipe.get()))
1000  {
1001  if(fgets(buffer.data(), 128, pipe.get()) != nullptr)
1002  result += buffer.data();
1003  }
1004  __COUTV__(result);
1005  return result;
1006 } // end exec()
1007 
1008 //==============================================================================
1009 // stackTrace
1010 // static function
1011 // https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
1012 #include <cxxabi.h> //for abi::__cxa_demangle
1013 #include <execinfo.h> //for back trace of stack
1014 //#include "TUnixSystem.h"
1015 std::string StringMacros::stackTrace()
1016 {
1017  __SS__ << "ots::stackTrace:\n";
1018 
1019  void* array[10];
1020  size_t size;
1021 
1022  // get void*'s for all entries on the stack
1023  size = backtrace(array, 10);
1024  // backtrace_symbols_fd(array, size, STDERR_FILENO);
1025 
1026  // https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes
1027  char** messages = backtrace_symbols(array, size);
1028 
1029  // skip first stack frame (points here)
1030  //char syscom[256];
1031  for(unsigned int i = 1; i < size && messages != NULL; ++i)
1032  {
1033  // mangled name needs to be converted to get nice name and line number
1034  // line number not working... FIXME
1035 
1036  // sprintf(syscom,"addr2line %p -e %s",
1037  // array[i],
1038  // messages[i]); //last parameter is the name of this app
1039  // ss << StringMacros::exec(syscom) << __E__;
1040  // system(syscom);
1041 
1042  // continue;
1043 
1044  char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;
1045 
1046  // find parentheses and +address offset surrounding mangled name
1047  for(char* p = messages[i]; *p; ++p)
1048  {
1049  if(*p == '(')
1050  {
1051  mangled_name = p;
1052  }
1053  else if(*p == '+')
1054  {
1055  offset_begin = p;
1056  }
1057  else if(*p == ')')
1058  {
1059  offset_end = p;
1060  break;
1061  }
1062  }
1063 
1064  // if the line could be processed, attempt to demangle the symbol
1065  if(mangled_name && offset_begin && offset_end && mangled_name < offset_begin)
1066  {
1067  *mangled_name++ = '\0';
1068  *offset_begin++ = '\0';
1069  *offset_end++ = '\0';
1070 
1071  int status;
1072  char* real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
1073 
1074  // if demangling is successful, output the demangled function name
1075  if(status == 0)
1076  {
1077  ss << "[" << i << "] " << messages[i] << " : " << real_name << "+" << offset_begin << offset_end << std::endl;
1078  }
1079  // otherwise, output the mangled function name
1080  else
1081  {
1082  ss << "[" << i << "] " << messages[i] << " : " << mangled_name << "+" << offset_begin << offset_end << std::endl;
1083  }
1084  free(real_name);
1085  }
1086  // otherwise, print the whole line
1087  else
1088  {
1089  ss << "[" << i << "] " << messages[i] << std::endl;
1090  }
1091  }
1092  ss << std::endl;
1093 
1094  free(messages);
1095 
1096  // call ROOT's stack trace to get line numbers of ALL threads
1097  // gSystem->StackTrace();
1098 
1099  return ss.str();
1100 } // end stackTrace
1101 
1102 //==============================================================================
1103 // otsGetEnvironmentVarable
1104 // declare special ots environment variable get,
1105 // that throws exception instead of causing crashes with null pointer.
1106 // Note: usually called with __ENV__(X) in CoutMacros.h
1107 char* StringMacros::otsGetEnvironmentVarable(const char* name, const std::string& location, const unsigned int& line)
1108 {
1109  char* environmentVariablePtr = getenv(name);
1110  if(!environmentVariablePtr)
1111  {
1112  __SS__ << "Environment variable '" << name << "' not defined at " << location << "[" << line << "]" << __E__;
1113  ss << "\n\n" << StringMacros::stackTrace() << __E__;
1114  __SS_THROW__;
1115  }
1116  return environmentVariablePtr;
1117 } // end otsGetEnvironmentVarable()
1118 
1119 #ifdef __GNUG__
1120 #include <cxxabi.h>
1121 #include <cstdlib>
1122 #include <memory>
1123 
1124 //==============================================================================
1125 // demangleTypeName
1126 std::string StringMacros::demangleTypeName(const char* name)
1127 {
1128  int status = -4; // some arbitrary value to eliminate the compiler warning
1129 
1130  // enable c++11 by passing the flag -std=c++11 to g++
1131  std::unique_ptr<char, void (*)(void*)> res{abi::__cxa_demangle(name, NULL, NULL, &status), std::free};
1132 
1133  return (status == 0) ? res.get() : name;
1134 } // end demangleTypeName()
1135 
1136 #else // does nothing if not g++
1137 //==============================================================================
1138 // demangleTypeName
1139 //
1140 std::string StringMacros::demangleTypeName(const char* name) { return name; }
1141 #endif