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