/todo

To get this branch, use:
bzr branch http://bzr.ed.am/todo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#!/usr/bin/perl

use feature "state";

use strict;
use warnings;
use Getopt::Long;
use File::Basename;

# get app name
my $app_name = basename( $0 );

# option defaults
my $todo_dir = "~/.todo";
my $display_all = 0;
my $display_section = "TODO";
my $display_headers = 0;
my $mode_edit = 0;
my $mode_less = 0;
my $mode_help = 0;
my $mode_version = 0;

# parse command line opts
Getopt::Long::Configure( 'gnu_getopt' );
if( !GetOptions(
    'a|all' => \$display_all,
    'e|edit' => \$mode_edit,
    'h|headers' => \$display_headers,
	'l|list' => \$mode_less,
    's|section=s' => \$display_section,
    'help' => \$mode_help,
    'version' => \$mode_version,
) || @ARGV > 0 ) {
    print "Try `$app_name --help' for more information.\n";
    exit( 1 );
}

# help mode
if( $mode_help ) {
    print "todo - display your todo file\n\n".
	#01234567890123456789012345678901234567890123456789012345678901234567890123456789
    "Usage: $app_name [OPTIONS]\n\n".
    "Options:\n".
    "  -a, --all              display all sections\n".
    "  -e, --edit             edit your todo file\n".
    "  -h, --headers          show setion headers\n".
	"  -l, --list             show the list in your pager (see notes)\n".
    "  -s, --section=SECTION  display sections matching the regular expression\n".
    "      --help     display this help and exit\n".
    "      --version  output version information and exit\n".
	"\n".
	"Running without any options is the same as running with --section=TODO and\n".
	"lists the default section of the todo file.\n".
	"\n".
	"The advantage of using '--list' is that the pager is run as if the\n".
	"todo list were being edited. This means that if you decide to spawn an\n".
	"editor from you pager and edit the list, these changes get noticed.\n".
	"\n".
	"The environment variables EDITOR and PAGER are used.\n".
	"\n".
	"Please report bugs to Tim Marston <tim\@ed.am>.\n";
    exit( 0 );
}

# version mode
if( $mode_version ) {
    print "todo 1.0\n".
        "Copyright (C) 2011, 2012 Tim Marston.\n".
        "http://ed.am/software/todo\n";
    exit( 0 );
}

#_______________________________________________________________________________


# check we have bazaar installed
my $output = `which bzr`;
chomp $output;
$output eq "" and die "Bazaar is not installed";

# glob todo directory
$todo_dir = glob( $todo_dir );
( -f $todo_dir ) and die "$todo_dir exists and is a file";

# less mode
if( $mode_less )
{
    ( ! -f "$todo_dir/todo" ) && die "no todo file";
	$mode_edit = 1;
}

# edit mode
if( $mode_edit )
{
    # create the todo directory, as necessary
    if( ! -d $todo_dir ) {
        mkdir $todo_dir or die "couldn't create todo directory";
        `bzr init --no-aliases -q "$todo_dir"`;
        $? == 0 or die "couldn't init bzr repo";
    }
    
    # create a default todo file, as necessary
    if( ! -f "$todo_dir/todo" ) {
        open FILE, ">$todo_dir/todo" or die "couldn't create default todo file";
        my $content = <<"EOT";
<!-- This file uses Markdown syntax. For more info about Markdown
     syntax, see http://daringfireball.net/projects/markdown/syntax.

     There should be a main H1 header called "TODO" for the main todo
     list section (one has been added for you below). You can also add
     as many more sections as you like for other lists. And feel free
     to delete this comment! -->

TODO
====

* make a list of things to do

EOT
        print FILE $content or die "couldn't write default todo file";
        close FILE;
        `bzr add --no-aliases -q "$todo_dir/todo"`;
        $? == 0 or die "couldn't add todo file to bzr repo";
    }

    # determine editor from environment, default to "emacs -nw"
	my $editor;
	if( $mode_less ) {
		$editor = $ENV{ 'PAGER' };
		defined $editor or $editor = 'less';
	}
	else {
		$editor = $ENV{ 'EDITOR' };
		defined $editor or $editor = 'emacs -nw';
	}
    my @exec_array = split( / +/, $editor );
    push( @exec_array, "$todo_dir/todo" );

    # detect emacs and try to use markdown-mode
    $exec_array[ 0 ] eq "emacs" and
        push( @exec_array, '--funcall=markdown-mode' );

    # edit todo file
    system( @exec_array );
    $? == 0 or
        die "can't start editor, check EDITOR envionment variable";

    # check for changes and commit it
    $output = `bzr status --no-aliases "$todo_dir/todo"`;
    $? == 0 or die "couldn't check bzr rerpo status";
    chomp $output;
    if( $output ne "" ) {
        `bzr commit --no-aliases -q -m - "$todo_dir/todo"`;
        $? == 0 or die "couldn't commit to bzr repo";
    }

    # after editing, exit
    exit
}

# function to display a line
sub display_line
{
	my ( $line, $section ) = @_;
	state $old_section = '';

	# detect section change
	if( $section ne $old_section ) {
		$old_section = $section;
		
		# display section heading
		if( $display_headers || $display_all ) {
			print "$section\n".
				( "=" x length( $section ) )."\n";
		}
	}

	# replace tabs with 4 spaces
	$line =~ s/\t/    /g;
	
	# display the line
	print $line;
}


# scan through file
my $candidate_section = '';
my $section = '';
my $last_line = '';
open FILE, "<$todo_dir/todo" or die "can't open todo file";
while( <FILE> )
{
    # detect the line after section headings, and thus sections
    if( /^[-=]{2,}/ && $candidate_section ne '' ) {
        $section = $candidate_section;
        $candidate_section = '';
		$last_line = '';
		next;
    }

    # detect section headings
    if( /^[-_\.A-Za-z0-9 ]+$/ ) {
        $candidate_section = $_;
		chomp $candidate_section;
	}
    else {
        $candidate_section = '';
	}

	# display last line
	display_line( $last_line, $section ) if( $last_line ne '' );

    # display line
	if( ( lc( $section ) eq lc( $display_section ) ) ||
		( $section && $display_all ) )
	{
		$last_line = $_;
    }
	else {
		$last_line = '';
	}
}
display_line( $last_line, $section ) if( $last_line ne '' );