From: Michael D. Lowis Date: Sat, 2 Nov 2019 03:56:34 +0000 (-0400) Subject: fetch now supports a local rules file X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=fa9bd73dd44dbda969fd0ce991ec591704c19211;p=projs%2Ftide.git fetch now supports a local rules file --- diff --git a/TODO.md b/TODO.md index 6c6a3e0..19e4f12 100644 --- a/TODO.md +++ b/TODO.md @@ -4,6 +4,7 @@ ## STAGING +* fetch: handle quotes around second arg * tide: move looping of cursor movements into selmoveby * all: eliminate multiple return statements and other lint * tide: byrune, byword, byline functions should be hidden in buf.c diff --git a/config.h b/config.h index 1a940d0..2a198ce 100644 --- a/config.h +++ b/config.h @@ -28,7 +28,7 @@ static char* ShellCmd[] = { 0, "-c", 0, 0 }; static char* SedCmd[] = { "sed", "-Ee", 0, 0 }; /* Command used to fetch some text based on a set of rules */ -static char* FetchCmd[] = { "fetch", "--", 0, 0 }; +static char* FetchCmd[] = { "fetch", 0, 0 }; /* Default tag region text in editor windows */ static char* TagString = "Del Put Undo Redo | Font Tabs Eol | Find "; @@ -86,50 +86,50 @@ static int Palette[ClrCount] = { #define OPENCMD "xdg-open" #endif -static Rule* BuiltinRules[] = { - (Rule[]){ /* Match URLS and open them with the browser */ - { ISSET, "BROWSER", NULL }, - { MATCHES, "data", "^(https?|ftp)://.*" }, - { LAUNCH, "$BROWSER \"$M0\"", NULL }, - { COMPLETE, NULL, NULL } - }, - (Rule[]){ /* Open files with addresses in the editor */ - { MATCHES, "data", "^([^:]+):([0-9]+)" }, - { ISFILE, "$M1", NULL }, - { LAUNCH, "edit \"$M0\"", NULL }, - { COMPLETE, NULL, NULL } - }, - (Rule[]){ /* Open addresses in the current file */ - { MATCHES, "data", "^:?([0-9]+)" }, - { ISFILE, "$file", NULL }, - { LAUNCH, "edit \"$file:$M1\"", NULL }, - { COMPLETE, NULL, NULL } - }, - (Rule[]){ /* If it's an existing text file, open it with editor */ - { ISFILE, "$data", NULL }, - { EXEC, "file --mime \"$file\" | grep -q 'text/'", NULL }, - { LAUNCH, "edit \"$file\"", NULL }, - { COMPLETE, NULL, NULL } - }, - (Rule[]){ /* Look it up in ctags database */ - { ISFILE, "tags", NULL }, - { EXEC, "grep -q \"^$data\\s\\+\" tags", NULL }, - { LAUNCH, "picktag fetch tags \"$data\"", NULL }, - { COMPLETE, NULL, NULL } - }, - (Rule[]){ /* Look up .c or .h files in Code/ */ - { MATCHES, "data", "\\.[ch]$" }, - { ISDIR, "Code", NULL }, - { EXEC, "[[ \"$(find Code -type f -name \"*$data\")\" ]]", NULL }, - { LAUNCH, "find Code -type f -name \"*$data\" | xargs -r edit", NULL }, - { COMPLETE, NULL, NULL } - }, - (Rule[]){ /* If it's an existing directory, open it with system default */ - { ISDIR, "$data", NULL }, - { LAUNCH, "cd $data && ls -ap | tide -", NULL }, - { COMPLETE, NULL, NULL } - }, -}; +//static Rule* BuiltinRules[] = { +// (Rule[]){ /* Match URLS and open them with the browser */ +// { ISSET, "BROWSER", NULL }, +// { MATCHES, "data", "^(https?|ftp)://.*" }, +// { LAUNCH, "$BROWSER \"$M0\"", NULL }, +// { COMPLETE, NULL, NULL } +// }, +// (Rule[]){ /* Open files with addresses in the editor */ +// { MATCHES, "data", "^([^:]+):([0-9]+)" }, +// { ISFILE, "$M1", NULL }, +// { LAUNCH, "edit \"$M0\"", NULL }, +// { COMPLETE, NULL, NULL } +// }, +// (Rule[]){ /* Open addresses in the current file */ +// { MATCHES, "data", "^:?([0-9]+)" }, +// { ISFILE, "$file", NULL }, +// { LAUNCH, "edit \"$file:$M1\"", NULL }, +// { COMPLETE, NULL, NULL } +// }, +// (Rule[]){ /* If it's an existing text file, open it with editor */ +// { ISFILE, "$data", NULL }, +// { EXEC, "file --mime \"$file\" | grep -q 'text/'", NULL }, +// { LAUNCH, "edit \"$file\"", NULL }, +// { COMPLETE, NULL, NULL } +// }, +// (Rule[]){ /* Look it up in ctags database */ +// { ISFILE, "tags", NULL }, +// { EXEC, "grep -q \"^$data\\s\\+\" tags", NULL }, +// { LAUNCH, "picktag fetch tags \"$data\"", NULL }, +// { COMPLETE, NULL, NULL } +// }, +// (Rule[]){ /* Look up .c or .h files in Code/ */ +// { MATCHES, "data", "\\.[ch]$" }, +// { ISDIR, "Code", NULL }, +// { EXEC, "[[ \"$(find Code -type f -name \"*$data\")\" ]]", NULL }, +// { LAUNCH, "find Code -type f -name \"*$data\" | xargs -r edit", NULL }, +// { COMPLETE, NULL, NULL } +// }, +// (Rule[]){ /* If it's an existing directory, open it with system default */ +// { ISDIR, "$data", NULL }, +// { LAUNCH, "cd $data && ls -ap | tide -", NULL }, +// { COMPLETE, NULL, NULL } +// }, +//}; #endif diff --git a/src/fetch.c b/src/fetch.c index 6bb2c7a..c8f4050 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -7,17 +7,66 @@ typedef struct { enum { COMPLETE=0, MATCHES, IS, ISSET, ISDIR, ISFILE, - SET, UNSET, FINDFILE, EXEC, LAUNCH + SET, UNSET, EXEC, LAUNCH } type; char* arg1; char* arg2; } Rule; char* Matches[10]; +Rule*** Rulesets = NULL; +size_t NumRulesets = 0; #define FETCH_RULES #include "config.h" +static Rule*** BuiltinRules = (Rule**[]){ + (Rule*[]){ /* Match URLS and open them with the browser */ + &(Rule){ ISSET, "BROWSER", NULL }, + &(Rule){ MATCHES, "data", "^(https?|ftp)://.*" }, + &(Rule){ LAUNCH, "$BROWSER \"$M0\"", NULL }, + &(Rule){ COMPLETE, NULL, NULL } + }, + (Rule*[]){ /* Open files with addresses in the editor */ + &(Rule){ MATCHES, "data", "^([^:]+):([0-9]+)" }, + &(Rule){ ISFILE, "$M1", NULL }, + &(Rule){ LAUNCH, "edit \"$M0\"", NULL }, + &(Rule){ COMPLETE, NULL, NULL } + }, + (Rule*[]){ /* Open addresses in the current file */ + &(Rule){ MATCHES, "data", "^:?([0-9]+)" }, + &(Rule){ ISFILE, "$file", NULL }, + &(Rule){ LAUNCH, "edit \"$file:$M1\"", NULL }, + &(Rule){ COMPLETE, NULL, NULL } + }, + (Rule*[]){ /* If it's an existing text file, open it with editor */ + &(Rule){ ISFILE, "$data", NULL }, + &(Rule){ EXEC, "file --mime \"$file\" | grep -q 'text/'", NULL }, + &(Rule){ LAUNCH, "edit \"$file\"", NULL }, + &(Rule){ COMPLETE, NULL, NULL } + }, + (Rule*[]){ /* Look it up in ctags database */ + &(Rule){ ISFILE, "tags", NULL }, + &(Rule){ EXEC, "grep -q \"^$data\\s\\+\" tags", NULL }, + &(Rule){ LAUNCH, "picktag fetch tags \"$data\"", NULL }, + &(Rule){ COMPLETE, NULL, NULL } + }, +// (Rule*[]){ /* Look up .c or .h files in Code/ */ +// &(Rule){ MATCHES, "data", "\\.[ch]$" }, +// &(Rule){ ISDIR, "Code", NULL }, +// &(Rule){ EXEC, "[[ \"$(find Code -type f -name \"*$data\")\" ]]", NULL }, +// &(Rule){ LAUNCH, "find Code -type f -name \"*$data\" | xargs -r edit", NULL }, +// &(Rule){ COMPLETE, NULL, NULL } +// }, + (Rule*[]){ /* If it's an existing directory, open it with system default */ + &(Rule){ ISDIR, "$data", NULL }, + &(Rule){ LAUNCH, "cd $data && ls -ap | tide -", NULL }, + &(Rule){ COMPLETE, NULL, NULL } + }, + NULL +}; + + /******************************************************************************/ char* getvar(char* val) @@ -130,12 +179,6 @@ bool var_unset(char* var) return (unsetenv(var) == 0); } -bool find_file(char* file) -{ - (void)file; - return false; -} - void runcmd(char* cmd) { char* shellcmd[] = { getvar("SHELL"), "-c", cmd, NULL }; @@ -190,7 +233,6 @@ bool apply_rule(Rule* rule) case ISFILE: ret = var_isfile(rule->arg1); break; case SET: ret = var_set(rule->arg1, rule->arg2); break; case UNSET: ret = var_unset(rule->arg1); break; - case FINDFILE: ret = find_file(rule->arg1); break; case EXEC: ret = exec(rule->arg1); break; case LAUNCH: ret = launch(rule->arg1); break; } @@ -218,37 +260,215 @@ char* type2str(int type) case ISFILE: ret = "ISFILE"; break; case SET: ret = "SET"; break; case UNSET: ret = "UNSET"; break; - case FINDFILE: ret = "FINDFILE"; break; case EXEC: ret = "EXEC"; break; case LAUNCH: ret = "LAUNCH"; break; } return ret; } -int main(int argc, char** argv) +char* nextline(char** raw) { - ARGV0 = *argv; - if (!argc) usage(ARGV0); - setenv("data", *argv, 1); + char* line = NULL; + char* eol = strchr(*raw, '\n'); + if (eol) + { + *eol = '\0'; + line = *raw; + *raw = eol+1; + } + if (line) + { + line = strdup(line); + } + return line; +} + +char* nextfield(char** raw) +{ + char* field = *raw; + /* skip whitespace */ + for (; *field && isspace(*field); field++); + + char* eof = field; + for (; *eof && !isspace(*eof); eof++); + *eof = '\0'; + *raw = eof + 1; + + return (field && *field ? field : NULL); +} + +void parse_args1(int type, Rule* rule, char* args) +{ + rule->type = type; + rule->arg1 = args; + if (!rule->arg1) + { + printf("action '%s' requires an argument\n", type2str(type)); + exit(1); + } +} + +void parse_args2(int type, Rule* rule, char* args) +{ + rule->type = type; + rule->arg1 = nextfield(&args); + rule->arg2 = nextfield(&args); + if (!rule->arg1 || !rule->arg2) + { + printf("action '%s' requires two arguments\n", type2str(type)); + exit(1); + } +} + +Rule* parse_rule(char* act, char* args) +{ + Rule* rule = calloc(1, sizeof(Rule)); + if (!strcmp(act, "is")) + { + parse_args2(IS, rule, args); + } + else if (!strcmp(act, "isset")) + { + parse_args1(ISSET, rule, args); + } + else if (!strcmp(act, "isdir")) + { + parse_args1(ISDIR, rule, args); + } + else if (!strcmp(act, "isfile")) + { + parse_args1(ISFILE, rule, args); + } + else if (!strcmp(act, "set")) + { + parse_args2(SET, rule, args); + } + else if (!strcmp(act, "unset")) + { + parse_args1(UNSET, rule, args); + } + else if (!strcmp(act, "exec")) + { + parse_args1(EXEC, rule, args); + } + else if (!strcmp(act, "launch")) + { + parse_args1(LAUNCH, rule, args); + } + else if (!strcmp(act, "matches")) + { + parse_args2(MATCHES, rule, args); + } + else + { + printf("error: unknown action '%s'\n", act); + exit(1); + } + return rule; +} + +void add_ruleset(Rule** rules, size_t nrules, Rule* rule) +{ + if (rules) + { + rules = realloc(rules, sizeof(Rule) * ++nrules); + rules[nrules-1] = rule; + } + Rulesets = realloc(Rulesets, sizeof(Rule) * ++NumRulesets); + Rulesets[NumRulesets-1] = rules; +} - telem_send("FETCH(%s)\n", *argv); - for (unsigned int i = 0; i < nelem(BuiltinRules); i++) +void parse_rules(char* path) +{ + char* line = NULL; + char* raw = readfile(path); + telem_send("RULES: %s\n", path); + if (raw) { - Rule* rule = BuiltinRules[i]; - telem_send("RULESET(%d)\n", i); + Rule** rules = NULL; + size_t nrules = 0; - for (; rule->type != COMPLETE; rule++) + while ((line = nextline(&raw)) != NULL) { - telem_send(" RULE(%s '%s' '%s')\n", type2str(rule->type), rule->arg1, rule->arg2); - if (!apply_rule(rule)) + char* cmd = line; + /* skip whitespace */ + for (; *cmd && isspace(*cmd); cmd++); + + if (!*cmd) + { + add_ruleset(rules, nrules, calloc(1, sizeof(Rule))); + rules = NULL; + nrules = 0; + } + else if (*cmd != '#') + { + char* action = nextfield(&cmd); + rules = realloc(rules, sizeof(Rule) * ++nrules); + rules[nrules-1] = parse_rule(action, cmd); + } + else { - break; + free(line); } + line = NULL; } - if (rule->type == COMPLETE) + + if (rules) + { + add_ruleset(rules, nrules, calloc(1, sizeof(Rule))); + } + add_ruleset(NULL, 0, NULL); + } + telem_send("DONE\n", path); +} + +void check_ruleset(Rule** rule) +{ + for (; (*rule)->type != COMPLETE; rule++) + { + telem_send(" RULE(%s '%s' '%s')\n", type2str((*rule)->type), (*rule)->arg1, (*rule)->arg2); + if (!apply_rule(*rule)) + { + break; + } + } + + if ((*rule)->type == COMPLETE) + { + telem_send(" ACCEPT\n"); + exit(0); + } + else + { + telem_send(" NEXT\n"); + } +} + +int main(int argc, char** argv) +{ + ARGV0 = *argv; + if (argc != 2) + { + usage(ARGV0); + } + else + { + /* load rules from files */ + parse_rules("fetch.rules"); + + telem_send("FETCH(%s)\n", argv[1]); + setenv("data", argv[1], 1); + + /* process parsed rules */ + for (; Rulesets && *Rulesets; Rulesets++) + { + check_ruleset(*Rulesets); + } + + /* process builtin rules */ + for (unsigned int i = 0; BuiltinRules[i]; i++) { - telem_send(" ACCEPT\n"); - exit(0); + check_ruleset(BuiltinRules[i]); } } return 1; diff --git a/src/tide.c b/src/tide.c index d7448d9..938e75d 100644 --- a/src/tide.c +++ b/src/tide.c @@ -192,7 +192,7 @@ static void xmousebtn(XConf* x, XEvent* e) } case MouseActFetch: { - FetchCmd[2] = view_fetch(win_view(Focused), row, col, risfile); + FetchCmd[1] = view_fetch(win_view(Focused), row, col, risfile); if (job_run(FetchCmd) != 0) { SearchDir *= (win_keymodsset(ModShift) ? -1 : +1); @@ -201,7 +201,7 @@ static void xmousebtn(XConf* x, XEvent* e) view_findstr(win_view(EDIT), SearchDir, SearchTerm); SyncMouse = true; } - free(FetchCmd[2]); + free(FetchCmd[1]); break; } case MouseActScrollUp: