//Kanban boards #include #include #include enum state_t {TODO =1, DOING, DONE}; char *state_strings[] = { "INVALID", "TODO","DOING","DONE"}; enum type_t {TICKET=1, COMMENT,EPIC,SCRUM}; char *type_strings[] = { "INVALID", "TICKET","EPIC","SCRUM"}; #define MAXCOMMENTS 20 typedef struct { char *name; char *desc; char *closemesg; char *assignee; char *reporter; char *scrum; int type; int state; int points; int priority; char *parent; time_t otime; time_t dtime; time_t ctime; time_t etime; int ephemeral; char *comments[MAXCOMMENTS]; int commentcount; } ticket; #define MAX_TICKETS 10000 ticket tickets[MAX_TICKETS]; int lastticket=-1; int multiusermode=1; char *scrum=""; int fortnight=86400*14; char *dbpath; int savedb=0; int indexforname(char *name){ for(int i=0;i<=lastticket;i++)if(!strcmp(name,tickets[i].name))return i; return -1; } void error(char *m,int n){ printf("%s,%d\n",m,n); exit(1); } void invalidkey(int lnum,char *name,char *key,char *val){ printf("#Incompatible Datum at line number:%d will be dropped.\n",lnum); if(!strcmp(name,"global")) printf("%s=%s\n#Hint:open a [ticket] first.",key,val); else printf("[%s]\n%s=%s\n",name,key,val); } void updatesetting(int lnum,char *l){ if(index(l,'=')==NULL) return; char *val=strdup(index(l,'=')+1); val[strlen(val)-1]='\0'; *index(l,'=')='\0'; if(!strcasecmp(l,"scrum"))scrum=val; else{//free val if(!strcasecmp(l,"fortnight"))fortnight=atoi(val); else if(!strcasecmp(l,"multiusermode"))multiusermode=atoi(val); else invalidkey(lnum,"global",l,val); free(val); } } void updatefield(int lnum,char *l,int t){ if(index(l,'=')==NULL) return; char *val=strdup(index(l,'=')+1);//TODO: Handle comments val[strlen(val)1]='\0'; *index(l,'=')='\0'; if(!strcmp(l,"desc"))tickets[t].desc=val; else if(!strcasecmp(l,"assignee"))tickets[t].assignee=val; else if(!strcasecmp(l,"reporter"))tickets[t].reporter=val; else if(!strcasecmp(l,"parent"))tickets[t].parent=val; else if(!strcasecmp(l,"closemesg"))tickets[t].closemesg=val; else if(!strcasecmp(l,"scrum"))tickets[t].scrum=val; else if(l[0]=='#'){ if(tickets[t].commentcount=0){ printf("#Note: duplicate ticket on line %d. Updating ticket. New data will overwrite old.\n",lcount); curticket=indexforname(curline+1); }else{ lastticket++; memset(tickets+lastticket,0,sizeof(ticket)); tickets[lastticket].name=strdup(curline+1); curticket=lastticket; } }else if(curticket>=0){ updatefield(lcount,curline,curticket); }else updatesetting(lcount,curline); } } void writeoutticket(FILE *f,ticket t){ fprintf(f,"[%s]\n",t.name); fprintf(f,"desc=%s\n",t.desc); if(t.assignee!=NULL) fprintf(f,"assignee=%s\n",t.assignee); if(t.reporter!=NULL) fprintf(f,"reporter=%s\n",t.reporter); if(t.parent!=NULL) fprintf(f,"parent=%s\n",t.parent); if(t.scrum!=NULL) fprintf(f,"scrum=%s\n",t.parent); fprintf(f,"state=%s\n",state_strings[t.state]); fprintf(f,"type=%s\n",type_strings[t.type]); //fprintf(f,"state=%d\n",t.state); //fprintf(f,"type=%d\n",t.type); fprintf(f,"points=%d\n",t.points); if(t.otime) fprintf(f,"otime=%ld\n",t.otime); if(t.dtime) fprintf(f,"dtime=%ld\n",t.dtime); if(t.ctime) fprintf(f,"ctime=%ld\n",t.ctime); if(t.etime) fprintf(f,"etime=%ld\n",t.etime); if(t.priority) fprintf(f,"priority=%d\n",t.priority); for(int i=0;i0||!strcmp(name,"global")){printf("name already in use.\n");exit(-1);} if(argc<2){ printf("The description message is a human readable description of the work to be done, you might include the path to a wiki entry if it is complex\ndesc: "); fgets(desc,500,stdin); } lastticket++; memset(tickets+lastticket,0,sizeof(ticket)); tickets[lastticket].name=strdup(name); tickets[lastticket].desc=strdup(desc); tickets[lastticket].reporter=strdup(getenv("USER")); tickets[lastticket].otime=0; tickets[lastticket].state=TODO; tickets[lastticket].type=TICKET; } void assign(int argc, char **argv) { char assignee[500]; char points[500]; if(multiusermode){ if(argc<1){printf("USAGE: assign ticket [assignee [points]]\n Assign a ticket to a worker and point it.\n"); exit(-1);} int t=indexforname(argv[0]); if(t<0){printf("no such ticket:%s\n",argv[0]);} argc--; argv++; } if(argc<1){ printf("The assignee will be responsible for completing the specified work unit, preferably before etime or the end of the scrum\nassignee: "); fgets(assignee,500,stdin); strcpy(assignee,strdup(argv[0])); } if(argc<3){ printf("The amount of \"points\" this work unit will take is only meaningful to your team. Often 1 means about a day or so and generally only prime numbers below 21 are considered meaningful. For larger units consider creating an \"epic\" ticket with children.\npoints: "); fgets(points,500,stdin); } tickets[t].assignee=strdup(assignee); tickets[t].points=atoi(points); } void doticket(int argc, char **argv){ if(argc<1){printf("USAGE: do ticket\n Move ticket to \"Doing\" column.\n"); exit(-1);} int t=indexforname(argv[0]); if(t>0){ tickets[t].state=DOING; } else printf("No such ticket.\n"); } void closeticket(int argc, char **argv){ if(argc<1){printf("USAGE: close ticket\n Move ticket to \"Done\" column.\n"); exit(-1);} int t=indexforname(argv[0]); if(t>0){ tickets[t].state=DONE; } else printf("No such ticket.\n"); } void showticket(int argc, char **argv){ if(argc<1){printf("USAGE: show ticket\n Export ticket to stdout.\n"); exit(-1);} int t=indexforname(argv[0]); if(t>=0){ writeoutticket(stdout,tickets[t]); } else printf("No such ticket.\n"); } void reportswimlane(ticket t){ for(int i=TODO;i1) scrumname=argv[0]; if(swimlane) printf("TODO\t\t\t\tDOING\t\t\t\tDONE\n"); else printf("ASIGNEE\tPOINTS\tTIMEOPEN\tTIMEDOING\n"); for(int i=0;i<=lastticket;i++){ if(scrumname==NULL||strcmp(scrumname,"")||tickets[i].scrum==NULL||!strcmp(tickets[i].scrum,scrumname)){ if(swimlane) reportswimlane(tickets[i]); else reportone(tickets[i]); } } } void comment(int argc,char **argv){ if(argc<2){printf("USAGE: comment ticket \"comment\"\n add a short comment to a ticket.\n"); exit(-1);} int t=indexforname(argv[0]); if(t>0){ if( tickets[t].commentcount