#!/usr/bin/perl -w # # Tool to parse todo files. # use strict; # Parse a todo file and add tasks to \%todos. sub parse_todo { my ($todos, $path, $file) = @_; my $owner; local $_; local *FILE; # Check for duplicates and create task list. die "duplicate path: $path" if exists $$todos{''}; $$todos{''} = [ ]; my $tasks = $$todos{''}; # Open file. open FILE, "<$file" or die "$file: $!"; # Read each lines. while () { chomp; # Task line. /^([-+=x]) (?:\(([^)]+)\) |)(.+)$/ and do { my ($state, $owner, $text) = ($1, $2 || $owner, $3); #$owner = $1 if $2 =~ /^\((.*)\) $/; my @owners = split /, */, $owner; # Read next lines. while () { chomp; # Continued task, new paragraph. /^ $/ and $text .= "\n", next; # Continued task. /^ (.*)$/ and $text .= ' ' . $1, next; last; } # Add task. push @$tasks, { state => $state, owner => [ @owners ], text => $text }; last unless defined $_; redo; }; # Default owner line. /^\(([^)]+)\)$/ and $owner = $1, next; # Subtask line. /^[-\w\d]+$/ and do { my $path = $path . '/' . $_; $$todos{$_} ||= { }; my $todos = $$todos{$_}; die "duplicate path: $path" if exists $$todos{''}; $$todos{''} = [ ]; $tasks = $$todos{''}; next; }; # Empty line. /^$/ and next; # Else, die. die 'Invalid format'; } # Close file. close FILE; } # Read dir and parse each todo files it contains. sub parse_dir { my ($todos, $path, $dir) = @_; local $_; local @_; local *DIR; # Read files list. opendir DIR, $dir or die "$dir: $!"; @_ = readdir DIR; closedir DIR; # Filter todo files & dirs lists. my @todofiles = grep { !/^\./ && /\.todo$/ && -f "$dir/$_" } @_; s/\.todo$// foreach @todofiles; my @tododirs = grep { !/^\./ && -d "$dir/$_" } @_; # Create empty hashes. $$todos{$_} ||= { } foreach @todofiles, @tododirs; # Process each todo files. parse_todo ($$todos{$_}, "$path/$_", "$dir/$_.todo") foreach @todofiles; # Recurse into each dirs. parse_dir ($$todos{$_}, "$path/$_", "$dir/$_") foreach @tododirs; } my %todos; parse_dir (\%todos, '', '.'); { use Data::Dumper; print Dumper \%todos; }