diff --git a/src/args.c b/src/args.c index 520ae98..47a4d82 100644 --- a/src/args.c +++ b/src/args.c @@ -95,6 +95,8 @@ time_t pit_arg_date(char **arg, char *required) if (required && (!*arg || pit_arg_is_option(arg))) { die("missing %s", required); + } else if (!strcmp(*arg, "none")) { /* Drop dat value */ + return -1; } else { bool alpha_date = isalpha(**arg); bool slash_date = (strchr(*arg, '/') != NULL); @@ -169,6 +171,8 @@ time_t pit_arg_time(char **arg, char *required) if (required && (!*arg || pit_arg_is_option(arg))) { die("missing %s", required); + } else if (!strcmp(*arg, "none")) { /* Drop time value */ + return -1; } else { /* Suppored time formats are :MM, HH:MM, and HH */ char *colon = strchr(*arg, ':'); if (colon == *arg) { /* :MM - minutes only */ diff --git a/src/db.c b/src/db.c index 8d648f3..c1def52 100644 --- a/src/db.c +++ b/src/db.c @@ -65,7 +65,7 @@ void pit_init(char *argv[]) { if (!access(file_name, F_OK)) { if (!*arg || strcmp(*arg, "-f")) { /* Do not prompt user if forced init (-f). */ - printf("%s already exist, do you want to override it [y/N]: ", file_name); + printf("%s already exists, do you want to override it [y/N]: ", file_name); reply = getchar(); } } @@ -80,8 +80,8 @@ void pit_info(char *argv[]) { pit_db_load(); printf("Pit file name: %s\n", pit_file_name()); - printf("Created by: %s on %s", header->created_by, ctime(&header->created_at)); - printf("Last updated by: %s on %s", header->updated_by, ctime(&header->updated_at)); + printf("Created by: %s on %s\n", header->created_by, format_timestamp(header->created_at)); + printf("Last updated by: %s on %s\n", header->updated_by, format_timestamp(header->updated_at)); printf("Schema version: %d\n", header->schema_version); printf("Projects: %d\n", projects->number_of_records); printf("Tasks: %d\n", tasks->number_of_records); diff --git a/src/help.c b/src/help.c index ea8773e..8a2eeaf 100644 --- a/src/help.c +++ b/src/help.c @@ -3,7 +3,172 @@ #include #include "pit.h" +static void usage() +{ + char *msg[] = { + "usage: pit command [args]\n", + "The commands are:", + " init Create empty pit database or reinitialize an existing one", + " project Create, search, and manage pit projects", + " task Create, search, and manage pit tasks", + " note Create, search, and manage pit notes", + " log Show chronological pit activity log", + " info Show summary information about your pit database", + " help Show help information about pit", + " version Show pit version number\n", + "All commands might be shortened as long as they remain unambiguous. See 'pit help ' for more", + "information on a specific command.\n", NULL + }; + printa(msg); +} + +static void help_project() +{ + char *msg[] = { + "Pit project is basic entity used to group related tasks together. In pit project has name and status, neither", + "of which have any semantical meaning and are just arbitrary strings.\n", + "Creating a project:\n", + " $ pit project -c name [-s status]\n", + "Editing a project:\n", + " $ pit project -e [number] [-n name] [-s status]\n", + "Deleting a project:\n", + " $ pit project -d [number]\n", + "Viewing a project:\n", + " $ pit project [[-q] number]\n", + "Searching projects:\n", + " $ pit project -q [number | [-n name] [-s status]]\n", + "Examples:\n", + " $ pit project -c 'My Tasks'", + " created project 1: My Tasks (status: active)\n", + " $ pit project -c 'Feature Requests' -s backlog", + " created project 2: Feature Requests (status: backlog)\n", + " $ pit pro", + " 1: (username) [active ] My Tasks (0 tasks)", + " * 2: (username) [backlog] Feature Requests (0 tasks)\n", + " $ pit p 1 -e -n 'Task and Bugs' -s current", + " 1: Task and Bugs (current, 0 tasks)\n", + " $ pit p", + " 1: (username) [current] Task and Bugs (0 tasks)", + " * 2: (username) [backlog] Feature Requests (0 tasks)\n", + " $ pit p -d", + " deleted project 2: Feature Requests\n", + " $ pit p", + " * 1: (username) [current] Task and Bugs (0 tasks)\n", NULL + }; + printa(msg); +} + +static void help_task() +{ + char *msg[] = { + "In pit a task belongs to particular project. A task has name, status, priority, date, and time. All attributes", + "except the task name are optional.\n", + "Creating a task:\n", + " pit task -c name [-s status] [-p priority] [-d date] [-t time]\n", + "Editing a task:\n", + " pit task -e [number] [-n name] [-s status] [-p priority] [-d date] [-t time]\n", + "Deleting a task:\n", + " pit task -d [number]\n", + "Viewing a task:\n", + " pit task [[-q] number]\n", + "Searching tasks:\n", + " pit task -q [number | [-n name] [-s status] [-p priority] [-d date] [-t time]]\n", + "Supported date formats:\n", + " none, 4/26, 4/26/2012, 4/26/12, '4/26 3pm', '4/26 19:30', '4/26/2012 3:15am', '4/26/12 17:00'", + " 'Apr 26', 'Apr 26, 2012', 'Apr 26 3pm', 'Apr 26 19:30', 'Apr 26, 12 3:15am', 'Apr 26, 2012 17:00'\n", + "Supported time formats:\n", + " none, 17, 17:00, 17:30, 5pm, 1:15am\n", + "Examples:\n", + " $ pit task -c 'Hack this'", + " created task 1/1: Hack this (status: open, priority: normal)\n", + " $ pit task -c 'And hack that' -s new -p urgent -d 'Dec 31'", + " created task 2/1: And hack that (status: new, priority: urgent, date: Dec 31, 2010)\n", + " $ pit t", + " 1: (username) [open] [normal] Hack this (0 notes)", + " * 2: (username) [new ] [urgent] Dec 31, 2010 And hack that (0 notes)\n", + " $ pit t -e 1 -s done -d 10/10 -t 4:30", + " updated task 1: Hack that (status: done, date: Oct 10, 2010, time: 4:30)\n", + " $ pit t", + " 1: (username) [done] [normal] Oct 10, 2010 4:30 Hack this (0 notes)", + " * 2: (username) [new] [urgent] Dec 31, 2010 And hack that (0 notes)\n", + " $ pit t -d", + " deleted task 2: And hack that\n", + " $ pit t", + " 1: (username) [done] [normal] Oct 10, 2010 4:30 Hack this (0 notes)\n", NULL + }; + printa(msg); +} + +static void help_note() +{ + puts("pit note is not implemented yet."); +} + +static void help_log() +{ + char *msg[] = { + "Show summary information about your pit database. This command is as simple as:\n", + " pit log\n", NULL + }; + printa(msg); +} + +static void help_init() +{ + char *msg[] = { + "Create empty pit database or reinitialize an existing one. Default file name for the pit database", + "is ~/.pit -- you can override the default by setting PITFILE environment variable.\n", + " $ pit init [-f]\n", + " -f force initialization without prompt\n", + "Example:\n", + " $ pit init", + " /home/user/.pit already exists, do you want to override it [y/N]: y", + " Created empty /home/user/.pit\n", NULL + }; + printa(msg); +} + +static void help_info() +{ + char *msg[] = { + "Show summary information about your pit database. This command is as simple as:\n", + " pit info\n", NULL + }; + printa(msg); +} + +static void help_version() +{ + puts("No kidding :-)"); + pit_version(); +} + void pit_help(char *argv[]) { - puts("pit: help is not implemented yet"); + char *command[] = { "project", "task", "note", "log", "init", "info", "help", "version" }; + void (*handler[])() = { help_project, help_task, help_note, help_log, help_init, help_info, usage, help_version }; + + if (!argv[1]) { + usage(); + } else { + register int i, candidate = -1; + + for(i = 0; i < ARRAY_SIZE(command); i++) { + if (strstr(command[i], argv[1]) == command[i]) { + if (candidate < 0) { + candidate = i; + } else { + printf("no help, <%s> is a bit ambiguous\n", argv[1]); + usage(); + } + } + } + + if (candidate < 0) { + printf("no help, <%s> is unknown command\n", argv[1]); + usage(); + } else { + handler[candidate](); + } + } } \ No newline at end of file diff --git a/src/object.h b/src/object.h index a6cdcbd..e757e1a 100644 --- a/src/object.h +++ b/src/object.h @@ -16,8 +16,6 @@ typedef struct _Project { char name[128]; /* Project name. */ char status[16]; /* Project status. */ int number_of_tasks; /* Number of tasks for the project. */ - int created_by; /* Who created the project? */ - int updated_by; /* Who last updated the project? */ time_t created_at; /* When the project was created? */ time_t updated_at; /* When the project was last updated? */ } Project, *PProject; diff --git a/src/pager.c b/src/pager.c index 55ee4b3..1e15ffc 100644 --- a/src/pager.c +++ b/src/pager.c @@ -45,7 +45,7 @@ static void print_tasks(PPager ppager) char **pentry; char format[64]; - sprintf(format, "%%c %%%dd: (%%-%ds) [%%-%ds] [%%-%ds] %%-%ds (%%d note%%s)\n", + sprintf(format, "%%c %%%dd: (%%-%ds) [%%-%ds] [%%-%ds] %%-%ds (%%d note%%s)\n", ppager->max.task.id, ppager->max.task.username, ppager->max.task.status, ppager->max.task.priority, ppager->max.task.name ); for_each_entry(ppager, pentry) { @@ -67,7 +67,7 @@ static void print_tasks_with_date(PPager ppager) char **pentry; char format[64]; - sprintf(format, "%%c %%%dd: (%%-%ds) [%%-%ds] [%%-%ds] %%-%ds %%-%ds (%%d note%%s)\n", + sprintf(format, "%%c %%%dd: (%%-%ds) [%%-%ds] [%%-%ds] %%-%ds %%-%ds (%%d note%%s)\n", ppager->max.task.id, ppager->max.task.username, ppager->max.task.status, ppager->max.task.priority, ppager->max.task.date, ppager->max.task.name ); for_each_entry(ppager, pentry) { @@ -90,7 +90,7 @@ static void print_tasks_with_time(PPager ppager) char **pentry; char format[64]; - sprintf(format, "%%c %%%dd: (%%-%ds) %%-%ds %%-%ds %%%ds %%-%ds (%%d note%%s)\n", + sprintf(format, "%%c %%%dd: (%%-%ds) [%%-%ds] [%%-%ds] %%%ds %%-%ds (%%d note%%s)\n", ppager->max.task.id, ppager->max.task.username, ppager->max.task.status, ppager->max.task.priority, ppager->max.task.time, ppager->max.task.name ); for_each_entry(ppager, pentry) { @@ -113,7 +113,7 @@ static void print_tasks_with_date_and_time(PPager ppager) char **pentry; char format[64]; - sprintf(format, "%%c %%%dd: (%%-%ds) [%%-%ds] [%%-%ds] %%-%ds %%%ds %%-%ds (%%d note%%s)\n", + sprintf(format, "%%c %%%dd: (%%-%ds) [%%-%ds] [%%-%ds] %%-%ds %%%ds %%-%ds (%%d note%%s)\n", ppager->max.task.id, ppager->max.task.username, ppager->max.task.status, ppager->max.task.priority, ppager->max.task.date, ppager->max.task.time, ppager->max.task.name ); for_each_entry(ppager, pentry) { diff --git a/src/pit.c b/src/pit.c index 73b5e21..fbadf92 100644 --- a/src/pit.c +++ b/src/pit.c @@ -61,7 +61,8 @@ int main(int argc, char *argv[]) { char *command[] = { "project", "task", "note", "log", "init", "info", "help", "version" }; void (*handler[])(char *argv[]) = { pit_project, pit_task, pit_note, pit_log, pit_init, pit_info, pit_help, pit_version }; - if (argc == 1) argv[1] = "help"; + if (argc == 1) { argv[1] = "help"; argv[2] = NULL; } + for(i = 0; i < ARRAY_SIZE(command); i++) { if (strstr(command[i], argv[1]) == command[i]) { if (candidate < 0) { diff --git a/src/pit.h b/src/pit.h index 3a23c77..f5d29d4 100644 --- a/src/pit.h +++ b/src/pit.h @@ -5,7 +5,8 @@ typedef int bool; -#include +#define __USE_XOPEN +#include /* __USE_XOPEN needed for strptime() */ #include "object.h" #include "table.h" #include "pager.h" @@ -47,8 +48,9 @@ void pit_project(char *argv[]); void pit_task(char *argv[]); void pit_note(char *argv[]); void pit_log(char *argv[]); -void pit_status(char *argv[]); +void pit_info(char *argv[]); void pit_help(char *argv[]); +void pit_version(); void pit_db_load(); void pit_db_save(); void pit_db_initialize(); @@ -70,12 +72,14 @@ void die(char *msg, ...); void perish(char *prefix); char *str2str(char *str); char *mem2str(char *mem, int len); -bool zero(char *mem, int len); +bool is_zero(char *mem, int len); +void printa(char *msg[]); char *current_user(); char *home_dir(char *username, int len); char *expand_path(char *path, char *expanded); char *format_date(time_t date); char *format_time(time_t time); +char *format_timestamp(time_t timestamp); char *inline_replace(char *this, char *old, char *new); #endif diff --git a/src/project.c b/src/project.c index 176dbe3..eeab621 100644 --- a/src/project.c +++ b/src/project.c @@ -46,6 +46,23 @@ static void project_list(POptions po) } } +static void project_show(int id) +{ + PProject pp; + + pit_db_load(); + id = project_find_current(id, &pp); + + if (pp) { + printf("* %d: (%s) %s (status: %s, %d task%s)\n", pp->id, pp->username, pp->name, pp->status, pp->number_of_tasks, (pp->number_of_tasks != 1 ? "s" : "")); + printf("The project was created on %s, last updated on %s\n", format_timestamp(pp->created_at), format_timestamp(pp->updated_at)); + pit_table_mark(projects, pp->id); + pit_db_save(); + } else { + die("could not find the project"); + } +} + static void project_create(POptions po) { pit_db_load(); @@ -71,28 +88,6 @@ static void project_create(POptions po) } } -static void project_show(int id) -{ - PProject pp; - - pit_db_load(); - pp = (PProject)pit_table_find(projects, id); - if (pp) { - printf("%d: %s (%s, %d task%s)\n", - pp->id, pp->name, pp->status, pp->number_of_tasks, (pp->number_of_tasks != 1 ? "s" : "")); - if (pp->number_of_tasks > 0) { - puts("Tasks:"); - for_each_task(pt) { - printf(" %c %d: %s (%d notes)\n", (pt->id == tasks->current ? '*' : ' '), pt->id, pt->name, pt->number_of_notes); - } - } - pit_table_mark(projects, pp->id); - pit_db_save(); - } else { - die("could not find the project"); - } -} - static void project_update(int id, POptions po) { PProject pp; @@ -246,7 +241,7 @@ void pit_project(char *argv[]) number = pit_arg_number(++arg, NULL); if (!number) --arg; project_parse_options(arg, &opt); - if (zero((char *)&opt.project, sizeof(opt.project))) { + if (is_zero((char *)&opt.project, sizeof(opt.project))) { die("nothing to update"); } else { project_update(number, &opt); @@ -262,7 +257,7 @@ void pit_project(char *argv[]) project_show(number); } else { project_parse_options(--arg, &opt); - if (zero((char *)&opt.project, sizeof(opt.project))) { + if (is_zero((char *)&opt.project, sizeof(opt.project))) { project_show(0); /* Show current project if any. */ } else { project_list(&opt); diff --git a/src/table.c b/src/table.c index 5aa34ea..1f3bd0f 100644 --- a/src/table.c +++ b/src/table.c @@ -146,7 +146,7 @@ char *pit_table_delete(PTable pt, int id) { */ char *pit_table_insert(PTable pt, char *record) { register char **pi; - register time_t now; + register time_t now = time(NULL); if (pt->number_of_records >= pt->number_of_slots) { pt = table_extend(pt); @@ -170,7 +170,6 @@ char *pit_table_insert(PTable pt, char *record) { ** fields of type "time_t". */ if (pt->flags & TABLE_HAS_CREATED_AT || pt->flags & TABLE_HAS_UPDATED_AT) { - now = time(NULL); *(time_t *)(*pi + pt->record_size - sizeof(time_t)) = now; } if (pt->flags & TABLE_HAS_CREATED_AT && pt->flags & TABLE_HAS_UPDATED_AT) { diff --git a/src/task.c b/src/task.c index 4a555c0..1fdd205 100644 --- a/src/task.c +++ b/src/task.c @@ -49,7 +49,22 @@ static void task_list(POptions po) static void task_show(int id) { - printf("task_show(%d)\n", id); + PTask pt; + + pit_db_load(); + id = task_find_current(id, &pt); + + if (pt) { + printf("* %d: (%s) %s (project: %d, status: %s, priority: %s", pt->id, pt->username, pt->name, pt->project_id, pt->status, pt->priority); + if (pt->date) printf(", date: %s", format_date(pt->date)); + if (pt->time) printf(", time: %s", format_time(pt->time)); + printf(", %d note%s)\n", pt->number_of_notes, pt->number_of_notes == 1 ? "" : "s"); + printf("The task was created on %s, last updated on %s\n", format_timestamp(pt->created_at), format_timestamp(pt->updated_at)); + pit_table_mark(tasks, pt->id); + pit_db_save(); + } else { + die("could not find the task"); + } } static void task_create(POptions po) @@ -72,8 +87,8 @@ static void task_create(POptions po) strncpy(t.priority, po->task.priority, sizeof(t.priority) - 1); strncpy(t.username, current_user(), sizeof(t.username) - 1); t.project_id = pp->id; - t.date = po->task.date; - t.time = po->task.time; + t.date = max(0, po->task.date); + t.time = max(0, po->task.time); pt = (PTask)pit_table_insert(tasks, (char *)&t); pit_table_mark(tasks, pt->id); @@ -93,8 +108,8 @@ static void task_update(int id, POptions po) if (po->task.name) strncpy(pt->name, po->task.name, sizeof(pt->name) - 1); if (po->task.status) strncpy(pt->status, po->task.status, sizeof(pt->status) - 1); if (po->task.priority) strncpy(pt->priority, po->task.priority, sizeof(pt->priority) - 1); - if (po->task.date) pt->date = po->task.date; - if (po->task.time) pt->time = po->task.time; + if (po->task.date) pt->date = max(0, po->task.date); + if (po->task.time) pt->time = max(0, po->task.time); pit_table_mark(tasks, pt->id); task_log_update(pt, po); @@ -168,8 +183,8 @@ static void task_log_create(PTask pt, POptions po) char str[256]; sprintf(str, "created task %d: %s (status: %s, priority: %s", pt->id, po->task.name, po->task.status, po->task.priority); - if (po->task.date) sprintf(str + strlen(str), ", date: %s", format_date(po->task.date)); - if (po->task.time) sprintf(str + strlen(str), ", time: %s", format_time(po->task.time)); + if (po->task.date > 0) sprintf(str + strlen(str), ", date: %s", format_date(po->task.date)); + if (po->task.time > 0) sprintf(str + strlen(str), ", time: %s", format_time(po->task.time)); strcat(str, ")"); puts(str); pit_action(pt->id, "task", str); @@ -196,10 +211,20 @@ static void task_log_update(PTask pt, POptions po) empty = FALSE; } if (po->task.date) { - sprintf(str + strlen(str), "%sdate: %s", (empty ? "" : ", "), format_date(po->task.date)); + if (po->task.date < 0) { + sprintf(str + strlen(str), "%sdate: none", (empty ? "" : ", ")); + } else { + sprintf(str + strlen(str), "%sdate: %s", (empty ? "" : ", "), format_date(po->task.date)); + } empty = FALSE; } - if (po->task.time) sprintf(str + strlen(str), "%stime: %s", (empty ? "" : ", "), format_time(po->task.time)); + if (po->task.time) { + if (po->task.time < 0) { + sprintf(str + strlen(str), "%stime: none", (empty ? "" : ", ")); + } else { + sprintf(str + strlen(str), "%stime: %s", (empty ? "" : ", "), format_time(po->task.time)); + } + } strcat(str, ")"); puts(str); pit_action(pt->id, "task", str); @@ -266,7 +291,7 @@ void pit_task(char *argv[]) number = pit_arg_number(++arg, NULL); if (!number) --arg; task_parse_options(arg, &opt); - if (!opt.task.name && !opt.task.status && !opt.task.priority && !opt.task.date && !opt.task.time) { + if (is_zero((char *)&opt.task, sizeof(opt.task))) { die("nothing to update"); } else { task_update(number, &opt); @@ -282,7 +307,7 @@ void pit_task(char *argv[]) task_show(opt.task.id); } else { task_parse_options(--arg, &opt); - if (!opt.task.name && !opt.task.status && !opt.task.priority && !opt.task.date && !opt.task.time) { + if (is_zero((char *)&opt.task, sizeof(opt.task))) { task_show(0); /* Show current task if any. */ } else { task_list(&opt); diff --git a/src/util.c b/src/util.c index 32e5241..3d760e0 100644 --- a/src/util.c +++ b/src/util.c @@ -18,7 +18,7 @@ char *mem2str(char *mem, int len) { return str; } -bool zero(char *mem, int len) { +bool is_zero(char *mem, int len) { char *pch = mem; while(pch - mem < len) { @@ -27,6 +27,10 @@ bool zero(char *mem, int len) { return TRUE; } +void printa(char *msg[]) { + while(*msg) puts(*msg++); +} + char *current_user() { static char *username = NULL; @@ -99,6 +103,16 @@ char *format_time(time_t time) return str; } +char *format_timestamp(time_t timestamp) +{ + static char str[32]; + struct tm *ptm = localtime(×tamp); + + strftime(str, sizeof(str), "%b %d, %Y at %H:%M", ptm); + + return str; +} + char *inline_replace(char *this, char *old, char *new) { char *start = this;