/*
* StringUtils.cpp
*
* Copyright (C) 2009-19 by RStudio, PBC
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef _WIN32
#include
#include
#endif
#ifndef CP_ACP
# define CP_ACP 0
#endif
namespace rstudio {
namespace core {
namespace string_utils {
bool isTruthy(const std::string& string,
bool valueIfEmpty)
{
// allow user-configurable behavior for empty strings
if (string.empty())
return valueIfEmpty;
// check for special 'falsy' values
std::string lower = toLower(string);
if (lower == "0" || lower == "false")
return false;
// assume all other values are 'truthy'
return true;
}
bool isSubsequence(std::string const& self,
std::string const& other,
std::string::size_type other_n)
{
std::string::size_type self_n = self.length();
if (other_n == 0)
return true;
if (other_n > other.length())
other_n = other.length();
if (other_n > self_n)
return false;
std::string::size_type self_idx = 0;
std::string::size_type other_idx = 0;
while (self_idx < self_n)
{
char selfChar = self[self_idx];
char otherChar = other[other_idx];
if (otherChar == selfChar)
{
++other_idx;
if (other_idx == other_n)
{
return true;
}
}
++self_idx;
}
return false;
}
bool isSubsequence(std::string const& self,
std::string const& other,
std::string::size_type other_n,
bool caseInsensitive)
{
return caseInsensitive ?
isSubsequence(boost::algorithm::to_lower_copy(self),
boost::algorithm::to_lower_copy(other),
other_n) :
isSubsequence(self, other, other_n)
;
}
bool isSubsequence(std::string const& self,
std::string const& other)
{
return isSubsequence(self, other, other.length());
}
bool isSubsequence(std::string const& self,
std::string const& other,
bool caseInsensitive)
{
return isSubsequence(self, other, other.length(), caseInsensitive);
}
std::vector subsequenceIndices(std::string const& sequence,
std::string const& query)
{
std::string::size_type querySize = query.length();
std::vector result;
result.reserve(querySize);
std::string::size_type prevMatchIndex = -1;
for (std::string::size_type i = 0; i < querySize; i++)
{
std::string::size_type index = sequence.find(query[i], prevMatchIndex + 1);
if (index == std::string::npos)
continue;
result.push_back(gsl::narrow_cast(index));
prevMatchIndex = index;
}
return result;
}
bool subsequenceIndices(std::string const& sequence,
std::string const& query,
std::vector *pIndices)
{
pIndices->clear();
pIndices->reserve(query.length());
int query_n = gsl::narrow_cast(query.length());
int prevMatchIndex = -1;
for (int i = 0; i < query_n; i++)
{
int index = gsl::narrow_cast(sequence.find(query[i], prevMatchIndex + 1));
if (index == -1)
return false;
pIndices->push_back(index);
prevMatchIndex = index;
}
return true;
}
std::string getExtension(std::string const& x)
{
std::size_t lastDotIndex = x.rfind('.');
if (lastDotIndex != std::string::npos)
return x.substr(lastDotIndex);
else
return std::string();
}
void convertLineEndings(std::string* pStr, LineEnding type)
{
std::string replacement;
switch (type)
{
case LineEndingWindows:
replacement = "\r\n";
break;
case LineEndingPosix:
replacement = "\n";
break;
case LineEndingNative:
#if _WIN32
replacement = "\r\n";
#else
replacement = "\n";
#endif
break;
case LineEndingPassthrough:
default:
return;
}
*pStr = boost::regex_replace(*pStr, boost::regex("\\r?\\n|\\r|\\xE2\\x80[\\xA8\\xA9]"), replacement);
}
bool detectLineEndings(const FilePath& filePath, LineEnding* pType)
{
if (!filePath.exists())
return false;
std::shared_ptr pIfs;
Error error = filePath.openForRead(pIfs);
if (error)
{
LOG_ERROR(error);
return false;
}
// read file character-by-character using a streambuf
try
{
std::istream::sentry se(*pIfs, true);
std::streambuf* sb = pIfs->rdbuf();
while(true)
{
int ch = sb->sbumpc();
if (ch == '\n')
{
// using posix line endings
*pType = string_utils::LineEndingPosix;
return true;
}
else if (ch == '\r' && sb->sgetc() == '\n')
{
// using windows line endings
*pType = string_utils::LineEndingWindows;
return true;
}
else if (ch == EOF)
{
break;
}
else if (pIfs->fail())
{
LOG_WARNING_MESSAGE("I/O Error reading file " +
filePath.getAbsolutePath());
break;
}
}
}
CATCH_UNEXPECTED_EXCEPTION
// no detection possible (perhaps the file is empty or has only one line)
return false;
}
std::string utf8ToSystem(const std::string& str,
bool escapeInvalidChars)
{
if (str.empty())
return std::string();
#ifdef _WIN32
std::vector wide(str.length() + 1);
int chars = ::MultiByteToWideChar(
CP_UTF8, 0,
str.c_str(), -1,
&wide[0], gsl::narrow_cast(wide.size()));
if (chars < 0)
{
LOG_ERROR(LAST_SYSTEM_ERROR());
return str;
}
std::ostringstream output;
char buffer[16];
// Only go up to chars - 1 because last char is \0
for (int i = 0; i < chars - 1; i++)
{
int n = wctomb(buffer, wide[i]);
if (n == -1)
{
if (escapeInvalidChars)
output at(len-1) == '"'))
*pStr = pStr->substr(0, len -1);
}
std::string strippedOfQuotes(const std::string& string)
{
std::string::size_type n = string.length();
if (n < 2) return string;
char first = string[0];
char last = string[n - 1];
if ((first == '\'' && last == '\'') ||
(first == '"' && last == '"') |\
(first == '`' && last == '`'))
{
return string.substr(1, n - 2);
}
return string;
}
template
Iter countNewlinesImpl(Iter begin,
Iter end,
const U& CR,
const U& LF,
std::size_t* pNewlineCount)
{
std::size_t newlineCount = 0;
Iter it = begin;
Iter lastNewline = end;
for (; it != end; ++it)
{
// Detect '\r\n'
if (*it == CR)
{
if (it + 1 != end &&
*(it + 1) == LF)
{
lastNewline = it;
++it;
++newlineCount;
continue;
}
}
// Detect '\n'
if (*it == LF)
{
lastNewline = it;
++newlineCount;
}
}
*pNewlineCount = newlineCount;
return lastNewline;
}
std::size_t countNewlines(const std::wstring& string)
{
std::size_t count = 0;
countNewlinesImpl(string.begin(), string.end(), L'\r', L'\n', &count);
return count;
}
std::size_t countNewlines(const std::string& string)
{
std::size_t count = 0;
countNewlinesImpl(string.begin(), string.end(), '\r', '\n', &count);
return count;
}
std::size_t countNewlines(std::string::iterator begin,
std::string::iterator end)
{
std::size_t count = 0;
countNewlinesImpl(begin, end, '\r', '\n', &count);
return count;
}
std::size_t countNewlines(std::wstring::iterator begin,
std::wstring::iterator end)
{
std::size_t count = 0;
countNewlinesImpl(begin, end, '\r', '\n', &count);
return count;
}
std::wstring::const_iterator countNewlines(std::wstring::const_iterator begin,
std::wstring::const_iterator end,
std::size_t* pCount)
{
return countNewlinesImpl(begin, end, '\r', '\n', pCount);
}
bool isPrefixOf(const std::string& self, const std::string& prefix)
{
return boost::algorithm::starts_with(self, prefix);
}
std::string makeRandomByteString(std::size_t n)
{
std::string result;
result.resize(n);
for (std::size_t i = 0; i < n; ++i)
result[i] = (unsigned char) (::rand() % UCHAR_MAX);
return result;
}
bool extractCommentHeader(const std::string& contents,
const std::string& reCommentPrefix,
std::string* pHeader)
{
// construct newline-based token iterator
boost::regex reNewline("(?:\\r?\\n|$)");
boost::sregex_token_iterator it(
contents.begin(),
contents.end(),
reNewline,
-1);
boost::sregex_token_iterator end;
// first, skip blank lines
boost::regex reWhitespace("^\\s*$");
while (it != end)
{
if (boost::regex_match(it->begin(), it->end(), reWhitespace))
{
++it;
continue;
}
break;
}
// if we're at the end now, bail
if (it == end)
return false;
// check to see if we landed on our comment prefix and
// quit early if we haven't
boost::regex rePrefix(reCommentPrefix);
if (!boost::regex_search(it->begin(), it->end(), rePrefix))
return false;
// we have a prefix: start iterating and extracting these
for (; it != end; ++it)
{
boost::smatch match;
if (!boost::regex_search(it->begin(), it->end(), match, rePrefix))
{
// this is no longer a commented line; time to go home
break;
}
// extract the line (sans prefix)
std::string line(it->begin() + match.length(), it->end());
pHeader->append(line + "\n");
}
// report success to the user
return true;
}
} // namespace string_utils
} // namespace core
} // namespace rstudio