xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
<root> / src / utils / regexp.c (664df455077eb80e337e9cb8d31fa2c9fc1095b7) (5,016B) (mode 100644) [raw]
/* vifm
 * Copyright (C) 2015 xaizek.
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "regexp.h"

#include <regex.h> /* regex_t regmatch_t regcomp() regerror() regexec() */

#include <ctype.h> /* isdigit() */
#include <stdlib.h> /* free() malloc() */
#include <string.h> /* strlen() */

#include "../cfg/config.h"
#include "str.h"

int
get_regexp_cflags(const char pattern[])
{
	int result = REG_EXTENDED;
	if(regexp_should_ignore_case(pattern))
	{
		result |= REG_ICASE;
	}
	return result;
}

int
regexp_should_ignore_case(const char pattern[])
{
	int ignore_case = cfg.ignore_case;

	if(cfg.ignore_case && cfg.smart_case)
	{
		if(has_uppercase_letters(pattern))
		{
			ignore_case = 0;
		}
	}

	/* Look for \c and \C in the pattern, they have the highest priority. */
	const char *s = pattern;
	while(s[0] != '\0')
	{
		if(s[0] == '\\' && s[1] != '\0')
		{
			if(s[1] == 'c')
			{
				ignore_case = 1;
			}
			else if(s[1] == 'C')
			{
				ignore_case = 0;
			}
			++s;
		}

		++s;
	}

	return ignore_case;
}

int
regexp_compile(regex_t *re, const char pattern[], int cflags)
{
	char *mod_pattern = malloc(strlen(pattern) + 1);
	if(mod_pattern == NULL)
	{
		return REG_ESPACE;
	}

	char *p = mod_pattern;
	const char *s = pattern;
	while(s[0] != '\0')
	{
		if(s[0] == '\\' && s[1] != '\0')
		{
			if(s[1] == 'c')
			{
				cflags |= REG_ICASE;
				s += 2;
				continue;
			}
			else if(s[1] == 'C')
			{
				cflags &= ~REG_ICASE;
				s += 2;
				continue;
			}

			*p++ = *s++;
		}

		*p++ = *s++;
	}
	*p = '\0';

	int result = regcomp(re, mod_pattern, cflags);
	free(mod_pattern);

	return result;
}

const char *
get_regexp_error(int err, const regex_t *re)
{
	static char buf[360];

	regerror(err, re, buf, sizeof(buf));
	return buf;
}

int
parse_case_flag(const char flags[], int *case_sensitive)
{
	/* TODO: maybe generalize code with substitute_cmd(). */

	while(*flags != '\0')
	{
		switch(*flags)
		{
			case 'i': *case_sensitive = 0; break;
			case 'I': *case_sensitive = 1; break;

			default:
				return 1;
		}

		++flags;
	}

	return 0;
}

regmatch_t
get_group_match(const regex_t *re, const char str[])
{
	regmatch_t matches[2];

	if(regexec(re, str, 2, matches, 0) != 0 || matches[1].rm_so == -1)
	{
		matches[1].rm_so = 0;
		matches[1].rm_eo = 0;
	}

	return matches[1];
}

const char *
regexp_replace(const char line[], const char pattern[], const char sub[],
		int glob, int ignore_case)
{
	static char buf[PATH_MAX + 1];
	regex_t re;
	regmatch_t matches[10];
	const char *dst;

	copy_str(buf, sizeof(buf), line);

	const int flags = REG_EXTENDED | (ignore_case ? REG_ICASE : 0);
	if(regexp_compile(&re, pattern, flags) != 0)
	{
		regfree(&re);
		return buf;
	}

	if(regexec(&re, line, ARRAY_LEN(matches), matches, 0) != 0)
	{
		regfree(&re);
		return buf;
	}

	if(glob && pattern[0] != '^')
		dst = regexp_gsubst(&re, line, sub, matches);
	else
		dst = regexp_subst(line, sub, matches, NULL);
	copy_str(buf, sizeof(buf), dst);

	regfree(&re);
	return buf;
}

const char *
regexp_gsubst(regex_t *re, const char src[], const char sub[],
		regmatch_t matches[])
{
	static char buf[NAME_MAX + 1];
	int off = 0;

	copy_str(buf, sizeof(buf), src);
	do
	{
		int i;
		for(i = 0; i < 10; ++i)
		{
			matches[i].rm_so += off;
			matches[i].rm_eo += off;
		}

		copy_str(buf, sizeof(buf), regexp_subst(buf, sub, matches, &off));

		if(matches[0].rm_eo == matches[0].rm_so)
		{
			/* If we found an empty match, repeating will cause infinite loop. */
			break;
		}
	}
	while(regexec(re, buf + off, 10, matches, 0) == 0);

	return buf;
}

const char *
regexp_subst(const char src[], const char sub[], const regmatch_t matches[],
		int *off)
{
	static char buf[NAME_MAX + 1];
	char *dst = buf;
	int i;

	for(i = 0; i < matches[0].rm_so; ++i)
	{
		*dst++ = src[i];
	}

	while(*sub != '\0')
	{
		if(*sub == '\\')
		{
			if(sub[1] == '\0')
				break;
			else if(isdigit(sub[1]))
			{
				int n = sub[1] - '0';
				for(i = matches[n].rm_so; i < matches[n].rm_eo; i++)
					*dst++ = src[i];
				sub += 2;
				continue;
			}
			else
				sub++;
		}
		*dst++ = *sub++;
	}
	if(off != NULL)
		*off = dst - buf;

	for(i = matches[0].rm_eo; src[i] != '\0'; i++)
		*dst++ = src[i];

	*dst = '\0';

	return buf;
}

/* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
/* vim: set cinoptions+=t0 filetype=c : */
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master