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 }