1 /*
2 ** Copyright (c) 2010 Michael Dvorkin
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 **
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 */
12 #include <stdlib.h>
13 #include <string.h>
14 #include <stdio.h>
15 #include "pit.h"
16 
project_already_exist(char * name)17 static bool project_already_exist(char *name)
18 {
19     pit_db_load();
20     for_each_project(pp) {
21         if (!strcmp(pp->name, name)) {
22             return TRUE;
23         }
24     }
25     return FALSE;
26 }
27 
project_find_current(int id,PProject * ppp)28 static int project_find_current(int id, PProject *ppp)
29 {
30     if (id) {
31         *ppp = (PProject)pit_table_find(projects, id);
32         if (!*ppp) die("could not find project %d", id);
33     } else {
34         *ppp = (PProject)pit_table_current(projects);
35         if (!*ppp) die("could not find current project");
36     }
37     return *ppp ? (*(PProject *)ppp)->id : 0;
38 }
39 
project_log_create(PProject pp,POptions po)40 static void project_log_create(PProject pp, POptions po)
41 {
42     Action a = { pp->id, 0 };
43 
44     sprintf(a.message, "created project %d: %s (status: %s)", pp->id, po->project.name, po->project.status);
45     pit_action(&a);
46 }
47 
project_log_update(PProject pp,POptions po)48 static void project_log_update(PProject pp, POptions po)
49 {
50     Action a = { pp->id, 0 };
51     bool empty = TRUE;
52 
53     sprintf(a.message, "updated project %d:", pp->id);
54     if (po->project.name) {
55         sprintf(a.message + strlen(a.message), " (name: %s", po->project.name);
56         empty = FALSE;
57     } else {
58         sprintf(a.message + strlen(a.message), " %s (", pp->name);
59     }
60     if (po->project.status) {
61         sprintf(a.message + strlen(a.message), "%sstatus: %s", (empty ? "" : ", "), po->project.status);
62     }
63     strcat(a.message, ")");
64     pit_action(&a);
65 }
66 
project_log_delete(int id,char * name,int number_of_tasks)67 static void project_log_delete(int id, char *name, int number_of_tasks)
68 {
69     Action a = { id, 0 };
70 
71     sprintf(a.message, "deleted project %d: %s", id, name);
72     if (number_of_tasks > 0) {
73         sprintf(a.message + strlen(a.message), " with %d task%s", number_of_tasks, (number_of_tasks == 1 ? "" : "s"));
74     }
75     pit_action(&a);
76 }
77 
project_list(POptions po)78 static void project_list(POptions po)
79 {
80     PFormat pf;
81 
82     pit_db_load();
83     if (projects->number_of_records > 0) {
84         pf = pit_format_initialize(FORMAT_PROJECT, 0, projects->number_of_records);
85         for_each_project(pp) {
86             if ((po->project.name && !stristr(pp->name, po->project.name)) ||
87                (po->project.status && !stristr(pp->status, po->project.status)))
88                continue;
89             pit_format(pf, (char *)pp);
90         }
91         pit_format_flush(pf);
92     }
93 }
94 
project_show(int id)95 static void project_show(int id)
96 {
97     PProject pp;
98 
99     pit_db_load();
100     id = project_find_current(id, &pp);
101 
102     if (pp) {
103         /* printf("The project was created on %s, last updated on %s\n", format_timestamp(pp->created_at), format_timestamp(pp->updated_at)); */
104         printf("* %d: (%s) %s (status: %s, %d task%s)\n",
105             pp->id, pp->username, pp->name, pp->status, pp->number_of_tasks, pp->number_of_tasks != 1 ? "s" : "");
106         pit_table_mark(projects, pp->id);
107         if (pp->number_of_tasks > 0)
108             pit_task_list(NULL, pp);
109         pit_db_save();
110     } else {
111         die("could not find the project");
112     }
113 }
114 
project_create(POptions po)115 static void project_create(POptions po)
116 {
117     pit_db_load();
118 
119     if (project_already_exist(po->project.name)) {
120         die("project with the same name already exists");
121     } else {
122         Project p = { 0 }, *pp;
123 
124         if (!po->project.status) po->project.status = "active";
125 
126         strncpy(p.name,     po->project.name,   sizeof(p.name)     - 1);
127         strncpy(p.status,   po->project.status, sizeof(p.status)   - 1);
128         strncpy(p.username, current_user(),     sizeof(p.username) - 1);
129 
130         pp = (PProject)pit_table_insert(projects, (char *)&p);
131         pit_table_mark(projects, pp->id);
132 
133         project_log_create(pp, po);
134         pit_db_save();
135     }
136 }
137 
project_update(int id,POptions po)138 static void project_update(int id, POptions po)
139 {
140     PProject pp;
141 
142     pit_db_load();
143     id = project_find_current(id, &pp);
144 
145     if (po->project.name)   strncpy(pp->name,   po->project.name,   sizeof(pp->name)   - 1);
146     if (po->project.status) strncpy(pp->status, po->project.status, sizeof(pp->status) - 1);
147     strncpy(pp->username, current_user(), sizeof(pp->username) - 1);
148     pit_table_mark(projects, pp->id);
149 
150     project_log_update(pp, po);
151     pit_db_save();
152 }
153 
project_delete(int id)154 static void project_delete(int id)
155 {
156     PProject pp;
157 
158     pit_db_load();
159     id = project_find_current(id, &pp);
160     /*
161     ** Delete project tasks.
162     */
163     if (pp->number_of_tasks > 0) {
164         for_each_task(pt) {
165             if (pt->project_id == id) {
166                 pit_task_delete(pt->id, pp);
167                 --pt; /* Make the task pointer stay since it now points to the next task. */
168             }
169         }
170     }
171     /*
172     ** Ready to delete the project itself. But first preserve the
173     ** name and number of tasks since we need these bits for logging.
174     */
175     char *deleted_name = str2str(pp->name);
176     int deleted_number_of_tasks = pp->number_of_tasks;
177 
178     pp = (PProject)pit_table_delete(projects, id);
179     if (pp) {
180         pit_table_mark(projects, 0); /* TODO: find better current project candidate. */
181         project_log_delete(id, deleted_name, deleted_number_of_tasks);
182         pit_db_save();
183     } else {
184         die("could not delete the project");
185     }
186 }
187 
project_parse_options(int cmd,char ** arg,POptions po)188 static void project_parse_options(int cmd, char **arg, POptions po)
189 {
190     while(*++arg) {
191         switch(pit_arg_option(arg)) {
192         case 'n':
193             po->project.name = pit_arg_string(++arg, "project name");
194             break;
195         case 's':
196             po->project.status = pit_arg_string(++arg, "project status");
197             break;
198         default:
199             die("invalid project option: %s", *arg);
200         }
201     }
202 }
203 
204 /*
205 ** CREATING PROJECTS:
206 **   pit project -c name [-s status]
207 **
208 ** EDITING PROJECTS:
209 **   pit project -e [number] [-n name] [-s status]
210 **
211 ** DELETING PROJECTS:
212 **   pit project -d [number]
213 **
214 ** VIEWING PROJECT:
215 **   pit project [[-q] number]
216 **
217 ** LISTING PROJECTS:
218 **   pit project -q [number | [-n name] [-s status]]
219 */
pit_project(char * argv[])220 void pit_project(char *argv[])
221 {
222     char **arg = &argv[1];
223     int number = 0;
224     Options opt = {{ 0 }};
225 
226     if (!*arg) {
227         project_list(&opt); /* Show all projects. */
228     } else { /* pit project [number] */
229         number = pit_arg_number(arg, NULL);
230         if (number) {
231             project_show(number);
232         } else {
233             int cmd = pit_arg_option(arg);
234             switch(cmd) {
235             case 'c': /* pit project -c name [-s status] */
236                 opt.project.name = pit_arg_string(++arg, "project name");
237                 project_parse_options(cmd, arg, &opt);
238                 project_create(&opt);
239                 break;
240             case 'e': /* pit project -e [number] [-n name] [-s status] */
241                 number = pit_arg_number(++arg, NULL);
242                 if (!number) --arg;
243                 project_parse_options(cmd, arg, &opt);
244                 if (is_zero((char *)&opt.project, sizeof(opt.project))) {
245                     die("nothing to update");
246                 } else {
247                     project_update(number, &opt);
248                 }
249                 break;
250             case 'd': /* pit project -d [number] */
251                 number = pit_arg_number(++arg, NULL);
252                 project_delete(number);
253                 break;
254             case 'q': /* pit project -q [number | [-n name] [-s status]] */
255                 number = pit_arg_number(++arg, NULL);
256                 if (number) {
257                     project_show(number);
258                 } else {
259                     project_parse_options(cmd, --arg, &opt);
260                     if (is_zero((char *)&opt.project, sizeof(opt.project))) {
261                         project_show(0); /* Show current project if any. */
262                     } else {
263                         project_list(&opt);
264                     }
265                 }
266                 break;
267             default:
268                 die("invalid project option: %s", *arg);
269             }
270         }
271     }
272 }
273