Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-09 07:49:48

0001 #pragma once
0002 
0003 #include <string>
0004 #include <vector>
0005 #include <cstring>
0006 #include <sstream>
0007 #include <fstream>
0008 #include <iostream>
0009 #include <iomanip>
0010 #include <cassert>
0011 #include <algorithm>
0012 #include <csignal>
0013 
0014 #include <charconv> // std::from_chars
0015 #include <type_traits>
0016 
0017 
0018 struct sstr
0019 {
0020     enum { MATCH_ALL, MATCH_START, MATCH_END } ;
0021 
0022     static void Write(const char* path, const char* txt );
0023 
0024     static bool Match( const char* s, const char* q, bool starting=true );
0025     static bool Match_(     const char* s, const char* q, int mode);
0026     static bool MatchAll(   const char* s, const char* q);
0027     static bool MatchStart( const char* s, const char* q);
0028     static bool StartsWith( const char* s, const char* q);
0029     static bool MatchEnd(   const char* s, const char* q);
0030     static bool EndsWith(   const char* s, const char* q);
0031 
0032     static bool StartsWithElem(const char* s, const char* qq,  char delim=',' );
0033     static bool StartsWithElem(const char* s, std::vector<std::string>& qq );
0034 
0035 
0036     static constexpr const char* AZaz = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ;
0037     static bool StartsWithLetterAZaz(const char* q );
0038     static bool Contains(   const char* s_ , const char* q_);
0039 
0040     static const char* TrimLeading(const char* s);
0041     static const char* TrimTrailing(const char* s);
0042     static const char* Trim(const char* s); // both leading and trailing
0043 
0044     static std::string TrimString(const std::string& str, const std::string& whitespace=" \t" );
0045 
0046     static bool        HasTail(const std::vector<std::string>& names, const char* end="0x");
0047     static size_t      CountTail(const std::vector<std::string>& names, const char* end="0x");
0048     static bool        HasTail(const std::string& name, const char* end="0x");
0049 
0050     static std::string StripTail(const std::string& name, const char* end="0x");
0051     static std::string StripComment(const std::string& name);
0052 
0053     static std::string StripTail(const char* name, const char* end="0x");
0054     static void        StripTail(       std::vector<std::string>& dst, const std::vector<std::string>& src, const char* end="0x");
0055     static void        StripTail_Unique(std::vector<std::string>& dst, const std::vector<std::string>& src, const char* end="0x");
0056     static std::string DescKeySrc(const std::vector<std::string>& key, const std::vector<std::string>& src );
0057 
0058     static std::string RemoveSpaces(const char* s);
0059     static std::string Replace(const char* s, char q, char r);
0060 
0061 
0062 
0063     static const char* ReplaceChars(const char* str, const char* repl, char to );
0064     static std::string ReplaceEnd_( const char* s, const char* q, const char* r  );
0065     static const char* ReplaceEnd(  const char* s, const char* q, const char* r  );
0066 
0067     static void PrefixSuffixParse(std::vector<std::string>& elem, const char* prefix, const char* suffix, const char* lines);
0068     static void Split(     const char* str, char delim,   std::vector<std::string>& elem );
0069     static void SplitTrim( const char* str, char delim,   std::vector<std::string>& elem );
0070     static void SplitTrimSuppress( const char* str, char delim,   std::vector<std::string>& elem );
0071 
0072     static int ekv_split( std::vector<std::pair<std::string, std::string> > & ekv, const char* line_, char edelim, char kvdelim) ;
0073 
0074 
0075     static void Chop( std::pair<std::string, std::string>& head__tail, const char* delim, const char* str );
0076     static void chop( char** head, char** tail, const char* delim, const char* str );
0077     static bool prefix_suffix( char** pfx, char** sfx, const char* start_sfx, const char* str );
0078 
0079 
0080 
0081     template<typename T>
0082     static int split(std::vector<T>& elem, const char* str, char delim  );
0083 
0084     static int split_count(const char* str, char delim  );
0085     static bool looks_like_list(const char* str, char delim, int len_min=1, int len_max=4 );
0086 
0087 
0088 
0089     template<typename T>
0090     static std::string desc( const std::vector<T>& elem );
0091 
0092 
0093     template<typename ... Args>
0094     static std::string Format_( const char* fmt, Args ... args );
0095 
0096     template<typename ... Args>
0097     static const char* Format( const char* fmt, Args ... args );
0098 
0099 
0100     static std::string FormatIndexDefault_( int idx, const char* hdr=nullptr  );  // "A000" "A001" "A002" ...
0101     static std::string FormatIndex_( int idx, char prefix, int wid, const char* hdr );
0102     static const char* FormatIndex(  int idx, char prefix, int wid, const char* hdr );
0103 
0104 
0105 
0106 
0107     template<typename ... Args>
0108     static std::string Join( const char* delim, Args ... args );
0109 
0110     template<typename ... Args>
0111     static std::string Concat_( Args ... args );
0112 
0113     template<typename ... Args>
0114     static const char* Concat( Args ... args );
0115 
0116 
0117 
0118     static bool Blank(const char* s );
0119     static bool All(const char* s, char q );
0120     static unsigned Count(const char* s, char q );
0121 
0122     template<typename T>
0123     static T To(const char* arg) ;
0124 
0125     static int AsInt(const char* arg, int fallback=-1 ) ;
0126     static const char* ParseStringIntInt( const char* triplet, int& y, int& z, char delim=':' );
0127 
0128     template<typename T>
0129     static void ParsePair( const char* txt, T& x, T& y, char delim=':' );
0130 
0131     static bool IsInteger(const char* str);
0132     static bool IsWhitespace(const std::string& s );
0133 
0134     static bool isdigit_(char c );
0135     static bool isalnum_(char c );
0136     static bool isupper_(char c );
0137     static bool islower_(char c );
0138 
0139 
0140     template<typename T>
0141     static T ParseIntSpec( const char* spec, T& scale );
0142 
0143     template<typename T>
0144     static T ParseInt( const char* spec );
0145 
0146     template<typename T>
0147     static void ParseIntSpecList( std::vector<T>& ii, const char* spec, char delim=',' );
0148 
0149     template<typename T>
0150     static void ParseScale( const char* spec, T& scale );
0151 
0152     template<typename T>
0153     static std::vector<T>* ParseIntSpecList( const char* spec, char delim=',' ) ;
0154 
0155 
0156     static void truncated_copy( char* dst, const char* src, int dst_size );
0157     static void Extract( std::vector<long>& vals, const char* s );
0158 
0159     template <typename T>
0160     static void Extract_(std::vector<T>& vals, const char* s);
0161 
0162 
0163     static long   ExtractLong( const char* s, long fallback );
0164     static size_t ExtractSize( const char* s, size_t fallback );
0165 
0166 
0167     static bool LooksLikePath(const char* arg);
0168     static void LoadList(const char* arg, std::vector<std::string>& lines, char delim  );
0169     static std::vector<std::string>* LoadList( const char* arg, char delim );
0170 
0171 };
0172 
0173 inline void sstr::Write(const char* path, const char* txt )
0174 {
0175     std::ofstream fp(path);
0176     fp << txt ;
0177 }
0178 
0179 inline bool sstr::Match( const char* s, const char* q, bool starting )
0180 {
0181     return starting ? MatchStart(s, q) : MatchAll(s, q) ;
0182 }
0183 
0184 inline bool sstr::Match_( const char* s, const char* q, int mode )
0185 {
0186     bool ret = false ;
0187     switch(mode)
0188     {
0189         case MATCH_ALL:    ret = MatchAll(  s, q) ; break ;
0190         case MATCH_START:  ret = MatchStart(s, q) ; break ;
0191         case MATCH_END:    ret = MatchEnd(  s, q) ; break ;
0192     }
0193     return ret ;
0194 }
0195 
0196 inline bool sstr::MatchAll( const char* s, const char* q)
0197 {
0198     return s && q && strcmp(s, q) == 0 ;
0199 }
0200 
0201 /**
0202 sstr::MatchStart (NB this can replace SStr::StartsWith with same args)
0203 -------------------------------------------------------------------------
0204 
0205 The 2nd query string must be less than or equal to the length of the first string and
0206 all the characters of the query string must match with the first string in order
0207 to return true.::
0208 
0209                     s              q
0210    sstr::MatchStart("hello/world", "hello") == true
0211 
0212 **/
0213 inline bool sstr::MatchStart( const char* s, const char* q)
0214 {
0215     return s && q && strlen(q) <= strlen(s) && strncmp(s, q, strlen(q)) == 0 ;
0216 }
0217 
0218 
0219 /**
0220 sstr::StartsWith
0221 --------------------
0222 
0223 Returns true when the test string *s* starts with the
0224 same chars as the query string *q*, eg::
0225 
0226     s : abcdefg
0227     q : abcd
0228 
0229     ssstr::StartsWith("abcdefg", "abcd" ) == true
0230 
0231 **/
0232 
0233 inline bool sstr::StartsWith( const char* s, const char* q)  // synonym for sstr::MatchStart
0234 {
0235     return s && q && strlen(q) <= strlen(s) && strncmp(s, q, strlen(q)) == 0 ;
0236 }
0237 
0238 
0239 inline bool sstr::MatchEnd( const char* s, const char* q)
0240 {
0241     int pos = strlen(s) - strlen(q) ;
0242     return pos > 0 && strncmp(s + pos, q, strlen(q)) == 0 ;
0243 }
0244 
0245 /**
0246 sstr::EndsWith
0247 ----------------
0248 
0249 sstr::EndsWith("file.npy", ".npy") == true
0250 
0251 **/
0252 
0253 inline bool sstr::EndsWith( const char* s, const char* q)
0254 {
0255     int pos = strlen(s) - strlen(q) ;
0256     return pos > 0 && strncmp(s + pos, q, strlen(q)) == 0 ;
0257 }
0258 
0259 
0260 inline bool sstr::StartsWithElem(const char* s, const char* _qq, char delim )
0261 {
0262     std::vector<std::string> qq ;
0263     Split(_qq, delim, qq );
0264     return StartsWithElem(s, qq);
0265 }
0266 
0267 inline bool sstr::StartsWithElem(const char* s, std::vector<std::string>& qq )
0268 {
0269     for(int i=0 ; i < int(qq.size()) ; i++)
0270     {
0271         const char* q = qq[i].c_str();
0272         if(StartsWith(s,q)) return true ;
0273     }
0274     return false ;
0275 }
0276 
0277 
0278 
0279 
0280 
0281 inline bool sstr::StartsWithLetterAZaz(const char* q )
0282 {
0283     const char* p = q != nullptr && strlen(q) > 0 ? strchr(AZaz, q[0]) : nullptr ;
0284     return p != nullptr ;
0285 }
0286 
0287 
0288 /**
0289 sstr::Contains
0290 ----------------
0291 
0292 Returns true when first arg *s* contains second arg *q*
0293 
0294 **/
0295 
0296 
0297 inline bool sstr::Contains( const char* s_ , const char* q_ )
0298 {
0299     std::string s(s_);
0300     std::string q(q_);
0301     return s.find(q) != std::string::npos ;
0302 }
0303 
0304 
0305 inline const char* sstr::TrimLeading(const char* s)
0306 {
0307     char* p = strdup(s);
0308     while( *p && ( *p == ' ' || *p == '\n' )) p++ ;
0309     return p ;
0310 }
0311 inline const char* sstr::TrimTrailing(const char* s) // reposition null terminator to skip trailing whitespace
0312 {
0313     char* p = strdup(s);
0314     char* e = p + strlen(p) - 1 ;
0315     while(e > p && ( *e == ' ' || *e == '\n' )) e-- ;
0316     e[1] = '\0' ;
0317     return p ;
0318 }
0319 inline const char* sstr::Trim(const char* s)  // trim leading and trailing whitespace
0320 {
0321     char* p = strdup(s);
0322     char* e = p + strlen(p) - 1 ;
0323     while(e > p && ( *e == ' ' || *e == '\n' )) e-- ;
0324     *(e+1) = '\0' ;
0325     while( *p && ( *p == ' ' || *p == '\n')) p++ ;
0326     return p ;
0327 }
0328 
0329 inline std::string sstr::TrimString(const std::string& str, const std::string& whitespace )
0330 {
0331     const auto beg = str.find_first_not_of(whitespace);
0332     if(beg == std::string::npos) return "" ;
0333     const auto end = str.find_last_not_of(whitespace);
0334     const auto rng = end - beg + 1 ;
0335     return str.substr(beg, rng);
0336 }
0337 
0338 
0339 
0340 inline bool sstr::HasTail(const std::vector<std::string>& names, const char* end)
0341 {
0342     size_t num_names = names.size() ;
0343     size_t num_tail = CountTail(names, end );
0344     return num_names == num_tail ;
0345 }
0346 
0347 inline size_t sstr::CountTail(const std::vector<std::string>& names, const char* end)
0348 {
0349     size_t num_names = names.size() ;
0350     size_t count = 0 ;
0351     for(unsigned i=0 ; i < num_names ; i++) if(HasTail(names[i], end)) count += 1 ;
0352     return count ;
0353 }
0354 
0355 inline bool sstr::HasTail(const std::string& name, const char* end)
0356 {
0357     bool found = name.find(end) != std::string::npos ;
0358     return found ;
0359 }
0360 
0361 inline std::string sstr::StripTail(const std::string& name, const char* end)  // static
0362 {
0363     std::string sname = name.substr(0, name.find(end)) ;
0364     return sname ;
0365 }
0366 
0367 inline std::string sstr::StripComment(const std::string& name)  // static
0368 {
0369     const char* end = "#" ; // 1st
0370     std::string sname = name.substr(0, name.find(end)) ;
0371     return TrimString(sname) ;
0372 }
0373 
0374 
0375 
0376 inline std::string sstr::StripTail(const char* name_, const char* end)  // static
0377 {
0378     std::string name(name_);
0379     return StripTail(name, end) ;
0380 }
0381 
0382 inline void sstr::StripTail(std::vector<std::string>& dst, const std::vector<std::string>& src, const char* end)  // static
0383 {
0384     int num_src = src.size();
0385     for(int i=0 ; i < num_src ; i++ )
0386     {
0387         const std::string&  _src = src[i] ;
0388         std::string _dst = StripTail(_src, end);
0389         dst.push_back(_dst) ;
0390     }
0391 }
0392 
0393 /**
0394 sstr::StripTail_Unique
0395 -----------------------
0396 
0397 When the stripped name is unique amoungst all
0398 the stripped names use it as the key otherwise
0399 try different numbered suffix _0 _1 _2 _3 until
0400 a unique key amoungst the keys is found.
0401 
0402 The default end is "0x" for pointer tail suffix.
0403 
0404 **/
0405 
0406 inline void sstr::StripTail_Unique( std::vector<std::string>& keys, const std::vector<std::string>& src, const char* end )
0407 {
0408     std::vector<std::string> stripped ;
0409     StripTail( stripped, src, end );
0410 
0411     int num_src = src.size();
0412     [[maybe_unused]] int num_stripped = stripped.size();
0413     assert( num_src == num_stripped );
0414 
0415     for(int i=0 ; i < num_src ; i++)
0416     {
0417         const char* cand0 = stripped[i].c_str();
0418         int count0 = std::count( stripped.begin(), stripped.end(), cand0 );
0419         assert( count0 >= 1 );
0420         if( count0 == 1 )
0421         {
0422             keys.push_back(cand0);
0423         }
0424         else
0425         {
0426             for(int j=0 ; j < 1000000 ; j++ )
0427             {
0428                 std::string cand = Format_("%s_%d", cand0, j );
0429                 int count_key = std::count( keys.begin(), keys.end(), cand.c_str() );
0430                 assert( count_key == 0 || count_key == 1 );
0431                 if( count_key == 0 )
0432                 {
0433                     keys.push_back(cand);
0434                     break ;
0435                 }
0436             }
0437         }
0438     }
0439 }
0440 
0441 inline std::string sstr::DescKeySrc(const std::vector<std::string>& key, const std::vector<std::string>& src )
0442 {
0443     std::stringstream ss ;
0444     ss << "sstr::DescKeySrc" << std::endl ;
0445     int num_src = src.size();
0446     [[maybe_unused]] int num_key = key.size();
0447     assert( num_src == num_key );
0448 
0449     for(int i=0 ; i < num_src ; i++)
0450     {
0451         ss << std::setw(4) << i
0452            << " : "
0453            << std::setw(50) << src[i]
0454            << " : "
0455            << std::setw(50) << key[i]
0456            << std::endl
0457            ;
0458     }
0459     std::string str = ss.str();
0460     return str ;
0461 }
0462 
0463 
0464 inline std::string sstr::RemoveSpaces(const char* s) // static
0465 {
0466     std::stringstream ss ;
0467     for(int i=0 ; i < int(strlen(s)) ; i++) if(s[i] != ' ') ss << s[i] ;
0468     std::string str = ss.str();
0469     return str ;
0470 }
0471 inline std::string sstr::Replace(const char* s, char q, char r) // static
0472 {
0473     std::stringstream ss ;
0474     for(int i=0 ; i < int(strlen(s)) ; i++) ss << ( s[i] == q ? r : s[i] ) ;
0475     std::string str = ss.str();
0476     return str ;
0477 }
0478 
0479 
0480 
0481 inline const char* sstr::ReplaceChars(const char* str, const char* repl, char to )
0482 {
0483     char* s = strdup(str);
0484     for(unsigned i=0 ; i < strlen(s) ; i++) if(strchr(repl, s[i]) != nullptr) s[i] = to ;
0485     return s ;
0486 }
0487 
0488 
0489 
0490 
0491 /**
0492 sstr::ReplaceEnd_
0493 ------------------
0494 
0495 String s is required to have ending q.
0496 New string n is returned with the ending q replaced with r.
0497 
0498 **/
0499 
0500 inline std::string sstr::ReplaceEnd_( const char* s, const char* q, const char* r  )
0501 {
0502     int pos = strlen(s) - strlen(q) ;
0503     assert( pos > 0 && strncmp(s + pos, q, strlen(q)) == 0 ); // check q is at end of s
0504 
0505     std::stringstream ss ;
0506     for(int i=0 ; i < pos ; i++) ss << *(s+i) ;  // copy front of s
0507     ss << r ;    // replace the end
0508 
0509     std::string str = ss.str();
0510     return str ;
0511 }
0512 
0513 inline const char* sstr::ReplaceEnd( const char* s, const char* q, const char* r  )
0514 {
0515     std::string str = ReplaceEnd_(s, q, r);
0516     return strdup(str.c_str());
0517 }
0518 
0519 
0520 
0521 
0522 
0523 
0524 inline void sstr::PrefixSuffixParse(std::vector<std::string>& elem, const char* prefix, const char* suffix, const char* lines)
0525 {
0526     std::stringstream ss;
0527     ss.str(lines)  ;
0528     std::string s;
0529     while (std::getline(ss, s, '\n'))
0530     {
0531         if(s.empty()) continue ;
0532         const char* l = s.c_str();
0533         bool has_prefix = strlen(l) > strlen(prefix) && strncmp(l, prefix, strlen(prefix)) == 0 ;
0534         bool has_suffix = strlen(l) > strlen(suffix) && strncmp(l+strlen(l)-strlen(suffix), suffix, strlen(suffix)) == 0 ;
0535         //std::cout << "[" << l << "]"<< " has_prefix " << has_prefix << " has_suffix " << has_suffix << std::endl ;
0536         if(has_prefix && has_suffix)
0537         {
0538               int count = strlen(l) - strlen(prefix) - strlen(suffix) ;
0539               std::string sub = s.substr(strlen(prefix), count );
0540               //std::cout << " count " << count << " sub [" << sub << "]" << std::endl ;
0541               elem.push_back(sub);
0542         }
0543     }
0544 }
0545 
0546 
0547 
0548 
0549 inline void sstr::Split( const char* str, char delim,   std::vector<std::string>& elem )
0550 {
0551     std::stringstream ss;
0552     ss.str(str)  ;
0553     std::string s;
0554     while (std::getline(ss, s, delim)) elem.push_back(s) ;
0555 }
0556 
0557 inline void sstr::SplitTrim( const char* str, char delim,   std::vector<std::string>& elem  )
0558 {
0559     std::stringstream ss;
0560     ss.str(str)  ;
0561     std::string s;
0562     while (std::getline(ss, s, delim)) elem.push_back(Trim(s.c_str())) ;
0563 }
0564 
0565 /**
0566 sstr::SplitTrimSuppress
0567 ------------------------
0568 
0569 * when str contains '\n' the argument delim is overridden to become '\n'
0570 * elem that start with # are skipped
0571 
0572 **/
0573 
0574 inline void sstr::SplitTrimSuppress( const char* str, char delim,   std::vector<std::string>& elem  )
0575 {
0576     bool is_multiline = Contains(str,"\n");
0577     char udelim = is_multiline ? '\n' : delim ;
0578 
0579     std::stringstream ss;
0580     ss.str(str)  ;
0581     std::string s;
0582     while (std::getline(ss, s, udelim))
0583     {
0584         const char* t = Trim(s.c_str());
0585         if(t && strlen(t) > 0)
0586         {
0587             if(t[0] == '#') continue ;
0588             elem.push_back(t) ;
0589         }
0590     }
0591 }
0592 
0593 
0594 /**
0595 sstr::ekv_split
0596 -----------------
0597 
0598 **/
0599 
0600 
0601 inline int sstr::ekv_split( std::vector<std::pair<std::string, std::string> > & ekv, const char* line_, char edelim, char kvdelim)
0602 {
0603     int err = 0 ;
0604     bool warn = true ;
0605     const char* line = strdup(line_);
0606     typedef std::pair<std::string,std::string> KV ;
0607     std::istringstream f(line);
0608     std::string s;
0609     while (getline(f, s, edelim))
0610     {
0611         std::vector<std::string> kv ;
0612         Split( s.c_str(), kvdelim, kv );
0613 
0614         if(kv.size() == 2)
0615         {
0616             ekv.push_back(KV(kv[0],kv[1]));
0617         }
0618         else
0619         {
0620             if(warn) std::cerr
0621                << "sstr::ekv_split ignoring malformed kv [" << s.c_str() << "]\n"
0622                << "sstr::ekv_split line [" << line << "]\n"
0623                ;
0624             err++ ;
0625             std::raise(SIGINT);
0626         }
0627     }
0628     return err ;
0629 }
0630 
0631 
0632 
0633 
0634 
0635 template<typename T>
0636 inline int sstr::split( std::vector<T>& elem, const char* str, char delim )
0637 {
0638     std::stringstream ss;
0639     ss.str(str)  ;
0640     std::string s;
0641     while (std::getline(ss, s, delim))
0642     {
0643         std::istringstream iss(s);
0644         T v ;
0645         iss >> v ;
0646         elem.push_back(v) ;
0647     }
0648     int count = elem.size();
0649     return count ;
0650 }
0651 
0652 inline int sstr::split_count( const char* str, char delim )
0653 {
0654     std::stringstream ss;
0655     ss.str(str)  ;
0656     std::string s;
0657     int count(0);
0658     while (std::getline(ss, s, delim)) count += 1 ;
0659     return count ;
0660 }
0661 
0662 inline bool sstr::looks_like_list(const char* str, char delim, int len_min, int len_max )
0663 {
0664     int len = split_count(str, delim);
0665     return len >= len_min && len <= len_max ;
0666 }
0667 
0668 
0669 
0670 
0671 template<typename T>
0672 inline std::string sstr::desc( const std::vector<T>& elem )
0673 {
0674     std::stringstream ss ;
0675     for(int i=0 ; i < int(elem.size()); i++)
0676     {
0677         if(i % 4 == 0) ss << "\n" ;
0678         ss << " " << std::fixed << std::setw(10) << std::setprecision(4) << elem[i]  ;
0679     }
0680     ss << "\n" ;
0681     std::string str = ss.str() ;
0682     return str ;
0683 }
0684 
0685 
0686 
0687 
0688 
0689 
0690 
0691 
0692 inline void sstr::Chop( std::pair<std::string, std::string>& head__tail, const char* delim, const char* str )
0693 {
0694     char* head = strdup(str);
0695     char* p = strstr(head, delim);  // pointer to first occurence of delim in str or null if not found
0696     if(p) p[0] = '\0' ;
0697     const char* tail = p ? p + strlen(delim)  : nullptr ;
0698     head__tail.first = head ;
0699     head__tail.second = tail ? tail : ""  ;
0700 }
0701 
0702 /**
0703 sstr::chop
0704 -----------
0705 
0706 Chop *str* into *head* and *tail* strings at first occurence of *delim*.
0707 Note that *delim* is not included with either *head* or *tail*.
0708 
0709 **/
0710 
0711 
0712 inline void sstr::chop( char** head, char** tail, const char* delim, const char* str )
0713 {
0714     *head = strdup(str);
0715     char* p = strstr(*head, delim);  // pointer to first occurence of delim in str or null if not found
0716     if(p) p[0] = '\0' ;
0717     *tail = p ? p + strlen(delim) : nullptr ;
0718 }
0719 
0720 /**
0721 sstr::prefix_suffix
0722 ---------------------
0723 
0724 Splits *str* into *pfx* and *sfx* where *sfx* begins with the *start_sfx* argument
0725 returning true when the suffix is found. For example with *start_sfx* "["::
0726 
0727     str: /tmp/w54.npy[0:1]
0728     pfx: /tmp/w54.npy
0729     sfx: [0:1]
0730 
0731 A string starting with *start_sfx* is not regarded as a suffix, causing false
0732 to be returned.
0733 
0734 **/
0735 
0736 inline bool sstr::prefix_suffix( char** pfx, char** sfx, const char* start_sfx, const char* str )
0737 {
0738     if(!str || !start_sfx) return false ;
0739 
0740     *pfx = strdup(str);
0741     char* p = strstr(*pfx, start_sfx);
0742 
0743     bool has_suffix = p && (p > *pfx) ;
0744     if(has_suffix)
0745     {
0746         *sfx = strdup(p);
0747         p[0] = '\0' ; // terminate pfx at position of start_sfx
0748     }
0749    return has_suffix ;
0750 }
0751 
0752 
0753 
0754 
0755 
0756 /**
0757 sstr::Format_
0758 ---------------
0759 
0760 See sysrap/tests/StringFormatTest.cc
0761 
0762 
0763 **/
0764 
0765 template<typename ... Args>
0766 inline std::string sstr::Format_( const char* fmt, Args ... args )
0767 {
0768     int sz = std::snprintf( nullptr, 0, fmt, args ... ) + 1 ; // +1 for null termination
0769     assert( sz > 0 );
0770     std::vector<char> buf(sz) ;
0771     std::snprintf( buf.data(), sz, fmt, args ... );
0772     return std::string( buf.begin(), buf.begin() + sz - 1 );  // exclude null termination
0773 }
0774 
0775 template std::string sstr::Format_( const char*, const char*, int, int );
0776 template std::string sstr::Format_( const char*, int );
0777 template std::string sstr::Format_( const char*, unsigned long long );
0778 
0779 
0780 template<typename ... Args>
0781 inline const char* sstr::Format( const char* fmt, Args ... args )
0782 {
0783     std::string str = Format_(fmt, std::forward<Args>(args)... );
0784     return strdup(str.c_str());
0785 }
0786 
0787 template const char* sstr::Format( const char*, const char*, int, int );
0788 template const char* sstr::Format( const char*, int  );
0789 template const char* sstr::Format( const char*, unsigned long long  );
0790 
0791 
0792 
0793 
0794 inline std::string sstr::FormatIndexDefault_( int idx, const char* hdr )
0795 {
0796     char prefix = 'A' ;
0797     int wid = 3 ;
0798     return FormatIndex_(idx,  prefix, wid, hdr );
0799 }
0800 
0801 /**
0802 sstr::FormatIndex_
0803 -------------------
0804 
0805 Canonically the prefix hails from SEvt::getInstancePrefix
0806 
0807 **/
0808 
0809 
0810 inline std::string sstr::FormatIndex_( int idx, char prefix, int wid, const char* hdr )
0811 {
0812     assert( prefix == '\0' || prefix == 'A' || prefix == 'B' || prefix == 'M' || prefix == 'D' );
0813     assert( idx >= 0 );
0814 
0815     std::stringstream ss ;
0816     if(hdr) ss << hdr ;
0817 
0818     //if(prefix) ss << ( idx == 0 ? "z" : ( idx < 0 ? "n" : "p" ) ) ;
0819     if(prefix != '\0') ss << prefix  ;
0820 
0821     ss << std::setfill('0') << std::setw(wid) << std::abs(idx) ;
0822     std::string str = ss.str();
0823     return str ;
0824 }
0825 
0826 /**
0827 sstr::FormatIndex
0828 -------------------
0829 
0830 prefix:A wid:3
0831 
0832 +-----+--------+
0833 | idx |  val   |
0834 +=====+========+
0835 |  1  | A001   |
0836 +-----+--------+
0837 |  -1 | *FAIL* |
0838 +-----+--------+
0839 |  0  | A000   |
0840 +-----+--------+
0841 
0842 **/
0843 
0844 inline const char* sstr::FormatIndex( int idx, char prefix, int wid, const char* hdr )
0845 {
0846     std::string str = FormatIndex_(idx, prefix, wid, hdr );
0847     return strdup(str.c_str());
0848 }
0849 
0850 
0851 
0852 
0853 template<typename ... Args>
0854 inline std::string sstr::Join( const char* delim, Args ... args_ )
0855 {
0856     std::vector<const char*> args = {args_ ...};
0857     int num_args = args.size() ;
0858     std::stringstream ss ;
0859     for(int i=0 ; i < num_args ; i++) ss << ( args[i] ? args[i] : "" ) << ( i < num_args - 1 ? delim : "" ) ;
0860     std::string str = ss.str();
0861     return str ;
0862 }
0863 template std::string sstr::Join( const char*, const char*, const char* );
0864 template std::string sstr::Join( const char*, const char*, const char*, const char*  );
0865 template std::string sstr::Join( const char*, const char*, const char*, const char*, const char* );
0866 
0867 
0868 
0869 
0870 
0871 template<typename ... Args>
0872 inline std::string sstr::Concat_( Args ... args_ )
0873 {
0874     std::vector<const char*> args = {args_ ...};
0875     int num_args = args.size() ;
0876     std::stringstream ss ;
0877     for(int i=0 ; i < num_args ; i++) ss << ( args[i] ? args[i] : "" ) ;
0878     std::string str = ss.str();
0879     return str ;
0880 }
0881 
0882 template std::string sstr::Concat_( const char*, const char* );
0883 template std::string sstr::Concat_( const char*, const char*, const char* );
0884 template std::string sstr::Concat_( const char*, const char*, const char*, const char*  );
0885 template std::string sstr::Concat_( const char*, const char*, const char*, const char*, const char* );
0886 
0887 
0888 
0889 
0890 
0891 template<typename ... Args>
0892 inline const char* sstr::Concat( Args ... args )
0893 {
0894     std::string str = Concat_(std::forward<Args>(args)... );
0895     return strdup(str.c_str());
0896 }
0897 
0898 template const char* sstr::Concat( const char*, const char* );
0899 template const char* sstr::Concat( const char*, const char*, const char* );
0900 template const char* sstr::Concat( const char*, const char*, const char*, const char*  );
0901 template const char* sstr::Concat( const char*, const char*, const char*, const char*, const char* );
0902 
0903 
0904 
0905 
0906 
0907 
0908 
0909 
0910 inline bool sstr::Blank( const char* s )
0911 {
0912    unsigned n = strlen(s) ;
0913    return n == 0 || All(s, ' ') ;
0914 }
0915 
0916 inline bool sstr::All( const char* s , char q )
0917 {
0918    unsigned n = strlen(s) ;
0919    return n > 0 && Count(s, q) == n ;
0920 
0921 }
0922 inline unsigned sstr::Count( const char* s , char q )
0923 {
0924    unsigned n = strlen(s) ;
0925    unsigned count = 0 ;
0926    for(unsigned i=0 ; i < n ; i++) if( s[i] == q ) count += 1 ;
0927    return count ;
0928 }
0929 
0930 template<typename T>
0931 inline T sstr::To(const char* arg )
0932 {
0933     std::string str(arg);
0934     std::istringstream iss(str);
0935     T v ;
0936     iss >> v ;
0937     return v ;
0938 }
0939 
0940 // specialization for std::string as the above truncates at the first blank in the string, see tests/NP_set_meta_get_meta_test.cc
0941 template<> inline std::string sstr::To(const char* a )
0942 {
0943     std::string s(a);
0944     return s ;
0945 }
0946 
0947 
0948 
0949 
0950 inline int sstr::AsInt(const char* arg, int fallback )
0951 {
0952     char* end ;
0953     char** endptr = &end ;
0954     int base = 10 ;
0955     unsigned long ul = strtoul(arg, endptr, base);
0956     bool end_points_to_terminator = end == arg + strlen(arg) ;
0957     return end_points_to_terminator ? int(ul) : fallback ;
0958 }
0959 
0960 
0961 inline const char* sstr::ParseStringIntInt( const char* triplet, int& y, int& z, char delim )
0962 {
0963     std::stringstream ss;
0964     ss.str(triplet)  ;
0965     std::string s;
0966     std::vector<std::string> elem ;
0967     while (std::getline(ss, s, delim)) elem.push_back(s) ;
0968     assert(elem.size() == 3 );
0969     y = AsInt( elem[1].c_str() );
0970     z = AsInt( elem[2].c_str() );
0971     return strdup(elem[0].c_str());
0972 }
0973 
0974 
0975 template<typename T>
0976 inline void sstr::ParsePair( const char* txt, T& x, T& y, char delim )
0977 {
0978     std::stringstream ss;
0979     ss.str(txt)  ;
0980     std::string s;
0981     std::vector<std::string> elem ;
0982     while (std::getline(ss, s, delim)) elem.push_back(s) ;
0983     int num_elem = elem.size();
0984     bool expect = num_elem == 2 ;
0985     if(!expect) std::cerr
0986         << "sstr::ParsePair"
0987         << " txt [" << ( txt ? txt : "-" ) << "]"
0988         << " delim " << delim
0989         << " num_elem " << num_elem
0990         << std::endl
0991         ;
0992 
0993     assert(expect);
0994     x = To<T>( elem[0].c_str() );
0995     y = To<T>( elem[1].c_str() );
0996 }
0997 
0998 inline bool sstr::IsInteger(const char* str)
0999 {
1000     if(!str) return false ;
1001     if(strlen(str)==0) return false ;
1002 
1003     std::string s(str);
1004     return s.find_first_not_of("0123456789") == std::string::npos ;
1005 }
1006 
1007 inline bool sstr::IsWhitespace(const std::string& str )
1008 {
1009     return str.find_first_not_of(" \t\n\v\f\r") == std::string::npos ;
1010 }
1011 
1012 inline bool sstr::isdigit_(char c ) { return std::isdigit(static_cast<unsigned char>(c)) ; }
1013 inline bool sstr::isalnum_(char c ) { return std::isalnum(static_cast<unsigned char>(c)) ; }
1014 inline bool sstr::isupper_(char c ) { return std::isupper(static_cast<unsigned char>(c)) ; }
1015 inline bool sstr::islower_(char c ) { return std::islower(static_cast<unsigned char>(c)) ; }
1016 
1017 
1018 /**
1019 sstr::ParseIntSpec
1020 --------------------
1021 
1022 +------+--------------+-------------+----------------------------+
1023 | spec | value        | scale       | note                       |
1024 | (in) | (out)        | (in/out)    |                            |
1025 +======+==============+=============+============================+
1026 |  "1" |  1           |             |                            |
1027 | "h1" |  100         |    100      |                            |
1028 | "H1" |  100000      |    100000   |                            |
1029 | "K1" |  1000        |    1000     |                            |
1030 | "K2" |  2000        |    1000     |                            |
1031 | "M1" |  1000000     |    1000000  |                            |
1032 | "M2" |  2000000     |    1000000  |                            |
1033 | "G1" |  1000000000  | 1000000000  |                            |
1034 | "G2" |  2000000000  | 1000000000  |                            |
1035 | "G3" |  3000000000  | 1000000000  |                            |
1036 | "X1" | (0x1<<1)-1   |    1        | 0x1                        |
1037 | "X31"| (0x1<<31)-1  |    1        | 0x7fffffff    2.14 billion |
1038 | "X32"| (0x1<<32)-1  |    1        | 0xffffffff    4.29 billion |
1039 | "X40"| (0x1<<40)-1  |    1        | 0xffffffffff  1099 billion |
1040 | "X64"| (0x1<<64)-1  |    1        | zero with uint64_t         |
1041 +------+--------------+-------------+----------------------------+
1042 
1043 * "K","M" and "G" are prefixes to avoid lots of zeros
1044 * spec with prefix both uses the scales to multiply the value and sets the scale for subsequent
1045 * spec without prefix is multiplied by the current scale
1046 
1047 +-----+-----------------------+------------+
1048 | pfx |       scale           |            |
1049 +=====+=======================+============+
1050 |  h  |                100    |            |
1051 |  K  |              1,000    |            |
1052 |  H  |            100,000    |            |
1053 |  M  |          1,000,000    |   million  |
1054 |  G  |      1,000,000,000    |   billion  |
1055 |  T  |  1,000,000,000,000    |   trillion |
1056 +-----+-----------------------+------------+
1057 
1058 Examples::
1059 
1060     H1,2,3,4,5,6,7,8,9,10
1061 
1062 
1063 **/
1064 
1065 template<typename T>
1066 inline T sstr::ParseIntSpec( const char* spec, T& scale ) // static
1067 {
1068     bool valid = spec != nullptr && strlen(spec) > 0 ;
1069     if(!valid) return 0 ;
1070 
1071     bool is_digit = isdigit_(spec[0]);
1072     const char* e = is_digit ? spec : spec + 1 ;
1073 
1074     T value = 0ull ;
1075 
1076     if( spec[0] == 'X' )
1077     {
1078         T bitshift = std::atol(e);
1079         value = ( 0x1ull << bitshift ) - 1ull ;
1080         scale = 1ull ;
1081     }
1082     else
1083     {
1084         value = To<T>( e) ;
1085         ParseScale<T>(spec, scale);
1086     }
1087     return value*scale  ;
1088 }
1089 
1090 template<typename T>
1091 inline T sstr::ParseInt( const char* spec) // static
1092 {
1093     T scale(1) ;
1094     return ParseIntSpec<T>( spec, scale );
1095 }
1096 
1097 
1098 
1099 template<typename T>
1100 inline void sstr::ParseScale( const char* spec, T& scale )
1101 {
1102     bool is_digit = isdigit_(spec[0]);
1103     if(!is_digit)
1104     {
1105         switch(spec[0])
1106         {
1107             case 'h': scale = 100               ; break ;
1108             case 'K': scale = 1'000             ; break ;
1109             case 'H': scale = 100'000           ; break ;
1110             case 'M': scale = 1'000'000         ; break ;
1111             case 'G': scale = 1'000'000'000     ; break ;  // maximum 32 bit int 2,147,483,647 , uint 4.29 billion
1112             case 'T': scale = 1'000'000'000'000 ; break ;  // 40 bits of integer needed to hold a trillion
1113         }
1114     }
1115 }
1116 
1117 
1118 /**
1119 sstr::ParseIntSpecList
1120 ------------------------
1121 
1122 Parses delimited string into vector of ints, eg:
1123 
1124 +---------------------+------------------------------------------------------+
1125 |  spec               | values                                               |
1126 +=====================+======================================================+
1127 |  "M1,2,3,4,5,K1,2"  | 1000000,2000000,3000000,4000000,5000000,1000,2000    |
1128 |  "M1:5,K1:2"        | 1000000,2000000,3000000,4000000,5000000,1000,2000    |
1129 |  "1x5,M1x5          | 1,1,1,1,1,1000000,1000000,1000000,1000000,1000000    |
1130 +---------------------+------------------------------------------------------+
1131 
1132 The repeated value spec "M1x5" meaning 5 times M1 puts the multiplicity to the right
1133 in order to work with the scale specification to the left.
1134 
1135 **/
1136 
1137 template<typename T>
1138 inline void sstr::ParseIntSpecList( std::vector<T>& values, const char* spec, char delim ) // static
1139 {
1140     values.clear();
1141     std::stringstream ss;
1142     ss.str(spec)  ;
1143     std::string elem ;
1144     T scale = 1 ;
1145     while (std::getline(ss, elem, delim))
1146     {
1147         const char* e = elem.c_str();
1148         const char* p = strstr(e, ":" );
1149         const char* x = strstr(e, "x" );
1150 
1151 
1152         if( p == nullptr && x == nullptr)
1153         {
1154             values.push_back(ParseIntSpec<T>( e, scale ));
1155         }
1156         else if ( p || x )
1157         {
1158             const char* q = isdigit_(e[0]) ? e : e + 1 ;  // jump past scale char if present
1159             T i0, i1 ;
1160             ParsePair<T>( q , i0, i1, p ? ':' : 'x' );
1161             ParseScale<T>( e, scale );
1162 
1163             if( p )
1164             {
1165                for(T i=i0 ; i <= i1 ; i++) values.push_back(i*scale) ;
1166             }
1167             else if( x )
1168             {
1169                 for(T i=0 ; i < i1 ; i++) values.push_back(i0*scale) ;
1170             }
1171         }
1172 
1173 
1174     }
1175 }
1176 
1177 template<typename T>
1178 inline std::vector<T>* sstr::ParseIntSpecList( const char* spec, char delim )
1179 {
1180     if(spec == nullptr) return nullptr ;
1181     std::vector<T>* ls = new std::vector<T> ;
1182     ParseIntSpecList<T>( *ls, spec, delim );
1183     return ls ;
1184 }
1185 
1186 inline void sstr::truncated_copy( char* dst, const char* src, int dst_size )
1187 {
1188     memset(dst, 0, dst_size);
1189 
1190     int srclen = strlen(src) ;
1191     int num_char = std::min(dst_size, srclen);
1192     memcpy(dst, src, num_char) ;
1193 }
1194 
1195 
1196 
1197 /**
1198 sstr::Extract
1199 -----------------------
1200 
1201 Extract integers from a string into a vector.
1202 
1203 The 2nd strtol endptr arg increments p beyond each group of integer digits
1204 
1205 **/
1206 
1207 inline void sstr::Extract( std::vector<long>& vals, const char* s )  // static
1208 {
1209     bool invalid = strlen(s) == 1 && ( s[0] == '+' || s[0] == '-' ) ;
1210     if(invalid) return ;
1211 
1212     char* s0 = strdup(s);
1213     char* p = s0 ;
1214 
1215     while (*p)
1216     {
1217         if( (*p >= '0' && *p <= '9') || *p == '+' || *p == '-')
1218         {
1219             // EDGE CASE PROTECTION:
1220             // If it's a sign, make sure the NEXT char is a digit.
1221             // If it's just a sign followed by space or another sign, skip it.
1222             if ((*p == '+' || *p == '-') && !(p[1] >= '0' && p[1] <= '9'))
1223             {
1224                 p++;
1225                 continue;
1226             }
1227 
1228             char* endptr;
1229             long val = strtol(p, &endptr, 10);
1230 
1231             // Ensure we actually consumed characters
1232             if (p != endptr)
1233             {
1234                 vals.push_back(val);
1235                 p = endptr;
1236             }
1237             else
1238             {
1239                 p++;
1240             }
1241         }
1242         else
1243         {
1244             p++ ;
1245         }
1246     }
1247     free(s0);
1248 }
1249 
1250 
1251 /**
1252 sstr::Extract_
1253 ---------------
1254 
1255 When T is unsigned/size_t skip signs in the string but still parse digits.
1256 
1257 **/
1258 
1259 template <typename T>
1260 inline void sstr::Extract_(std::vector<T>& vals, const char* s)
1261 {
1262     if (!s) return;
1263     const char* p = s;
1264     const char* end = s + std::strlen(s);
1265 
1266     while (p < end)
1267     {
1268         if ((*p >= '0' && *p <= '9') || *p == '+' || *p == '-')
1269         {
1270             const char* parse_start = p;
1271             if constexpr (std::is_unsigned_v<T>)  // compile time variation
1272             {
1273                 if (*p == '+' || *p == '-') parse_start++; // Skip sign for unsigned parsing
1274             }
1275 
1276             T val;
1277             auto [ptr, ec] = std::from_chars(parse_start, end, val);
1278 
1279             if (ec == std::errc()) {
1280                 vals.push_back(val);
1281                 p = ptr; // Advance to where the number ended
1282             } else {
1283                 p++; // It was just a lone '+' or '-' without digits
1284             }
1285         }
1286         else
1287         {
1288             p++;
1289         }
1290     }
1291 }
1292 
1293 
1294 
1295 
1296 inline long sstr::ExtractLong( const char* s, long fallback ) // static
1297 {
1298     std::vector<long> vals;
1299     Extract(vals, s);
1300     return vals.size() == 1 ? vals[0] : fallback ;
1301 }
1302 
1303 
1304 
1305 inline size_t sstr::ExtractSize( const char* s, size_t fallback ) // static
1306 {
1307     std::vector<size_t> vals;
1308     Extract_(vals, s);
1309     return vals.size() == 1 ? vals[0] : fallback ;
1310 }
1311 
1312 
1313 
1314 
1315 
1316 inline bool sstr::LooksLikePath(const char* arg)
1317 {
1318     if(!arg) return false ;
1319     if(strlen(arg) < 2) return false ;
1320     return arg[0] == '/' || arg[0] == '$' ;
1321 }
1322 
1323 
1324 
1325 /**
1326 sstr::LoadList
1327 ----------------
1328 
1329 Interprets the arg as either a filepath with lines to be loaded
1330 or a comma delimited string to be split into lines.
1331 
1332 **/
1333 
1334 inline void sstr::LoadList(const char* arg, std::vector<std::string>& lines, char delim  )
1335 {
1336     if(arg == nullptr) return ;
1337 
1338     if(LooksLikePath(arg) && delim == '\n' )  // eg starts with slash
1339     {
1340         std::ifstream ifs(arg);
1341         std::string line;
1342         while(std::getline(ifs, line)) lines.push_back(line) ;
1343     }
1344     else if( delim == ',' )
1345     {
1346         sstr::Split( arg,  delim, lines );
1347     }
1348     else
1349     {
1350         lines.push_back(arg);
1351     }
1352 }
1353 
1354 inline std::vector<std::string>* sstr::LoadList( const char* arg, char delim )
1355 {
1356     if(arg == nullptr) return nullptr ;
1357     typedef std::vector<std::string> VS ;
1358     VS* lines = new VS ;
1359     LoadList(arg, *lines, delim );
1360     return lines ;
1361 }
1362 
1363