task

creates a plain-text task file in the current directory
git clone git://brookjeynes.dev/bjeynes/task.git
Log | Files | Refs | README | LICENSE

commit 9fd901758d3877caa5fca48f9e446a13b7630193
parent 54ec2336968986b96ad46ff3adc73d9677bc25c0
Author: brookjeynes <me@brookjeynes.dev>
Date:   Thu, 25 Jun 2026 07:57:20 +1000

refactor: consolidate contexts / projects into single sub-command

Task-id: nrqnlxvtksoyytvz
Task-id: xwqnwoxwwtywnpry
Signed-off-by: brookjeynes <me@brookjeynes.dev>

Diffstat:
MREADME | 6+++---
Mtask.c | 217++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Dtasks/add_subcommands.txt | 9---------
Atasks/done/add_subcommands.txt | 9+++++++++
Rtasks/consolidate_contexts_and_projects_into_properties_command.txt -> tasks/done/consolidate_contexts_and_projects_into_properties_command.txt | 0
5 files changed, 149 insertions(+), 92 deletions(-)

diff --git a/README b/README @@ -1,14 +1,14 @@ task ==== -Create a plain-text task file in the current directory. +Create a plain-text task file in tasks/ Usage ----- - $ task "Call Mom" - $ task -p A "Call Mom" @phone +family + $ task new "Call Mom" + $ task new -p A "Call Mom" @phone +family The task title becomes the filename. Spaces, slashes, backslashes, and leading dots are replaced with underscores. diff --git a/task.c b/task.c @@ -8,12 +8,17 @@ #include <stdlib.h> #include <string.h> #include <sys/random.h> +#include <sys/stat.h> #include <unistd.h> #define TASK_ID_BYTES 8 #define TASK_ID_CHARS (TASK_ID_BYTES * 2) -#define TASKS_DIR "." +#define TASKS_DIR "tasks" +#define TASKS_PATH \ + "." \ + "/" TASKS_DIR #define DONE_TASKS_DIR "done" +#define DONE_TASKS_PATH TASKS_PATH "/" DONE_TASKS_DIR static const char reverse_hex[] = "klmnopqrstuvwxyz"; @@ -22,21 +27,43 @@ struct options { char priority; }; +static int filter_entries(const struct dirent *dp) { + if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) { + return 0; + } + if (strcmp(dp->d_name, DONE_TASKS_DIR) == 0) { + return 0; + } + return 1; +} + +static int count_files_scandir(const char *dir_path) { + struct dirent **namelist; + int count; + + count = scandir(dir_path, &namelist, filter_entries, alphasort); + if (count == -1) { + return -1; + } + + for (int i = 0; i < count; i++) { + free(namelist[i]); + } + free(namelist); + + return count; +} + static void print_help(const char *program) { - printf("usage: %s <command> [options]\n", + printf("usage: %s <command> [options]\n\n" + "commands:\n" + " commits <task-id> list all commits tied <task-id>.\n" + " new <title> [properties...] create a new task.\n" + " summary list contexts, projects, etc.\n" + "\noptions:\n" + " -h, --help show this help\n" + " -p, --priority A set priority A-Z\n", program); - printf("\n"); - printf("create a new task.\n"); - printf("\n"); - printf("commands:\n"); - printf(" commits <task-id> list all commits tied <task-id>.\n"); - printf(" new [--priority A|-p A] <title> [properties...] create a new task.\n"); - printf("\n"); - printf("options:\n"); - printf(" -h, --help show this help\n"); - printf(" -p, --priority A set priority A-Z\n"); - printf(" --context list all known contexts\n"); - printf(" --projects list all known projects\n"); } static int has_txt_extension(const char *name) { @@ -77,7 +104,7 @@ static int task_id_exists_in_dir(const char *dir, const char *id, return 0; } - fprintf(stderr, "err: failed to open directory: %s\n", strerror(errno)); + fprintf(stderr, "err: failed to open %s: %s\n", dir, strerror(errno)); goto cleanup; } @@ -88,8 +115,7 @@ static int task_id_exists_in_dir(const char *dir, const char *id, if (errno == 0) { break; } - fprintf(stderr, "err: failed to iterate directory: %s\n", - strerror(errno)); + perror("err: failed to iterate directory"); goto cleanup; } @@ -101,7 +127,7 @@ static int task_id_exists_in_dir(const char *dir, const char *id, continue; } - char path[1024]; + char path[4096]; int path_len; if (strcmp(dir, ".") == 0) { path_len = snprintf(path, sizeof path, "%s", dp->d_name); @@ -116,20 +142,20 @@ static int task_id_exists_in_dir(const char *dir, const char *id, fd = fopen(path, "r"); if (fd == NULL) { - fprintf(stderr, "err: failed to open file: %s\n", strerror(errno)); + perror("err: failed to open file"); goto cleanup; } char line[1024]; if (fgets(line, sizeof line, fd) == NULL) { if (ferror(fd)) { - fprintf(stderr, "err: failed to read file: %s\n", strerror(errno)); + perror("err: failed to read file"); goto cleanup; } // empty file if (fclose(fd) != 0) { - fprintf(stderr, "err: failed to close file: %s\n", strerror(errno)); + perror("err: failed to close file"); goto cleanup; } @@ -139,12 +165,12 @@ static int task_id_exists_in_dir(const char *dir, const char *id, if (fgets(line, sizeof line, fd) == NULL) { if (ferror(fd)) { - fprintf(stderr, "err: failed to read file: %s\n", strerror(errno)); + perror("err: failed to read file"); goto cleanup; } if (fclose(fd) != 0) { - fprintf(stderr, "err: failed to close file: %s\n", strerror(errno)); + perror("err: failed to close file"); goto cleanup; } @@ -161,7 +187,7 @@ static int task_id_exists_in_dir(const char *dir, const char *id, if (fclose(fd) != 0) { fd = NULL; - fprintf(stderr, "err: failed to close file: %s\n", strerror(errno)); + perror("err: failed to close file"); goto cleanup; } fd = NULL; @@ -172,13 +198,13 @@ static int task_id_exists_in_dir(const char *dir, const char *id, cleanup: if (fd != NULL) { if (fclose(fd) != 0) { - fprintf(stderr, "err: failed to close file: %s\n", strerror(errno)); + perror("err: failed to close file"); rc = -1; } } if (dirp != NULL && closedir(dirp) != 0) { - fprintf(stderr, "err: failed to close directory: %s\n", strerror(errno)); + perror("err: failed to close directory"); rc = -1; } @@ -186,12 +212,12 @@ cleanup: } static int task_id_exists(const char *id) { - int exists = task_id_exists_in_dir(TASKS_DIR, id, 0); + int exists = task_id_exists_in_dir(TASKS_PATH, id, 0); if (exists != 0) { return exists; } - return task_id_exists_in_dir(DONE_TASKS_DIR, id, 1); + return task_id_exists_in_dir(DONE_TASKS_PATH, id, 1); } static int generate_unique_task_id(char id[TASK_ID_CHARS + 1]) { @@ -219,9 +245,9 @@ static void print_properties(char identifier) { char *properties[1024]; size_t properties_len = 0; - DIR *dirp = opendir(TASKS_DIR); + DIR *dirp = opendir(TASKS_PATH); if (dirp == NULL) { - fprintf(stderr, "err: failed to open directory: %s\n", strerror(errno)); + perror("err: failed to open tasks directory"); goto cleanup; } @@ -232,8 +258,7 @@ static void print_properties(char identifier) { if (errno == 0) { break; } - fprintf(stderr, "err: failed to iterate directory: %s\n", - strerror(errno)); + perror("err: failed to iterate directory"); goto cleanup; } @@ -245,22 +270,25 @@ static void print_properties(char identifier) { continue; } - fd = fopen(dp->d_name, "r"); + char path[4096]; + snprintf(path, sizeof(path), "%s/%s", TASKS_PATH, dp->d_name); + + fd = fopen(path, "r"); if (fd == NULL) { - fprintf(stderr, "err: failed to open file: %s\n", strerror(errno)); + perror("err: failed to open file"); goto cleanup; } char line[1024]; if (fgets(line, sizeof line, fd) == NULL) { if (ferror(fd)) { - fprintf(stderr, "err: failed to read file: %s\n", strerror(errno)); + perror("err: failed to read file"); goto cleanup; } // empty file if (fclose(fd) != 0) { - fprintf(stderr, "err: failed to close file: %s\n", strerror(errno)); + perror("err: failed to close file"); goto cleanup; } @@ -287,8 +315,7 @@ static void print_properties(char identifier) { char *property_ptr = malloc(sizeof(char) * property_len); if (property_ptr == NULL) { - fprintf(stderr, "err: failed to alloc space for read property: %s\n", - strerror(errno)); + perror("err: failed to alloc space for read property"); goto cleanup; } @@ -311,7 +338,7 @@ static void print_properties(char identifier) { if (fclose(fd) != 0) { fd = NULL; - fprintf(stderr, "err: failed to close file: %s\n", strerror(errno)); + perror("err: failed to close file"); goto cleanup; } fd = NULL; @@ -345,7 +372,7 @@ cleanup: } if (dirp != NULL && closedir(dirp) != 0 && rc == 0) { - fprintf(stderr, "err: failed to close directory: %s\n", strerror(errno)); + perror("err: failed to close directory"); rc = 1; } @@ -354,7 +381,7 @@ cleanup: } } -int new_command(int argc, char *argv[], struct options *cli_args) { +static int new_command(int argc, char *argv[], struct options *cli_args) { if (argc == 1) { fprintf(stderr, "err: title required\n"); return 1; @@ -433,32 +460,42 @@ int new_command(int argc, char *argv[], struct options *cli_args) { } strcpy(title_to_file + title_len, ".txt"); - char id[TASK_ID_CHARS + 1]; - if (generate_unique_task_id(id) != 0) { - perror("generate_unique_task_id"); + char path[4096]; + snprintf(path, sizeof(path), "%s/%s", TASKS_PATH, title_to_file); + + struct stat st = {0}; + if (stat(TASKS_PATH, &st) == -1) { + mkdir(TASKS_PATH, 0700); + } + + DIR *tasks_dir = opendir(TASKS_PATH); + if (tasks_dir == NULL) { + if (errno == ENOENT) { + } + + fprintf(stderr, "err: failed to check for %s: %s\n", TASKS_PATH, + strerror(errno)); return 1; } + closedir(tasks_dir); - FILE *f = fopen(title_to_file, "wx"); + FILE *f = fopen(path, "wx"); if (f == NULL) { - fprintf(stderr, "err: failed to open file: %s\n", strerror(errno)); + perror("err: failed to open files"); return 1; } - int write_failed = 0; - int write_errno = 0; - if (cli_args->priority != '\0') { if (fprintf(f, "(%c) ", cli_args->priority) < 0) { fclose(f); - fprintf(stderr, "err: failed to write file: %s\n", strerror(errno)); + perror("err: failed to write file"); return 1; } } if (fprintf(f, "%s", cli_args->title) < 0) { fclose(f); - fprintf(stderr, "err: failed to write file: %s\n", strerror(errno)); + perror("err: failed to write file"); return 1; } @@ -476,43 +513,49 @@ int new_command(int argc, char *argv[], struct options *cli_args) { if (fprintf(f, " %s", argv[idx]) < 0) { fclose(f); - fprintf(stderr, "err: failed to write file: %s\n", strerror(errno)); + perror("err: failed to write file"); return 1; } } if (fprintf(f, "\n") < 0) { fclose(f); - fprintf(stderr, "err: failed to write file: %s\n", strerror(errno)); + perror("err: failed to write file"); return 1; } - if (fprintf(f, "id: %s\n", id) < 0) { - fclose(f); - fprintf(stderr, "err: failed to write file: %s\n", strerror(errno)); + char id[TASK_ID_CHARS + 1]; + if (generate_unique_task_id(id) != 0) { + perror("err: failed to generate unique id"); return 1; } - char *editor = getenv("VISUAL"); - if (editor != NULL && strcmp(editor, "") == 0) { - editor = NULL; - } - if (editor == NULL) { - editor = getenv("EDITOR"); + if (fprintf(f, "id: %s\n", id) < 0) { + fclose(f); + perror("err: failed to write file"); + return 1; } - if (editor == NULL || strcmp(editor, "") == 0) { - return 0; - } + // char *editor = getenv("VISUAL"); + // if (editor != NULL && strcmp(editor, "") == 0) { + // editor = NULL; + // } + // if (editor == NULL) { + // editor = getenv("EDITOR"); + // } + // + // if (editor == NULL || strcmp(editor, "") == 0) { + // return 0; + // } - execlp("sh", "sh", "-c", "exec $1 \"$2\"", "task", editor, title_to_file, - NULL); - fprintf(stderr, "err: failed to open editor: %s", strerror(errno)); + // execlp("sh", "sh", "-c", "exec $1 \"$2\"", "task", editor, path, NULL); + // perror("err: failed to open editor"); return 1; } -int related_commits_command(int argc, char *argv[], struct options *cli_args) { +static int related_commits_command(int argc, char *argv[], + struct options *cli_args) { if (argc != 2) { fprintf(stderr, "err: task id required\n"); return 1; @@ -605,16 +648,6 @@ int main(int argc, char *argv[]) { return 0; } - if (argc > 1 && (strcmp(argv[1], "--contexts") == 0)) { - print_properties('@'); - return 0; - } - - if (argc > 1 && (strcmp(argv[1], "--projects") == 0)) { - print_properties('+'); - return 0; - } - if (argc > 1 && strcmp(argv[1], "new") == 0) { if (new_command(argc - 1, argv + 1, &cli_args) != 0) { return 1; @@ -627,5 +660,29 @@ int main(int argc, char *argv[]) { } } - return 1; + if (argc > 1 && strcmp(argv[1], "summary") == 0) { + int open_tasks; + if ((open_tasks = count_files_scandir(TASKS_PATH)) < 0) { + perror("err: failed to read tasks/"); + return 1; + } + + int closed_tasks; + if ((closed_tasks = count_files_scandir(DONE_TASKS_PATH)) < 0) { + if (errno != ENOENT) { + perror("err: failed to read tasks/done/"); + return 1; + } + closed_tasks = 0; + } + + printf("open tasks: %d\n", open_tasks); + printf("closed tasks: %d\n", closed_tasks); + printf("\ncontexts:\n"); + print_properties('@'); + printf("\nprojects:\n"); + print_properties('+'); + } + + return 0; } diff --git a/tasks/add_subcommands.txt b/tasks/add_subcommands.txt @@ -1,9 +0,0 @@ -add subcommands @feat -id: nrqnlxvtksoyytvz - -task doesnt have subcommands. this is a little painful when adding new features -as they are forced behind flags. - -[x] new -[x] related-commits -[ ] contexts / projects diff --git a/tasks/done/add_subcommands.txt b/tasks/done/add_subcommands.txt @@ -0,0 +1,9 @@ +add subcommands @feat +id: nrqnlxvtksoyytvz + +task doesnt have subcommands. this is a little painful when adding new features +as they are forced behind flags. + +[x] new +[x] related-commits +[x] contexts / projects diff --git a/tasks/consolidate_contexts_and_projects_into_properties_command.txt b/tasks/done/consolidate_contexts_and_projects_into_properties_command.txt