sources/mystring.cpp

Wed, 27 Jan 2021 18:55:14 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Wed, 27 Jan 2021 18:55:14 +0200
changeset 187
53f9c7b2c068
parent 183
9b6a0daedfc0
child 190
90bf9049e5eb
permissions
-rw-r--r--

fixed an off-by-one error

/*
	Copyright 2014 - 2016 Teemu Piippo
	All rights reserved.

	Redistribution and use in source and binary forms, with or without
	modification, are permitted provided that the following conditions
	are met:

	1. Redistributions of source code must retain the above copyright
	   notice, this list of conditions and the following disclaimer.
	2. Redistributions in binary form must reproduce the above copyright
	   notice, this list of conditions and the following disclaimer in the
	   documentation and/or other materials provided with the distribution.
	3. Neither the name of the copyright holder nor the names of its
	   contributors may be used to endorse or promote products derived from
	   this software without specific prior written permission.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <cstring>
#include "main.h"
#include "mystring.h"

BEGIN_ZFC_NAMESPACE

/*!
 * \returns a lower-case version of this string.
 */
std::string to_lowercase(const std::string& string)
{
	std::string result = string;

	for (char& ch : result)
	{
		if (isupper(ch))
			ch += 'a' - 'A';
	}

	return result;
}

/*!
 * \brief Joins the elements of this string list into one longer string.
 * \param delimeter The delimeter to place between the element strings.
 * \returns the catenated string.
 */
std::string join_string_list(const std::vector<std::string>& strings, const std::string& delimeter)
{
	std::string result;

	for (const std::string &item : strings)
	{
		if (not result.empty())
			result += delimeter;

		result += item;
	}

	return result;
}

/*!
 * \brief Modifies the given index so that if it is negative, it is translated into a positive index starting from the
 *        end of the string. For example, an index of -1 will be modified to point to the last character in the string,
 *        -2 to the second last, etc.
 * \param index Index to translate.
 */
inline void modifyIndex(const std::string& str, int& index)
{
	if (index < 0)
		index = str.length() - index;
}

/*!
 * \param a Starting index of the range.
 * \param b Ending index of the range.
 * \returns a sub-string containing all characters from \c a to \c b, not including the character at \c b.
 */
std::string mid(const std::string& str, int rangeBegin, int rangeEnd)
{
	modifyIndex(str, rangeBegin);
	modifyIndex(str, rangeEnd);
	rangeBegin = max(rangeBegin, 0);
	rangeEnd = min(rangeEnd, static_cast<signed>(str.length()));

	if (rangeEnd <= rangeBegin)
		return "";
	else
		return str.substr(rangeBegin, rangeEnd - rangeBegin);
}

/*!
 * \param length Amount of characters to return.
 * \returns the \c length right-most characters of the string.
 */
std::string right(const std::string& str, int length)
{
	if (length >= static_cast<signed>(str.length()))
		return str;
	else
		return std::string{str.data() + str.length() - length};
}

/*!
 * \brief Formats this string using \c vsnprintf, using the provided arguments.
 * \param formatString Template string to use with formatting.
 * \param args Variadic arguments to use with formatting.
 */
std::string vsprintf(const char* formatString, va_list args)
{
	std::string result;

	// Copy the argument list so that we have something to provide to vsnprintf in case we have to call it again.
	va_list argsCopy;
	va_copy(argsCopy, args);

	// First, attempt to format using a fixed-size buffer.
	static char buffer[1024];
	size_t length = vsnprintf(buffer, sizeof buffer, formatString, args);

	if (length < sizeof buffer)
	{
		// vsnprintf succeeded in fitting the formatted string into the buffer, so we're done.
		result = buffer;
	}
	else
	{
		// vsnprintf needs more space, so we have to allocate a new buffer and try again.
		Vector<char> newBuffer(length + 1);
		vsnprintf(newBuffer.data(), length + 1, formatString, argsCopy);
		result = newBuffer.data();
	}
	
	return result;
}

/*!
 * \brief Formats this string using \c printf -like syntax.
 * \param formatString Template string to use with formatting.
 * \param ... Variadic arguments to use with formatting.
 */
std::string __cdecl sprintf(const char* formatString, ...)
{
	va_list args;
	va_start (args, formatString);
	std::string result = vsprintf(formatString, args);
	va_end (args);
	return result;
}

std::string remove_range(const std::string &string, int start, int end)
{
	std::string result;
	result.reserve(string.length() - (end - start));
	std::copy(string.begin(), string.begin() + start, std::back_inserter(result));
	std::copy(string.begin() + end, string.end(), std::back_inserter(result));
	return result;
}


/*!
 * \param other Sub-string to find from the beginning of this string.
 * \returns whether or not this string begins with the provided sub-string.
 */
bool starts_with(const std::string& str, const std::string& other)
{
	if (str.length() < other.length())
		return false;
	else
		return std::strncmp(str.data(), other.data(), other.length()) == 0;
}

/*!
 * \brief Replaces all instances of \c text with \c replacement.
 * \param text Text to replace away.
 * \param replacement Text to replace \c text with.
 */
void replace_all(std::string& str, const char* text, const char* replacement)
{
	int position;
	while ((position = str.find(text)) != -1)
	{
		str.replace(position, std::strlen(text), replacement);
	}
}

/*!
 * \brief Splits this string using the provided delimeter.
 * \param delimeter Delimeter to use for splitting.
 * \returns a string list containing the split strings.
 */
std::vector<std::string> split(const std::string& string, const std::string& delimeter)
{
	std::vector<std::string> result;
	int a = 0;
	int b;

	// Find all separators and store the text left to them.
	while ((b = string.find(delimeter, a)) != -1)
	{
		std::string sub = mid(string, a, b);

		if (sub.length() > 0)
			result.push_back(sub);

		a = b + delimeter.length();
	}

	// Add the string at the right of the last separator
	if (a < static_cast<int>(string.length()))
		result.push_back(mid(string, a, string.length()));

	return result;
}

/*!
 * \brief Converts this string to an integer.
 * \param ok An pointer to a boolean to store whether or not the conversion was successful.
 *           If \c ok is \c NULL, the success state is not stored.
 * \param base The base to interpret this string with.
 * \returns the resulting integer.
 */
std::optional<long> to_int(const char* str, int base)
{
	errno = 0;
	char* endPointer;
	long result = strtol(str, &endPointer, base);
	if (errno == 0 and *endPointer == '\0')
	{
		return result;
	}
	else
	{
		return {};
	}
}

/*!
 * \brief Removes leading and trailing whitespace from this string. Alternatively a custom filter can be used to strip
 *        something else than whitespace.
 * \param filter The filtering function to use.
 */
void normalize(std::string& string, int (*filter)(int))
{
	int a = 0;
	int b = string.length() - 1;
	while ((*filter)(string[a]) and a != b)
	{
		++a;
	}
	while ((*filter)(string[b]) and a != b)
	{
		--b;
	}
	if (a == b)
	{
		string = "";
	}
	else if (a != 0 or b != static_cast<signed>(string.length() - 1))
	{
		string = string.substr (a, b - a + 1);
	}
}

END_ZFC_NAMESPACE

mercurial