ap

Queue manager meant to be used as an audio player
git clone git://jacobedwards.org/ap
Log | Files | Refs | README | LICENSE

find.c (5117B)


      1 /*
      2  * Copyright 2021, 2022 Jacob R. Edwards
      3  *
      4  * ap -- audio player
      5  *
      6  * This program is free software: you can redistribute it and/or modify
      7  * it under the terms of the GNU General Public License as published by
      8  * the Free Software Foundation, either version 3 of the License, or
      9  * (at your option) any later version.
     10  *
     11  * This program is distributed in the hope that it will be useful,
     12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     14  * GNU General Public License for more details.
     15  *
     16  * You should have received a copy of the GNU General Public License
     17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
     18  */
     19 
     20 #include <ap/item.h>
     21 
     22 #include <errno.h>
     23 #include <limits.h>
     24 #include <regex.h>
     25 #include <stdlib.h>
     26 #include <string.h>
     27 
     28 #include "bug.h"
     29 #include "find.h"
     30 #include "util.h"
     31 
     32 struct item *
     33 nextitem(struct item *item)
     34 {
     35 	return item->next;
     36 }
     37 
     38 
     39 struct item *
     40 previtem(struct item *item)
     41 {
     42 	return item->prev;
     43 }
     44 
     45 char *
     46 findnum(char *str, int *i, char **ep)
     47 {
     48 	long n;
     49 
     50 	errno = 0;
     51 	n = strtol(str, ep, 10);
     52 	if (errno)
     53 		return errstr;
     54 	if (n < INT_MIN || n > INT_MAX)
     55 		return "Number too large";
     56 	*i = n;
     57 	return NULL;
     58 }
     59 
     60 struct item *
     61 matchreg(struct search *state)
     62 {
     63 	switch (regexec(&state->u.reg, state->cur->path, 0, NULL, 0)) {
     64 	case 0:
     65 		if (!state->inv)
     66 			return state->cur;
     67 		break;
     68 	case REG_NOMATCH:
     69 		if (state->inv)
     70 			return state->cur;
     71 		break;
     72 	default:
     73 		bug("Regex failed to execute");
     74 	}
     75 
     76 	return NULL;
     77 }
     78 
     79 struct item *
     80 matchnum(struct search *state)
     81 {
     82 	struct item *match;
     83 
     84 	match = NULL;
     85 	if (state->u.num.ind > state->u.num.max)
     86 		state->fin = 1;
     87 	else if (state->u.num.ind >= state->u.num.min)
     88 		match = state->cur;
     89 
     90 	state->u.num.ind += 1;
     91 
     92 	return match;
     93 }
     94 
     95 struct item *
     96 matchstr(struct search *state)
     97 {
     98 	return strcmp(state->u.str, state->cur->path) == 0 ? state->cur : NULL;
     99 }
    100 
    101 void
    102 renewsearch(struct search *state, struct item *start)
    103 {
    104 	state->cur = start;
    105 	state->end = start;
    106 	state->fin = start == NULL;
    107 	state->new = 1;
    108 	if (state->match == matchnum) {
    109 		state->u.num.ind = 0;
    110 		if (state->u.num.min < 0) {
    111 			while (state->u.num.ind > state->u.num.min) {
    112 				state->cur = previtem(state->cur);
    113 				--state->u.num.ind;
    114 				if (state->cur == state->end)
    115 					break;
    116 			}
    117 			state->end = state->cur;
    118 		}
    119 	}
    120 }
    121 
    122 char *
    123 prepsearch(struct search *state, struct item *start, char *pattern)
    124 {
    125 	static char err[128];
    126 	int rcode;
    127 	char *p, *ep;
    128 
    129 	state->inc = nextitem;
    130 	state->inv = 0;
    131 
    132 	if (!pattern || !pattern[0]) {
    133 		return "Empty pattern";
    134 	} else if (*pattern == '!' || *pattern == '/' || *pattern == '?') {
    135                 /* NOTE: I'd like '!' to be a global modifier, but
    136                  * it wouldn't work with the current numeric patterns.
    137 		 */
    138 		if (*pattern == '!') {
    139 			state->inv = 1;
    140 			++pattern;
    141 		}
    142 
    143 		if (*pattern == '?')
    144 			state->inc = previtem;
    145 		else if (*pattern != '/')
    146 			return "Invalid regex direction (not / or ?)";
    147 		++pattern;
    148 
    149 		if ((rcode = regcomp(&state->u.reg, pattern,
    150 		    REG_EXTENDED | REG_NOSUB | REG_ICASE))) {
    151 			regerror(rcode, &state->u.reg, err, sizeof(err));
    152 			return err;
    153 		}
    154 		state->match = matchreg;
    155 	} else if (pattern[0] == '"') {
    156 		state->u.str = strdup(pattern + 1);
    157 		if (!state->u.str)
    158 			return "Unable to duplicate string";
    159 		state->match = matchstr;
    160 	} else if (pattern[0]) {
    161 		if ((ep = findnum(pattern, &state->u.num.min, &p)))
    162 			return ep;
    163 		if (p[0] == ',') {
    164 			if ((ep = findnum(p + 1, &state->u.num.max, &p)))
    165 				return ep;
    166 			if (p[0])
    167 				return "Invalid character in numeric pattern";
    168 			if (state->u.num.max < state->u.num.min)
    169 				return "Range end is smaller than beginning";
    170 		} else if (p[0])
    171 			return "Invalid character in numeric pattern";
    172 		else
    173 			state->u.num.max = state->u.num.min;
    174 		if (state->u.num.min < 0 && state->u.num.max > 0) {
    175                         /* Since I'm not sure how I want these to
    176                          * behave yet, they're not allowed. The
    177                          * question is how duplicates should be
    178                          * handled?
    179 			 */
    180 			return "Ranges spanning from negative to positive indexes not yet implemented";
    181 		}
    182 		state->match = matchnum;
    183 	}
    184 
    185 	renewsearch(state, start);
    186 	return NULL;
    187 }
    188 
    189 void
    190 stopsearch(struct search *state)
    191 {
    192 	if (state->match == matchreg)
    193 		regfree(&state->u.reg);
    194 	else if (state->match == matchstr)
    195 		free(state->u.str);
    196 }
    197 
    198 struct item *
    199 findnext(struct search *state)
    200 {
    201 	struct item *match;
    202 
    203 	if (state->fin)
    204 		return NULL;
    205 
    206 	/* NOTE: state->inc cannot return NULL */
    207 	for (match = NULL; !match && !state->fin; state->cur = state->inc(state->cur)) {
    208 		if (!state->new && state->cur == state->end)
    209 			state->fin = 1;
    210 		else 
    211 			match = state->match(state);
    212 
    213 		if (state->new)
    214 			state->new = 0;
    215 	}
    216 
    217 	return match;
    218 }
    219 
    220 struct item *
    221 find(char *pattern, struct item *start, char **ep)
    222 {
    223 	struct item *match;
    224 	struct search search;
    225 	char *err;
    226 
    227 	err = prepsearch(&search, start, pattern);
    228 	if (err) {
    229 		if (ep)
    230 			*ep = err;
    231 		return NULL;
    232 	}
    233 	match = findnext(&search);
    234 	stopsearch(&search);
    235 	return match;
    236 }