]> git.defcon.no Git - trk/blob - trk
Moved starting and stopping to subs, allowing more flexible code. Added auto-switch
[trk] / trk
1 #!/usr/bin/perl
2 use Time::Local;
3 use Digest::MD5 qw(md5_hex);
4 use File::Basename;
5 use POSIX;
6
7
8 my $trk_dir = "$ENV{HOME}/.trk";
9
10 use constant {
11 START => 1,
12 TIMEFORMAT => 2,
13 STOP => 3,
14 EDIT => 4,
15 };
16
17 sub help
18 {
19 my $code = shift;
20 printf("It seems you require assistance\n");
21
22 if ( $code )
23 {
24 printf("How to start\n") if $code == START;
25 printf("How to time\n") if $code == TIMEFORMAT;
26 }
27 exit(-1);
28 }
29
30
31 sub gen_puuid (;$)
32 {
33 my $id_length = shift;
34 $id_length = 32 if not defined $id_length;
35
36 my $w = time;
37
38 for(my $i=0 ; $i<128;)
39 {
40 my $tc = chr(int(rand(127)));
41 if($tc =~ /[a-zA-Z0-9]/)
42 {
43 $w .=$tc;
44 $i++;
45 }
46 }
47 $w = md5_hex( $w );
48
49 while ( length($w) < $id_length )
50 {
51 $w .= gen_puuid( $id_length - length( $w ) );
52 }
53
54 $w = substr( $w, 0, $id_length );
55 return $w;
56 }
57
58 # Input to parse_time is:
59 # * date -> date-string in the form YYYY-MM-DD
60 # * time -> time-string in the form HH:MM
61 # Return value is a unix timestamp, as returned by time()
62 sub parse_time ($$)
63 {
64 my ( $Y, $M, $D ) = split ("-", shift );
65 my ( $h, $m ) = split(":", shift );
66 return timelocal(0, $m, $h, $D, ($M-1), $Y);
67 }
68
69 sub str2time ($)
70 {
71 my $i = shift;
72 printf("%s\n", $i);
73 return 0 if not $i =~ m/(\d\d\d\d-\d\d-\d\d) (\d\d:\d\d)/;
74 return parse_time($1, $2);
75 }
76
77 sub time2str ($)
78 {
79 my $t = shift;
80 return strftime("%Y-%m-%d %H:%M", localtime($t));
81 }
82
83 sub parse_arguments ($)
84 {
85
86 my $step = shift;
87
88 my $start_time = time;
89 my $title = undef;
90
91 if (( $#ARGV >= 1) && ( $ARGV[1] eq "at" ))
92 {
93 # Start and Activity require a title to be present.
94 # All other (stop, main...) do not ^^.
95 if ( ($step == START) || ($step == TASK) )
96 {
97 # TODO: Allow no title!
98 # If no title is given, read ID of previously used project in stead :)
99 help($step) unless $#ARGV > 3;
100 $title = join(" ", @ARGV[4..$#ARGV]);
101 }
102 help(TIMEFORMAT) unless ( $ARGV[2] =~ m/\d\d\d\d-\d\d-\d\d/ && $ARGV[3] =~ m/\d\d:\d\d/);
103
104 $start_time = parse_time( $ARGV[2], $ARGV[3] );
105 }
106 elsif ( ($step == START) || ($step == TASK) || ($step == EDIT))
107 {
108 shift(@ARGV);
109 $title = join(" ", @ARGV);
110 }
111
112 if ( not defined $title )
113 {
114 return $start_time;
115 }
116 else
117 {
118 return ( $start_time, $title );
119 }
120 }
121
122 sub get_last_project
123 {
124 return undef if ( ! -f $trk_dir . "/last" );
125 open ( CUR, "<" . $trk_dir . "/last" ) or die ("Unable to read last project file");
126 my $id = <CUR>;
127 chomp($id);
128 close(CUR);
129 return $id;
130 }
131
132 sub get_current_project
133 {
134 return undef if ( ! -f $trk_dir . "/current" );
135 open ( CUR, "<" . $trk_dir . "/current" ) or die ("Unable to read current project file");
136 my $id = <CUR>;
137 chomp($id);
138 close(CUR);
139 return $id;
140 }
141
142 sub set_current_project ($)
143 {
144 my $id = shift;
145 return undef if ( -f $trk_dir . "/current" );
146 open ( CUR, ">" . $trk_dir . "/current" ) or die ("Unable to write current project file");
147 printf(CUR "%s\n", $id );
148 close(CUR);
149
150 open ( LAST, ">" . $trk_dir . "/last" ) or die ("Unable to write last project file");
151 printf(LAST "%s\n", $id );
152 close(LAST);
153 }
154
155 sub current_task
156 {
157 my $project = get_current_project();
158 return undef if not $project;
159
160 open ( CUR, "<" . $trk_dir . "/current" ) or die ("Unable to read current project file");
161 <CUR>;
162 my $id = <CUR>;
163 chomp($id);
164 close(CUR);
165 return $id;
166
167 }
168
169 sub get_projects
170 {
171 my %projects;
172
173 foreach my $d ( <$trk_dir/*> )
174 {
175 next if not -d $d;
176 next if not -f $d . "/info";
177
178 my $id = basename($d);
179 my $title = get_project_name( $id );
180
181 $projects{$id} = $title unless not defined $title;
182 }
183
184 return \%projects;
185
186 }
187
188 sub get_project_id ($)
189 {
190 my $title = shift;
191
192 # Get hash of project-id's and -names from get_projects
193 my $projects = get_projects();
194
195 # Look up name in list
196 foreach my $id ( keys $projects )
197 {
198 # Return ID for name
199 return $id if ( $projects->{$id} eq $title )
200 }
201
202 # If no match, return undef.
203 return undef;
204 }
205
206 sub get_project_name ($)
207 {
208 my $id = shift;
209 open(PRO, "<" . $trk_dir . "/" . $id . "/info" ) or die ("Unable to read project medatata file!");
210 my $title = undef;
211 while (<PRO>)
212 {
213 next if not $_ =~ /^title:(.*)/;
214 $title = $1;
215 }
216 close(PRO);
217 return $title;
218 }
219
220 sub create_project ($)
221 {
222 my $title = shift;
223
224 my $id;
225 do
226 {
227 $id = gen_puuid(8);
228
229 } while ( -d $trk_dir . "/" . $id );
230 mkdir ( $trk_dir . "/" . $id );
231
232 open(PRO, ">" . $trk_dir . "/" . $id . "/info" ) or die ("Unable to create project medatata file!");
233 printf(PRO "title:%s\n", $title);
234 close(PRO);
235
236 return $id;
237 }
238
239 sub start_project ($$)
240 {
241 my $start_time = shift;
242 my $title = shift;
243
244
245 my $current = get_current_project();
246 if ( not $current )
247 {
248 if ( not $title )
249 {
250 $current = get_last( );
251 }
252 else
253 {
254 $current = get_project_id( $title );
255 if ( not $current )
256 {
257 printf("No project by that name! Creating a new one.\n");
258 $current = create_project($title);
259 }
260 }
261
262 # Break off here if we haven't gotten an ID yet.
263 return undef if not $current;
264
265 set_current_project($current);
266
267 # First iteration is VERY naive: simply add the start time to the bottom of the tracking file
268 # Will have to do more logic: if the start point is before one of the times already in the track,
269 # the file will have to be manipulated to get coherent tracking!
270 open (TRACK, ">>" . $trk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
271 printf(TRACK "[%s]", time2str($start_time));
272 close (TRACK);
273
274 return $current;
275 }
276
277 return undef;
278 }
279
280 sub close_project ($)
281 {
282
283 my $stop_time = shift;
284
285 my $current = get_current_project();
286
287 die ("Project exists, but tracking file does not!") if ( not -f $trk_dir . "/" . $current . "/tracking" );
288
289 # First iteration is VERY naive: simply add the stop time to the bottom line of the tracking file
290 # Will have to do more logic: if the start point is before one of the times already in the track,
291 # the file will have to be manipulated to get coherent tracking!
292 # In addtion to this: actually do some file sanity checking!
293 open (TRACK, ">>" . $trk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
294 printf(TRACK " to [%s]\n", time2str($stop_time));
295 close (TRACK);
296
297 unlink ( $trk_dir . "/current" );
298 }
299
300 ############################################################
301
302 if ( ! -d $trk_dir )
303 {
304 mkdir $trk_dir or die("Unable to create data directory");
305 }
306
307 if ( $#ARGV < 0 )
308 {
309 help();
310 }
311
312 my $command = $ARGV[0];
313
314 if ( ( $command eq "start") || ($command eq "on" ) )
315 {
316 if ( $#ARGV < 1)
317 {
318 help(START);
319 }
320
321 my ( $start_time, $title ) = parse_arguments(START);
322
323 my $current = get_current_project();
324 if ( not $current )
325 {
326 $current = start_project( $start_time, $title );
327
328 if ( not $current )
329 {
330 printf("Something weird happened.\n");
331 exit(1);
332 }
333 }
334 else
335 {
336 printf("A project is being tracked: %s\n", get_project_name( $current ) );
337 close_project($start_time);
338 $current = start_project( $start_time, $title );
339 }
340
341 printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time);
342 }
343 elsif ( ( $command eq "stop") || ($command eq "off" ) )
344 {
345 if ( $#ARGV < 0)
346 {
347 help(STOP);
348 }
349
350
351 my $stop_time = parse_arguments(STOP);
352
353 my $current = get_current_project();
354 if ( not $current )
355 {
356 printf("No project is currently tracked. To stop, please start first\n");
357 exit(0);
358 }
359 my $title = get_project_name( $current );
360
361 close_project($stop_time);
362
363 printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time);
364 }
365 elsif ( ( $command eq "projects" ) || ( $command eq "list" ) )
366 {
367 # Todo/future extensions:
368 # TODO: Sort list of names alphabetically
369 # TODO: Get total-hours for projects
370 # TODO:
371 my $projects = get_projects();
372 printf("Currently tracked project names:\n\n");
373 my $current = get_current_project();
374
375 foreach my $id ( keys $projects )
376 {
377 printf(" %s %s\n", ($id eq $current ? ">" : " " ),$projects->{$id} );
378 }
379 print("\n");
380 }
381 elsif ( $command eq "edit" )
382 {
383
384 my ( undef, $title ) = parse_arguments(EDIT);
385 my $id = get_last_project();
386
387 if ( $title )
388 {
389 $id = get_project_id($title);
390 if ( not $id )
391 {
392 printf("No project by that name. Try 'list'\n");
393 exit(0);
394 }
395 }
396
397 system( "/usr/bin/editor " . $trk_dir . "/" . $id . "/tracking" );
398 }
399 else
400 {
401 help();
402 }