From 2a2cf23c473b4c05ff10dee2789233767e831b4b Mon Sep 17 00:00:00 2001 From: AJ Kelly Date: Mon, 2 Jul 2012 21:52:31 -0500 Subject: [PATCH] [RageUtil] Add detection of .hg to StripCvsAndSvn. --- Docs/Changelog_sm5.txt | 1 + src/RageUtil.cpp | 4689 ++++++++++++++++++++-------------------- 2 files changed, 2347 insertions(+), 2343 deletions(-) diff --git a/Docs/Changelog_sm5.txt b/Docs/Changelog_sm5.txt index 82013431f7..84b622c7b8 100644 --- a/Docs/Changelog_sm5.txt +++ b/Docs/Changelog_sm5.txt @@ -11,6 +11,7 @@ StepMania 5.0 ???? ?? | 20120??? 2012/07/02 ---------- * [ScreenHowToPlay] Run InitCommand on Characters if enabled. [AJ] +* [RageUtil] Add detection of ".hg" to StripCvsAndSvn. [AJ] 2012/06/27 ---------- diff --git a/src/RageUtil.cpp b/src/RageUtil.cpp index 43baf1756a..8b3273d575 100644 --- a/src/RageUtil.cpp +++ b/src/RageUtil.cpp @@ -1,2343 +1,2346 @@ -#include "global.h" -#include "RageUtil.h" -#include "RageMath.h" -#include "RageLog.h" -#include "RageFile.h" -#include "Foreach.h" -#include "LocalizedString.h" -#include "LuaBinding.h" -#include "LuaManager.h" -#include - -#include -#include -#include -#include -#include -#include -#include - -bool HexToBinary(const RString&, RString&); -void utf8_sanitize(RString &); -void UnicodeUpperLower(wchar_t *, size_t, const unsigned char *); - -RandomGen g_RandomNumberGenerator; - -MersenneTwister::MersenneTwister( int iSeed ) : m_iNext(0) -{ - Reset( iSeed ); -} - -void MersenneTwister::Reset( int iSeed ) -{ - if( iSeed == 0 ) - iSeed = time(NULL); - - m_Values[0] = iSeed; - m_iNext = 0; - for( int i = 1; i < 624; ++i ) - m_Values[i] = ((69069 * m_Values[i-1]) + 1) & 0xFFFFFFFF; - - GenerateValues(); -} - -void MersenneTwister::GenerateValues() -{ - static const int mask[] = { 0, 0x9908B0DF }; - - for( int i = 0; i < 227; ++i ) - { - int iVal = (m_Values[i] & 0x80000000) | (m_Values[i+1] & 0x7FFFFFFF); - int iNext = (i + 397); - - m_Values[i] = m_Values[iNext]; - m_Values[i] ^= (iVal >> 1); - m_Values[i] ^= mask[iVal&1]; - } - - for( int i = 227; i < 623; ++i ) - { - int iVal = (m_Values[i] & 0x80000000) | (m_Values[i+1] & 0x7FFFFFFF); - int iNext = (i + 397) - 624; - - m_Values[i] = m_Values[iNext]; - m_Values[i] ^= (iVal >> 1); - m_Values[i] ^= mask[iVal&1]; - } - - int iVal = (m_Values[623] & 0x80000000) + (m_Values[0] & 0x7FFFFFFF); - int iNext = (623 + 397) - 624; - m_Values[623] = m_Values[iNext] ^ (iVal>>1); - m_Values[623] ^= mask[iVal&1]; -} - -int MersenneTwister::Temper( int iVal ) -{ - iVal ^= (iVal >> 11); - iVal ^= (iVal << 7) & 0x9D2C5680; - iVal ^= (iVal << 15) & 0xEFC60000; - iVal ^= (iVal >> 18); - return iVal; -} - -int MersenneTwister::operator()() -{ - if( m_iNext == 624 ) - { - m_iNext = 0; - GenerateValues(); - } - - return Temper( m_Values[m_iNext++] ); -} - -/* Extend MersenneTwister into Lua space. This is intended to replace - * math.randomseed and math.random, so we conform to their behavior. */ - -namespace -{ - MersenneTwister g_LuaPRNG; - - /* To map from [0..2^32-1] to [0..1), we divide by 2^32. */ - const double DIVISOR = pow( double(2), double(32) ); - - static int Seed( lua_State *L ) - { - g_LuaPRNG.Reset( IArg(1) ); - return 0; - } - - static int Random( lua_State *L ) - { - switch( lua_gettop(L) ) - { - /* [0..1) */ - case 0: - { - double r = double(g_LuaPRNG()) / DIVISOR; - lua_pushnumber( L, r ); - return 1; - } - - /* [1..u] */ - case 1: - { - int upper = IArg(1); - luaL_argcheck( L, 1 <= upper, 1, "interval is empty" ); - lua_pushnumber( L, g_LuaPRNG(upper) + 1 ); - return 1; - } - /* [l..u] */ - case 2: - { - int lower = IArg(1); - int upper = IArg(2); - luaL_argcheck( L, lower < upper, 2, "interval is empty" ); - lua_pushnumber( L, (int(g_LuaPRNG()) % (upper-lower+1)) + lower ); - return 1; - } - - /* wrong amount of arguments */ - default: - { - return luaL_error( L, "wrong number of arguments" ); - } - } - } - - const luaL_Reg MersenneTwisterTable[] = - { - LIST_METHOD( Seed ), - LIST_METHOD( Random ), - { NULL, NULL } - }; -} - -LUA_REGISTER_NAMESPACE( MersenneTwister ); - -void fapproach( float& val, float other_val, float to_move ) -{ - ASSERT_M( to_move >= 0, ssprintf("to_move: %f < 0", to_move) ); - if( val == other_val ) - return; - float fDelta = other_val - val; - float fSign = fDelta / fabsf( fDelta ); - float fToMove = fSign*to_move; - if( fabsf(fToMove) > fabsf(fDelta) ) - fToMove = fDelta; // snap - val += fToMove; -} - -/* Return a positive x mod y. */ -float fmodfp(float x, float y) -{ - x = fmodf(x, y); /* x is [-y,y] */ - x += y; /* x is [0,y*2] */ - x = fmodf(x, y); /* x is [0,y] */ - return x; -} - -int power_of_two( int iInput ) -{ - int iValue = 1; - - while( iValue < iInput ) - iValue <<= 1; - - return iValue; -} - -bool IsAnInt( const RString &s ) -{ - if( !s.size() ) - return false; - - for( size_t i=0; i < s.size(); ++i ) - if( s[i] < '0' || s[i] > '9' ) - return false; - - return true; -} - -bool IsHexVal( const RString &s ) -{ - if( !s.size() ) - return false; - - for( size_t i=0; i < s.size(); ++i ) - if( !(s[i] >= '0' && s[i] <= '9') && - !(toupper(s[i]) >= 'A' && toupper(s[i]) <= 'F')) - return false; - - return true; -} - -RString BinaryToHex( const void *pData_, int iNumBytes ) -{ - const unsigned char *pData = (const unsigned char *) pData_; - RString s; - for( int i=0; i arrayBits; - split( sHHMMSS, ":", arrayBits, false ); - - while( arrayBits.size() < 3 ) - arrayBits.insert(arrayBits.begin(), "0" ); // pad missing bits - - float fSeconds = 0; - fSeconds += StringToInt( arrayBits[0] ) * 60 * 60; - fSeconds += StringToInt( arrayBits[1] ) * 60; - fSeconds += StringToFloat( arrayBits[2] ); - - return fSeconds; -} - -RString SecondsToHHMMSS( float fSecs ) -{ - const int iMinsDisplay = (int)fSecs/60; - const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; - RString sReturn = ssprintf( "%02d:%02d:%02d", iMinsDisplay/60, iMinsDisplay%60, iSecsDisplay ); - return sReturn; -} - -RString SecondsToMMSSMsMs( float fSecs ) -{ - const int iMinsDisplay = (int)fSecs/60; - const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; - const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 ); - RString sReturn = ssprintf( "%02d:%02d.%02d", iMinsDisplay, iSecsDisplay, min(99,iLeftoverDisplay) ); - return sReturn; -} - -RString SecondsToMSSMsMs( float fSecs ) -{ - const int iMinsDisplay = (int)fSecs/60; - const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; - const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 ); - RString sReturn = ssprintf( "%01d:%02d.%02d", iMinsDisplay, iSecsDisplay, min(99,iLeftoverDisplay) ); - return sReturn; -} - -RString SecondsToMMSSMsMsMs( float fSecs ) -{ - const int iMinsDisplay = (int)fSecs/60; - const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; - const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 1000 ); - RString sReturn = ssprintf( "%02d:%02d.%03d", iMinsDisplay, iSecsDisplay, min(999,iLeftoverDisplay) ); - return sReturn; -} - -RString SecondsToMSS( float fSecs ) -{ - const int iMinsDisplay = (int)fSecs/60; - const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; - RString sReturn = ssprintf( "%01d:%02d", iMinsDisplay, iSecsDisplay); - return sReturn; -} - -RString SecondsToMMSS( float fSecs ) -{ - const int iMinsDisplay = (int)fSecs/60; - const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; - RString sReturn = ssprintf( "%02d:%02d", iMinsDisplay, iSecsDisplay); - return sReturn; -} - -RString PrettyPercent( float fNumerator, float fDenominator) -{ - return ssprintf("%0.2f%%",fNumerator/fDenominator*100); -} - -RString Commify( int iNum ) -{ - RString sNum = ssprintf("%d",iNum); - return Commify( sNum ); -} - -RString Commify( RString sNum, RString sSeperator ) -{ - RString sReturn; - for( unsigned i=0; i= 3 && iPercent > 2 && sStr[iEnd-2] == 'l' && sStr[iEnd-1] == 'l' ) - { - sRet.append( sStr, iPercent, iEnd - iPercent - 2 ); // % - sRet.append( "I64" ); // %I64 - sRet.append( sStr, iEnd, 1 ); // %I64i - iOffset = iEnd + 1; - } - else - { - if( iEnd == sStr.npos ) - iEnd = sStr.size() - 1; - sRet.append( sStr, iOffset, iEnd - iOffset + 1 ); - iOffset = iEnd + 1; - } - } - return sRet; -} -#else -RString ConvertI64FormatString( const RString &sStr ) { return sStr; } -#endif - -/* ISO-639-1 codes: http://www.loc.gov/standards/iso639-2/php/code_list.php - * native forms: http://people.w3.org/rishida/names/languages.html - * We don't use 3-letter codes, so we don't bother supporting them. */ -static const LanguageInfo g_langs[] = -{ - {"aa", "Afar"}, - {"ab", "Abkhazian"}, - {"af", "Afrikaans"}, - {"am", "Amharic"}, - {"ar", "Arabic"}, - {"as", "Assamese"}, - {"ay", "Aymara"}, - {"az", "Azerbaijani"}, - {"ba", "Bashkir"}, - {"be", "Byelorussian"}, - {"bg", "Bulgarian"}, - {"bh", "Bihari"}, - {"bi", "Bislama"}, - {"bn", "Bengali"}, - {"bo", "Tibetan"}, - {"br", "Breton"}, - {"ca", "Catalan"}, - {"co", "Corsican"}, - {"cs", "Czech"}, - {"cy", "Welsh"}, - {"da", "Danish"}, - {"de", "German"}, - {"dz", "Bhutani"}, - {"el", "Greek"}, - {"en", "English"}, - {"eo", "Esperanto"}, - {"es", "Spanish"}, - {"et", "Estonian"}, - {"eu", "Basque"}, - {"fa", "Persian"}, - {"fi", "Finnish"}, - {"fj", "Fiji"}, - {"fo", "Faeroese"}, - {"fr", "French"}, - {"fy", "Frisian"}, - {"ga", "Irish"}, - {"gd", "Gaelic"}, - {"gl", "Galician"}, - {"gn", "Guarani"}, - {"gu", "Gujarati"}, - {"ha", "Hausa"}, - {"he", "Hebrew"}, - {"hi", "Hindi"}, - {"hr", "Croatian"}, - {"hu", "Hungarian"}, - {"hy", "Armenian"}, - {"ia", "Interlingua"}, - {"id", "Indonesian"}, - {"ie", "Interlingue"}, - {"ik", "Inupiak"}, - {"in", "Indonesian"}, // compatibility - {"is", "Icelandic"}, - {"it", "Italian"}, - {"iw", "Hebrew"}, // compatibility - {"ja", "Japanese"}, - {"ji", "Yiddish"}, // compatibility - {"jw", "Javanese"}, - {"ka", "Georgian"}, - {"kk", "Kazakh"}, - {"kl", "Greenlandic"}, - {"km", "Cambodian"}, - {"kn", "Kannada"}, - {"ko", "Korean"}, - {"ks", "Kashmiri"}, - {"ku", "Kurdish"}, - {"ky", "Kirghiz"}, - {"la", "Latin"}, - {"ln", "Lingala"}, - {"lo", "Laothian"}, - {"lt", "Lithuanian"}, - {"lv", "Latvian"}, - {"mg", "Malagasy"}, - {"mi", "Maori"}, - {"mk", "Macedonian"}, - {"ml", "Malayalam"}, - {"mn", "Mongolian"}, - {"mo", "Moldavian"}, - {"mr", "Marathi"}, - {"ms", "Malay"}, - {"mt", "Maltese"}, - {"my", "Burmese"}, - {"na", "Nauru"}, - {"ne", "Nepali"}, - {"nl", "Dutch"}, - {"no", "Norwegian"}, - {"oc", "Occitan"}, - {"om", "Oromo"}, - {"or", "Oriya"}, - {"pa", "Punjabi"}, - {"pl", "Polish"}, - {"ps", "Pashto"}, - {"pt", "Portuguese"}, - {"qu", "Quechua"}, - {"rm", "Rhaeto-Romance"}, - {"rn", "Kirundi"}, - {"ro", "Romanian"}, - {"ru", "Russian"}, - {"rw", "Kinyarwanda"}, - {"sa", "Sanskrit"}, - {"sd", "Sindhi"}, - {"sg", "Sangro"}, - {"sh", "Serbo-Croatian"}, - {"si", "Singhalese"}, - {"sk", "Slovak"}, - {"sl", "Slovenian"}, - {"sm", "Samoan"}, - {"sn", "Shona"}, - {"so", "Somali"}, - {"sq", "Albanian"}, - {"sr", "Serbian"}, - {"ss", "Siswati"}, - {"st", "Sesotho"}, - {"su", "Sudanese"}, - {"sv", "Swedish"}, - {"sw", "Swahili"}, - {"ta", "Tamil"}, - {"te", "Tegulu"}, - {"tg", "Tajik"}, - {"th", "Thai"}, - {"ti", "Tigrinya"}, - {"tk", "Turkmen"}, - {"tl", "Tagalog"}, - {"tn", "Setswana"}, - {"to", "Tonga"}, - {"tr", "Turkish"}, - {"ts", "Tsonga"}, - {"tt", "Tatar"}, - {"tw", "Twi"}, - {"uk", "Ukrainian"}, - {"ur", "Urdu"}, - {"uz", "Uzbek"}, - {"vi", "Vietnamese"}, - {"vo", "Volapuk"}, - {"wo", "Wolof"}, - {"xh", "Xhosa"}, - {"yi", "Yiddish"}, - {"yo", "Yoruba"}, - {"zh", "Chinese"}, - {"zu", "Zulu"}, -}; - -void GetLanguageInfos( vector &vAddTo ) -{ - for( unsigned i=0; i &sSource) -{ - if( sSource.empty() ) - return RString(); - - RString sTmp; - - for( unsigned iNum = 0; iNum < sSource.size()-1; iNum++ ) - { - sTmp += sSource[iNum]; - sTmp += sDeliminator; - } - sTmp += sSource.back(); - return sTmp; -} - -RString join( const RString &sDelimitor, vector::const_iterator begin, vector::const_iterator end ) -{ - if( begin == end ) - return RString(); - - RString sRet; - while( begin != end ) - { - sRet += *begin; - ++begin; - if( begin != end ) - sRet += sDelimitor; - } - - return sRet; -} - -RString SmEscape( const RString &sUnescaped ) -{ - return SmEscape( sUnescaped.c_str(), sUnescaped.size() ); -} - -RString SmEscape( const char *cUnescaped, int len ) -{ - RString answer = ""; - for( int i = 0; i < len; ++i ) - { - // Other characters we could theoretically escape: - // NotesWriterSM.cpp used to claim ',' should be escaped, but there was no explanation why - // '#' is both a control character and a valid part of a parameter. The only way for there to be - // any confusion is in a misformatted .sm file, though, so it is unnecessary to escape it. - if( cUnescaped[i] == '/' && i + 1 < len && cUnescaped[i + 1] == '/' ) - { - answer += "\\/\\/"; - ++i; // increment here so we skip both //s - continue; - } - if( cUnescaped[i] == '\\' || cUnescaped[i] == ':' || cUnescaped[i] == ';' ) - answer += "\\"; - answer += cUnescaped[i]; - } - return answer; -} - -RString DwiEscape( const RString &sUnescaped ) -{ - return DwiEscape( sUnescaped.c_str(), sUnescaped.size() ); -} - -RString DwiEscape( const char *cUnescaped, int len ) -{ - RString answer = ""; - for( int i = 0; i < len; ++i ) - { - switch( cUnescaped[i] ) - { - // TODO: Which of these characters actually affect DWI? - case '\\': - case ':': - case ';': answer += '|'; break; - case '[': answer += '('; break; - case ']': answer += ')'; break; - default: answer += cUnescaped[i]; - } - } - return answer; -} - -template -static int DelimitorLength( const S &Delimitor ) -{ - return Delimitor.size(); -} - -static int DelimitorLength( char Delimitor ) -{ - return 1; -} - -static int DelimitorLength( wchar_t Delimitor ) -{ - return 1; -} - -template -void do_split( const S &Source, const C Delimitor, vector &AddIt, const bool bIgnoreEmpty ) -{ - /* Short-circuit if the source is empty; we want to return an empty vector if - * the string is empty, even if bIgnoreEmpty is true. */ - if( Source.empty() ) - return; - - size_t startpos = 0; - - do { - size_t pos; - pos = Source.find( Delimitor, startpos ); - if( pos == Source.npos ) - pos = Source.size(); - - if( pos-startpos > 0 || !bIgnoreEmpty ) - { - /* Optimization: if we're copying the whole string, avoid substr; this - * allows this copy to be refcounted, which is much faster. */ - if( startpos == 0 && pos-startpos == Source.size() ) - AddIt.push_back(Source); - else - { - const S AddRString = Source.substr(startpos, pos-startpos); - AddIt.push_back(AddRString); - } - } - - startpos = pos+DelimitorLength(Delimitor); - } while ( startpos <= Source.size() ); -} - -void split( const RString &sSource, const RString &sDelimitor, vector &asAddIt, const bool bIgnoreEmpty ) -{ - if( sDelimitor.size() == 1 ) - do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty ); - else - do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty ); -} - -void split( const wstring &sSource, const wstring &sDelimitor, vector &asAddIt, const bool bIgnoreEmpty ) -{ - if( sDelimitor.size() == 1 ) - do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty ); - else - do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty ); -} - -/* Use: - -RString str="a,b,c"; -int start = 0, size = -1; -while( 1 ) -{ - do_split( str, ",", start, size ); - if( start == str.size() ) - break; - str[start] = 'Q'; -} - -*/ - -template -void do_split( const S &Source, const S &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty ) -{ - if( size != -1 ) - { - // Start points to the beginning of the last delimiter. Move it up. - begin += size+Delimitor.size(); - begin = min( begin, len ); - } - - size = 0; - - if( bIgnoreEmpty ) - { - // Skip delims. - while( begin + Delimitor.size() < Source.size() && - !Source.compare( begin, Delimitor.size(), Delimitor ) ) - ++begin; - } - - /* Where's the string function to find within a substring? - * C++ strings apparently are missing that ... */ - size_t pos; - if( Delimitor.size() == 1 ) - pos = Source.find( Delimitor[0], begin ); - else - pos = Source.find( Delimitor, begin ); - if( pos == Source.npos || (int) pos > len ) - pos = len; - size = pos - begin; -} - -void split( const RString &Source, const RString &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty ) -{ - do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty ); -} - -void split( const wstring &Source, const wstring &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty ) -{ - do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty ); -} - -void split( const RString &Source, const RString &Delimitor, int &begin, int &size, const bool bIgnoreEmpty ) -{ - do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty ); -} - -void split( const wstring &Source, const wstring &Delimitor, int &begin, int &size, const bool bIgnoreEmpty ) -{ - do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty ); -} - -/* - * foo\fum\ -> "foo\fum\", "", "" - * c:\foo\bar.txt -> "c:\foo\", "bar", ".txt" - * \\foo\fum -> "\\foo\", "fum", "" - */ -void splitpath( const RString &sPath, RString &sDir, RString &sFilename, RString &sExt ) -{ - sDir = sFilename = sExt = ""; - - vector asMatches; - - /* - * One level of escapes for the regex, one for C. Ew. - * This is really: - * ^(.*[\\/])?(.*)$ - */ - static Regex sep("^(.*[\\\\/])?(.*)$"); - bool bCheck = sep.Compare( sPath, asMatches ); - ASSERT( bCheck ); - - sDir = asMatches[0]; - const RString sBase = asMatches[1]; - - /* ^(.*)(\.[^\.]+)$ */ - static Regex SplitExt("^(.*)(\\.[^\\.]+)$"); - if( SplitExt.Compare(sBase, asMatches) ) - { - sFilename = asMatches[0]; - sExt = asMatches[1]; - } - else - { - sFilename = sBase; - } -} - -/* "foo.bar", "baz" -> "foo.baz" - * "foo", "baz" -> "foo.baz" - * "foo.bar", "" -> "foo" */ -RString SetExtension( const RString &sPath, const RString &sExt ) -{ - RString sDir, sFileName, sOldExt; - splitpath( sPath, sDir, sFileName, sOldExt ); - return sDir + sFileName + (sExt.size()? ".":"") + sExt; -} - -RString GetExtension( const RString &sPath ) -{ - size_t pos = sPath.rfind( '.' ); - if( pos == sPath.npos ) - return RString(); - - size_t slash = sPath.find( '/', pos ); - if( slash != sPath.npos ) - return RString(); /* rare: path/dir.ext/fn */ - - return sPath.substr( pos+1, sPath.size()-pos+1 ); -} - -RString GetFileNameWithoutExtension( const RString &sPath ) -{ - RString sThrowAway, sFName; - splitpath( sPath, sThrowAway, sFName, sThrowAway ); - return sFName; -} - -void MakeValidFilename( RString &sName ) -{ - wstring wsName = RStringToWstring( sName ); - wstring wsInvalid = L"/\\:*?\"<>|"; - for( unsigned i = 0; i < wsName.size(); ++i ) - { - wchar_t w = wsName[i]; - if( w >= 32 && - w < 126 && - wsInvalid.find_first_of(w) == wsInvalid.npos ) - continue; - - if( w == L'"' ) - { - wsName[i] = L'\''; - continue; - } - - /* We could replace with closest matches in ASCII: convert the character - * to UTF-8 NFD (decomposed) (maybe NFKD?), and see if the first - * character is ASCII. This is useless for non-Western languages, - * since we'll replace the whole filename. */ - wsName[i] = '_'; - } - - sName = WStringToRString( wsName ); -} - -int g_argc = 0; -char **g_argv = NULL; - -void SetCommandlineArguments( int argc, char **argv ) -{ - g_argc = argc; - g_argv = argv; -} - -void GetCommandLineArguments( int &argc, char **&argv ) -{ - argc = g_argc; - argv = g_argv; -} - -/* Search for the commandline argument given; eg. "test" searches for the - * option "--test". All commandline arguments are getopt_long style: --foo; - * short arguments (-x) are not supported. (These are not intended for - * common, general use, so having short options isn't currently needed.) - * If argument is non-NULL, accept an argument. */ -bool GetCommandlineArgument( const RString &option, RString *argument, int iIndex ) -{ - const RString optstr = "--" + option; - - for( int arg = 1; arg < g_argc; ++arg ) - { - const RString CurArgument = g_argv[arg]; - - const size_t i = CurArgument.find( "=" ); - RString CurOption = CurArgument.substr(0,i); - if( CurOption.CompareNoCase(optstr) ) - continue; // no match - - // Found it. - if( iIndex ) - { - --iIndex; - continue; - } - - if( argument ) - { - if( i != RString::npos ) - *argument = CurArgument.substr( i+1 ); - else - *argument = ""; - } - - return true; - } - - return false; -} - -RString GetCwd() -{ - char buf[PATH_MAX]; - bool ret = getcwd(buf, PATH_MAX) != NULL; - ASSERT(ret); - return buf; -} - -/* - * Calculate a standard CRC32. iCRC should be initialized to 0. - * References: - * http://www.theorem.com/java/CRC32.java, - * http://www.faqs.org/rfcs/rfc1952.html - */ -void CRC32( unsigned int &iCRC, const void *pVoidBuffer, size_t iSize ) -{ - static unsigned tab[256]; - static bool initted = false; - if( !initted ) - { - initted = true; - const unsigned POLY = 0xEDB88320; - - for( int i = 0; i < 256; ++i ) - { - tab[i] = i; - for( int j = 0; j < 8; ++j ) - { - if( tab[i] & 1 ) - tab[i] = (tab[i] >> 1) ^ POLY; - else - tab[i] >>= 1; - } - } - } - - iCRC ^= 0xFFFFFFFF; - - const char *pBuffer = (const char *) pVoidBuffer; - for( unsigned i = 0; i < iSize; ++i ) - iCRC = (iCRC >> 8) ^ tab[(iCRC ^ pBuffer[i]) & 0xFF]; - - iCRC ^= 0xFFFFFFFF; -} - -unsigned int GetHashForString( const RString &s ) -{ - unsigned crc = 0; - CRC32( crc, s.data(), s.size() ); - return crc; -} - -/* Return true if "dir" is empty or does not exist. */ -bool DirectoryIsEmpty( const RString &sDir ) -{ - if( sDir.empty() ) - return true; - if( !DoesFileExist(sDir) ) - return true; - - vector asFileNames; - GetDirListing( sDir, asFileNames ); - return asFileNames.empty(); -} - -bool CompareRStringsAsc( const RString &sStr1, const RString &sStr2 ) -{ - return sStr1.CompareNoCase( sStr2 ) < 0; -} - -bool CompareRStringsDesc( const RString &sStr1, const RString &sStr2 ) -{ - return sStr1.CompareNoCase( sStr2 ) > 0; -} - -void SortRStringArray( vector &arrayRStrings, const bool bSortAscending ) -{ - sort( arrayRStrings.begin(), arrayRStrings.end(), - bSortAscending?CompareRStringsAsc:CompareRStringsDesc ); -} - -float calc_mean( const float *pStart, const float *pEnd ) -{ - return accumulate( pStart, pEnd, 0.f ) / distance( pStart, pEnd ); -} - -float calc_stddev( const float *pStart, const float *pEnd, bool bSample ) -{ - /* Calculate the mean. */ - float fMean = calc_mean( pStart, pEnd ); - - /* Calculate stddev. */ - float fDev = 0.0f; - for( const float *i=pStart; i != pEnd; ++i ) - fDev += (*i - fMean) * (*i - fMean); - fDev /= distance( pStart, pEnd ) - (bSample ? 1 : 0); - fDev = sqrtf( fDev ); - - return fDev; -} - -bool CalcLeastSquares( const vector< pair > &vCoordinates, - float &fSlope, float &fIntercept, float &fError ) -{ - if( vCoordinates.empty() ) - return false; - float fSumXX = 0.0f, fSumXY = 0.0f, fSumX = 0.0f, fSumY = 0.0f; - for( unsigned i = 0; i < vCoordinates.size(); ++i ) - { - fSumXX += vCoordinates[i].first * vCoordinates[i].first; - fSumXY += vCoordinates[i].first * vCoordinates[i].second; - fSumX += vCoordinates[i].first; - fSumY += vCoordinates[i].second; - } - const float fDenominator = vCoordinates.size() * fSumXX - fSumX * fSumX; - fSlope = (vCoordinates.size() * fSumXY - fSumX * fSumY) / fDenominator; - fIntercept = (fSumXX * fSumY - fSumX * fSumXY) / fDenominator; - - fError = 0.0f; - for( unsigned i = 0; i < vCoordinates.size(); ++i ) - { - const float fOneError = fIntercept + fSlope * vCoordinates[i].first - vCoordinates[i].second; - fError += fOneError * fOneError; - } - fError /= vCoordinates.size(); - fError = sqrtf( fError ); - return true; -} - -void FilterHighErrorPoints( vector< pair > &vCoordinates, - float fSlope, float fIntercept, float fCutoff ) -{ - unsigned int iOut = 0; - for( unsigned int iIn = 0; iIn < vCoordinates.size(); ++iIn ) - { - const float fError = fIntercept + fSlope * vCoordinates[iIn].first - vCoordinates[iIn].second; - if( fabsf(fError) < fCutoff ) - { - vCoordinates[iOut] = vCoordinates[iIn]; - ++iOut; - } - } - vCoordinates.resize( iOut ); -} - -void TrimLeft( RString &sStr, const char *s ) -{ - int n = 0; - while( n < int(sStr.size()) && strchr(s, sStr[n]) ) - n++; - - sStr.erase( sStr.begin(), sStr.begin()+n ); -} - -void TrimRight( RString &sStr, const char *s ) -{ - int n = sStr.size(); - while( n > 0 && strchr(s, sStr[n-1]) ) - n--; - - /* Delete from n to the end. If n == sStr.size(), nothing is deleted; - * if n == 0, the whole string is erased. */ - sStr.erase( sStr.begin()+n, sStr.end() ); -} - -void Trim( RString &sStr, const char *s ) -{ - RString::size_type b = 0, e = sStr.size(); - while( b < e && strchr(s, sStr[b]) ) - ++b; - while( b < e && strchr(s, sStr[e-1]) ) - --e; - sStr.assign( sStr.substr(b, e-b) ); -} - -void StripCrnl( RString &s ) -{ - while( s.size() && (s[s.size()-1] == '\r' || s[s.size()-1] == '\n') ) - s.erase( s.size()-1 ); -} - -bool BeginsWith( const RString &sTestThis, const RString &sBeginning ) -{ - ASSERT( !sBeginning.empty() ); - return sTestThis.compare( 0, sBeginning.length(), sBeginning ) == 0; -} - -bool EndsWith( const RString &sTestThis, const RString &sEnding ) -{ - ASSERT( !sEnding.empty() ); - if( sTestThis.size() < sEnding.size() ) - return false; - return sTestThis.compare( sTestThis.length()-sEnding.length(), sEnding.length(), sEnding ) == 0; -} - -RString URLEncode( const RString &sStr ) -{ - RString sOutput; - for( unsigned k = 0; k < sStr.size(); k++ ) - { - char t = sStr[k]; - if( t >= '!' && t <= 'z' ) - sOutput += t; - else - sOutput += "%" + ssprintf( "%02X", t ); - } - return sOutput; -} - -static bool CVSOrSVN( const RString& s ) -{ - return s.Right(3).EqualsNoCase("CVS") || s.Right(4) == ".svn"; -} - -void StripCvsAndSvn( vector &vs ) -{ - RemoveIf( vs, CVSOrSVN ); -} - -static bool MacResourceFork( const RString& s ) -{ - return s.Left(2).EqualsNoCase("._"); -} - -void StripMacResourceForks( vector &vs ) -{ - RemoveIf( vs, MacResourceFork ); -} - -// path is a .redir pathname. Read it and return the real one. -RString DerefRedir( const RString &_path ) -{ - RString sPath = _path; - - for( int i=0; i<100; i++ ) - { - if( GetExtension(sPath) != "redir" ) - return sPath; - - RString sNewFileName; - GetFileContents( sPath, sNewFileName, true ); - - // Empty is invalid. - if( sNewFileName == "" ) - return RString(); - - RString sPath2 = Dirname(sPath) + sNewFileName; - - CollapsePath( sPath2 ); - - sPath2 += "*"; - - vector matches; - GetDirListing( sPath2, matches, false, true ); - - if( matches.empty() ) - RageException::Throw( "The redirect \"%s\" references a file \"%s\" which doesn't exist.", sPath.c_str(), sPath2.c_str() ); - else if( matches.size() > 1 ) - RageException::Throw( "The redirect \"%s\" references a file \"%s\" with multiple matches.", sPath.c_str(), sPath2.c_str() ); - - sPath = matches[0]; - } - - RageException::Throw( "Circular redirect \"%s\".", sPath.c_str() ); -} - -bool GetFileContents( const RString &sPath, RString &sOut, bool bOneLine ) -{ - // Don't warn if the file doesn't exist, but do warn if it exists and fails to open. - if( !IsAFile(sPath) ) - return false; - - RageFile file; - if( !file.Open(sPath) ) - { - LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() ); - return false; - } - - // todo: figure out how to make this UTF-8 safe. -aj - RString sData; - int iGot; - if( bOneLine ) - iGot = file.GetLine( sData ); - else - iGot = file.Read( sData, file.GetFileSize() ); - - if( iGot == -1 ) - { - LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() ); - return false; - } - - if( bOneLine ) - StripCrnl( sData ); - - sOut = sData; - return true; -} - -bool GetFileContents( const RString &sFile, vector &asOut ) -{ - RageFile file; - if( !file.Open(sFile) ) - { - LOG->Warn( "GetFileContents(%s): %s", sFile.c_str(), file.GetError().c_str() ); - return false; - } - - RString sLine; - while( file.GetLine(sLine) ) - asOut.push_back( sLine ); - return true; -} - -#include "../extern/pcre/pcre.h" -void Regex::Compile() -{ - const char *error; - int offset; - m_pReg = pcre_compile( m_sPattern.c_str(), PCRE_CASELESS, &error, &offset, NULL ); - - if( m_pReg == NULL ) - RageException::Throw( "Invalid regex: \"%s\" (%s).", m_sPattern.c_str(), error ); - - int iRet = pcre_fullinfo( (pcre *) m_pReg, NULL, PCRE_INFO_CAPTURECOUNT, &m_iBackrefs ); - ASSERT( iRet >= 0 ); - - ++m_iBackrefs; - ASSERT( m_iBackrefs < 128 ); -} - -void Regex::Set( const RString &sStr ) -{ - Release(); - m_sPattern = sStr; - Compile(); -} - -void Regex::Release() -{ - pcre_free( m_pReg ); - m_pReg = NULL; - m_sPattern = RString(); -} - -Regex::Regex( const RString &sStr ): m_pReg(NULL), m_iBackrefs(0), m_sPattern(RString()) -{ - Set( sStr ); -} - -Regex::Regex( const Regex &rhs ): m_pReg(NULL), m_iBackrefs(0), m_sPattern(RString()) -{ - Set( rhs.m_sPattern ); -} - -Regex &Regex::operator=( const Regex &rhs ) -{ - if( this != &rhs ) - Set( rhs.m_sPattern ); - return *this; -} - -Regex::~Regex() -{ - Release(); -} - -bool Regex::Compare( const RString &sStr ) -{ - int iMat[128*3]; - int iRet = pcre_exec( (pcre *) m_pReg, NULL, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 ); - - if( iRet < -1 ) - RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet ); - - return iRet >= 0; -} - -bool Regex::Compare( const RString &sStr, vector &asMatches ) -{ - asMatches.clear(); - - int iMat[128*3]; - int iRet = pcre_exec( (pcre *) m_pReg, NULL, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 ); - - if( iRet < -1 ) - RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet ); - - if( iRet == -1 ) - return false; - - for( unsigned i = 1; i < m_iBackrefs; ++i ) - { - const int iStart = iMat[i*2], end = iMat[i*2+1]; - if( iStart == -1 ) - asMatches.push_back( RString() ); /* no match */ - else - asMatches.push_back( sStr.substr(iStart, end - iStart) ); - } - - return true; -} - -// Arguments and behavior are the same are similar to -// http://us3.php.net/manual/en/function.preg-replace.php -bool Regex::Replace( const RString &sReplacement, const RString &sSubject, RString &sOut ) -{ - vector asMatches; - if( !Compare(sSubject, asMatches) ) - return false; - - sOut = sReplacement; - - // TODO: optimize me by iterating only once over the string - for( unsigned i=0; i= s.size() ) - return false; - - if( is_utf8_continuation_byte( s[start] ) || /* misplaced continuation byte */ - (s[start] & 0xFE) == 0xFE ) /* 0xFE, 0xFF */ - { - start += 1; - return false; - } - - int len = utf8_get_char_len( s[start] ); - - const int first_byte_mask[] = { -1, 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 }; - - ch = wchar_t(s[start] & first_byte_mask[len]); - - for( int i = 1; i < len; ++i ) - { - if( start+i >= s.size() ) - { - /* We expected a continuation byte, but didn't get one. Return error, and point - * start at the unexpected byte; it's probably a new sequence. */ - start += i; - return false; - } - - char byte = s[start+i]; - if( !is_utf8_continuation_byte(byte) ) - { - /* We expected a continuation byte, but didn't get one. Return error, and point - * start at the unexpected byte; it's probably a new sequence. */ - start += i; - return false; - } - ch = (ch << 6) | (byte & 0x3F); - } - - bool bValid = true; - { - unsigned c1 = (unsigned) s[start] & 0xFF; - unsigned c2 = (unsigned) s[start+1] & 0xFF; - int c = (c1 << 8) + c2; - if( (c & 0xFE00) == 0xC000 || - (c & 0xFFE0) == 0xE080 || - (c & 0xFFF0) == 0xF080 || - (c & 0xFFF8) == 0xF880 || - (c & 0xFFFC) == 0xFC80 ) - { - bValid = false; - } - } - - if( ch == 0xFFFE || ch == 0xFFFF ) - bValid = false; - - start += len; - return bValid; -} - -/* Like utf8_to_wchar_ec, but only does enough error checking to prevent crashing. */ -bool utf8_to_wchar( const char *s, size_t iLength, unsigned &start, wchar_t &ch ) -{ - if( start >= iLength ) - return false; - - int len = utf8_get_char_len( s[start] ); - - if( start+len > iLength ) - { - // We don't have room for enough continuation bytes. Return error. - start += len; - ch = L'?'; - return false; - } - - switch( len ) - { - case 1: - ch = (s[start+0] & 0x7F); - break; - case 2: - ch = ( (s[start+0] & 0x1F) << 6 ) | - (s[start+1] & 0x3F); - break; - case 3: - ch = ( (s[start+0] & 0x0F) << 12 ) | - ( (s[start+1] & 0x3F) << 6 ) | - (s[start+2] & 0x3F); - break; - case 4: - ch = ( (s[start+0] & 0x07) << 18 ) | - ( (s[start+1] & 0x3F) << 12 ) | - ( (s[start+2] & 0x3F) << 6 ) | - (s[start+3] & 0x3F); - break; - case 5: - ch = ( (s[start+0] & 0x03) << 24 ) | - ( (s[start+1] & 0x3F) << 18 ) | - ( (s[start+2] & 0x3F) << 12 ) | - ( (s[start+3] & 0x3F) << 6 ) | - (s[start+4] & 0x3F); - break; - - case 6: - ch = ( (s[start+0] & 0x01) << 30 ) | - ( (s[start+1] & 0x3F) << 24 ) | - ( (s[start+2] & 0x3F) << 18 ) | - ( (s[start+3] & 0x3F) << 12) | - ( (s[start+4] & 0x3F) << 6 ) | - (s[start+5] & 0x3F); - break; - - } - - start += len; - return true; -} - - -// UTF-8 encode ch and append to out. -void wchar_to_utf8( wchar_t ch, RString &out ) -{ - if( ch < 0x80 ) { out.append( 1, (char) ch ); return; } - - int cbytes = 0; - if( ch < 0x800 ) cbytes = 1; - else if( ch < 0x10000 ) cbytes = 2; - else if( ch < 0x200000 ) cbytes = 3; - else if( ch < 0x4000000 ) cbytes = 4; - else cbytes = 5; - - { - int shift = cbytes*6; - const int init_masks[] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; - out.append( 1, (char) (init_masks[cbytes-1] | (ch>>shift)) ); - } - - for( int i = 0; i < cbytes; ++i ) - { - int shift = (cbytes-i-1)*6; - out.append( 1, (char) (0x80 | ((ch>>shift)&0x3F)) ); - } -} - -wchar_t utf8_get_char( const RString &s ) -{ - unsigned start = 0; - wchar_t ret; - if( !utf8_to_wchar_ec( s, start, ret ) ) - return INVALID_CHAR; - return ret; -} - -// Replace invalid sequences in s. -void utf8_sanitize( RString &s ) -{ - RString ret; - for( unsigned start = 0; start < s.size(); ) - { - wchar_t ch; - if( !utf8_to_wchar_ec( s, start, ch ) ) - ch = INVALID_CHAR; - - wchar_to_utf8( ch, ret ); - } - - s = ret; -} - -bool utf8_is_valid( const RString &s ) -{ - for( unsigned start = 0; start < s.size(); ) - { - wchar_t ch; - if( !utf8_to_wchar_ec( s, start, ch ) ) - return false; - } - return true; -} - -/* Windows tends to drop garbage BOM characters at the start of UTF-8 text files. - * Remove them. */ -void utf8_remove_bom( RString &sLine ) -{ - if( !sLine.compare(0, 3, "\xef\xbb\xbf") ) - sLine.erase(0, 3); -} - -static int UnicodeDoUpper( char *p, size_t iLen, const unsigned char pMapping[256] ) -{ - // Note: this has problems with certain accented characters. -aj - wchar_t wc = L'\0'; - unsigned iStart = 0; - if( !utf8_to_wchar(p, iLen, iStart, wc) ) - return 1; - - wchar_t iUpper = wc; - if( wc < 256 ) - iUpper = pMapping[wc]; - if( iUpper != wc ) - { - RString sOut; - wchar_to_utf8( iUpper, sOut ); - if( sOut.size() == iStart ) - memcpy( p, sOut.data(), sOut.size() ); - else - WARN( ssprintf("UnicodeDoUpper: invalid character at \"%s\"", RString(p,iLen).c_str()) ); - } - - return iStart; -} - -/* Fast in-place MakeUpper and MakeLower. This only replaces characters with characters of the same UTF-8 - * length, so we never have to move the whole string. This is optimized for strings that have no - * non-ASCII characters. */ -void MakeUpper( char *p, size_t iLen ) -{ - char *pStart = p; - char *pEnd = p + iLen; - while( p < pEnd ) - { - // Fast path: - if( likely( !(*p & 0x80) ) ) - { - if( unlikely(*p >= 'a' && *p <= 'z') ) - *p += 'A' - 'a'; - ++p; - continue; - } - - int iRemaining = iLen - (p-pStart); - p += UnicodeDoUpper( p, iRemaining, g_UpperCase ); - } -} - -void MakeLower( char *p, size_t iLen ) -{ - char *pStart = p; - char *pEnd = p + iLen; - while( p < pEnd ) - { - // Fast path: - if( likely( !(*p & 0x80) ) ) - { - if( unlikely(*p >= 'A' && *p <= 'Z') ) - *p -= 'A' - 'a'; - ++p; - continue; - } - - int iRemaining = iLen - (p-pStart); - p += UnicodeDoUpper( p, iRemaining, g_LowerCase ); - } -} - -void UnicodeUpperLower( wchar_t *p, size_t iLen, const unsigned char pMapping[256] ) -{ - wchar_t *pEnd = p + iLen; - while( p != pEnd ) - { - if( *p < 256 ) - *p = pMapping[*p]; - ++p; - } -} - -void MakeUpper( wchar_t *p, size_t iLen ) -{ - UnicodeUpperLower( p, iLen, g_UpperCase ); -} - -void MakeLower( wchar_t *p, size_t iLen ) -{ - UnicodeUpperLower( p, iLen, g_LowerCase ); -} - -int StringToInt( const RString &sString ) -{ - int ret; - istringstream ( sString ) >> ret; - return ret; -} - -RString IntToString( const int &iNum ) -{ - stringstream ss; - ss << iNum; - return ss.str(); -} - -float StringToFloat( const RString &sString ) -{ - float ret = strtof( sString, NULL ); - - if( !isfinite(ret) ) - ret = 0.0f; - return ret; -} - -bool StringToFloat( const RString &sString, float &fOut ) -{ - char *endPtr; - - fOut = strtof( sString, &endPtr ); - return sString.size() && *endPtr == '\0' && isfinite( fOut ); -} - -RString FloatToString( const float &num ) -{ - stringstream ss; - ss << num; - return ss.str(); -} - -const wchar_t INVALID_CHAR = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */ - -wstring RStringToWstring( const RString &s ) -{ - wstring ret; - ret.reserve( s.size() ); - for( unsigned start = 0; start < s.size(); ) - { - char c = s[start]; - if( !(c&0x80) ) - { - // ASCII fast path - ret += c; - ++start; - continue; - } - - wchar_t ch = L'\0'; - if( !utf8_to_wchar( s.data(), s.size(), start, ch ) ) - ch = INVALID_CHAR; - ret += ch; - } - - return ret; -} - -RString WStringToRString( const wstring &sStr ) -{ - RString sRet; - - for( unsigned i = 0; i < sStr.size(); ++i ) - wchar_to_utf8( sStr[i], sRet ); - - return sRet; -} - -RString WcharToUTF8( wchar_t c ) -{ - RString ret; - wchar_to_utf8( c, ret ); - return ret; -} - -// &a; -> a -void ReplaceEntityText( RString &sText, const map &m ) -{ - RString sRet; - - size_t iOffset = 0; - while( iOffset != sText.size() ) - { - size_t iStart = sText.find( '&', iOffset ); - if( iStart == sText.npos ) - { - // Optimization: if we didn't replace anything at all, do nothing. - if( iOffset == 0 ) - return; - - // Append the rest of the string. - sRet.append( sText, iOffset, sRet.npos ); - break; - } - - // Append the text between iOffset and iStart. - sRet.append( sText, iOffset, iStart-iOffset ); - iOffset += iStart-iOffset; - - // Optimization: stop early on "&", so "&&&&&&&&&&&" isn't n^2. - size_t iEnd = sText.find_first_of( "&;", iStart+1 ); - if( iEnd == sText.npos || sText[iEnd] == '&' ) - { - // & with no matching ;, or two & in a row. Append the & and continue. - sRet.append( sText, iStart, 1 ); - ++iOffset; - continue; - } - - RString sElement = sText.substr( iStart+1, iEnd-iStart-1 ); - sElement.MakeLower(); - - map::const_iterator it = m.find( sElement ); - if( it == m.end() ) - { - sRet.append( sText, iStart, iEnd-iStart+1 ); - iOffset = iEnd + 1; - continue; - } - - const RString &sTo = it->second; - sRet.append( sTo ); - iOffset = iEnd + 1; - } - - sText = sRet; -} - -// abcd -> &a; &b; &c; &d; -void ReplaceEntityText( RString &sText, const map &m ) -{ - RString sFind; - - FOREACHM_CONST( char, RString, m, c ) - sFind.append( 1, c->first ); - - RString sRet; - - size_t iOffset = 0; - while( iOffset != sText.size() ) - { - size_t iStart = sText.find_first_of( sFind, iOffset ); - if( iStart == sText.npos ) - { - // Optimization: if we didn't replace anything at all, do nothing. - if( iOffset == 0 ) - return; - - // Append the rest of the string. - sRet.append( sText, iOffset, sRet.npos ); - break; - } - - // Append the text between iOffset and iStart. - sRet.append( sText, iOffset, iStart-iOffset ); - iOffset += iStart-iOffset; - - char sElement = sText[iStart]; - - map::const_iterator it = m.find( sElement ); - ASSERT( it != m.end() ); - - const RString &sTo = it->second; - sRet.append( 1, '&' ); - sRet.append( sTo ); - sRet.append( 1, ';' ); - ++iOffset; - } - - sText = sRet; -} - -// Replace &#nnnn; (decimal) and &xnnnn; (hex) with corresponding UTF-8 characters. -void Replace_Unicode_Markers( RString &sText ) -{ - unsigned iStart = 0; - while( iStart < sText.size() ) - { - // Look for &#digits; - bool bHex = false; - size_t iPos = sText.find( "&#", iStart ); - if( iPos == sText.npos ) - { - bHex = true; - iPos = sText.find( "&x", iStart ); - } - - if( iPos == sText.npos ) - break; - iStart = iPos+1; - - unsigned p = iPos; - p += 2; - - // Found &# or &x. Is it followed by digits and a semicolon? - if( p >= sText.size() ) - continue; - - int iNumDigits = 0; - while( p < sText.size() && bHex? isxdigit(sText[p]):isdigit(sText[p]) ) - { - p++; - iNumDigits++; - } - - if( !iNumDigits ) - continue; // must have at least one digit - if( p >= sText.size() || sText[p] != ';' ) - continue; - p++; - - int iNum; - if( bHex ) - sscanf( sText.c_str()+iPos, "&x%x;", &iNum ); - else - sscanf( sText.c_str()+iPos, "&#%i;", &iNum ); - if( iNum > 0xFFFF ) - iNum = INVALID_CHAR; - - sText.replace( iPos, p-iPos, WcharToUTF8(wchar_t(iNum)) ); - } -} - -// Form a string to identify a wchar_t with ASCII. -RString WcharDisplayText( wchar_t c ) -{ - RString sChr; - sChr = ssprintf( "U+%4.4x", c ); - if( c < 128 ) - sChr += ssprintf( " ('%c')", char(c) ); - return sChr; -} - -/* Return the last named component of dir: - * a/b/c -> c - * a/b/c/ -> c - */ -RString Basename( const RString &sDir ) -{ - size_t iEnd = sDir.find_last_not_of( "/\\" ); - if( iEnd == sDir.npos ) - return RString(); - - size_t iStart = sDir.find_last_of( "/\\", iEnd ); - if( iStart == sDir.npos ) - iStart = 0; - else - ++iStart; - - return sDir.substr( iStart, iEnd-iStart+1 ); -} - -/* Return all but the last named component of dir: - * - * a/b/c -> a/b/ - * a/b/c/ -> a/b/ - * c/ -> ./ - * /foo -> / - * / -> / - */ -RString Dirname( const RString &dir ) -{ - // Special case: "/" -> "/". - if( dir.size() == 1 && dir[0] == '/' ) - return "/"; - - int pos = dir.size()-1; - // Skip trailing slashes. - while( pos >= 0 && dir[pos] == '/' ) - --pos; - - // Skip the last component. - while( pos >= 0 && dir[pos] != '/' ) - --pos; - - if( pos < 0 ) - return "./"; - - return dir.substr(0, pos+1); -} - -RString Capitalize( const RString &s ) -{ - if( s.empty() ) - return RString(); - - RString s2 = s; - char *pBuf = s2.GetBuffer(); - UnicodeDoUpper( pBuf, s2.size(), g_UpperCase ); - s2.ReleaseBuffer(); - - return s2; -} - -unsigned char g_UpperCase[256] = -{ - 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, - 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, - 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, - 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, - 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, - 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F, - 0x60,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, - 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x7F, - 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, - 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, - 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, - 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, - 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, - 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, - 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, - 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF, -}; - -unsigned char g_LowerCase[256] = -{ - 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, - 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, - 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, - 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, - 0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, - 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x5B,0x5C,0x5D,0x5E,0x5F, - 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, - 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F, - 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, - 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, - 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, - 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, - 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, - 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, - 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, - 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF, -}; - -void FixSlashesInPlace( RString &sPath ) -{ - for( unsigned i = 0; i < sPath.size(); ++i ) - if( sPath[i] == '\\' ) - sPath[i] = '/'; -} - -/* Keep trailing slashes, since that can be used to illustrate that a path always - * represents a directory. - * - * foo/bar -> foo/bar - * foo/bar/ -> foo/bar/ - * foo///bar/// -> foo/bar/ - * foo/bar/./baz -> foo/bar/baz - * foo/bar/../baz -> foo/baz - * ../foo -> ../foo - * ../../foo -> ../../foo - * ./foo -> foo (if bRemoveLeadingDot), ./foo (if !bRemoveLeadingDot) - * ./ -> ./ - * ./// -> ./ - */ - -void CollapsePath( RString &sPath, bool bRemoveLeadingDot ) -{ - RString sOut; - sOut.reserve( sPath.size() ); - - size_t iPos = 0; - size_t iNext; - for( ; iPos < sPath.size(); iPos = iNext ) - { - // Find the next slash. - iNext = sPath.find( '/', iPos ); - if( iNext == RString::npos ) - iNext = sPath.size(); - else - ++iNext; - - /* Strip extra slashes, but don't remove slashes from the beginning of the string. */ - if( iNext - iPos == 1 && sPath[iPos] == '/' ) - { - if( !sOut.empty() ) - continue; - } - - // If this is a dot, skip it. - if( iNext - iPos == 2 && sPath[iPos] == '.' && sPath[iPos+1] == '/' ) - { - if( bRemoveLeadingDot || !sOut.empty() ) - continue; - } - - // If this is two dots, - if( iNext - iPos == 3 && sPath[iPos] == '.' && sPath[iPos+1] == '.' && sPath[iPos+2] == '/' ) - { - /* If this is the first path element (nothing to delete), - * or all we have is a slash, leave it. */ - if( sOut.empty() || (sOut.size() == 1 && sOut[0] == '/') ) - { - sOut.append( sPath, iPos, iNext-iPos ); - continue; - } - - // Search backwards for the previous path element. - size_t iPrev = sOut.rfind( '/', sOut.size()-2 ); - if( iPrev == RString::npos ) - iPrev = 0; - else - ++iPrev; - - // If the previous element is also .., leave it. - bool bLastIsTwoDots = (sOut.size() - iPrev == 3 && sOut[iPrev] == '.' && sOut[iPrev+1] == '.' ); - if( bLastIsTwoDots ) - { - sOut.append( sPath, iPos, iNext-iPos ); - continue; - } - - sOut.erase( iPrev ); - continue; - } - - sOut.append( sPath, iPos, iNext-iPos ); - } - - sOut.swap( sPath ); -} - -namespace StringConversion -{ - template<> bool FromString( const RString &sValue, int &out ) - { - if( sscanf( sValue.c_str(), "%d", &out ) == 1 ) - return true; - - out = 0; - return false; - } - - template<> bool FromString( const RString &sValue, unsigned &out ) - { - if( sscanf( sValue.c_str(), "%u", &out ) == 1 ) - return true; - - out = 0; - return false; - } - - template<> bool FromString( const RString &sValue, float &out ) - { - const char *endptr = sValue.data() + sValue.size(); - out = strtof( sValue, (char **) &endptr ); - if( endptr != sValue.data() && isfinite( out ) ) - return true; - out = 0; - return false; - } - - template<> bool FromString( const RString &sValue, bool &out ) - { - if( sValue.size() == 0 ) - return false; - - out = (StringToInt(sValue) != 0); - return true; - } - - template<> RString ToString( const int &value ) - { - return ssprintf( "%i", value ); - } - - template<> RString ToString( const unsigned &value ) - { - return ssprintf( "%u", value ); - } - - template<> RString ToString( const float &value ) - { - return ssprintf( "%f", value ); - } - - template<> RString ToString( const bool &value ) - { - return ssprintf( "%i", value ); - } -} - -bool FileCopy( const RString &sSrcFile, const RString &sDstFile ) -{ - if( !sSrcFile.CompareNoCase(sDstFile) ) - { - LOG->Warn( "Tried to copy \"%s\" over itself", sSrcFile.c_str() ); - return false; - } - - RageFile in; - if( !in.Open(sSrcFile, RageFile::READ) ) - return false; - - RageFile out; - if( !out.Open(sDstFile, RageFile::WRITE) ) - return false; - - RString sError; - if( !FileCopy(in, out, sError) ) - { - LOG->Warn( "FileCopy(%s,%s): %s", - sSrcFile.c_str(), sDstFile.c_str(), sError.c_str() ); - return false; - } - - return true; -} - -bool FileCopy( RageFileBasic &in, RageFileBasic &out, RString &sError, bool *bReadError ) -{ - while(1) - { - RString data; - if( in.Read(data, 1024*32) == -1 ) - { - sError = ssprintf( "read error: %s", in.GetError().c_str() ); - if( bReadError != NULL ) - *bReadError = true; - return false; - } - if( data.empty() ) - break; - int i = out.Write(data); - if( i == -1 ) - { - sError = ssprintf( "write error: %s", out.GetError().c_str() ); - if( bReadError != NULL ) - *bReadError = false; - return false; - } - } - - if( out.Flush() == -1 ) - { - sError = ssprintf( "write error: %s", out.GetError().c_str() ); - if( bReadError != NULL ) - *bReadError = false; - return false; - } - - return true; -} - -LuaFunction( SecondsToMSSMsMs, SecondsToMSSMsMs( FArg(1) ) ) -LuaFunction( SecondsToHHMMSS, SecondsToHHMMSS( FArg(1) ) ) -LuaFunction( SecondsToMMSSMsMs, SecondsToMMSSMsMs( FArg(1) ) ) -LuaFunction( SecondsToMMSSMsMsMs, SecondsToMMSSMsMsMs( FArg(1) ) ) -LuaFunction( SecondsToMSS, SecondsToMSS( FArg(1) ) ) -LuaFunction( SecondsToMMSS, SecondsToMMSS( FArg(1) ) ) -LuaFunction( FormatNumberAndSuffix, FormatNumberAndSuffix( IArg(1) ) ) -LuaFunction( Basename, Basename( SArg(1) ) ) -static RString MakeLower( RString s ) { s.MakeLower(); return s; } -LuaFunction( Lowercase, MakeLower( SArg(1) ) ) -static RString MakeUpper( RString s ) { s.MakeUpper(); return s; } -LuaFunction( Uppercase, MakeUpper( SArg(1) ) ) -LuaFunction( mbstrlen, (int)RStringToWstring(SArg(1)).length() ) -LuaFunction( URLEncode, URLEncode( SArg(1) ) ); -LuaFunction( PrettyPercent, PrettyPercent( FArg(1), FArg(2) ) ); -//LuaFunction( IsHexVal, IsHexVal( SArg(1) ) ); -static bool UndocumentedFeature( RString s ){ sm_crash(s); return true; } -LuaFunction( UndocumentedFeature, UndocumentedFeature(SArg(1)) ); - -/* - * Copyright (c) 2001-2005 Chris Danford, Glenn Maynard - * All rights reserved. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, and/or sell copies of the Software, and to permit persons to - * whom the Software is furnished to do so, provided that the above - * copyright notice(s) and this permission notice appear in all copies of - * the Software and that both the above copyright notice(s) and this - * permission notice appear in supporting documentation. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF - * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS - * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT - * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS - * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR - * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR - * PERFORMANCE OF THIS SOFTWARE. - */ +#include "global.h" +#include "RageUtil.h" +#include "RageMath.h" +#include "RageLog.h" +#include "RageFile.h" +#include "Foreach.h" +#include "LocalizedString.h" +#include "LuaBinding.h" +#include "LuaManager.h" +#include + +#include +#include +#include +#include +#include +#include +#include + +bool HexToBinary(const RString&, RString&); +void utf8_sanitize(RString &); +void UnicodeUpperLower(wchar_t *, size_t, const unsigned char *); + +RandomGen g_RandomNumberGenerator; + +MersenneTwister::MersenneTwister( int iSeed ) : m_iNext(0) +{ + Reset( iSeed ); +} + +void MersenneTwister::Reset( int iSeed ) +{ + if( iSeed == 0 ) + iSeed = time(NULL); + + m_Values[0] = iSeed; + m_iNext = 0; + for( int i = 1; i < 624; ++i ) + m_Values[i] = ((69069 * m_Values[i-1]) + 1) & 0xFFFFFFFF; + + GenerateValues(); +} + +void MersenneTwister::GenerateValues() +{ + static const int mask[] = { 0, 0x9908B0DF }; + + for( int i = 0; i < 227; ++i ) + { + int iVal = (m_Values[i] & 0x80000000) | (m_Values[i+1] & 0x7FFFFFFF); + int iNext = (i + 397); + + m_Values[i] = m_Values[iNext]; + m_Values[i] ^= (iVal >> 1); + m_Values[i] ^= mask[iVal&1]; + } + + for( int i = 227; i < 623; ++i ) + { + int iVal = (m_Values[i] & 0x80000000) | (m_Values[i+1] & 0x7FFFFFFF); + int iNext = (i + 397) - 624; + + m_Values[i] = m_Values[iNext]; + m_Values[i] ^= (iVal >> 1); + m_Values[i] ^= mask[iVal&1]; + } + + int iVal = (m_Values[623] & 0x80000000) + (m_Values[0] & 0x7FFFFFFF); + int iNext = (623 + 397) - 624; + m_Values[623] = m_Values[iNext] ^ (iVal>>1); + m_Values[623] ^= mask[iVal&1]; +} + +int MersenneTwister::Temper( int iVal ) +{ + iVal ^= (iVal >> 11); + iVal ^= (iVal << 7) & 0x9D2C5680; + iVal ^= (iVal << 15) & 0xEFC60000; + iVal ^= (iVal >> 18); + return iVal; +} + +int MersenneTwister::operator()() +{ + if( m_iNext == 624 ) + { + m_iNext = 0; + GenerateValues(); + } + + return Temper( m_Values[m_iNext++] ); +} + +/* Extend MersenneTwister into Lua space. This is intended to replace + * math.randomseed and math.random, so we conform to their behavior. */ + +namespace +{ + MersenneTwister g_LuaPRNG; + + /* To map from [0..2^32-1] to [0..1), we divide by 2^32. */ + const double DIVISOR = pow( double(2), double(32) ); + + static int Seed( lua_State *L ) + { + g_LuaPRNG.Reset( IArg(1) ); + return 0; + } + + static int Random( lua_State *L ) + { + switch( lua_gettop(L) ) + { + /* [0..1) */ + case 0: + { + double r = double(g_LuaPRNG()) / DIVISOR; + lua_pushnumber( L, r ); + return 1; + } + + /* [1..u] */ + case 1: + { + int upper = IArg(1); + luaL_argcheck( L, 1 <= upper, 1, "interval is empty" ); + lua_pushnumber( L, g_LuaPRNG(upper) + 1 ); + return 1; + } + /* [l..u] */ + case 2: + { + int lower = IArg(1); + int upper = IArg(2); + luaL_argcheck( L, lower < upper, 2, "interval is empty" ); + lua_pushnumber( L, (int(g_LuaPRNG()) % (upper-lower+1)) + lower ); + return 1; + } + + /* wrong amount of arguments */ + default: + { + return luaL_error( L, "wrong number of arguments" ); + } + } + } + + const luaL_Reg MersenneTwisterTable[] = + { + LIST_METHOD( Seed ), + LIST_METHOD( Random ), + { NULL, NULL } + }; +} + +LUA_REGISTER_NAMESPACE( MersenneTwister ); + +void fapproach( float& val, float other_val, float to_move ) +{ + ASSERT_M( to_move >= 0, ssprintf("to_move: %f < 0", to_move) ); + if( val == other_val ) + return; + float fDelta = other_val - val; + float fSign = fDelta / fabsf( fDelta ); + float fToMove = fSign*to_move; + if( fabsf(fToMove) > fabsf(fDelta) ) + fToMove = fDelta; // snap + val += fToMove; +} + +/* Return a positive x mod y. */ +float fmodfp(float x, float y) +{ + x = fmodf(x, y); /* x is [-y,y] */ + x += y; /* x is [0,y*2] */ + x = fmodf(x, y); /* x is [0,y] */ + return x; +} + +int power_of_two( int iInput ) +{ + int iValue = 1; + + while( iValue < iInput ) + iValue <<= 1; + + return iValue; +} + +bool IsAnInt( const RString &s ) +{ + if( !s.size() ) + return false; + + for( size_t i=0; i < s.size(); ++i ) + if( s[i] < '0' || s[i] > '9' ) + return false; + + return true; +} + +bool IsHexVal( const RString &s ) +{ + if( !s.size() ) + return false; + + for( size_t i=0; i < s.size(); ++i ) + if( !(s[i] >= '0' && s[i] <= '9') && + !(toupper(s[i]) >= 'A' && toupper(s[i]) <= 'F')) + return false; + + return true; +} + +RString BinaryToHex( const void *pData_, int iNumBytes ) +{ + const unsigned char *pData = (const unsigned char *) pData_; + RString s; + for( int i=0; i arrayBits; + split( sHHMMSS, ":", arrayBits, false ); + + while( arrayBits.size() < 3 ) + arrayBits.insert(arrayBits.begin(), "0" ); // pad missing bits + + float fSeconds = 0; + fSeconds += StringToInt( arrayBits[0] ) * 60 * 60; + fSeconds += StringToInt( arrayBits[1] ) * 60; + fSeconds += StringToFloat( arrayBits[2] ); + + return fSeconds; +} + +RString SecondsToHHMMSS( float fSecs ) +{ + const int iMinsDisplay = (int)fSecs/60; + const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; + RString sReturn = ssprintf( "%02d:%02d:%02d", iMinsDisplay/60, iMinsDisplay%60, iSecsDisplay ); + return sReturn; +} + +RString SecondsToMMSSMsMs( float fSecs ) +{ + const int iMinsDisplay = (int)fSecs/60; + const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; + const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 ); + RString sReturn = ssprintf( "%02d:%02d.%02d", iMinsDisplay, iSecsDisplay, min(99,iLeftoverDisplay) ); + return sReturn; +} + +RString SecondsToMSSMsMs( float fSecs ) +{ + const int iMinsDisplay = (int)fSecs/60; + const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; + const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 100 ); + RString sReturn = ssprintf( "%01d:%02d.%02d", iMinsDisplay, iSecsDisplay, min(99,iLeftoverDisplay) ); + return sReturn; +} + +RString SecondsToMMSSMsMsMs( float fSecs ) +{ + const int iMinsDisplay = (int)fSecs/60; + const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; + const int iLeftoverDisplay = (int) ( (fSecs - iMinsDisplay*60 - iSecsDisplay) * 1000 ); + RString sReturn = ssprintf( "%02d:%02d.%03d", iMinsDisplay, iSecsDisplay, min(999,iLeftoverDisplay) ); + return sReturn; +} + +RString SecondsToMSS( float fSecs ) +{ + const int iMinsDisplay = (int)fSecs/60; + const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; + RString sReturn = ssprintf( "%01d:%02d", iMinsDisplay, iSecsDisplay); + return sReturn; +} + +RString SecondsToMMSS( float fSecs ) +{ + const int iMinsDisplay = (int)fSecs/60; + const int iSecsDisplay = (int)fSecs - iMinsDisplay*60; + RString sReturn = ssprintf( "%02d:%02d", iMinsDisplay, iSecsDisplay); + return sReturn; +} + +RString PrettyPercent( float fNumerator, float fDenominator) +{ + return ssprintf("%0.2f%%",fNumerator/fDenominator*100); +} + +RString Commify( int iNum ) +{ + RString sNum = ssprintf("%d",iNum); + return Commify( sNum ); +} + +RString Commify( RString sNum, RString sSeperator ) +{ + RString sReturn; + for( unsigned i=0; i= 3 && iPercent > 2 && sStr[iEnd-2] == 'l' && sStr[iEnd-1] == 'l' ) + { + sRet.append( sStr, iPercent, iEnd - iPercent - 2 ); // % + sRet.append( "I64" ); // %I64 + sRet.append( sStr, iEnd, 1 ); // %I64i + iOffset = iEnd + 1; + } + else + { + if( iEnd == sStr.npos ) + iEnd = sStr.size() - 1; + sRet.append( sStr, iOffset, iEnd - iOffset + 1 ); + iOffset = iEnd + 1; + } + } + return sRet; +} +#else +RString ConvertI64FormatString( const RString &sStr ) { return sStr; } +#endif + +/* ISO-639-1 codes: http://www.loc.gov/standards/iso639-2/php/code_list.php + * native forms: http://people.w3.org/rishida/names/languages.html + * We don't use 3-letter codes, so we don't bother supporting them. */ +static const LanguageInfo g_langs[] = +{ + {"aa", "Afar"}, + {"ab", "Abkhazian"}, + {"af", "Afrikaans"}, + {"am", "Amharic"}, + {"ar", "Arabic"}, + {"as", "Assamese"}, + {"ay", "Aymara"}, + {"az", "Azerbaijani"}, + {"ba", "Bashkir"}, + {"be", "Byelorussian"}, + {"bg", "Bulgarian"}, + {"bh", "Bihari"}, + {"bi", "Bislama"}, + {"bn", "Bengali"}, + {"bo", "Tibetan"}, + {"br", "Breton"}, + {"ca", "Catalan"}, + {"co", "Corsican"}, + {"cs", "Czech"}, + {"cy", "Welsh"}, + {"da", "Danish"}, + {"de", "German"}, + {"dz", "Bhutani"}, + {"el", "Greek"}, + {"en", "English"}, + {"eo", "Esperanto"}, + {"es", "Spanish"}, + {"et", "Estonian"}, + {"eu", "Basque"}, + {"fa", "Persian"}, + {"fi", "Finnish"}, + {"fj", "Fiji"}, + {"fo", "Faeroese"}, + {"fr", "French"}, + {"fy", "Frisian"}, + {"ga", "Irish"}, + {"gd", "Gaelic"}, + {"gl", "Galician"}, + {"gn", "Guarani"}, + {"gu", "Gujarati"}, + {"ha", "Hausa"}, + {"he", "Hebrew"}, + {"hi", "Hindi"}, + {"hr", "Croatian"}, + {"hu", "Hungarian"}, + {"hy", "Armenian"}, + {"ia", "Interlingua"}, + {"id", "Indonesian"}, + {"ie", "Interlingue"}, + {"ik", "Inupiak"}, + {"in", "Indonesian"}, // compatibility + {"is", "Icelandic"}, + {"it", "Italian"}, + {"iw", "Hebrew"}, // compatibility + {"ja", "Japanese"}, + {"ji", "Yiddish"}, // compatibility + {"jw", "Javanese"}, + {"ka", "Georgian"}, + {"kk", "Kazakh"}, + {"kl", "Greenlandic"}, + {"km", "Cambodian"}, + {"kn", "Kannada"}, + {"ko", "Korean"}, + {"ks", "Kashmiri"}, + {"ku", "Kurdish"}, + {"ky", "Kirghiz"}, + {"la", "Latin"}, + {"ln", "Lingala"}, + {"lo", "Laothian"}, + {"lt", "Lithuanian"}, + {"lv", "Latvian"}, + {"mg", "Malagasy"}, + {"mi", "Maori"}, + {"mk", "Macedonian"}, + {"ml", "Malayalam"}, + {"mn", "Mongolian"}, + {"mo", "Moldavian"}, + {"mr", "Marathi"}, + {"ms", "Malay"}, + {"mt", "Maltese"}, + {"my", "Burmese"}, + {"na", "Nauru"}, + {"ne", "Nepali"}, + {"nl", "Dutch"}, + {"no", "Norwegian"}, + {"oc", "Occitan"}, + {"om", "Oromo"}, + {"or", "Oriya"}, + {"pa", "Punjabi"}, + {"pl", "Polish"}, + {"ps", "Pashto"}, + {"pt", "Portuguese"}, + {"qu", "Quechua"}, + {"rm", "Rhaeto-Romance"}, + {"rn", "Kirundi"}, + {"ro", "Romanian"}, + {"ru", "Russian"}, + {"rw", "Kinyarwanda"}, + {"sa", "Sanskrit"}, + {"sd", "Sindhi"}, + {"sg", "Sangro"}, + {"sh", "Serbo-Croatian"}, + {"si", "Singhalese"}, + {"sk", "Slovak"}, + {"sl", "Slovenian"}, + {"sm", "Samoan"}, + {"sn", "Shona"}, + {"so", "Somali"}, + {"sq", "Albanian"}, + {"sr", "Serbian"}, + {"ss", "Siswati"}, + {"st", "Sesotho"}, + {"su", "Sudanese"}, + {"sv", "Swedish"}, + {"sw", "Swahili"}, + {"ta", "Tamil"}, + {"te", "Tegulu"}, + {"tg", "Tajik"}, + {"th", "Thai"}, + {"ti", "Tigrinya"}, + {"tk", "Turkmen"}, + {"tl", "Tagalog"}, + {"tn", "Setswana"}, + {"to", "Tonga"}, + {"tr", "Turkish"}, + {"ts", "Tsonga"}, + {"tt", "Tatar"}, + {"tw", "Twi"}, + {"uk", "Ukrainian"}, + {"ur", "Urdu"}, + {"uz", "Uzbek"}, + {"vi", "Vietnamese"}, + {"vo", "Volapuk"}, + {"wo", "Wolof"}, + {"xh", "Xhosa"}, + {"yi", "Yiddish"}, + {"yo", "Yoruba"}, + {"zh", "Chinese"}, + {"zu", "Zulu"}, +}; + +void GetLanguageInfos( vector &vAddTo ) +{ + for( unsigned i=0; i &sSource) +{ + if( sSource.empty() ) + return RString(); + + RString sTmp; + + for( unsigned iNum = 0; iNum < sSource.size()-1; iNum++ ) + { + sTmp += sSource[iNum]; + sTmp += sDeliminator; + } + sTmp += sSource.back(); + return sTmp; +} + +RString join( const RString &sDelimitor, vector::const_iterator begin, vector::const_iterator end ) +{ + if( begin == end ) + return RString(); + + RString sRet; + while( begin != end ) + { + sRet += *begin; + ++begin; + if( begin != end ) + sRet += sDelimitor; + } + + return sRet; +} + +RString SmEscape( const RString &sUnescaped ) +{ + return SmEscape( sUnescaped.c_str(), sUnescaped.size() ); +} + +RString SmEscape( const char *cUnescaped, int len ) +{ + RString answer = ""; + for( int i = 0; i < len; ++i ) + { + // Other characters we could theoretically escape: + // NotesWriterSM.cpp used to claim ',' should be escaped, but there was no explanation why + // '#' is both a control character and a valid part of a parameter. The only way for there to be + // any confusion is in a misformatted .sm file, though, so it is unnecessary to escape it. + if( cUnescaped[i] == '/' && i + 1 < len && cUnescaped[i + 1] == '/' ) + { + answer += "\\/\\/"; + ++i; // increment here so we skip both //s + continue; + } + if( cUnescaped[i] == '\\' || cUnescaped[i] == ':' || cUnescaped[i] == ';' ) + answer += "\\"; + answer += cUnescaped[i]; + } + return answer; +} + +RString DwiEscape( const RString &sUnescaped ) +{ + return DwiEscape( sUnescaped.c_str(), sUnescaped.size() ); +} + +RString DwiEscape( const char *cUnescaped, int len ) +{ + RString answer = ""; + for( int i = 0; i < len; ++i ) + { + switch( cUnescaped[i] ) + { + // TODO: Which of these characters actually affect DWI? + case '\\': + case ':': + case ';': answer += '|'; break; + case '[': answer += '('; break; + case ']': answer += ')'; break; + default: answer += cUnescaped[i]; + } + } + return answer; +} + +template +static int DelimitorLength( const S &Delimitor ) +{ + return Delimitor.size(); +} + +static int DelimitorLength( char Delimitor ) +{ + return 1; +} + +static int DelimitorLength( wchar_t Delimitor ) +{ + return 1; +} + +template +void do_split( const S &Source, const C Delimitor, vector &AddIt, const bool bIgnoreEmpty ) +{ + /* Short-circuit if the source is empty; we want to return an empty vector if + * the string is empty, even if bIgnoreEmpty is true. */ + if( Source.empty() ) + return; + + size_t startpos = 0; + + do { + size_t pos; + pos = Source.find( Delimitor, startpos ); + if( pos == Source.npos ) + pos = Source.size(); + + if( pos-startpos > 0 || !bIgnoreEmpty ) + { + /* Optimization: if we're copying the whole string, avoid substr; this + * allows this copy to be refcounted, which is much faster. */ + if( startpos == 0 && pos-startpos == Source.size() ) + AddIt.push_back(Source); + else + { + const S AddRString = Source.substr(startpos, pos-startpos); + AddIt.push_back(AddRString); + } + } + + startpos = pos+DelimitorLength(Delimitor); + } while ( startpos <= Source.size() ); +} + +void split( const RString &sSource, const RString &sDelimitor, vector &asAddIt, const bool bIgnoreEmpty ) +{ + if( sDelimitor.size() == 1 ) + do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty ); + else + do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty ); +} + +void split( const wstring &sSource, const wstring &sDelimitor, vector &asAddIt, const bool bIgnoreEmpty ) +{ + if( sDelimitor.size() == 1 ) + do_split( sSource, sDelimitor[0], asAddIt, bIgnoreEmpty ); + else + do_split( sSource, sDelimitor, asAddIt, bIgnoreEmpty ); +} + +/* Use: + +RString str="a,b,c"; +int start = 0, size = -1; +while( 1 ) +{ + do_split( str, ",", start, size ); + if( start == str.size() ) + break; + str[start] = 'Q'; +} + +*/ + +template +void do_split( const S &Source, const S &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty ) +{ + if( size != -1 ) + { + // Start points to the beginning of the last delimiter. Move it up. + begin += size+Delimitor.size(); + begin = min( begin, len ); + } + + size = 0; + + if( bIgnoreEmpty ) + { + // Skip delims. + while( begin + Delimitor.size() < Source.size() && + !Source.compare( begin, Delimitor.size(), Delimitor ) ) + ++begin; + } + + /* Where's the string function to find within a substring? + * C++ strings apparently are missing that ... */ + size_t pos; + if( Delimitor.size() == 1 ) + pos = Source.find( Delimitor[0], begin ); + else + pos = Source.find( Delimitor, begin ); + if( pos == Source.npos || (int) pos > len ) + pos = len; + size = pos - begin; +} + +void split( const RString &Source, const RString &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty ) +{ + do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty ); +} + +void split( const wstring &Source, const wstring &Delimitor, int &begin, int &size, int len, const bool bIgnoreEmpty ) +{ + do_split( Source, Delimitor, begin, size, len, bIgnoreEmpty ); +} + +void split( const RString &Source, const RString &Delimitor, int &begin, int &size, const bool bIgnoreEmpty ) +{ + do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty ); +} + +void split( const wstring &Source, const wstring &Delimitor, int &begin, int &size, const bool bIgnoreEmpty ) +{ + do_split( Source, Delimitor, begin, size, Source.size(), bIgnoreEmpty ); +} + +/* + * foo\fum\ -> "foo\fum\", "", "" + * c:\foo\bar.txt -> "c:\foo\", "bar", ".txt" + * \\foo\fum -> "\\foo\", "fum", "" + */ +void splitpath( const RString &sPath, RString &sDir, RString &sFilename, RString &sExt ) +{ + sDir = sFilename = sExt = ""; + + vector asMatches; + + /* + * One level of escapes for the regex, one for C. Ew. + * This is really: + * ^(.*[\\/])?(.*)$ + */ + static Regex sep("^(.*[\\\\/])?(.*)$"); + bool bCheck = sep.Compare( sPath, asMatches ); + ASSERT( bCheck ); + + sDir = asMatches[0]; + const RString sBase = asMatches[1]; + + /* ^(.*)(\.[^\.]+)$ */ + static Regex SplitExt("^(.*)(\\.[^\\.]+)$"); + if( SplitExt.Compare(sBase, asMatches) ) + { + sFilename = asMatches[0]; + sExt = asMatches[1]; + } + else + { + sFilename = sBase; + } +} + +/* "foo.bar", "baz" -> "foo.baz" + * "foo", "baz" -> "foo.baz" + * "foo.bar", "" -> "foo" */ +RString SetExtension( const RString &sPath, const RString &sExt ) +{ + RString sDir, sFileName, sOldExt; + splitpath( sPath, sDir, sFileName, sOldExt ); + return sDir + sFileName + (sExt.size()? ".":"") + sExt; +} + +RString GetExtension( const RString &sPath ) +{ + size_t pos = sPath.rfind( '.' ); + if( pos == sPath.npos ) + return RString(); + + size_t slash = sPath.find( '/', pos ); + if( slash != sPath.npos ) + return RString(); /* rare: path/dir.ext/fn */ + + return sPath.substr( pos+1, sPath.size()-pos+1 ); +} + +RString GetFileNameWithoutExtension( const RString &sPath ) +{ + RString sThrowAway, sFName; + splitpath( sPath, sThrowAway, sFName, sThrowAway ); + return sFName; +} + +void MakeValidFilename( RString &sName ) +{ + wstring wsName = RStringToWstring( sName ); + wstring wsInvalid = L"/\\:*?\"<>|"; + for( unsigned i = 0; i < wsName.size(); ++i ) + { + wchar_t w = wsName[i]; + if( w >= 32 && + w < 126 && + wsInvalid.find_first_of(w) == wsInvalid.npos ) + continue; + + if( w == L'"' ) + { + wsName[i] = L'\''; + continue; + } + + /* We could replace with closest matches in ASCII: convert the character + * to UTF-8 NFD (decomposed) (maybe NFKD?), and see if the first + * character is ASCII. This is useless for non-Western languages, + * since we'll replace the whole filename. */ + wsName[i] = '_'; + } + + sName = WStringToRString( wsName ); +} + +int g_argc = 0; +char **g_argv = NULL; + +void SetCommandlineArguments( int argc, char **argv ) +{ + g_argc = argc; + g_argv = argv; +} + +void GetCommandLineArguments( int &argc, char **&argv ) +{ + argc = g_argc; + argv = g_argv; +} + +/* Search for the commandline argument given; eg. "test" searches for the + * option "--test". All commandline arguments are getopt_long style: --foo; + * short arguments (-x) are not supported. (These are not intended for + * common, general use, so having short options isn't currently needed.) + * If argument is non-NULL, accept an argument. */ +bool GetCommandlineArgument( const RString &option, RString *argument, int iIndex ) +{ + const RString optstr = "--" + option; + + for( int arg = 1; arg < g_argc; ++arg ) + { + const RString CurArgument = g_argv[arg]; + + const size_t i = CurArgument.find( "=" ); + RString CurOption = CurArgument.substr(0,i); + if( CurOption.CompareNoCase(optstr) ) + continue; // no match + + // Found it. + if( iIndex ) + { + --iIndex; + continue; + } + + if( argument ) + { + if( i != RString::npos ) + *argument = CurArgument.substr( i+1 ); + else + *argument = ""; + } + + return true; + } + + return false; +} + +RString GetCwd() +{ + char buf[PATH_MAX]; + bool ret = getcwd(buf, PATH_MAX) != NULL; + ASSERT(ret); + return buf; +} + +/* + * Calculate a standard CRC32. iCRC should be initialized to 0. + * References: + * http://www.theorem.com/java/CRC32.java, + * http://www.faqs.org/rfcs/rfc1952.html + */ +void CRC32( unsigned int &iCRC, const void *pVoidBuffer, size_t iSize ) +{ + static unsigned tab[256]; + static bool initted = false; + if( !initted ) + { + initted = true; + const unsigned POLY = 0xEDB88320; + + for( int i = 0; i < 256; ++i ) + { + tab[i] = i; + for( int j = 0; j < 8; ++j ) + { + if( tab[i] & 1 ) + tab[i] = (tab[i] >> 1) ^ POLY; + else + tab[i] >>= 1; + } + } + } + + iCRC ^= 0xFFFFFFFF; + + const char *pBuffer = (const char *) pVoidBuffer; + for( unsigned i = 0; i < iSize; ++i ) + iCRC = (iCRC >> 8) ^ tab[(iCRC ^ pBuffer[i]) & 0xFF]; + + iCRC ^= 0xFFFFFFFF; +} + +unsigned int GetHashForString( const RString &s ) +{ + unsigned crc = 0; + CRC32( crc, s.data(), s.size() ); + return crc; +} + +/* Return true if "dir" is empty or does not exist. */ +bool DirectoryIsEmpty( const RString &sDir ) +{ + if( sDir.empty() ) + return true; + if( !DoesFileExist(sDir) ) + return true; + + vector asFileNames; + GetDirListing( sDir, asFileNames ); + return asFileNames.empty(); +} + +bool CompareRStringsAsc( const RString &sStr1, const RString &sStr2 ) +{ + return sStr1.CompareNoCase( sStr2 ) < 0; +} + +bool CompareRStringsDesc( const RString &sStr1, const RString &sStr2 ) +{ + return sStr1.CompareNoCase( sStr2 ) > 0; +} + +void SortRStringArray( vector &arrayRStrings, const bool bSortAscending ) +{ + sort( arrayRStrings.begin(), arrayRStrings.end(), + bSortAscending?CompareRStringsAsc:CompareRStringsDesc ); +} + +float calc_mean( const float *pStart, const float *pEnd ) +{ + return accumulate( pStart, pEnd, 0.f ) / distance( pStart, pEnd ); +} + +float calc_stddev( const float *pStart, const float *pEnd, bool bSample ) +{ + /* Calculate the mean. */ + float fMean = calc_mean( pStart, pEnd ); + + /* Calculate stddev. */ + float fDev = 0.0f; + for( const float *i=pStart; i != pEnd; ++i ) + fDev += (*i - fMean) * (*i - fMean); + fDev /= distance( pStart, pEnd ) - (bSample ? 1 : 0); + fDev = sqrtf( fDev ); + + return fDev; +} + +bool CalcLeastSquares( const vector< pair > &vCoordinates, + float &fSlope, float &fIntercept, float &fError ) +{ + if( vCoordinates.empty() ) + return false; + float fSumXX = 0.0f, fSumXY = 0.0f, fSumX = 0.0f, fSumY = 0.0f; + for( unsigned i = 0; i < vCoordinates.size(); ++i ) + { + fSumXX += vCoordinates[i].first * vCoordinates[i].first; + fSumXY += vCoordinates[i].first * vCoordinates[i].second; + fSumX += vCoordinates[i].first; + fSumY += vCoordinates[i].second; + } + const float fDenominator = vCoordinates.size() * fSumXX - fSumX * fSumX; + fSlope = (vCoordinates.size() * fSumXY - fSumX * fSumY) / fDenominator; + fIntercept = (fSumXX * fSumY - fSumX * fSumXY) / fDenominator; + + fError = 0.0f; + for( unsigned i = 0; i < vCoordinates.size(); ++i ) + { + const float fOneError = fIntercept + fSlope * vCoordinates[i].first - vCoordinates[i].second; + fError += fOneError * fOneError; + } + fError /= vCoordinates.size(); + fError = sqrtf( fError ); + return true; +} + +void FilterHighErrorPoints( vector< pair > &vCoordinates, + float fSlope, float fIntercept, float fCutoff ) +{ + unsigned int iOut = 0; + for( unsigned int iIn = 0; iIn < vCoordinates.size(); ++iIn ) + { + const float fError = fIntercept + fSlope * vCoordinates[iIn].first - vCoordinates[iIn].second; + if( fabsf(fError) < fCutoff ) + { + vCoordinates[iOut] = vCoordinates[iIn]; + ++iOut; + } + } + vCoordinates.resize( iOut ); +} + +void TrimLeft( RString &sStr, const char *s ) +{ + int n = 0; + while( n < int(sStr.size()) && strchr(s, sStr[n]) ) + n++; + + sStr.erase( sStr.begin(), sStr.begin()+n ); +} + +void TrimRight( RString &sStr, const char *s ) +{ + int n = sStr.size(); + while( n > 0 && strchr(s, sStr[n-1]) ) + n--; + + /* Delete from n to the end. If n == sStr.size(), nothing is deleted; + * if n == 0, the whole string is erased. */ + sStr.erase( sStr.begin()+n, sStr.end() ); +} + +void Trim( RString &sStr, const char *s ) +{ + RString::size_type b = 0, e = sStr.size(); + while( b < e && strchr(s, sStr[b]) ) + ++b; + while( b < e && strchr(s, sStr[e-1]) ) + --e; + sStr.assign( sStr.substr(b, e-b) ); +} + +void StripCrnl( RString &s ) +{ + while( s.size() && (s[s.size()-1] == '\r' || s[s.size()-1] == '\n') ) + s.erase( s.size()-1 ); +} + +bool BeginsWith( const RString &sTestThis, const RString &sBeginning ) +{ + ASSERT( !sBeginning.empty() ); + return sTestThis.compare( 0, sBeginning.length(), sBeginning ) == 0; +} + +bool EndsWith( const RString &sTestThis, const RString &sEnding ) +{ + ASSERT( !sEnding.empty() ); + if( sTestThis.size() < sEnding.size() ) + return false; + return sTestThis.compare( sTestThis.length()-sEnding.length(), sEnding.length(), sEnding ) == 0; +} + +RString URLEncode( const RString &sStr ) +{ + RString sOutput; + for( unsigned k = 0; k < sStr.size(); k++ ) + { + char t = sStr[k]; + if( t >= '!' && t <= 'z' ) + sOutput += t; + else + sOutput += "%" + ssprintf( "%02X", t ); + } + return sOutput; +} + +// remove various version control-related files +static bool CVSOrSVN( const RString& s ) +{ + return s.Right(3).EqualsNoCase("CVS") || + s.Right(4) == ".svn" || + s.Right(3).EqualsNoCase(".hg"); +} + +void StripCvsAndSvn( vector &vs ) +{ + RemoveIf( vs, CVSOrSVN ); +} + +static bool MacResourceFork( const RString& s ) +{ + return s.Left(2).EqualsNoCase("._"); +} + +void StripMacResourceForks( vector &vs ) +{ + RemoveIf( vs, MacResourceFork ); +} + +// path is a .redir pathname. Read it and return the real one. +RString DerefRedir( const RString &_path ) +{ + RString sPath = _path; + + for( int i=0; i<100; i++ ) + { + if( GetExtension(sPath) != "redir" ) + return sPath; + + RString sNewFileName; + GetFileContents( sPath, sNewFileName, true ); + + // Empty is invalid. + if( sNewFileName == "" ) + return RString(); + + RString sPath2 = Dirname(sPath) + sNewFileName; + + CollapsePath( sPath2 ); + + sPath2 += "*"; + + vector matches; + GetDirListing( sPath2, matches, false, true ); + + if( matches.empty() ) + RageException::Throw( "The redirect \"%s\" references a file \"%s\" which doesn't exist.", sPath.c_str(), sPath2.c_str() ); + else if( matches.size() > 1 ) + RageException::Throw( "The redirect \"%s\" references a file \"%s\" with multiple matches.", sPath.c_str(), sPath2.c_str() ); + + sPath = matches[0]; + } + + RageException::Throw( "Circular redirect \"%s\".", sPath.c_str() ); +} + +bool GetFileContents( const RString &sPath, RString &sOut, bool bOneLine ) +{ + // Don't warn if the file doesn't exist, but do warn if it exists and fails to open. + if( !IsAFile(sPath) ) + return false; + + RageFile file; + if( !file.Open(sPath) ) + { + LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() ); + return false; + } + + // todo: figure out how to make this UTF-8 safe. -aj + RString sData; + int iGot; + if( bOneLine ) + iGot = file.GetLine( sData ); + else + iGot = file.Read( sData, file.GetFileSize() ); + + if( iGot == -1 ) + { + LOG->Warn( "GetFileContents(%s): %s", sPath.c_str(), file.GetError().c_str() ); + return false; + } + + if( bOneLine ) + StripCrnl( sData ); + + sOut = sData; + return true; +} + +bool GetFileContents( const RString &sFile, vector &asOut ) +{ + RageFile file; + if( !file.Open(sFile) ) + { + LOG->Warn( "GetFileContents(%s): %s", sFile.c_str(), file.GetError().c_str() ); + return false; + } + + RString sLine; + while( file.GetLine(sLine) ) + asOut.push_back( sLine ); + return true; +} + +#include "../extern/pcre/pcre.h" +void Regex::Compile() +{ + const char *error; + int offset; + m_pReg = pcre_compile( m_sPattern.c_str(), PCRE_CASELESS, &error, &offset, NULL ); + + if( m_pReg == NULL ) + RageException::Throw( "Invalid regex: \"%s\" (%s).", m_sPattern.c_str(), error ); + + int iRet = pcre_fullinfo( (pcre *) m_pReg, NULL, PCRE_INFO_CAPTURECOUNT, &m_iBackrefs ); + ASSERT( iRet >= 0 ); + + ++m_iBackrefs; + ASSERT( m_iBackrefs < 128 ); +} + +void Regex::Set( const RString &sStr ) +{ + Release(); + m_sPattern = sStr; + Compile(); +} + +void Regex::Release() +{ + pcre_free( m_pReg ); + m_pReg = NULL; + m_sPattern = RString(); +} + +Regex::Regex( const RString &sStr ): m_pReg(NULL), m_iBackrefs(0), m_sPattern(RString()) +{ + Set( sStr ); +} + +Regex::Regex( const Regex &rhs ): m_pReg(NULL), m_iBackrefs(0), m_sPattern(RString()) +{ + Set( rhs.m_sPattern ); +} + +Regex &Regex::operator=( const Regex &rhs ) +{ + if( this != &rhs ) + Set( rhs.m_sPattern ); + return *this; +} + +Regex::~Regex() +{ + Release(); +} + +bool Regex::Compare( const RString &sStr ) +{ + int iMat[128*3]; + int iRet = pcre_exec( (pcre *) m_pReg, NULL, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 ); + + if( iRet < -1 ) + RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet ); + + return iRet >= 0; +} + +bool Regex::Compare( const RString &sStr, vector &asMatches ) +{ + asMatches.clear(); + + int iMat[128*3]; + int iRet = pcre_exec( (pcre *) m_pReg, NULL, sStr.data(), sStr.size(), 0, 0, iMat, 128*3 ); + + if( iRet < -1 ) + RageException::Throw( "Unexpected return from pcre_exec('%s'): %i.", m_sPattern.c_str(), iRet ); + + if( iRet == -1 ) + return false; + + for( unsigned i = 1; i < m_iBackrefs; ++i ) + { + const int iStart = iMat[i*2], end = iMat[i*2+1]; + if( iStart == -1 ) + asMatches.push_back( RString() ); /* no match */ + else + asMatches.push_back( sStr.substr(iStart, end - iStart) ); + } + + return true; +} + +// Arguments and behavior are the same are similar to +// http://us3.php.net/manual/en/function.preg-replace.php +bool Regex::Replace( const RString &sReplacement, const RString &sSubject, RString &sOut ) +{ + vector asMatches; + if( !Compare(sSubject, asMatches) ) + return false; + + sOut = sReplacement; + + // TODO: optimize me by iterating only once over the string + for( unsigned i=0; i= s.size() ) + return false; + + if( is_utf8_continuation_byte( s[start] ) || /* misplaced continuation byte */ + (s[start] & 0xFE) == 0xFE ) /* 0xFE, 0xFF */ + { + start += 1; + return false; + } + + int len = utf8_get_char_len( s[start] ); + + const int first_byte_mask[] = { -1, 0x7F, 0x1F, 0x0F, 0x07, 0x03, 0x01 }; + + ch = wchar_t(s[start] & first_byte_mask[len]); + + for( int i = 1; i < len; ++i ) + { + if( start+i >= s.size() ) + { + /* We expected a continuation byte, but didn't get one. Return error, and point + * start at the unexpected byte; it's probably a new sequence. */ + start += i; + return false; + } + + char byte = s[start+i]; + if( !is_utf8_continuation_byte(byte) ) + { + /* We expected a continuation byte, but didn't get one. Return error, and point + * start at the unexpected byte; it's probably a new sequence. */ + start += i; + return false; + } + ch = (ch << 6) | (byte & 0x3F); + } + + bool bValid = true; + { + unsigned c1 = (unsigned) s[start] & 0xFF; + unsigned c2 = (unsigned) s[start+1] & 0xFF; + int c = (c1 << 8) + c2; + if( (c & 0xFE00) == 0xC000 || + (c & 0xFFE0) == 0xE080 || + (c & 0xFFF0) == 0xF080 || + (c & 0xFFF8) == 0xF880 || + (c & 0xFFFC) == 0xFC80 ) + { + bValid = false; + } + } + + if( ch == 0xFFFE || ch == 0xFFFF ) + bValid = false; + + start += len; + return bValid; +} + +/* Like utf8_to_wchar_ec, but only does enough error checking to prevent crashing. */ +bool utf8_to_wchar( const char *s, size_t iLength, unsigned &start, wchar_t &ch ) +{ + if( start >= iLength ) + return false; + + int len = utf8_get_char_len( s[start] ); + + if( start+len > iLength ) + { + // We don't have room for enough continuation bytes. Return error. + start += len; + ch = L'?'; + return false; + } + + switch( len ) + { + case 1: + ch = (s[start+0] & 0x7F); + break; + case 2: + ch = ( (s[start+0] & 0x1F) << 6 ) | + (s[start+1] & 0x3F); + break; + case 3: + ch = ( (s[start+0] & 0x0F) << 12 ) | + ( (s[start+1] & 0x3F) << 6 ) | + (s[start+2] & 0x3F); + break; + case 4: + ch = ( (s[start+0] & 0x07) << 18 ) | + ( (s[start+1] & 0x3F) << 12 ) | + ( (s[start+2] & 0x3F) << 6 ) | + (s[start+3] & 0x3F); + break; + case 5: + ch = ( (s[start+0] & 0x03) << 24 ) | + ( (s[start+1] & 0x3F) << 18 ) | + ( (s[start+2] & 0x3F) << 12 ) | + ( (s[start+3] & 0x3F) << 6 ) | + (s[start+4] & 0x3F); + break; + + case 6: + ch = ( (s[start+0] & 0x01) << 30 ) | + ( (s[start+1] & 0x3F) << 24 ) | + ( (s[start+2] & 0x3F) << 18 ) | + ( (s[start+3] & 0x3F) << 12) | + ( (s[start+4] & 0x3F) << 6 ) | + (s[start+5] & 0x3F); + break; + + } + + start += len; + return true; +} + + +// UTF-8 encode ch and append to out. +void wchar_to_utf8( wchar_t ch, RString &out ) +{ + if( ch < 0x80 ) { out.append( 1, (char) ch ); return; } + + int cbytes = 0; + if( ch < 0x800 ) cbytes = 1; + else if( ch < 0x10000 ) cbytes = 2; + else if( ch < 0x200000 ) cbytes = 3; + else if( ch < 0x4000000 ) cbytes = 4; + else cbytes = 5; + + { + int shift = cbytes*6; + const int init_masks[] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + out.append( 1, (char) (init_masks[cbytes-1] | (ch>>shift)) ); + } + + for( int i = 0; i < cbytes; ++i ) + { + int shift = (cbytes-i-1)*6; + out.append( 1, (char) (0x80 | ((ch>>shift)&0x3F)) ); + } +} + +wchar_t utf8_get_char( const RString &s ) +{ + unsigned start = 0; + wchar_t ret; + if( !utf8_to_wchar_ec( s, start, ret ) ) + return INVALID_CHAR; + return ret; +} + +// Replace invalid sequences in s. +void utf8_sanitize( RString &s ) +{ + RString ret; + for( unsigned start = 0; start < s.size(); ) + { + wchar_t ch; + if( !utf8_to_wchar_ec( s, start, ch ) ) + ch = INVALID_CHAR; + + wchar_to_utf8( ch, ret ); + } + + s = ret; +} + +bool utf8_is_valid( const RString &s ) +{ + for( unsigned start = 0; start < s.size(); ) + { + wchar_t ch; + if( !utf8_to_wchar_ec( s, start, ch ) ) + return false; + } + return true; +} + +/* Windows tends to drop garbage BOM characters at the start of UTF-8 text files. + * Remove them. */ +void utf8_remove_bom( RString &sLine ) +{ + if( !sLine.compare(0, 3, "\xef\xbb\xbf") ) + sLine.erase(0, 3); +} + +static int UnicodeDoUpper( char *p, size_t iLen, const unsigned char pMapping[256] ) +{ + // Note: this has problems with certain accented characters. -aj + wchar_t wc = L'\0'; + unsigned iStart = 0; + if( !utf8_to_wchar(p, iLen, iStart, wc) ) + return 1; + + wchar_t iUpper = wc; + if( wc < 256 ) + iUpper = pMapping[wc]; + if( iUpper != wc ) + { + RString sOut; + wchar_to_utf8( iUpper, sOut ); + if( sOut.size() == iStart ) + memcpy( p, sOut.data(), sOut.size() ); + else + WARN( ssprintf("UnicodeDoUpper: invalid character at \"%s\"", RString(p,iLen).c_str()) ); + } + + return iStart; +} + +/* Fast in-place MakeUpper and MakeLower. This only replaces characters with characters of the same UTF-8 + * length, so we never have to move the whole string. This is optimized for strings that have no + * non-ASCII characters. */ +void MakeUpper( char *p, size_t iLen ) +{ + char *pStart = p; + char *pEnd = p + iLen; + while( p < pEnd ) + { + // Fast path: + if( likely( !(*p & 0x80) ) ) + { + if( unlikely(*p >= 'a' && *p <= 'z') ) + *p += 'A' - 'a'; + ++p; + continue; + } + + int iRemaining = iLen - (p-pStart); + p += UnicodeDoUpper( p, iRemaining, g_UpperCase ); + } +} + +void MakeLower( char *p, size_t iLen ) +{ + char *pStart = p; + char *pEnd = p + iLen; + while( p < pEnd ) + { + // Fast path: + if( likely( !(*p & 0x80) ) ) + { + if( unlikely(*p >= 'A' && *p <= 'Z') ) + *p -= 'A' - 'a'; + ++p; + continue; + } + + int iRemaining = iLen - (p-pStart); + p += UnicodeDoUpper( p, iRemaining, g_LowerCase ); + } +} + +void UnicodeUpperLower( wchar_t *p, size_t iLen, const unsigned char pMapping[256] ) +{ + wchar_t *pEnd = p + iLen; + while( p != pEnd ) + { + if( *p < 256 ) + *p = pMapping[*p]; + ++p; + } +} + +void MakeUpper( wchar_t *p, size_t iLen ) +{ + UnicodeUpperLower( p, iLen, g_UpperCase ); +} + +void MakeLower( wchar_t *p, size_t iLen ) +{ + UnicodeUpperLower( p, iLen, g_LowerCase ); +} + +int StringToInt( const RString &sString ) +{ + int ret; + istringstream ( sString ) >> ret; + return ret; +} + +RString IntToString( const int &iNum ) +{ + stringstream ss; + ss << iNum; + return ss.str(); +} + +float StringToFloat( const RString &sString ) +{ + float ret = strtof( sString, NULL ); + + if( !isfinite(ret) ) + ret = 0.0f; + return ret; +} + +bool StringToFloat( const RString &sString, float &fOut ) +{ + char *endPtr; + + fOut = strtof( sString, &endPtr ); + return sString.size() && *endPtr == '\0' && isfinite( fOut ); +} + +RString FloatToString( const float &num ) +{ + stringstream ss; + ss << num; + return ss.str(); +} + +const wchar_t INVALID_CHAR = 0xFFFD; /* U+FFFD REPLACEMENT CHARACTER */ + +wstring RStringToWstring( const RString &s ) +{ + wstring ret; + ret.reserve( s.size() ); + for( unsigned start = 0; start < s.size(); ) + { + char c = s[start]; + if( !(c&0x80) ) + { + // ASCII fast path + ret += c; + ++start; + continue; + } + + wchar_t ch = L'\0'; + if( !utf8_to_wchar( s.data(), s.size(), start, ch ) ) + ch = INVALID_CHAR; + ret += ch; + } + + return ret; +} + +RString WStringToRString( const wstring &sStr ) +{ + RString sRet; + + for( unsigned i = 0; i < sStr.size(); ++i ) + wchar_to_utf8( sStr[i], sRet ); + + return sRet; +} + +RString WcharToUTF8( wchar_t c ) +{ + RString ret; + wchar_to_utf8( c, ret ); + return ret; +} + +// &a; -> a +void ReplaceEntityText( RString &sText, const map &m ) +{ + RString sRet; + + size_t iOffset = 0; + while( iOffset != sText.size() ) + { + size_t iStart = sText.find( '&', iOffset ); + if( iStart == sText.npos ) + { + // Optimization: if we didn't replace anything at all, do nothing. + if( iOffset == 0 ) + return; + + // Append the rest of the string. + sRet.append( sText, iOffset, sRet.npos ); + break; + } + + // Append the text between iOffset and iStart. + sRet.append( sText, iOffset, iStart-iOffset ); + iOffset += iStart-iOffset; + + // Optimization: stop early on "&", so "&&&&&&&&&&&" isn't n^2. + size_t iEnd = sText.find_first_of( "&;", iStart+1 ); + if( iEnd == sText.npos || sText[iEnd] == '&' ) + { + // & with no matching ;, or two & in a row. Append the & and continue. + sRet.append( sText, iStart, 1 ); + ++iOffset; + continue; + } + + RString sElement = sText.substr( iStart+1, iEnd-iStart-1 ); + sElement.MakeLower(); + + map::const_iterator it = m.find( sElement ); + if( it == m.end() ) + { + sRet.append( sText, iStart, iEnd-iStart+1 ); + iOffset = iEnd + 1; + continue; + } + + const RString &sTo = it->second; + sRet.append( sTo ); + iOffset = iEnd + 1; + } + + sText = sRet; +} + +// abcd -> &a; &b; &c; &d; +void ReplaceEntityText( RString &sText, const map &m ) +{ + RString sFind; + + FOREACHM_CONST( char, RString, m, c ) + sFind.append( 1, c->first ); + + RString sRet; + + size_t iOffset = 0; + while( iOffset != sText.size() ) + { + size_t iStart = sText.find_first_of( sFind, iOffset ); + if( iStart == sText.npos ) + { + // Optimization: if we didn't replace anything at all, do nothing. + if( iOffset == 0 ) + return; + + // Append the rest of the string. + sRet.append( sText, iOffset, sRet.npos ); + break; + } + + // Append the text between iOffset and iStart. + sRet.append( sText, iOffset, iStart-iOffset ); + iOffset += iStart-iOffset; + + char sElement = sText[iStart]; + + map::const_iterator it = m.find( sElement ); + ASSERT( it != m.end() ); + + const RString &sTo = it->second; + sRet.append( 1, '&' ); + sRet.append( sTo ); + sRet.append( 1, ';' ); + ++iOffset; + } + + sText = sRet; +} + +// Replace &#nnnn; (decimal) and &xnnnn; (hex) with corresponding UTF-8 characters. +void Replace_Unicode_Markers( RString &sText ) +{ + unsigned iStart = 0; + while( iStart < sText.size() ) + { + // Look for &#digits; + bool bHex = false; + size_t iPos = sText.find( "&#", iStart ); + if( iPos == sText.npos ) + { + bHex = true; + iPos = sText.find( "&x", iStart ); + } + + if( iPos == sText.npos ) + break; + iStart = iPos+1; + + unsigned p = iPos; + p += 2; + + // Found &# or &x. Is it followed by digits and a semicolon? + if( p >= sText.size() ) + continue; + + int iNumDigits = 0; + while( p < sText.size() && bHex? isxdigit(sText[p]):isdigit(sText[p]) ) + { + p++; + iNumDigits++; + } + + if( !iNumDigits ) + continue; // must have at least one digit + if( p >= sText.size() || sText[p] != ';' ) + continue; + p++; + + int iNum; + if( bHex ) + sscanf( sText.c_str()+iPos, "&x%x;", &iNum ); + else + sscanf( sText.c_str()+iPos, "&#%i;", &iNum ); + if( iNum > 0xFFFF ) + iNum = INVALID_CHAR; + + sText.replace( iPos, p-iPos, WcharToUTF8(wchar_t(iNum)) ); + } +} + +// Form a string to identify a wchar_t with ASCII. +RString WcharDisplayText( wchar_t c ) +{ + RString sChr; + sChr = ssprintf( "U+%4.4x", c ); + if( c < 128 ) + sChr += ssprintf( " ('%c')", char(c) ); + return sChr; +} + +/* Return the last named component of dir: + * a/b/c -> c + * a/b/c/ -> c + */ +RString Basename( const RString &sDir ) +{ + size_t iEnd = sDir.find_last_not_of( "/\\" ); + if( iEnd == sDir.npos ) + return RString(); + + size_t iStart = sDir.find_last_of( "/\\", iEnd ); + if( iStart == sDir.npos ) + iStart = 0; + else + ++iStart; + + return sDir.substr( iStart, iEnd-iStart+1 ); +} + +/* Return all but the last named component of dir: + * + * a/b/c -> a/b/ + * a/b/c/ -> a/b/ + * c/ -> ./ + * /foo -> / + * / -> / + */ +RString Dirname( const RString &dir ) +{ + // Special case: "/" -> "/". + if( dir.size() == 1 && dir[0] == '/' ) + return "/"; + + int pos = dir.size()-1; + // Skip trailing slashes. + while( pos >= 0 && dir[pos] == '/' ) + --pos; + + // Skip the last component. + while( pos >= 0 && dir[pos] != '/' ) + --pos; + + if( pos < 0 ) + return "./"; + + return dir.substr(0, pos+1); +} + +RString Capitalize( const RString &s ) +{ + if( s.empty() ) + return RString(); + + RString s2 = s; + char *pBuf = s2.GetBuffer(); + UnicodeDoUpper( pBuf, s2.size(), g_UpperCase ); + s2.ReleaseBuffer(); + + return s2; +} + +unsigned char g_UpperCase[256] = +{ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, + 0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x5B,0x5C,0x5D,0x5E,0x5F, + 0x60,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4A,0x4B,0x4C,0x4D,0x4E,0x4F, + 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5A,0x7B,0x7C,0x7D,0x7E,0x7F, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF, +}; + +unsigned char g_LowerCase[256] = +{ + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F, + 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F, + 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F, + 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0x3E,0x3F, + 0x40,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x5B,0x5C,0x5D,0x5E,0x5F, + 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6A,0x6B,0x6C,0x6D,0x6E,0x6F, + 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F, + 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8A,0x8B,0x8C,0x8D,0x8E,0x8F, + 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9A,0x9B,0x9C,0x9D,0x9E,0x9F, + 0xA0,0xA1,0xA2,0xA3,0xA4,0xA5,0xA6,0xA7,0xA8,0xA9,0xAA,0xAB,0xAC,0xAD,0xAE,0xAF, + 0xB0,0xB1,0xB2,0xB3,0xB4,0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xBB,0xBC,0xBD,0xBE,0xBF, + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xD7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xDF, + 0xC0,0xC1,0xC2,0xC3,0xC4,0xC5,0xC6,0xC7,0xC8,0xC9,0xCA,0xCB,0xCC,0xCD,0xCE,0xCF, + 0xD0,0xD1,0xD2,0xD3,0xD4,0xD5,0xD6,0xF7,0xD8,0xD9,0xDA,0xDB,0xDC,0xDD,0xDE,0xFF, +}; + +void FixSlashesInPlace( RString &sPath ) +{ + for( unsigned i = 0; i < sPath.size(); ++i ) + if( sPath[i] == '\\' ) + sPath[i] = '/'; +} + +/* Keep trailing slashes, since that can be used to illustrate that a path always + * represents a directory. + * + * foo/bar -> foo/bar + * foo/bar/ -> foo/bar/ + * foo///bar/// -> foo/bar/ + * foo/bar/./baz -> foo/bar/baz + * foo/bar/../baz -> foo/baz + * ../foo -> ../foo + * ../../foo -> ../../foo + * ./foo -> foo (if bRemoveLeadingDot), ./foo (if !bRemoveLeadingDot) + * ./ -> ./ + * ./// -> ./ + */ + +void CollapsePath( RString &sPath, bool bRemoveLeadingDot ) +{ + RString sOut; + sOut.reserve( sPath.size() ); + + size_t iPos = 0; + size_t iNext; + for( ; iPos < sPath.size(); iPos = iNext ) + { + // Find the next slash. + iNext = sPath.find( '/', iPos ); + if( iNext == RString::npos ) + iNext = sPath.size(); + else + ++iNext; + + /* Strip extra slashes, but don't remove slashes from the beginning of the string. */ + if( iNext - iPos == 1 && sPath[iPos] == '/' ) + { + if( !sOut.empty() ) + continue; + } + + // If this is a dot, skip it. + if( iNext - iPos == 2 && sPath[iPos] == '.' && sPath[iPos+1] == '/' ) + { + if( bRemoveLeadingDot || !sOut.empty() ) + continue; + } + + // If this is two dots, + if( iNext - iPos == 3 && sPath[iPos] == '.' && sPath[iPos+1] == '.' && sPath[iPos+2] == '/' ) + { + /* If this is the first path element (nothing to delete), + * or all we have is a slash, leave it. */ + if( sOut.empty() || (sOut.size() == 1 && sOut[0] == '/') ) + { + sOut.append( sPath, iPos, iNext-iPos ); + continue; + } + + // Search backwards for the previous path element. + size_t iPrev = sOut.rfind( '/', sOut.size()-2 ); + if( iPrev == RString::npos ) + iPrev = 0; + else + ++iPrev; + + // If the previous element is also .., leave it. + bool bLastIsTwoDots = (sOut.size() - iPrev == 3 && sOut[iPrev] == '.' && sOut[iPrev+1] == '.' ); + if( bLastIsTwoDots ) + { + sOut.append( sPath, iPos, iNext-iPos ); + continue; + } + + sOut.erase( iPrev ); + continue; + } + + sOut.append( sPath, iPos, iNext-iPos ); + } + + sOut.swap( sPath ); +} + +namespace StringConversion +{ + template<> bool FromString( const RString &sValue, int &out ) + { + if( sscanf( sValue.c_str(), "%d", &out ) == 1 ) + return true; + + out = 0; + return false; + } + + template<> bool FromString( const RString &sValue, unsigned &out ) + { + if( sscanf( sValue.c_str(), "%u", &out ) == 1 ) + return true; + + out = 0; + return false; + } + + template<> bool FromString( const RString &sValue, float &out ) + { + const char *endptr = sValue.data() + sValue.size(); + out = strtof( sValue, (char **) &endptr ); + if( endptr != sValue.data() && isfinite( out ) ) + return true; + out = 0; + return false; + } + + template<> bool FromString( const RString &sValue, bool &out ) + { + if( sValue.size() == 0 ) + return false; + + out = (StringToInt(sValue) != 0); + return true; + } + + template<> RString ToString( const int &value ) + { + return ssprintf( "%i", value ); + } + + template<> RString ToString( const unsigned &value ) + { + return ssprintf( "%u", value ); + } + + template<> RString ToString( const float &value ) + { + return ssprintf( "%f", value ); + } + + template<> RString ToString( const bool &value ) + { + return ssprintf( "%i", value ); + } +} + +bool FileCopy( const RString &sSrcFile, const RString &sDstFile ) +{ + if( !sSrcFile.CompareNoCase(sDstFile) ) + { + LOG->Warn( "Tried to copy \"%s\" over itself", sSrcFile.c_str() ); + return false; + } + + RageFile in; + if( !in.Open(sSrcFile, RageFile::READ) ) + return false; + + RageFile out; + if( !out.Open(sDstFile, RageFile::WRITE) ) + return false; + + RString sError; + if( !FileCopy(in, out, sError) ) + { + LOG->Warn( "FileCopy(%s,%s): %s", + sSrcFile.c_str(), sDstFile.c_str(), sError.c_str() ); + return false; + } + + return true; +} + +bool FileCopy( RageFileBasic &in, RageFileBasic &out, RString &sError, bool *bReadError ) +{ + while(1) + { + RString data; + if( in.Read(data, 1024*32) == -1 ) + { + sError = ssprintf( "read error: %s", in.GetError().c_str() ); + if( bReadError != NULL ) + *bReadError = true; + return false; + } + if( data.empty() ) + break; + int i = out.Write(data); + if( i == -1 ) + { + sError = ssprintf( "write error: %s", out.GetError().c_str() ); + if( bReadError != NULL ) + *bReadError = false; + return false; + } + } + + if( out.Flush() == -1 ) + { + sError = ssprintf( "write error: %s", out.GetError().c_str() ); + if( bReadError != NULL ) + *bReadError = false; + return false; + } + + return true; +} + +LuaFunction( SecondsToMSSMsMs, SecondsToMSSMsMs( FArg(1) ) ) +LuaFunction( SecondsToHHMMSS, SecondsToHHMMSS( FArg(1) ) ) +LuaFunction( SecondsToMMSSMsMs, SecondsToMMSSMsMs( FArg(1) ) ) +LuaFunction( SecondsToMMSSMsMsMs, SecondsToMMSSMsMsMs( FArg(1) ) ) +LuaFunction( SecondsToMSS, SecondsToMSS( FArg(1) ) ) +LuaFunction( SecondsToMMSS, SecondsToMMSS( FArg(1) ) ) +LuaFunction( FormatNumberAndSuffix, FormatNumberAndSuffix( IArg(1) ) ) +LuaFunction( Basename, Basename( SArg(1) ) ) +static RString MakeLower( RString s ) { s.MakeLower(); return s; } +LuaFunction( Lowercase, MakeLower( SArg(1) ) ) +static RString MakeUpper( RString s ) { s.MakeUpper(); return s; } +LuaFunction( Uppercase, MakeUpper( SArg(1) ) ) +LuaFunction( mbstrlen, (int)RStringToWstring(SArg(1)).length() ) +LuaFunction( URLEncode, URLEncode( SArg(1) ) ); +LuaFunction( PrettyPercent, PrettyPercent( FArg(1), FArg(2) ) ); +//LuaFunction( IsHexVal, IsHexVal( SArg(1) ) ); +static bool UndocumentedFeature( RString s ){ sm_crash(s); return true; } +LuaFunction( UndocumentedFeature, UndocumentedFeature(SArg(1)) ); + +/* + * Copyright (c) 2001-2005 Chris Danford, Glenn Maynard + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */