/* stask - simple task manager * See LICENSE file for copyright and license details. */ #include #include #include #include #include #include "stask.h" static void ensure_dir(const char *path) { char tmp[1024]; char *p; snprintf(tmp, sizeof(tmp), "%s", path); for (p = tmp + 1; *p; p++) { if (*p == '/') { *p = '\0'; mkdir(tmp, 0755); *p = '/'; } } mkdir(tmp, 0755); } void doc_dir(TaskDB *tdb, const char *listname, char *buf, int len) { snprintf(buf, len, "%s/docs/%s", tdb->docdir, listname); } void doc_path(TaskDB *tdb, const char *listname, int task_id, char *buf, int len) { snprintf(buf, len, "%s/docs/%s/%d.md", tdb->docdir, listname, task_id); } int doc_create(TaskDB *tdb, const char *listname, int task_id, const char *title, const char *date) { char path[1024], dir[1024]; FILE *fp; doc_dir(tdb, listname, dir, sizeof(dir)); ensure_dir(dir); doc_path(tdb, listname, task_id, path, sizeof(path)); fp = fopen(path, "w"); if (!fp) { fprintf(stderr, "stask: cannot create %s\n", path); return -1; } fprintf(fp, "---\n"); fprintf(fp, "title: \"%s\"\n", title); fprintf(fp, "date: %s\n", date); fprintf(fp, "done: false\n"); fprintf(fp, "---\n\n"); fclose(fp); return 0; } int doc_delete(TaskDB *tdb, const char *listname, int task_id) { char path[1024]; doc_path(tdb, listname, task_id, path, sizeof(path)); return unlink(path); } int doc_delete_all(TaskDB *tdb, const char *listname) { Task *tasks; int i, n; List list; char dir[1024]; if (list_by_name(tdb, listname, &list) < 0) return -1; if (task_all(tdb, list.id, &tasks, &n) == 0) { for (i = 0; i < n; i++) doc_delete(tdb, listname, tasks[i].id); free(tasks); } doc_dir(tdb, listname, dir, sizeof(dir)); rmdir(dir); /* only succeeds if empty */ return 0; } int doc_edit(TaskDB *tdb, const char *listname, int task_id) { char path[1024]; const char *editor; char cmd[2048]; int ret; doc_path(tdb, listname, task_id, path, sizeof(path)); if (access(path, F_OK) != 0) { fprintf(stderr, "stask: no doc for task %d\n", task_id); return -1; } editor = getenv("EDITOR"); if (!editor) editor = "vi"; snprintf(cmd, sizeof(cmd), "%s '%s'", editor, path); ret = system(cmd); if (ret == 0) doc_sync_title(tdb, listname, task_id); return ret; } int doc_show(TaskDB *tdb, const char *listname, int task_id) { char path[1024]; FILE *fp; char line[1024]; doc_path(tdb, listname, task_id, path, sizeof(path)); fp = fopen(path, "r"); if (!fp) { fprintf(stderr, "stask: no doc for task %d\n", task_id); return -1; } while (fgets(line, sizeof(line), fp)) fputs(line, stdout); fclose(fp); return 0; } /* parse title from yaml frontmatter */ static int parse_title(const char *path, char *title, int len) { FILE *fp; char line[1024]; int in_fm = 0; char *p, *end; fp = fopen(path, "r"); if (!fp) return -1; while (fgets(line, sizeof(line), fp)) { /* trim trailing newline */ p = line + strlen(line) - 1; while (p >= line && (*p == '\n' || *p == '\r')) *p-- = '\0'; if (strcmp(line, "---") == 0) { if (!in_fm) { in_fm = 1; continue; } else { break; /* end of frontmatter */ } } if (!in_fm) continue; if (strncmp(line, "title:", 6) == 0) { p = line + 6; while (*p == ' ' || *p == '\t') p++; /* strip quotes */ if (*p == '"' || *p == '\'') { p++; end = p + strlen(p) - 1; if (end > p && (*end == '"' || *end == '\'')) *end = '\0'; } snprintf(title, len, "%s", p); fclose(fp); return 0; } } fclose(fp); return -1; } /* update done field in frontmatter */ static int update_fm_field(const char *path, const char *field, const char *value) { FILE *fp; char *buf; long fsize; char line[1024], out[65536]; int in_fm = 0, found = 0, olen = 0; char *p, *src; fp = fopen(path, "r"); if (!fp) return -1; fseek(fp, 0, SEEK_END); fsize = ftell(fp); fseek(fp, 0, SEEK_SET); buf = malloc(fsize + 1); if (!buf) { fclose(fp); return -1; } fsize = fread(buf, 1, fsize, fp); buf[fsize] = '\0'; fclose(fp); src = buf; out[0] = '\0'; while (*src) { /* read a line */ p = strchr(src, '\n'); if (p) { int llen = (int)(p - src); if (llen >= (int)sizeof(line) - 1) llen = sizeof(line) - 2; memcpy(line, src, llen); line[llen] = '\0'; src = p + 1; } else { snprintf(line, sizeof(line), "%s", src); src += strlen(src); } if (strcmp(line, "---") == 0) { if (!in_fm) { in_fm = 1; } else { in_fm = 0; } olen += snprintf(out + olen, sizeof(out) - olen, "%s\n", line); continue; } if (in_fm && strncmp(line, field, strlen(field)) == 0 && line[strlen(field)] == ':') { olen += snprintf(out + olen, sizeof(out) - olen, "%s: %s\n", field, value); found = 1; } else { olen += snprintf(out + olen, sizeof(out) - olen, "%s\n", line); } } free(buf); if (!found) return -1; fp = fopen(path, "w"); if (!fp) return -1; fwrite(out, 1, olen, fp); fclose(fp); return 0; } int doc_sync_title(TaskDB *tdb, const char *listname, int task_id) { char path[1024], title[512]; doc_path(tdb, listname, task_id, path, sizeof(path)); if (access(path, F_OK) != 0) return -1; if (parse_title(path, title, sizeof(title)) < 0) return -1; if (title[0] == '\0') return -1; return task_update_text(tdb, task_id, title); } /* called from data.c when task_done/task_undo changes */ void doc_sync_done(TaskDB *tdb, const char *listname, int task_id, int done) { char path[1024]; doc_path(tdb, listname, task_id, path, sizeof(path)); if (access(path, F_OK) != 0) return; update_fm_field(path, "done", done ? "true" : "false"); }