Back to home page

EIC code displayed by LXR

 
 

    


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

0001 #pragma once
0002 /**
0003 spath.h
0004 =========
0005 
0006 Q: Whats the difference between spath::ResolvePath and spath::Resolve ?
0007 A: ResolvePath accepts only a single string element whereas Resolve accepts
0008    from 1 to 4 elements. Also ResolvePath is private, the public interface is Resolve
0009 
0010 **/
0011 
0012 #include <cassert>
0013 #include <cstdlib>
0014 #include <cstring>
0015 #include <string>
0016 #include <sstream>
0017 #include <fstream>
0018 #include <vector>
0019 #include <iostream>
0020 #include <unistd.h>
0021 
0022 
0023 #include "sproc.h"
0024 #include "sdirectory.h"
0025 #include "sstamp.h"
0026 
0027 struct spath
0028 {
0029     friend struct spath_test ;
0030     static constexpr const bool VERBOSE = false ;
0031     static constexpr const bool DUMP = false ;
0032 
0033 private:
0034     static std::string _ResolvePath(const char* spec);
0035     static std::string _ResolvePathGeneralized(const char* spec_);
0036 
0037 
0038     static char* ResolvePath(const char* spec);
0039     static char* ResolvePathGeneralized(const char* spec);
0040 
0041     static char* DefaultTMP();
0042     static constexpr const char* _DefaultOutputDir = "$TMP/GEOM/$GEOM/$ExecutableName" ;
0043 
0044 public:
0045     static char* DefaultOutputDir();
0046     static std::string DefaultOutputName(const char* stem, int index, const char* ext);
0047     static const char* DefaultOutputPath(const char* stem, int index, const char* ext, bool unique);
0048 
0049 
0050 private:
0051     static char* ResolveToken(const char* token);
0052     static char* _ResolveToken(const char* token);
0053     static bool  IsTokenWithFallback(const char* token);
0054     static bool  IsToken(const char* token);
0055     static char* _ResolveTokenWithFallback(const char* token);
0056 
0057     template<typename ... Args>
0058     static std::string _Resolve(Args ... args );
0059 
0060 public:
0061     template<typename ... Args>
0062     static const char* Resolve(Args ... args );
0063 
0064 
0065     static bool LooksUnresolved(  const char* path , const char* _path );
0066     static bool LooksUnresolved0( const char* path , const char* _path );
0067     static const char* ResolveTopLevel( const char* spec );
0068 
0069 
0070     static bool StartsWith( const char* s, const char* q);
0071     static bool EndsWith( const char* path, const char* q);
0072     static int SplitExt0(std::string& dir, std::string& stem, std::string& ext, const char* path );
0073     static int SplitExt(std::string& dir, std::string& stem, std::string& ext, const char* path );
0074 
0075 private:
0076     template<typename ... Args>
0077     static std::string _Join( Args ... args_  );
0078 
0079     template<typename ... Args>
0080     static std::string _Check( char method, Args ... args_  );
0081 
0082     template<typename ... Args>
0083     static std::string _Name( Args ... args_  );
0084 
0085 public:
0086     template<typename ... Args>
0087     static const char* Join( Args ... args );
0088 
0089     template<typename ... Args>
0090     static const char* Name( Args ... args );
0091 
0092     template<typename ... Args>
0093     static char* Name_( Args ... args );
0094 
0095 
0096 
0097 
0098     template<typename ... Args>
0099     static bool Exists( Args ... args );
0100 
0101 
0102     static bool LooksLikePath(const char* arg);
0103     static const char* Dirname(const char* path);
0104     static const char* Basename(const char* path);
0105 
0106     static int Remove(const char* path_);
0107 
0108     static const char* SearchDirUpTreeWithFile( const char* startdir, const char* relf );
0109 
0110     static bool Read( std::string& str , const char* path );
0111     static bool Read( std::vector<char>&, const char* path );
0112 
0113 
0114     static bool Write(const char* txt, const char* base, const char* name );
0115     static bool Write(const char* txt, const char* path );
0116 private:
0117     static bool Write_( const char* str , const char* path );
0118 public:
0119     static void MakeDirsForFile(const char* path);
0120 
0121     static long Filesize(const char* dir, const char* name);
0122     static long Filesize(const char* path);
0123 
0124     static char* CWD();
0125 
0126     // WIP: replacing SOpticksResource
0127     static const char* GEOM(const char* _geom=nullptr);
0128     static const char* GEOM_Aux(const char* geom, const char* aux);
0129     static const char* GEOMSub(const char* _geom=nullptr);
0130     static const char* GEOMWrap(const char* _geom=nullptr);
0131     static const char* GEOMList(const char* _geom=nullptr);
0132 
0133 
0134     static char* CFBaseFromGEOM(const char* _geom=nullptr);
0135     static bool  has_CFBaseFromGEOM(const char* _geom=nullptr);
0136 
0137     static char* GDMLPathFromGEOM(const char* _geom=nullptr);
0138 
0139     static bool is_readable(const char* base, const char* name);
0140     static bool is_readable(const char* path );
0141 
0142     static int64_t last_write_time(const char* path, bool dump=false);
0143 
0144 
0145 };
0146 
0147 
0148 /**
0149 spath::_ResolvePath
0150 ----------------------
0151 
0152 This works with multiple tokens, eg::
0153 
0154     $HOME/.opticks/GEOM/$GEOM/CSGFoundry/meshname.txt
0155 
0156 But not yet with fallback paths, see _ResolvePathGeneralized
0157 
0158 **/
0159 
0160 inline std::string spath::_ResolvePath(const char* spec_)
0161 {
0162     if(spec_ == nullptr) return "" ;
0163     char* spec = strdup(spec_);
0164 
0165     std::stringstream ss ;
0166     int speclen = int(strlen(spec)) ;
0167     char* end = strchr(spec, '\0' );
0168     int i = 0 ;
0169 
0170     if(VERBOSE) std::cout << " spec " << spec << " speclen " << speclen << std::endl ;
0171 
0172     while( i < speclen )
0173     {
0174         if(VERBOSE) std::cout << " i " << i << " spec[i] " << spec[i] << std::endl ;
0175         if( spec[i] == '$' )
0176         {
0177             char* p = spec + i ;
0178             char* sep = strchr( p, '/' ) ; // first slash after $ : HMM too simple with fallback paths
0179             bool tok_plus =  sep && end && sep != end ;
0180             if(tok_plus) *sep = '\0' ;           // replace slash with null termination
0181             char* val = ResolveToken(p+1) ;  // skip '$'
0182             int toklen = int(strlen(p)) ;  // strlen("TOKEN")  no need for +1 as already at '$'
0183             if(VERBOSE) std::cout << " toklen " << toklen << std::endl ;
0184             if(val == nullptr)
0185             {
0186                 std::cerr
0187                     << "spath::_ResolvePath token ["
0188                     << p+1
0189                     << "] does not resolve "
0190                     << std::endl
0191                     ;
0192                 ss << "UNRESOLVED_TOKEN_" << (p+1) ;
0193             }
0194             else
0195             {
0196                 ss << val ;
0197             }
0198             if(tok_plus) *sep = '/' ;  // put back the slash
0199             i += toklen ;              // skip over the token
0200         }
0201         else
0202         {
0203            ss << spec[i] ;
0204            i += 1 ;
0205         }
0206     }
0207     std::string str = ss.str();
0208     return str ;
0209 }
0210 
0211 /**
0212 spath::_ResolvePathGeneralized
0213 ---------------------------------
0214 
0215 Simple paths with tokens::
0216 
0217     $HOME/.opticks/GEOM/$GEOM/CSGFoundry/meshname.txt
0218 
0219 More generalized paths with curlies and fallback paths::
0220 
0221     ${GEOM}Hello
0222     ${RNGDir:-$HOME/.opticks/rngcache/RNG}
0223 
0224 **/
0225 
0226 inline std::string spath::_ResolvePathGeneralized(const char* spec_)
0227 {
0228     if(spec_ == nullptr) return "" ;
0229     char* spec = strdup(spec_);
0230 
0231     std::stringstream ss ;
0232     int speclen = int(strlen(spec)) ;
0233     int speclen1 = speclen - 1 ;
0234     char* end = strchr(spec, '\0' );
0235     int i = 0 ;
0236 
0237     while( i < speclen  )
0238     {
0239         char* p = spec + i ;
0240         if( i < speclen1 && *p == '$' && *(p+1) != '{' )
0241         {
0242             char* sep = strchr( p, '/' ) ;       // first slash after $ : HMM too simple with fallback paths
0243             bool tok_plus =  sep && end && sep != end ;
0244             if(tok_plus) *sep = '\0' ;           // replace slash with null termination
0245             char* val = ResolveToken(p+1) ;      // skip '$'
0246             int toklen = int(strlen(p)) ;        // strlen("TOKEN")  no need for +1 as already at '$'
0247             ss << ( val ? val : p+1 )  ;
0248             if(tok_plus) *sep = '/' ;            // put back the slash
0249             i += toklen ;                        // skip over the token
0250         }
0251         else if( i < speclen1 && *p == '$' && *(p+1) == '{' )
0252         {
0253             char* sep = strchr(p, '}' ) + 1 ;   // one char beyond the first } after the $
0254             char keep = *sep ;
0255             bool tok_plus = sep && end && sep != end ;
0256             if(tok_plus) *sep = '\0' ;           // replace one beyond char with null termination
0257             char* val = ResolveToken(p+1) ;      // skip '$'
0258             int toklen = int(strlen(p)) ;        // strlen("TOKEN")  no need for +1 as already at '$'
0259             ss << ( val ? val : p+1 )  ;
0260             if(tok_plus) *sep = keep ;            // put back the changed char (could be '\0' )
0261             i += toklen ;                        // skip over the token
0262         }
0263         else
0264         {
0265            ss << *p ;
0266            i += 1 ;
0267         }
0268     }
0269     std::string str = ss.str();
0270     return str ;
0271 }
0272 
0273 
0274 
0275 
0276 inline char* spath::ResolvePath(const char* spec_)
0277 {
0278     std::string path = _ResolvePath(spec_) ;
0279     return strdup(path.c_str()) ;
0280 }
0281 inline char* spath::ResolvePathGeneralized(const char* spec_)
0282 {
0283     std::string path = _ResolvePathGeneralized(spec_) ;
0284     return strdup(path.c_str()) ;
0285 }
0286 
0287 
0288 
0289 
0290 inline char* spath::DefaultTMP()
0291 {
0292     char* user = getenv("USER") ;
0293     std::stringstream ss ;
0294     ss << "/tmp/" << ( user ? user : "MISSING_USER" ) << "/" << "opticks" ;
0295     std::string str = ss.str();
0296     return strdup(str.c_str());
0297 }
0298 
0299 inline char* spath::DefaultOutputDir()
0300 {
0301     return (char*)_DefaultOutputDir ;
0302 }
0303 
0304 
0305 /**
0306 spath::DefaultOutputName
0307 ---------------------------
0308 
0309   +--------+-----------+
0310   | arg    |  example  |
0311   +========+===========+
0312   |  stem  |   hello_  |
0313   +--------+-----------+
0314   | index  |     0     |
0315   +--------+-----------+
0316   |  ext   |  .jpg     |
0317   +--------+-----------+
0318 
0319 Result : hello_00000.jpg
0320 
0321 **/
0322 
0323 
0324 
0325 inline std::string spath::DefaultOutputName(const char* _stem, int index, const char* ext)
0326 {
0327     std::string stem = sstamp::FormatTimeStem(_stem, 0, false) ;
0328     std::stringstream ss ;
0329     ss << stem << std::setfill('0') << std::setw(5) << index << ext ;
0330     std::string str = ss.str();
0331     return str ;
0332 }
0333 
0334 inline const char* spath::DefaultOutputPath(const char* stem, int index, const char* ext, bool unique)
0335 {
0336     const char* outfold = DefaultOutputDir();
0337     std::string outname = DefaultOutputName(stem, index, ext);
0338     const char* outpath = Resolve(outfold, outname );
0339     if(unique)
0340     {
0341         // increment until find non-existing path
0342         int offset = 0 ;
0343         while( Exists(outpath) && offset < 100 )
0344         {
0345             offset += 1 ;
0346             outname = DefaultOutputName(stem, index+offset, ext);
0347             outpath = Resolve( outfold, outname.c_str() );
0348         }
0349     }
0350     return outpath ;
0351 }
0352 
0353 
0354 
0355 inline char* spath::ResolveToken(const char* token)
0356 {
0357     bool is_twf = IsTokenWithFallback(token) ;
0358 
0359     char* result0 = is_twf
0360                  ?
0361                     _ResolveTokenWithFallback(token)
0362                  :
0363                     _ResolveToken(token)
0364                  ;
0365 
0366     bool is_still_token = IsToken(result0) && strcmp(token, result0) != 0 ;
0367     char* result1 = is_still_token ? ResolvePath(result0) : result0  ;
0368 
0369     if(DUMP) std::cout
0370         << "spath::ResolveToken.DUMP" << std::endl
0371         << " token  [" << ( token ? token : "-" ) << "]"
0372         << " is_twf " << ( is_twf ? "YES" : "NO " )
0373         << " result0 [" << ( result0 ? result0 : "-" ) << "]"
0374         << " result1 [" << ( result1 ? result1 : "-" ) << "]"
0375         << std::endl
0376         ;
0377 
0378     return result1 ;
0379 }
0380 
0381 /**
0382 spath::_ResolveToken
0383 ----------------------
0384 
0385 Resolution of the below tokens are special cased when there
0386 is no corresponding envvar.
0387 
0388 +-------------------+-------------------------------------+---------------------------------------------------------------+
0389 |  token            |  resolves as                        | note                                                          |
0390 +===================+=====================================+===============================================================+
0391 |  TMP              | /tmp/$USER/opticks                  | USER resolved in 2nd pass                                     |
0392 +-------------------+-------------------------------------+---------------------------------------------------------------+
0393 |  ExecutableName   | result of sproc::ExecutableName()   | "python.." can be replaced by value of OPTICKS_SCRIPT envvar  |
0394 +-------------------+-------------------------------------+---------------------------------------------------------------+
0395 |  DefaultOutputDir | result of spath::DefaultOutputDir() | $TMP/GEOM/$GEOM/$ExecutableName                               |
0396 +-------------------+-------------------------------------+---------------------------------------------------------------+
0397 |  CFBaseFromGEOM   | result of spath::CFBaseFromGEOM()   | depends on two envvars : GEOM and ${GEOM}_CFBaseFromGEOM      |
0398 +-------------------+-------------------------------------+---------------------------------------------------------------+
0399 
0400 **/
0401 
0402 
0403 inline char* spath::_ResolveToken(const char* token)
0404 {
0405     char* tok = strdup(token) ;
0406 
0407     if( tok && strlen(tok) > 0 && tok[0] == '$') tok += 1 ;  // advance past leading '$'
0408     if( tok && strlen(tok) > 0 && tok[0] == '{' && tok[strlen(tok)-1] == '}') // trim leading and trailing { }
0409     {
0410         tok += 1 ;
0411         tok[strlen(tok)-1] = '\0' ;
0412     }
0413 
0414     char* val = getenv(tok) ;
0415     if( val == nullptr && strcmp(tok, "TMP") == 0)              val = DefaultTMP() ;
0416     if( val == nullptr && strcmp(tok, "ExecutableName")   == 0) val = sproc::ExecutableName() ;
0417     if( val == nullptr && strcmp(tok, "DefaultOutputDir") == 0) val = DefaultOutputDir() ;
0418     if( val == nullptr && strcmp(tok, "CFBaseFromGEOM") == 0)   val = CFBaseFromGEOM() ;
0419     return val ;
0420 }
0421 
0422 /**
0423 spath::IsTokenWithFallback
0424 ---------------------------
0425 
0426 Bash style tokens with fallback::
0427 
0428    ${VERSION:-0}
0429    ${U4Debug_SaveDir:-$TMP}   # original
0430    {U4Debug_SaveDir:-$TMP}    # when arrives here from ResolveToken
0431 
0432 
0433 **/
0434 
0435 inline bool spath::IsTokenWithFallback(const char* token)
0436 {
0437     return token && strlen(token) > 0 && token[0] == '{' && token[strlen(token)-1] == '}' && strstr(token,":-") != nullptr  ;
0438 }
0439 inline bool spath::IsToken(const char* token)
0440 {
0441     return token && strlen(token) > 0 && strstr(token,"$") != nullptr  ;
0442 }
0443 
0444 
0445 /**
0446 spath::_ResolveTokenWithFallback
0447 ----------------------------------
0448 
0449 Currently only simple fallback token are supported::
0450 
0451     ${FIRST:-$SECOND}
0452 
0453 TODO handle::
0454 
0455     ${VERSION:-0}
0456 
0457 **/
0458 
0459 inline char* spath::_ResolveTokenWithFallback(const char* token_)
0460 {
0461     char* token = strdup(token_);
0462     if(token && strlen(token) > 0 && token[0] == '$') token += 1 ;
0463 
0464     bool is_twf = IsTokenWithFallback(token) ;
0465     if(!is_twf) std::cerr << "spath::_ResolveTokenWithFallback"
0466          << " ERROR NOT-IsTokenWithFallback "
0467          << " token_ [" << ( token_ ? token_ : "-" ) << "]"
0468          << " token [" << ( token ? token : "-" ) << "]"
0469          << std::endl
0470          ;
0471     assert(is_twf);
0472 
0473     token[strlen(token)-1] = '\0' ;  // overwrite the trailing '}'
0474 
0475     const char* delim = ":-" ;
0476     char* split = strstr(token, delim) ;
0477 
0478     bool dump = false ;
0479 
0480     if(dump) std::cout
0481        << "spath::ResolveTokenWithFallback"
0482        << std::endl
0483        << " token " << ( token ? token : "-" )
0484        << std::endl
0485        << " split " << ( split ? split : "-" )
0486        << std::endl
0487        ;
0488 
0489     assert( split );
0490     char* tok1 = split + strlen(delim)  ;
0491 
0492     split[0] = '\0' ;
0493     char* tok0 = token + 1 ;
0494 
0495     if(dump) std::cout
0496        << "spath::ResolveTokenWithFallback"
0497        << std::endl
0498        << " tok0 " << ( tok0 ? tok0 : "-" )
0499        << std::endl
0500        << " tok1 " << ( tok1 ? tok1 : "-" )
0501        << std::endl
0502        ;
0503 
0504     char* val = _ResolveToken(tok0) ;
0505     if(val == nullptr) val = ResolvePath(tok1) ;
0506     return val ;
0507 }
0508 
0509 
0510 
0511 
0512 template<typename ... Args>
0513 inline std::string spath::_Resolve( Args ... args  )  // static
0514 {
0515     std::string spec = _Join(std::forward<Args>(args)... );
0516     return _ResolvePath(spec.c_str());
0517 }
0518 
0519 template std::string spath::_Resolve( const char* );
0520 template std::string spath::_Resolve( const char*, const char* );
0521 template std::string spath::_Resolve( const char*, const char*, const char* );
0522 template std::string spath::_Resolve( const char*, const char*, const char*, const char* );
0523 
0524 
0525 /**
0526 spath::Resolve
0527 ----------------
0528 
0529 All provided path elements must be non-nullptr
0530 otherwise get an assert when trying to convert the
0531 nullptr into a std::string.
0532 
0533 Although this could be avoided by ignoring such
0534 elements it is safer to require everything defined.
0535 Safety is important as the returned paths can be used
0536 for directory deletions.
0537 
0538 **/
0539 
0540 template<typename ... Args>
0541 inline const char* spath::Resolve( Args ... args  )  // static
0542 {
0543     std::string spec = _Join(std::forward<Args>(args)... );
0544 
0545     //std::string path = _ResolvePath(spec.c_str());
0546     std::string path = _ResolvePathGeneralized(spec.c_str());
0547 
0548     return strdup(path.c_str()) ;
0549 }
0550 
0551 template const char* spath::Resolve( const char* );
0552 template const char* spath::Resolve( const char*, const char* );
0553 template const char* spath::Resolve( const char*, const char*, const char* );
0554 template const char* spath::Resolve( const char*, const char*, const char*, const char* );
0555 
0556 
0557 
0558 /**
0559 spath::LooksUnresolved
0560 ------------------------
0561 
0562 An example of a path that starts with a TOKEN
0563 and the unresolved version of that::
0564 
0565     $TOKEN/rest/of/the/path
0566     TOKEN/rest/of/the/path
0567 
0568 Another example where the first path is a folder and the
0569 second an absolute path::
0570 
0571     $TOKEN/rest/of/the/path
0572     TOKEN/rest/of/the/path/record.npy
0573 
0574 **/
0575 
0576 inline bool spath::LooksUnresolved( const char* path , const char* _path )
0577 {
0578     bool _path_starts_with_dollar = _path && strlen(_path) > 1 && _path[0] == '$'  ;
0579     if(!_path_starts_with_dollar) return false ;
0580     return StartsWith( path, _path + 1 );
0581 }
0582 
0583 
0584 inline bool spath::LooksUnresolved0( const char* path , const char* _path )
0585 {
0586     bool _path_starts_with_dollar = _path && strlen(_path) > 1 && _path[0] == '$'  ;
0587     bool path_is_same_without_dollar = path && strlen(path) > 1 && strcmp(_path+1, path) == 0  ;
0588     return _path_starts_with_dollar && path_is_same_without_dollar ;
0589 }
0590 
0591 
0592 
0593 /**
0594 spath::ResolveTopLevel
0595 -----------------------
0596 
0597 spath::ResolveTopLevel checks the path returned by spath::Resolve for
0598 tokens that failed to resolve and returns nullptr and emits error messages
0599 in that case. This is typically appropriate for resolution of top level
0600 directories that must be resolved.
0601 
0602 **/
0603 
0604 inline const char* spath::ResolveTopLevel( const char* spec )
0605 {
0606     const char* path = spath::Resolve(spec) ;
0607     bool path_unresolved = spath::LooksUnresolved(path, spec);
0608     if(path_unresolved)
0609     {
0610         std::cout
0611             << "spath::ResolveTopLevel"
0612             << " ABORT as missing envvars path, see for example spath::CFBaseFromGEOM \n"
0613             << " spec [" << ( spec ? spec : "-" ) << "]\n"
0614             << " path [" << ( path ? path : "-" ) << "]\n"
0615             ;
0616     }
0617     return path_unresolved ? nullptr : path ;
0618 }
0619 
0620 
0621 
0622 
0623 
0624 /**
0625 spath::StartsWith
0626 --------------------
0627 
0628 Returns true when the test string *s* starts with the
0629 same chars as the query string *q*, eg::
0630 
0631     s : abcdefg
0632     q : abcd
0633 
0634 **/
0635 
0636 
0637 inline bool spath::StartsWith( const char* s, const char* q)
0638 {
0639     return s && q && strlen(q) <= strlen(s) && strncmp(s, q, strlen(q)) == 0 ;
0640 }
0641 
0642 
0643 
0644 
0645 inline bool spath::EndsWith( const char* path, const char* q)
0646 {
0647     const char* s = Resolve(path);
0648     int pos = strlen(s) - strlen(q) ;
0649     return pos > 0 && strncmp(s + pos, q, strlen(q)) == 0 ;
0650 }
0651 
0652 inline int spath::SplitExt0(std::string& dir, std::string& stem, std::string& ext, const char* _path )
0653 {
0654     std::string path = _path ;
0655     std::size_t pos0 = path.find_last_of("/");
0656     std::string name = pos0 == std::string::npos ? path : path.substr(pos0+1) ;
0657     dir = pos0 == std::string::npos ? "" : path.substr(0, pos0) ;
0658 
0659     std::size_t pos1 = name.find_last_of(".");
0660     ext = pos1 == std::string::npos ? "" : name.substr(pos1) ;
0661     stem = pos1 == std::string::npos ? name : name.substr(0, pos1) ;
0662 
0663     bool ok = strlen(dir.c_str())>0 && strlen(stem.c_str()) > 0 && strlen(ext.c_str()) > 0 ;
0664     return ok ? 0 : 1 ;
0665 }
0666 
0667 
0668 
0669 /**
0670 spath::SplitExt
0671 ----------------
0672 
0673 Currently this uses std::filesystem which requires c++17 (as opposed to c++11)
0674 
0675 ::
0676 
0677     TEST=SplitExt ~/o/sysrap/tests/spath_test.sh
0678 
0679 **/
0680 
0681 #if __cplusplus < 201703
0682 inline int spath::SplitExt(std::string& dir, std::string& stem, std::string& ext, const char* _path )
0683 {
0684     return SplitExt0(dir, stem, ext, _path );
0685 }
0686 #else
0687 #include <filesystem>
0688 inline int spath::SplitExt(std::string& dir, std::string& stem, std::string& ext, const char* _path )
0689 {
0690     std::filesystem::path path(_path);
0691     dir = path.parent_path().string() ;
0692     stem = path.stem().string() ;
0693     ext = path.extension().string() ;
0694     bool ok = strlen(dir.c_str())>0 && strlen(stem.c_str()) > 0 && strlen(ext.c_str()) > 0 ;
0695     return ok ? 0 : 1 ;
0696 }
0697 #endif
0698 
0699 
0700 
0701 
0702 template<typename ... Args>
0703 inline std::string spath::_Join( Args ... args_  )  // static
0704 {
0705     std::vector<std::string> args = {args_...};
0706     std::vector<std::string> elem ;
0707 
0708     for(unsigned i=0 ; i < args.size() ; i++)
0709     {
0710         const std::string& arg = args[i] ;
0711         if(!arg.empty()) elem.push_back(arg);
0712     }
0713 
0714     unsigned num_elem = elem.size() ;
0715     std::stringstream ss ;
0716     for(unsigned i=0 ; i < num_elem ; i++)
0717     {
0718         const std::string& ele = elem[i] ;
0719         ss << ele << ( i < num_elem - 1 ? "/" : "" ) ;
0720     }
0721     std::string s = ss.str();
0722     return s ;
0723 }
0724 
0725 template std::string spath::_Join( const char* );
0726 template std::string spath::_Join( const char*, const char* );
0727 template std::string spath::_Join( const char*, const char*, const char* );
0728 template std::string spath::_Join( const char*, const char*, const char*, const char* );
0729 
0730 template<typename ... Args>
0731 inline const char* spath::Join( Args ... args )  // static
0732 {
0733     std::string s = _Join(std::forward<Args>(args)...)  ;
0734     return strdup(s.c_str()) ;
0735 }
0736 
0737 template const char* spath::Join( const char* );
0738 template const char* spath::Join( const char*, const char* );
0739 template const char* spath::Join( const char*, const char*, const char* );
0740 template const char* spath::Join( const char*, const char*, const char*, const char* );
0741 
0742 
0743 
0744 
0745 template<typename ... Args>
0746 inline std::string spath::_Check( char method, Args ... args_  )  // static
0747 {
0748     std::vector<std::string> args = {args_...};
0749     std::stringstream ss ;
0750     ss << method << ":" ;
0751 
0752     for(int i=0 ; i < int(args.size()) ; i++)
0753     {
0754         const std::string& arg = args[i] ;
0755         if(arg.empty()) continue ;
0756         ss << arg << " " ;
0757     }
0758     std::string str = ss.str();
0759     return str ;
0760 }
0761 
0762 
0763 template std::string spath::_Check( char, const char* );
0764 template std::string spath::_Check( char, const char*, const char* );
0765 template std::string spath::_Check( char, const char*, const char*, const char* );
0766 template std::string spath::_Check( char, const char*, const char*, const char*, const char* );
0767 
0768 
0769 
0770 
0771 
0772 template<typename ... Args>
0773 inline std::string spath::_Name( Args ... args_  )  // static
0774 {
0775     std::vector<std::string> args = {args_...};
0776     std::vector<std::string> elem ;
0777 
0778     for(unsigned i=0 ; i < args.size() ; i++)
0779     {
0780         const std::string& arg = args[i] ;
0781         if(!arg.empty()) elem.push_back(arg);
0782     }
0783 
0784     const char* delim = "" ;
0785 
0786     unsigned num_elem = elem.size() ;
0787     std::stringstream ss ;
0788     for(unsigned i=0 ; i < num_elem ; i++)
0789     {
0790         const std::string& ele = elem[i] ;
0791         ss << ele << ( i < num_elem - 1 ? delim : "" ) ;
0792     }
0793     std::string s = ss.str();
0794     return s ;
0795 }
0796 
0797 template std::string spath::_Name( const char* );
0798 template std::string spath::_Name( const char*, const char* );
0799 template std::string spath::_Name( const char*, const char*, const char* );
0800 template std::string spath::_Name( const char*, const char*, const char*, const char* );
0801 
0802 template std::string spath::_Name( char* );
0803 template std::string spath::_Name( char*, char* );
0804 template std::string spath::_Name( char*, char*, char* );
0805 template std::string spath::_Name( char*, char*, char*, char* );
0806 
0807 
0808 
0809 
0810 template<typename ... Args>
0811 inline const char* spath::Name( Args ... args )  // static
0812 {
0813     std::string s = _Name(std::forward<Args>(args)...)  ;
0814     return strdup(s.c_str()) ;
0815 }
0816 
0817 template const char* spath::Name( const char* );
0818 template const char* spath::Name( const char*, const char* );
0819 template const char* spath::Name( const char*, const char*, const char* );
0820 template const char* spath::Name( const char*, const char*, const char*, const char* );
0821 
0822 
0823 
0824 template<typename ... Args>
0825 inline char* spath::Name_( Args ... args )  // static
0826 {
0827     std::string s = _Name(std::forward<Args>(args)...)  ;
0828     return strdup(s.c_str()) ;
0829 }
0830 
0831 template char* spath::Name_( char* );
0832 template char* spath::Name_( char*, char* );
0833 template char* spath::Name_( char*, char*, char* );
0834 template char* spath::Name_( char*, char*, char*, char* );
0835 
0836 
0837 
0838 
0839 
0840 
0841 
0842 
0843 
0844 
0845 
0846 
0847 
0848 
0849 
0850 
0851 
0852 
0853 
0854 template<typename ... Args>
0855 inline bool spath::Exists(Args ... args)
0856 {
0857     std::string path = _Resolve(std::forward<Args>(args)...) ;
0858     std::ifstream fp(path.c_str(), std::ios::in|std::ios::binary);
0859     return fp.fail() ? false : true ;
0860 }
0861 
0862 template bool spath::Exists( const char* );
0863 template bool spath::Exists( const char*, const char* );
0864 template bool spath::Exists( const char*, const char*, const char* );
0865 template bool spath::Exists( const char*, const char*, const char*, const char* );
0866 
0867 
0868 
0869 
0870 
0871 
0872 
0873 inline bool spath::LooksLikePath(const char* arg)
0874 {
0875     if(!arg) return false ;
0876     if(strlen(arg) < 2) return false ;
0877     return arg[0] == '/' || arg[0] == '$' ;
0878 }
0879 
0880 
0881 inline const char* spath::Dirname(const char* path)
0882 {
0883     std::string p = path ;
0884     std::size_t pos = p.find_last_of("/");
0885     std::string fold = pos == std::string::npos ? "" : p.substr(0, pos) ;  // not pos-1 as counting from zero
0886     return strdup( fold.c_str() ) ;
0887 }
0888 
0889 inline const char* spath::Basename(const char* path)
0890 {
0891     std::string p = path ;
0892     std::size_t pos = p.find_last_of("/");
0893     std::string base = pos == std::string::npos ? p : p.substr(pos+1) ;
0894     return strdup( base.c_str() ) ;
0895 }
0896 
0897 
0898 inline int spath::Remove(const char* path_)
0899 {
0900     const char* path = spath::Resolve(path_);
0901     assert( strlen(path) > 2 );
0902     return remove(path);
0903 }
0904 
0905 
0906 /**
0907 spath::SearchDirUpTreeWithFile
0908 -------------------------------
0909 
0910 Search up the directory tree starting from *startdir* for
0911 a directory that contains an existing relative filepath *relf*
0912 
0913 **/
0914 
0915 inline const char* spath::SearchDirUpTreeWithFile( const char* startdir, const char* relf )
0916 {
0917     if(startdir == nullptr || relf == nullptr) return nullptr ;
0918     char* dir = strdup(startdir) ;
0919     while(dir && strlen(dir) > 1)
0920     {
0921         if(spath::Exists(dir, relf)) break ;
0922         char* last = strrchr(dir, '/');
0923         *last = '\0' ;  // move the null termination inwards from right, going up directory by directory
0924     }
0925     return ( dir && strlen(dir) > 1 ) ? strdup(dir) : nullptr ;
0926 }
0927 
0928 
0929 inline bool spath::Read( std::string& str , const char* path_ )  // static
0930 {
0931     const char* path = Resolve(path_);
0932     std::ifstream fp(path);
0933     bool good = fp.good() ;
0934     if( good )
0935     {
0936         std::stringstream content ;
0937         content << fp.rdbuf();
0938         str = content.str();
0939     }
0940     return good ;
0941 }
0942 
0943 inline bool spath::Read( std::vector<char>& data, const char* path_ ) // static
0944 {
0945     const char* path = Resolve(path_);
0946     std::ifstream fp(path, std::ios::binary);
0947     bool good = fp.good() ;
0948     if( good )
0949     {
0950         fp.seekg(0, fp.end);
0951         std::streamsize size = fp.tellg();
0952         fp.seekg(0, fp.beg);
0953 
0954         if(size <= 0 ) std::cerr << "spath::Read ERROR stream size " << size << " for path " << ( path ? path : "-" )  << "\n" ;
0955         if(size <= 0) return false ;
0956 
0957         data.resize(size);
0958         fp.read(data.data(), size );
0959         bool fail = fp.fail();
0960 
0961         if(fail) std::cerr << "spath::Read ERROR failed to read size " << size << " bytes from path " << ( path ? path : "-" ) << "\n" ;
0962         if(fail) return false ;
0963     }
0964     return good ;
0965 }
0966 
0967 
0968 inline bool spath::Write( const char* str , const char* base, const char* name )  // static
0969 {
0970     const char* path = base == nullptr ? Resolve(name) : Resolve(base, name);
0971     return Write_(str, path );
0972 }
0973 inline bool spath::Write( const char* str , const char* path_ )  // static
0974 {
0975     const char* path = Resolve(path_);
0976     return Write_(str, path );
0977 }
0978 inline bool spath::Write_( const char* str , const char* path )  // static
0979 {
0980     sdirectory::MakeDirsForFile(path) ;
0981     std::ofstream fp(path);
0982     bool good = fp.good() ;
0983     if( good ) fp << str ;
0984     return good ;
0985 }
0986 
0987 inline void spath::MakeDirsForFile(const char* path)
0988 {
0989     sdirectory::MakeDirsForFile(path) ;
0990 }
0991 
0992 
0993 inline long spath::Filesize(const char* dir, const char* name)
0994 {
0995     const char* path = Resolve(dir, name);
0996     return Filesize(path);
0997 }
0998 
0999 inline long spath::Filesize(const char* path)
1000 {
1001     FILE *fp = fopen(path,"rb");
1002 
1003     bool failed = fp == nullptr ;
1004     if(failed) std::cerr << "spath::Filesize unable to open file [" << path << "]\n" ;
1005     assert(!failed);
1006 
1007     fseek(fp, 0L, SEEK_END);
1008     long file_size = ftell(fp);
1009     rewind(fp);
1010     fclose(fp);
1011 
1012     return file_size ;
1013 }
1014 
1015 inline char* spath::CWD()  // static
1016 {
1017     char path[256] ;
1018     char* ret = getcwd(path, 256);
1019     return ret == nullptr ? nullptr : strdup(path);
1020 }
1021 
1022 
1023 
1024 
1025 
1026 
1027 
1028 
1029 
1030 
1031 
1032 
1033 
1034 
1035 inline const char* spath::GEOM(const char* _geom){  return _geom ? _geom : getenv("GEOM") ; }
1036 inline const char* spath::GEOM_Aux(const char* _geom, const char* aux)
1037 {
1038     const char* geom = _geom ? _geom : GEOM() ;
1039     const char* name = spath::Name(geom, aux) ;
1040     return geom == nullptr ? nullptr : getenv(name) ;
1041 }
1042 
1043 inline const char* spath::GEOMSub( const char* _geom){  return GEOM_Aux( _geom, "_GEOMSub"  ); }
1044 inline const char* spath::GEOMWrap(const char* _geom){  return GEOM_Aux( _geom, "_GEOMWrap" ); }
1045 inline const char* spath::GEOMList(const char* _geom){  return GEOM_Aux( _geom, "_GEOMList" ); }
1046 
1047 
1048 
1049 /**
1050 spath::CFBaseFromGEOM
1051 ----------------------
1052 
1053 This relies on a geom argument OR GEOM envvar plus
1054 a second envvar with key ${geom}_CFBaseFromGEOM
1055 that points to the CFBase directory. Specifically:
1056 
1057 1. get "geom" from argument if provided or GEOM envvar if not
1058 2. form ekey "${geom}_CFBaseFromGEOM"
1059 3. obtain path (of CFBase directory) from getenv(ekey)
1060 
1061 This functionality was formerly provided by SOpticksResource
1062 **/
1063 
1064 inline char* spath::CFBaseFromGEOM(const char* _geom)
1065 {
1066     const char* geom = GEOM(_geom);
1067     char* name = spath::Name_(geom ? geom : "MISSING_GEOM", "_CFBaseFromGEOM") ;
1068     char* path = geom == nullptr ? nullptr : getenv(name) ;
1069 
1070     if(VERBOSE) std::cout
1071         << "spath::CFBaseFromGEOM"
1072         << " geom " << ( geom ? geom : "-" )
1073         << " name " << ( name ? name : "-" )
1074         << " path " << ( path ? path : "-" )
1075         << "\n"
1076         ;
1077     return path  ;
1078 }
1079 
1080 inline bool spath::has_CFBaseFromGEOM(const char* _geom)
1081 {
1082     char* cfb = CFBaseFromGEOM(_geom);
1083     return cfb != nullptr ;
1084 }
1085 
1086 
1087 inline char* spath::GDMLPathFromGEOM(const char* _geom)
1088 {
1089     char* cfb = CFBaseFromGEOM(_geom);
1090     return cfb ? spath::Name_(cfb, "/origin.gdml") : nullptr ;
1091 }
1092 
1093 
1094 inline bool spath::is_readable(const char* base, const char* name)  // static
1095 {
1096     std::stringstream ss ;
1097     if( base && name )
1098     {
1099         ss << base << "/" << name ;
1100     }
1101     else if( name )
1102     {
1103         ss << name ;
1104     }
1105     std::string s = ss.str();
1106     return is_readable(s.c_str());
1107 }
1108 inline bool spath::is_readable(const char* path_)  // static
1109 {
1110     const char* path = path_ ? spath::Resolve(path_) : nullptr ;
1111     if( path == nullptr ) return false ;
1112     std::ifstream fp(path, std::ios::in|std::ios::binary);
1113     bool readable = !fp.fail();
1114     fp.close();
1115     return readable ;
1116 }
1117 
1118 
1119 
1120 
1121 /**
1122 spath::last_write_time
1123 -----------------------
1124 
1125 Returns microseconds since the unix epoch of the mtime of the file,
1126 or zero if it does not exist.
1127 
1128 https://www.cppstories.com/2024/file-time-cpp20/
1129 
1130 **/
1131 
1132 inline int64_t spath::last_write_time(const char* path, bool dump)
1133 {
1134     int64_t lwt = 0 ;
1135 
1136 #ifdef WITH_FILESYSTEM_NOT_WORKING
1137     std::filesystem::path p(path);
1138     std::filesystem::file_time_type ftt = std::filesystem::last_write_time(p);
1139     //std::time_t cft = std::chrono::system_clock::to_time_t(ftt);
1140     //std::cout << "  cft:  " << std::ctime(&cft) << '\n';
1141 
1142     //std::time_t tt = decltype(ftt)::clock::to_time_t(ftt);
1143     //  ‘to_time_t’ is not a member of ‘std::chrono::time_point<std::filesystem::__file_clock>::clock’
1144 
1145     using namespace std::chrono;
1146     auto sys_now = system_clock::now();
1147     auto fil_now = decltype(ftt)::clock::now();
1148 
1149     int64_t t_sys = duration_cast<microseconds>(sys_now.time_since_epoch()).count() ;
1150     int64_t t_fil = duration_cast<microseconds>(fil_now.time_since_epoch()).count() ;
1151     int64_t t_ftt = duration_cast<microseconds>(ftt.time_since_epoch()).count() ;
1152 
1153     //const auto sct = std::chrono::clock_cast<std::chrono::system_clock>(ftt);
1154     //int64_t t = std::chrono::duration_cast<std::chrono::microseconds >(sct.time_since_epoch()).count() ;
1155 
1156     std::cout
1157         << " t_sys       [" << t_sys  << "]\n"
1158         << " t_fil       [" << t_fil << "]\n"
1159         << " t_ftt       [" << t_fil << "]\n"
1160         ;
1161 #else
1162 
1163     struct stat fs;
1164     int rc = stat(path, &fs) ;
1165     std::time_t mtime = rc == 0 ? fs.st_mtime : 0 ;
1166 
1167     if(dump)
1168     {
1169         char* str = std::asctime(std::localtime(&mtime));
1170         std::cout << "spath::last_write_time str [" << str << "]\n" ;
1171     }
1172 
1173     using namespace std::chrono;
1174     const auto from = system_clock::from_time_t(mtime);
1175     lwt = duration_cast<microseconds>(from.time_since_epoch()).count();
1176 #endif
1177     return lwt ;
1178 }
1179 
1180 
1181 
1182