]> git.defcon.no Git - trk/blob - trk
f52f49af68b73b821f19f1fa117ebfe2b128cdcc
[trk] / trk
1 #!/usr/bin/perl
2 use Time::Local;
3 use Digest::MD5 qw(md5_hex);
4 use File::Basename;
5
6
7 my $trk_dir = "$ENV{HOME}/.trk";
8
9 use constant {
10 START => 1,
11 TIMEFORMAT => 2,
12 STOP => 3,
13 };
14
15 sub help
16 {
17 my $code = shift;
18 printf("It seems you require assistance\n");
19
20 if ( $code )
21 {
22 printf("How to start\n") if $code == START;
23 printf("How to time\n") if $code == TIMEFORMAT;
24 }
25 exit(-1);
26 }
27
28 # Input to parse_time is:
29 # * date -> date-string in the form YYYY-MM-DD
30 # * time -> time-string in the form HH:MM
31 # Return value is a unix timestamp, as returned by time()
32 sub parse_time ($$)
33 {
34 my ( $Y, $M, $D ) = split ("-", shift );
35 my ( $h, $m ) = split(":", shift );
36 return timelocal(0, $m, $h, $D, ($M-1), $Y);
37 }
38
39 sub gen_puuid (;$)
40 {
41 my $id_length = shift;
42 $id_length = 32 if not defined $id_length;
43
44 my $w = time;
45
46 for(my $i=0 ; $i<128;)
47 {
48 my $tc = chr(int(rand(127)));
49 if($tc =~ /[a-zA-Z0-9]/)
50 {
51 $w .=$tc;
52 $i++;
53 }
54 }
55 $w = md5_hex( $w );
56
57 while ( length($w) < $id_length )
58 {
59 $w .= gen_puuid( $id_length - length( $w ) );
60 }
61
62 $w = substr( $w, 0, $id_length );
63 return $w;
64 }
65
66 sub parse_arguments ($)
67 {
68
69 my $step = shift;
70
71 my $start_time = time;
72 my $title = undef;
73
74 if (( $#ARGV >= 1) && ( $ARGV[1] eq "at" ))
75 {
76 # Start and Activity require a title to be present.
77 # All other (stop, main...) do not ^^.
78 if ( ($step == START) || ($step == TASK) )
79 {
80 # TODO: Allow no title!
81 # If no title is given, read ID of previously used project in stead :)
82 help($step) unless $#ARGV > 3;
83 $title = join(" ", @ARGV[4..$#ARGV]);
84 }
85 help(TIMEFORMAT) unless ( $ARGV[2] =~ m/\d\d\d\d-\d\d-\d\d/ && $ARGV[3] =~ m/\d\d:\d\d/);
86
87 $start_time = parse_time( $ARGV[2], $ARGV[3] );
88 }
89 elsif ( ($step == START) || ($step == TASK) )
90 {
91 shift(@ARGV);
92 $title = join(" ", @ARGV);
93 }
94
95 if ( not defined $title )
96 {
97 return $start_time;
98 }
99 else
100 {
101 return ( $start_time, $title );
102 }
103 }
104
105 sub get_current_project
106 {
107 return undef if ( ! -f $trk_dir . "/current" );
108 open ( CUR, "<" . $trk_dir . "/current" ) or die ("Unable to read current project file");
109 my $id = <CUR>;
110 chomp($id);
111 close(CUR);
112 return $id;
113 }
114
115 sub set_current_project ($)
116 {
117 my $id = shift;
118 return undef if ( -f $trk_dir . "/current" );
119 open ( CUR, ">" . $trk_dir . "/current" ) or die ("Unable to write current project file");
120 printf(CUR "%s\n", $id );
121 close(CUR);
122
123 open ( LAST, ">" . $trk_dir . "/last" ) or die ("Unable to write last project file");
124 printf(LAST "%s\n", $id );
125 close(LAST);
126 }
127
128 sub current_task
129 {
130 my $project = get_current_project();
131 return undef if not $project;
132
133 open ( CUR, "<" . $trk_dir . "/current" ) or die ("Unable to read current project file");
134 <CUR>;
135 my $id = <CUR>;
136 chomp($id);
137 close(CUR);
138 return $id;
139
140 }
141
142 sub get_projects
143 {
144 my %projects;
145
146 foreach my $d ( <$trk_dir/*> )
147 {
148 next if not -d $d;
149 next if not -f $d . "/info";
150
151 my $id = basename($d);
152 my $title = get_project_name( $id );
153
154 $projects{$id} = $title unless not defined $title;
155 }
156
157 return \%projects;
158
159 }
160
161 sub get_project_id ($)
162 {
163 my $title = shift;
164
165 # Get hash of project-id's and -names from get_projects
166 my $projects = get_projects();
167
168 # Look up name in list
169 foreach my $id ( keys $projects )
170 {
171 # Return ID for name
172 return $id if ( $projects->{$id} eq $title )
173 }
174
175 # If no match, return undef.
176 return undef;
177 }
178
179 sub get_project_name ($)
180 {
181 my $id = shift;
182 open(PRO, "<" . $trk_dir . "/" . $id . "/info" ) or die ("Unable to read project medatata file!");
183 my $title = undef;
184 while (<PRO>)
185 {
186 next if not $_ =~ /^title:(.*)/;
187 $title = $1;
188 }
189 close(PRO);
190 return $title;
191 }
192
193 sub create_project ($)
194 {
195 my $title = shift;
196
197 my $id;
198 do
199 {
200 $id = gen_puuid(8);
201
202 } while ( -d $trk_dir . "/" . $id );
203 mkdir ( $trk_dir . "/" . $id );
204
205 open(PRO, ">" . $trk_dir . "/" . $id . "/info" ) or die ("Unable to create project medatata file!");
206 printf(PRO "title:%s", $title);
207 close(PRO);
208
209 return $id;
210 }
211
212 ############################################################
213
214 if ( ! -d $trk_dir )
215 {
216 mkdir $trk_dir or die("Unable to create data directory");
217 }
218
219 if ( $#ARGV < 0 )
220 {
221 help();
222 }
223
224 my $command = $ARGV[0];
225
226 if ( ( $command eq "start") || ($command eq "on" ) )
227 {
228 if ( $#ARGV < 1)
229 {
230 help(START);
231 }
232
233 my ( $start_time, $title ) = parse_arguments(START);
234
235 my $current = get_current_project();
236 if ( not $current )
237 {
238 $current = get_project_id( $title );
239 if ( not $current )
240 {
241 printf("No project by that name! Creating a new one.\n");
242 $current = create_project($title);
243 }
244 else
245 {
246 printf("Continuing tracking for existing project.\n");
247 }
248 set_current_project($current);
249 }
250 else
251 {
252 printf("A project is being tracked: %s\n", get_project_name( $current ) );
253 printf("Stop current tracking before starting a new one\n");
254 exit(0);
255 }
256
257 # First iteration is VERY naive: simply add the start time to the bottom of the tracking file
258 # Will have to do more logic: if the start point is before one of the times already in the track,
259 # the file will have to be manipulated to get coherent tracking!
260 open (TRACK, ">>" . $trk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
261 printf(TRACK "[%s]", $start_time);
262 close (TRACK);
263
264 printf("Started tracking of '%s' at %s\n\n", $title, scalar localtime $start_time);
265 }
266 elsif ( ( $command eq "stop") || ($command eq "off" ) )
267 {
268 if ( $#ARGV < 0)
269 {
270 help(STOP);
271 }
272
273
274 my $stop_time = parse_arguments(STOP);
275
276 my $current = get_current_project();
277 if ( not $current )
278 {
279 printf("No project is currently tracked. To stop, please start first\n");
280 exit(0);
281 }
282 my $title = get_project_name( $current );
283
284 die ("Project exists, but tracking file does not!") if ( not -f $trk_dir . "/" . $current . "/tracking" );
285
286 # First iteration is VERY naive: simply add the stop time to the bottom line of the tracking file
287 # Will have to do more logic: if the start point is before one of the times already in the track,
288 # the file will have to be manipulated to get coherent tracking!
289 # In addtion to this: actually do some file sanity checking!
290 open (TRACK, ">>" . $trk_dir . "/" . $current . "/tracking" ) or die ("Unable to open file, $!");
291 printf(TRACK " to [%s]\n", $stop_time);
292 close (TRACK);
293
294 unlink ( $trk_dir . "/current" );
295
296 printf("Stopped tracking of '%s' at %s\n\n", $title, scalar localtime $stop_time);
297 }
298 else
299 {
300 help();
301 }