Blob Blame History Raw
/* string-utils.c - Functions dealing with string contents.
 *
 * Copyright (C) 1998 Oskar Liljeblad
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <ctype.h>		/* C89 */
#include <stdarg.h>		/* Gnulib/C89 */
#include <stdio.h>		/* Gnulib/C89 */
#include <string.h>		/* Gnulib/C89 */
#include <stdlib.h>		/* Gnulib/C89 */
#include "xalloc.h"		/* Gnulib */
#include "xvasprintf.h"		/* Gnulib */
#include "common.h"		/* common */
#include "error.h"		/* common */
#include "string-utils.h"	/* common */

/**
 * Return a zero-based of a character in a string.
 */
/*inline*/ int
strindex(const char *str, char ch)
{
	char *pos = strchr(str, ch);
	return (pos == NULL ? -1 : pos - str);
}

/**
 * Remove trailing newline from a character, if
 * the last character is a newline.
 */
void
chomp(char *str)
{
	int len;

	len = strlen(str);
	if (len > 0 && str[len-1] == '\n')
		str[len-1] = '\0';
}

/**
 * Strip leading characters from a string, modifying the
 * string. What characters to strip are determined by the
 * function, which can be an isalpha, isalnum etc.
 *
 * @param line
 *   Line to strip characters from. This string will be modified.
 * @param check
 *   A function to be called for each character. If it returns
 *   a non-zero value, the character will be stripped.
 */
void
strip_leading(char *line, int (*check)(int))
{
	int start;
	int end;

	if (line[0] != '\0' && check(line[0])) {
		for (start = 1; line[start] != '\0' && check(line[start]); start++);
		for (end = start; line[end] != '\0'; end++);
		memmove(line, line+start, end-start+1);
	}
}

/**
 * Return the index of the first character of some type.
 * For this purpose a check function is used.
 *
 * @param line
 *   String to look for characters in.
 * @param check 
 *   A function to be called for each character. If it returns
 *   a non-zero value, we has found a character we were looking
 *   for, and char_index returns with the index of this character.
 */
int
char_index(const char *line, int (*check)(int))
{
	int c;
	for (c = 0; line[c] != '\0'; c++) {
		if (check(line[c]))
			return c;
	}
	return -1;
}

/**
 * Return true if the first string ends with the second.
 *
 * @param str
 *   String to look for end in.
 * @param end
 *   String to look for.
 */
bool
ends_with(const char *str, const char *end)
{
	int c;
	int diff = strlen(str) - strlen(end);

	if (diff < 0)
		return false;

	for (c = 0; end[c] != '\0'; c++) {
		if (end[c] != str[c+diff])
			return false;
	}

	return true;
}

/**
 * Return true if the first string ends with the second,
 * ignoring case.
 *
 * @param str
 *   String to look for end in.
 * @param end
 *   String to look for.
 */
bool
ends_with_nocase(const char *str, const char *end)
{
	int c;
	int diff = strlen(str) - strlen(end);

	if (diff < 0)
		return false;

	for (c = 0; end[c] != '\0'; c++) {
		if (tolower(end[c]) != tolower(str[c+diff]))
			return false;
	}

	return true;
}

/**
 * Return true if the first string starts with the second.
 *
 * @param str
 *   String to look for start in.
 * @param start
 *   String to look for.
 */
bool
starts_with(const char *str, const char *start)
{
	int c;
	for (c = 0; str[c] != '\0'; c++) {
		if (str[c] != start[c])
			break;
	}
	return (start[c] == '\0');
}

/**
 * Return true if the first string starts with the second,
 * ignoring case.
 *
 * @param str
 *   String to look for start in.
 * @param start
 *   String to look for.
 */
bool
starts_with_nocase(const char *str, const char *start)
{
	int c;
	for (c = 0; str[c] != '\0'; c++) {
		if (tolower(str[c]) != tolower(start[c]))
			break;
	}
	return (start[c] == '\0');
}

/**
 * Translate each occurence of `from' in `str' to `to'.
 *
 * @returns
 *   Number of changes made.
 */
int
translate_char(char *str, char from, char to)
{
	int changes = 0;
	int c;
	for (c = 0; str[c] != '\0'; c++) {
		if (str[c] == from) {
			str[c] = to;
			changes++;
		}
	}
	return changes;
}

/**
 * Convert all characters in the specified string using
 * some character-conversion function.
 */
void
str_convert(char *str, int (*convert)(int))
{
	int c;
	for (c = 0; str[c] != '\0'; c++)
		str[c] = convert(str[c]);
}

/**
 * Return true if the specified character is a Perl "word"
 * character (`\w'), that is: if it is an alphanumeric character
 * or `_'.
 */
int
iswordchar(int ch)
{
	return isalnum(ch) || ch == '_';
}

/**
 * Replace an occurence of `from' in `str' with `to'.
 * If `from' is longer than `to', then `str' must contain
 * enough space for the new string.
 *
 * Note: This implementation is slow and not optimized.
 *
 * @returns
 *   true if a replacement was made.
 */
bool
replace_str(char *str, const char *from, const char *to)
{
	char *pos;
	int s1;
	int s2;
	
	if ((pos = strstr(str, from)) == NULL)
		return false;

	s1 = strlen(from);
	s2 = strlen(to);
	memmove(pos+s2, pos+s1, strlen(pos)-s2+1);
	memcpy(pos, to, s2);
	return true;	
}

/**
 * Concatenate two file names into a new string.
 * If file is "." return a duplicate of file2.
 */
char *
cat_files(const char *file, const char *file2)
{
	if (strcmp(file, ".") == 0)
		return xstrdup(file2);
	return xasprintf("%s%s%s", file, (ends_with(file, "/") ? "" : "/"), file2);
}

/**
 * Return the substring of a string, putting it into a newly allocated
 * string.
 *
 * @param start
 *   Index of first character to copy. If this value is
 *   negative, the index is counted from the end of the string.
 *   E.g. -1 is before last non-null character.
 * @param end
 *   Index of first character not to copy. If this value is
 *   negative, the index is counted from the end of the string.
 *   E.g. -1 is before last non-null character.
 */
char *
substring(const char *buf, int start, int end)
{
	int len = strlen(buf);

	if (start < 0)
		start += len;
	if (end < 0)
		end += len;

	return strndup(buf+start, end-start);
}

/**
 * Strip all trailing characters listed in `stripchars'.
 *
 * @param str
 *   String to remove characters from. This string will be
 *   modified.
 */
int
string_strip_trailing(char *str, const char *stripchars)
{
	int a = strlen(str)-1;
	int b = a;

	while (a >= 0 && string_index_of_c(stripchars, str[a]) != -1)
		a--;
	str[a+1] = '\0';

	return b-a;
}

/**
 * Strip all trailing characters that are `stripchar'.
 *
 * @param str
 *   String to remove characters from. This string will be
 *   modified.
 */
int
string_strip_trailing_c(char *str, char stripchar)
{
	int a = strlen(str-1);
	int b = a;

	while (a >= 0 && str[a] == stripchar)
		a--;
	str[a+1] = '\0';

	return b-a;
}

/* XXX: document */
int
string_strip_leading(char *str, const char *stripchars)
{
	int a = 0;
	int b = 0;

	while (str[a] != '\0' && string_index_of_c(stripchars, str[a]) != -1)
		a++;
	while (str[a] != '\0')
		str[b++] = str[a++];
	str[b] = str[a];

	return a-b;
}

/* XXX: document */
int
string_strip_leading_c(char *str, char stripchar)
{
	int a = 0;
	int b = 0;

	while (str[a] != '\0' && str[a] == stripchar)
		a++;
	while (str[a] != '\0')
		str[b++] = str[a++];
	str[b] = str[a];

	return a-b;
}

/**
 * Return the index of the first character listed in `findchars'
 * in `str'.
 *
 * @param str
 *   The string to look for characters in.
 * @param findchars
 *   The characters to look for.
 */
int
string_index_of_any(const char *str, const char *findchars)
{
	int c;
	for (c = 0; str[c] != '\0'; c++) {
		if (string_index_of_c(findchars, str[c]) != -1)
			return c;
	}
	return -1;
}

int
word_get_index(const char *str, int pos)
{
	bool in_word = false;
	int words = 0;
	int c;

	for (c = 0; str[c] != '\0' && c < pos; c++) {
		if (!in_word && !isspace(str[c])) {
			in_word = true;
		}
		if (in_word && isspace(str[c])) {
			in_word = false;
			words++;
		}
	}

	return words;
}

char *
word_get(const char *str, int idx)
{
	bool in_word = false;
	int words = 0;
	int start = -1;
	int c;

	for (c = 0; str[c] != '\0'; c++) {
		if (!in_word && !isspace(str[c])) {
			words++;
			start = c;
			in_word = true;
		}
		if (in_word && isspace(str[c])) {
			if (words > idx)
				return substring(str, start, c);
			in_word = false;
		}
	}

	if (words-1 == idx)
		return substring(str, start, c);
	return NULL;
}

int
uintlen(uint64_t value)
{
	int len;
	for (len = 1; value >= 10; len++)
		value /= 10;
	return len;
}

/* Like dirname, but returns a newly allocated string.
 */
char *
xdirname(const char *path)
{
	char *pos = strrchr(path, '/');

	if (pos == NULL)
		return xstrdup(".");

	if (pos == path)
		return xstrdup("/");

	return substring(path, 0, pos-path);
}