From: Michael D. Lowis Date: Fri, 2 Nov 2018 18:57:25 +0000 (-0400) Subject: added fetch command X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=c6b0ecf430625925719bace544aba2927a725536;p=projs%2Ftide.git added fetch command --- diff --git a/.gitignore b/.gitignore index 45e4aaf..4ca8276 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,4 @@ tctl flaws.txt edit registrar +fetch diff --git a/Makefile b/Makefile index 9f50973..51660d9 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ INCS = -Iinc/ -BINS = tide registrar edit +BINS = tide registrar edit fetch MAN1 = docs/tide.1 LIBEDIT_OBJS = \ diff --git a/plumber.c b/plumber.c new file mode 100644 index 0000000..48beb12 --- /dev/null +++ b/plumber.c @@ -0,0 +1,280 @@ +#define _XOPEN_SOURCE 700 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __MACH__ + #define OPENCMD "open" +#else + #define OPENCMD "xdg-open" +#endif + +Atom XA_PLUMB; + +typedef struct { + enum { + COMPLETE=0, MATCHES, IS, ISSET, ISDIR, ISFILE, + SET, UNSET, EXEC, LAUNCH, NTYPES + } type; + char* arg1; + char* arg2; +} Rule; + +char* TypeNames[NTYPES] = { + "complete", "matches", "is", "isset", "isdir", "isfile", "set", + "unset", "exec", "launch" +}; + +char* Matches[10]; + +Rule* BuiltinRules[] = { + (Rule[]){ // Look up .c or .h files in Code/ + { ISSET, "EDITOR", NULL }, + { MATCHES, "data", "\\.[ch]$" }, + { ISDIR, "Code", NULL }, + { EXEC, "[[ $(find Code -type f -name '*$data') ]]", NULL }, + { LAUNCH, "find Code -type f -name '*$data' | xargs -r $EDITOR", NULL }, + { COMPLETE, NULL, NULL } + }, + (Rule[]){ // Match URLS and open them with the browser + { ISSET, "BROWSER", NULL }, + { MATCHES, "data", "^(https?|ftp)://.*" }, + { LAUNCH, "$BROWSER $0", NULL }, + { COMPLETE, NULL, NULL } + }, + (Rule[]){ // Open files with addresses in the editor + { ISSET, "EDITOR", NULL }, + { MATCHES, "data", "^([^:]+):([0-9]+)" }, + { ISFILE, "$1", NULL }, + { LAUNCH, "tctl $0", NULL }, + { COMPLETE, NULL, NULL } + }, + (Rule[]){ // If it's an existing text file, open it with editor + { ISSET, "EDITOR", NULL }, + { ISFILE, "$data", NULL }, + { EXEC, "file --mime '$file' | grep -q 'text/'", NULL }, + { LAUNCH, "$EDITOR '$file'", NULL }, + { COMPLETE, NULL, NULL } + }, + (Rule[]){ // Look it up in ctags database + { ISSET, "EDITOR", NULL }, + { ISFILE, "tags", NULL }, + { EXEC, "grep -q '^$data\\s\\+' tags", NULL }, + { LAUNCH, "picktag fetch tags '$data' | xargs -r tide", NULL }, + { COMPLETE, NULL, NULL } + }, + (Rule[]){ // If it's an existing directory, open it with system default + { ISDIR, "$data", NULL }, + { LAUNCH, OPENCMD " $data", NULL }, + { COMPLETE, NULL, NULL } + }, +}; + +char* getvar(char* val) { + if (strlen(val) == 1 && isdigit(*val)) + val = Matches[*val - '0']; + else + val = getenv(val); + return (val ? val : ""); +} + +char* strappend(char* rs, size_t* sz, char c) { + *sz += 1; + rs = realloc(rs, *sz); + rs[*sz - 1] = c; + return rs; +} + +char* eval(char* s) { + size_t sz = 0; + char* rs = NULL; + while (s && *s) { + char c = *s++; + if ('$' == c && '$' == *s) { + rs = strappend(rs, &sz, *s++); + } else if ('$' == c) { + size_t vsz = 0; + char* var = calloc(1,1); + while (*s && (isalnum(*s) || *s == '_')) + var = strappend(var, &vsz, *s++); + char* val = getvar(var); + while (val && *val) + rs = strappend(rs, &sz, *val++); + free(var); + } else { + rs = strappend(rs, &sz, c); + } + } + rs = strappend(rs, &sz, '\0'); + return rs; +} + +bool matches(char* var, char* patt) { + regex_t regex = {0}; + regmatch_t matches[10] = {0}; + if (regcomp(®ex, patt, REG_EXTENDED) == 0) { + var = getvar(var); + memset(Matches, 0, sizeof(Matches)); + int err = regexec(®ex, var, nelem(matches), matches, 0); + for (int i = 0; i < 10 && matches[i].rm_so >= 0; i++) { + Matches[i] = strndup(var+matches[i].rm_so, matches[i].rm_eo-matches[i].rm_so); + } + return (err == 0); + } + return false; +} + +bool var_is(char* var, char* val) { + return (strcmp(getvar(var), eval(val)) == 0); +} + +bool var_isset(char* var) { + return (getenv(var) != NULL); +} + +bool var_isdir(char* var) { + struct stat st = {0}; + char* path = eval(var); + if ((stat(path, &st) < 0) && (errno == ENOENT)) { + return false; + } else if (S_ISDIR(st.st_mode)) { + setenv("dir", var, 1); + return true; + } else { + return false; + } +} + +bool var_isfile(char* var) { + struct stat st = {0}; + char* path = eval(var); + if ((stat(eval(var), &st) < 0) && (errno == ENOENT)) { + return false; + } else if (!S_ISDIR(st.st_mode)) { + setenv("file", path, 1); + return true; + } else { + return false; + } +} + +bool var_set(char* var, char* val) { + return (setenv(var, eval(val), 1) == 0); +} + +bool var_unset(char* var) { + return (unsetenv(var) == 0); +} + +void runcmd(char* cmd) { + char* shellcmd[] = { getvar("SHELL"), "-c", NULL, NULL }; + if (!shellcmd[0]) shellcmd[0] = "/bin/sh"; + shellcmd[2] = eval(cmd); + _exit(execvp(shellcmd[0], shellcmd)); +} + +bool exec(char* cmd) { + int pid, status, outpipe[2]; + if ((pid = fork()) < 0) return false; + if (pid == 0) { + runcmd(cmd); + } else { + waitpid(pid, &status, 0); + return (status == 0); + } + return false; +} + +bool launch(char* cmd) { + int pid = fork(); + if (pid > 0) + return true; + else if (pid == 0) + runcmd(cmd); + return false; +} + +bool apply_rule(Rule* rule) { + switch (rule->type) { + case COMPLETE: exit(0); + case MATCHES: return matches(rule->arg1, rule->arg2); + case IS: return var_is(rule->arg1, rule->arg2); + case ISSET: return var_isset(rule->arg1); + case ISDIR: return var_isdir(rule->arg1); + case ISFILE: return var_isfile(rule->arg1); + case SET: return var_set(rule->arg1, rule->arg2); + case UNSET: return var_unset(rule->arg1); + case EXEC: return exec(rule->arg1); + case LAUNCH: return launch(rule->arg1); + default: return false; + } +} + +void plumb(char* str) { + if (!str || !str[0]) return; + char* path = str; + for (; *str && *str != '\n'; str++); + *str = '\0', str++; + char* data = str; + if (fork() == 0) { + chdir(path); + setenv("data", str, 1); + for (int i = 0; i < nelem(BuiltinRules); i++) { + Rule* rule = BuiltinRules[i]; + for (; rule->type != COMPLETE; rule++) { + printf("%s '%s' '%s'\n", TypeNames[rule->type], rule->arg1, rule->arg2); + if (!apply_rule(rule)) + break; + } + puts(""); + if (rule->type == COMPLETE) + exit(0); + } + exit(1); + } +} + +void selclear(XConf* x, XEvent* e) { + exit(0); +} + +void propnotify(XConf* x, XEvent* e) { + int success = 0, nreturn = 0; + unsigned long nleft; + XTextProperty prop = {0}; + if (e->xproperty.atom != XA_PLUMB) return; + success = XGetWindowProperty( + x->display, x->self, XA_PLUMB, 0, -1, True, AnyPropertyType, + &prop.encoding, &prop.format, &prop.nitems, &nleft, &prop.value + ); + if (Success == success) { + char** strlist = NULL; + if (Success == XmbTextPropertyToTextList(x->display, &prop, &strlist, &nreturn)) + for (int i = 0; i < nreturn; i++) + plumb(strlist[i]); + if (strlist) XFreeStringList(strlist); + } + if (prop.value) XFree(prop.value); +} + +int main(int argc, char** argv) { + XConf x; + x11_init(&x); + x11_mkwin(&x, 1, 1, PropertyChangeMask); + XA_PLUMB = XInternAtom(x.display, "PLUMB", 0); + x.eventfns[SelectionClear] = selclear; + x.eventfns[PropertyNotify] = propnotify; + if (None == XGetSelectionOwner(x.display, XA_PLUMB)) { + XSetSelectionOwner(x.display, XA_PLUMB, x.self, CurrentTime); + if (x.self == XGetSelectionOwner(x.display, XA_PLUMB)) { + x11_event_loop(&x); + } + } + return 1; +} diff --git a/src/fetch.c b/src/fetch.c new file mode 100644 index 0000000..87b83a5 --- /dev/null +++ b/src/fetch.c @@ -0,0 +1,243 @@ +#define _XOPEN_SOURCE 700 +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __MACH__ + #define OPENCMD "open" +#else + #define OPENCMD "xdg-open" +#endif + +typedef struct { + enum { + COMPLETE=0, MATCHES, IS, ISSET, ISDIR, ISFILE, + SET, UNSET, FINDFILE, EXEC, LAUNCH + } type; + char* arg1; + char* arg2; +} Rule; + +char* Matches[10]; + +Rule* BuiltinRules[] = { + (Rule[]){ // Look up .c or .h files in Code/ + { ISSET, "EDITOR", NULL }, + { MATCHES, "data", "\\.[ch]$" }, + { ISDIR, "Code", NULL }, + { EXEC, "[[ $(find Code -type f -name '*$data') ]]", NULL }, + { LAUNCH, "find Code -type f -name '*$data' | xargs -r $EDITOR", NULL }, + { COMPLETE, NULL, NULL } + }, + (Rule[]){ // Match URLS and open them with the browser + { ISSET, "BROWSER", NULL }, + { MATCHES, "data", "^(https?|ftp)://.*" }, + { LAUNCH, "$BROWSER $0", NULL }, + { COMPLETE, NULL, NULL } + }, + (Rule[]){ // Open files with addresses in the editor + { ISSET, "EDITOR", NULL }, + { MATCHES, "data", "^([^:]+):([0-9]+)" }, + { ISFILE, "$1", NULL }, + { LAUNCH, "tctl $0", NULL }, + { COMPLETE, NULL, NULL } + }, + (Rule[]){ // If it's an existing text file, open it with editor + { ISSET, "EDITOR", NULL }, + { ISFILE, "$data", NULL }, + { EXEC, "file --mime '$file' | grep -q 'text/'", NULL }, + { LAUNCH, "$EDITOR '$file'", NULL }, + { COMPLETE, NULL, NULL } + }, + (Rule[]){ // Look it up in ctags database + { ISSET, "EDITOR", NULL }, + { ISFILE, "tags", NULL }, + { EXEC, "grep -q '^$data\\s\\+' tags", NULL }, + { LAUNCH, "picktag fetch tags '$data' | xargs -r tide", NULL }, + { COMPLETE, NULL, NULL } + }, + (Rule[]){ // If it's an existing directory, open it with system default + { ISDIR, "$data", NULL }, + { LAUNCH, OPENCMD " $data", NULL }, + { COMPLETE, NULL, NULL } + }, +}; + +/******************************************************************************/ + +char* getvar(char* val) { + if (strlen(val) == 1 && isdigit(*val)) + val = Matches[*val - '0']; + else + val = getenv(val); + return (val ? val : ""); +} + +char* eval(char* str) { + static bool inited = false; + static char* patt = "\\$([a-zA-Z0-9_]+)"; + static regex_t regex; + + if (!inited && (regcomp(®ex, patt, REG_EXTENDED) < 0)) { + perror("regcomp() :"); + exit(1); + } + + regmatch_t matches[2] = {0}; + if (regexec(®ex, str, nelem(matches), matches, 0) < 0) { + return str; + } else if (matches[1].rm_so > 0) { + char* var = strndup(str+matches[1].rm_so, matches[1].rm_eo-matches[1].rm_so); + char* val = getvar(var); + size_t sz = strlen(str) + strlen(val); + char* exp = calloc(1, sz); + strncat(exp, str, matches[0].rm_so); + strcat(exp, val); + strcat(exp, str + matches[0].rm_eo); + return eval(exp); + } else { + return str; + } +} + +/******************************************************************************/ + +bool complete(void) { + exit(0); + return false; +} + +bool matches(char* var, char* patt) { + regex_t regex = {0}; + regmatch_t matches[10] = {0}; + if (regcomp(®ex, patt, REG_EXTENDED) == 0) { + var = getvar(var); + memset(Matches, 0, sizeof(Matches)); + int err = regexec(®ex, var, nelem(matches), matches, 0); + for (int i = 0; i < 10 && matches[i].rm_so >= 0; i++) { + Matches[i] = strndup(var+matches[i].rm_so, matches[i].rm_eo-matches[i].rm_so); + } + return (err == 0); + } + return false; +} + +bool var_is(char* var, char* val) { + return (strcmp(getvar(var), eval(val)) == 0); +} + +bool var_isset(char* var) { + return (getenv(var) != NULL); +} + +bool var_isdir(char* var) { + struct stat st = {0}; + char* path = eval(var); + if ((stat(path, &st) < 0) && (errno == ENOENT)) { + return false; + } else if (S_ISDIR(st.st_mode)) { + setenv("dir", var, 1); + return true; + } else { + return false; + } +} + +bool var_isfile(char* var) { + struct stat st = {0}; + char* path = eval(var); + if ((stat(eval(var), &st) < 0) && (errno == ENOENT)) { + return false; + } else if (!S_ISDIR(st.st_mode)) { + setenv("file", path, 1); + return true; + } else { + return false; + } +} + +bool var_set(char* var, char* val) { + return (setenv(var, eval(val), 1) == 0); +} + +bool var_unset(char* var) { + return (unsetenv(var) == 0); +} + +bool find_file(char* file) { + return false; +} + +void runcmd(char* cmd) { + char* shellcmd[] = { getvar("SHELL"), "-c", NULL, NULL }; + if (!shellcmd[0]) shellcmd[0] = "/bin/sh"; + shellcmd[2] = eval(cmd); + _exit(execvp(shellcmd[0], shellcmd)); +} + +bool exec(char* cmd) { + int pid, status; + if ((pid = fork()) < 0) return false; + if (pid == 0) { + runcmd(cmd); + } else { + waitpid(pid, &status, 0); + return (status == 0); + } + return false; +} + +bool launch(char* cmd) { + int pid = fork(); + if (pid > 0) + return true; + else if (pid == 0) + runcmd(cmd); + return false; +} + +bool apply_rule(Rule* rule) { + switch (rule->type) { + case COMPLETE: exit(0); + case MATCHES: return matches(rule->arg1, rule->arg2); + case IS: return var_is(rule->arg1, rule->arg2); + case ISSET: return var_isset(rule->arg1); + case ISDIR: return var_isdir(rule->arg1); + case ISFILE: return var_isfile(rule->arg1); + case SET: return var_set(rule->arg1, rule->arg2); + case UNSET: return var_unset(rule->arg1); + case FINDFILE: return find_file(rule->arg1); + case EXEC: return exec(rule->arg1); + case LAUNCH: return launch(rule->arg1); + } + return false; +} + +/******************************************************************************/ + +void usage(char* pname) { + fprintf(stderr, "Usage: %s [ITEM]\n", pname); + exit(1); +} + +int main(int argc, char** argv) { + if (argc != 2) usage(argv[0]); + setenv("data", argv[1], 1); + for (int i = 0; i < nelem(BuiltinRules); i++) { + Rule* rule = BuiltinRules[i]; + for (; rule->type != COMPLETE; rule++) { + //printf("%d '%s' '%s'\n", rule->type, rule->arg1, rule->arg2); + if (!apply_rule(rule)) + break; + } + //puts(""); + if (rule->type == COMPLETE) + exit(0); + } + return 1; +}