]> git.donarmstrong.com Git - bin.git/blob - stodo
* default to .todo if TODODB not set
[bin.git] / stodo
1 #! /usr/bin/perl
2 # stodo commits the changes made to a subversion backed todo db, and
3 # is released under the terms of the GPL version 2, or any later
4 # version, at your option. See the file README and COPYING for more
5 # information. Copyright 2004 by Don Armstrong <don@donarmstrong.com>.
6 # $Id$
7
8
9 use warnings;
10 use strict;
11
12
13 use Getopt::Long;
14 use Pod::Usage;
15
16 =head1 NAME
17
18 stodo - devtodo event script which attempts to commit the changes made to a .todo file after a change
19
20 =head1 SYNOPSIS
21
22 stodo [options]
23
24  Options:
25   --simulate, -s simulate: don't actually do anything
26   --quiet, -q don't output to STDOUT
27   --debug, -d debugging level (Default 0)
28   --help, -h display this help
29   --man, -m display manual
30
31 =head1 OPTIONS
32
33 =over
34
35 =item B<--simulate, -s>
36
37 Simulation mode, don't actually commit anything.
38
39 =item B<--quiet, -q>
40
41 Don't output anything to STDOUT
42
43 =item B<--debug, -d>
44
45 Debug verbosity. (Default 0)
46
47 =item B<--help, -h>
48
49 Display brief useage information.
50
51 =item B<--man, -m>
52
53 Display this manual.
54
55 =back
56
57 =head1 ENVIRONMENTAL VARIABLES
58
59 =over
60
61 =item B<TODODB>
62
63 Todo databse location, set by devtodo
64
65 =item B<STODO_NO_COMMIT>
66
67 If set, stodo assumes that there is no network, and doesn't commit
68
69 =back
70
71 =head1 EXAMPLES
72
73  on save {
74     exec stodo
75  }
76
77
78 =cut
79
80
81
82 use Cwd qw(cwd);
83 use IO::File;
84 use XML::Simple;
85 use IO::Handle;
86 use Data::Diff qw(Diff);
87 use File::Basename qw(dirname basename);
88
89 use vars qw($DEBUG);
90
91 # XXX parse config file
92
93 my %options = (quiet           => 0,
94                debug           => 0,
95                help            => 0,
96                man             => 0,
97                simulate        => 0,
98               );
99
100 GetOptions(\%options,'debug|d+','help|h|?','man|m','quiet|q+','simulate|s+');
101
102 pod2usage() if $options{help} or not exists $ENV{TODODB};
103 pod2usage({verbose=>2}) if $options{man};
104
105 $DEBUG = $options{debug};
106
107 # Figure out what we're doing
108 my $tododb = exists $ENV{TODODB} ? $ENV{TODODB} : '.todo';
109 if ($tododb !~ m#^/#) {
110      $tododb = cwd().'/'.$tododb;
111 }
112 $tododb = full_readlink($tododb);
113 my $base_dir = dirname($tododb);
114 my $tododb_name = basename($tododb);
115
116 if (not -e "$base_dir/.svn") {
117      print "$base_dir/.svn doesn't exist, not commiting\n" unless $options{quiet};
118      exit 0;
119 }
120
121 if (not -e "$base_dir/.svn/text-base/${tododb_name}.svn-base") {
122      print "$base_dir/.svn/text-base/${tododb_name}.svn-base doesn't exist, not committing\n" unless $options{quiet};
123      exit 0;
124 }
125
126 # Make sure that we can ping the subversion server
127 my $svn_entries_fh = new IO::File "$base_dir/.svn/entries",'r'
128      or die "unable to open $base_dir/.svn/entries: $!";
129 my $url_type;
130 my $svn_host;
131 while (<$svn_entries_fh>) {
132      next unless m#^(?:\s+url=\")?(http|svn\+ssh|file):///?([^\/]+)#;
133      $url_type = $1;
134      last if $url_type eq 'file';
135      $svn_host = $2;
136      last;
137 }
138 if ($ENV{STODO_NO_COMMIT}) {
139      print "Exiting because of STODO_NO_COMMIT env variable\n" unless $options{quiet};
140      exit 0;
141 }
142 if (not defined $svn_host and (not defined $url_type or $url_type ne 'file')) {
143      die "Was unable to find which host the svn repository is located on";
144 }
145 if ($url_type ne 'file') {
146      qx(ping -q -c 3 $svn_host >/dev/null 2>&1);
147      if ($? != 0) {
148           print "Unable to ping $svn_host\n" unless $options{quiet};
149           exit 0 unless $options{simulate};
150      }
151 }
152
153
154 # See what has changed
155 #my $svn_fh = new IO::Handle;
156 #open($svn_fh,'-|','svn','diff',$tododb) or die "Unable to execute svn diff $tododb; $!";
157 #while (<$svn_fh>) {
158 #     next unless /^[-+]/;
159 #}
160
161 my $todo_db_old = fix_content(XMLin("$base_dir/.svn/text-base/${tododb_name}.svn-base",KeyAttr=>'time')->{note});
162 my $todo_db_new = fix_content(XMLin($tododb,KeyAttr=>'time')->{note});
163
164 my @commit_message;
165
166 my $diff = Diff($todo_db_new,$todo_db_old);
167
168 #use Data::Dumper;
169 #print Dumper($diff);
170
171 if (exists $diff->{uniq_a}) {
172      foreach (values %{$diff->{uniq_a}}) {
173           my ($content,$priority) = map {s/^\s*|\s*$//g; s/\n/ /g; $_;} @{$_}{qw(content priority)};
174           push @commit_message,qq( * Added todo: [$priority] $content);
175      }
176 }
177
178 if (exists $diff->{uniq_b}) {
179      foreach (values %{$diff->{uniq_b}}) {
180           my ($content,$priority) = map {s/^\s*|\s*$//g; s/\n/ /g; $_;} @{$_}{qw(content priority)};
181           push @commit_message,qq( * Deleted todo: [$priority] $content);
182      }
183 }
184
185 if (exists $diff->{diff}) {
186      # This entry has either been edited, completed, or uncompleted
187      foreach my $entry_key (keys %{$diff->{diff}}) {
188           my ($content,$priority) = map {s/^\s*|\s*$//g; s/\n/ /g; $_;} @{$todo_db_new->{$entry_key}}{qw(content priority)};
189           my $diff_entry = $diff->{diff}{$entry_key};
190           if (exists $diff_entry->{uniq_a} and exists $diff_entry->{uniq_a}{done}) {
191                push @commit_message,qq( * Finished todo: [$priority] $content);
192           }
193           if (exists $diff_entry->{uniq_a} and exists $diff_entry->{uniq_b}{done}) {
194                push @commit_message,qq( * Marked as undone: [$priority] $content);
195           }
196           if (exists $diff_entry->{diff}) {
197                my @what_changed;
198                if (exists $diff_entry->{diff}{content}) {
199                     push @what_changed,'content';
200                }
201                if (exists $diff_entry->{diff}{priority}) {
202                     push @what_changed,'priority';
203                }
204                if (not @what_changed) {
205                     @what_changed = keys %{$diff_entry->{diff}};
206                }
207                push @commit_message,qq( * Todo changed ).join(' and ',@what_changed).qq( [$priority] $content);
208           }
209      }
210 }
211
212 if (exists $diff->{same}) {
213      foreach my $entry_key (keys %{$diff->{same}}) {
214           my ($content,$priority) = map {s/^\s*|\s*$//g; s/\n/ /g; $_;} @{$todo_db_new->{$entry_key}}{qw(content priority)};
215           my $diff_entry = $diff->{same}{$entry_key};
216           if (exists $diff_entry->{uniq_a} and exists $diff_entry->{uniq_a}{done}) {
217                push @commit_message,qq( * Finished todo: [$priority] $content);
218           }
219           if (exists $diff_entry->{uniq_a} and exists $diff_entry->{uniq_b}{done}) {
220                push @commit_message,qq( * Marked as undone: [$priority] $content);
221           }
222           if (exists $diff_entry->{diff}) {
223                my @what_changed;
224                if (exists $diff_entry->{diff}{content}) {
225                     push @what_changed,'content';
226                }
227                if (exists $diff_entry->{diff}{priority}) {
228                     push @what_changed,'priority';
229                }
230                if (not @what_changed) {
231                     @what_changed = keys %{$diff_entry->{diff}};
232                }
233                push @commit_message,qq( * Todo changed ).join(' and ',@what_changed).qq( [$priority] $content);
234           }
235      }
236 }
237
238
239 if (not @commit_message) {
240      print "No commit message\n" if $DEBUG or $options{simulate};
241      exit 0;
242 }
243
244 my $commit_message = join('', map {qq($_\n)} @commit_message);
245 if ($options{simulate}) {
246      print "svn commit -F - $tododb <<EOF\n";
247      print $commit_message;
248      print "EOF\n";
249 }
250 else {
251      system('svn','commit','-m',$commit_message,$tododb);
252 }
253
254
255 # Commit the changes with an appropriate commit message
256
257
258 sub fix_content {
259      my ($s) = @_;
260      if (not ref(${[values %{$s}]}[0]) eq 'HASH') {
261           $s = {$s->{time} => $s};
262      }
263      foreach my $entry (values %{$s}) {
264           if (exists $entry->{content}) {
265                $entry->{content} =~ s/^\s*|\s*$//g;
266           }
267           if (exists $entry->{comment}) {
268                $entry->{comment} =~ s/^\s*|\s*$//g;
269           }
270      }
271      return $s;
272 }
273
274
275 sub full_readlink {
276      my ($file) = @_;
277      while (-l $file) {
278           $file = readlink $file;
279      }
280
281      return $file;
282 }
283
284 __END__