summaryrefslogtreecommitdiff
path: root/bin/cil
diff options
context:
space:
mode:
Diffstat (limited to 'bin/cil')
-rwxr-xr-xbin/cil630
1 files changed, 492 insertions, 138 deletions
diff --git a/bin/cil b/bin/cil
index b8bcd20..5aaa020 100755
--- a/bin/cil
+++ b/bin/cil
@@ -26,6 +26,9 @@ use File::Touch;
use File::Glob ':glob';
use File::Basename;
use File::Slurp qw(read_file write_file);
+use Email::Simple;
+use Email::Date qw(find_date);
+
use CIL;
use CIL::Issue;
use CIL::Comment;
@@ -36,7 +39,7 @@ use CIL::Attachment;
my $y = 'y';
-use constant VERSION => '0.3.0';
+use constant VERSION => '0.4.2';
my @IN_OPTS = (
# strings
@@ -56,6 +59,7 @@ my @IN_OPTS = (
# booleans
'is-open', # for 'summary', 'list'
'is-closed', # for 'summary', 'list'
+ 'is-mine', # for 'summary', 'list'
'help',
'version',
);
@@ -65,11 +69,9 @@ my %BOOLEAN_ARGS = (
'version' => 1,
'is-open' => 1,
'is-closed' => 1,
+ 'is-mine' => 1,
);
-my $gan = $ENV{GIT_AUTHOR_NAME} || 'Your Name';
-my $gae = $ENV{GIT_AUTHOR_EMAIL} || 'you@example.org';
-
## ----------------------------------------------------------------------------
# main program
@@ -92,13 +94,15 @@ my $gae = $ENV{GIT_AUTHOR_EMAIL} || 'you@example.org';
if @ARGV == 0;
my $command = shift @ARGV;
+ $command =~ s{-}{_}gxms;
no strict 'refs';
if ( not defined &{"cmd_$command"} ) {
Getopt::Mixed::abortMsg("'$command' is not a valid cil command.");
}
my $cil = CIL->new();
- $cil->read_config_file( '.cil' );
+ $cil->read_config_user();
+ $cil->read_config_file();
&{"cmd_$command"}($cil, $args, @ARGV);
}
@@ -196,10 +200,7 @@ sub cmd_show {
my ($cil, undef, $issue_name) = @_;
# firstly, read the issue in
- my $issue = CIL::Issue->new_from_name($cil, $issue_name);
- unless ( defined $issue ) {
- fatal("Couldn't load issue '$issue_name'");
- }
+ my $issue = load_issue_fuzzy( $cil, $issue_name );
display_issue_full($cil, $issue);
}
@@ -211,10 +212,7 @@ sub cmd_status {
}
# firstly, read the issue in
- my $issue = CIL::Issue->new_from_name($cil, $issue_name);
- unless ( defined $issue ) {
- fatal("Couldn't load issue '$issue_name'");
- }
+ my $issue = load_issue_fuzzy( $cil, $issue_name );
# set the status for this issue
$issue->Status( $status );
@@ -228,50 +226,20 @@ sub cmd_add {
CIL::Utils->ensure_interactive();
+ my $user = user($cil);
+
my $issue = CIL::Issue->new('tmpname');
$issue->Status('New');
- $issue->CreatedBy("$gan <$gae>");
- $issue->AssignedTo("$gan <$gae>");
+ $issue->CreatedBy( $user );
$issue->Description("Description ...");
- my $edit = $y;
-
- # keep going until we get a valid issue or we want to quit
- while ( $edit eq $y ) {
- # read in the new issue text
- my $fh = CIL::Utils->solicit( $issue->as_output );
- $issue = CIL::Issue->new_from_fh( 'tmp', $fh );
-
- # check if the issue is valid
- if ( $issue->is_valid($cil) ) {
- $edit = 'n';
- }
- else {
- msg($_) foreach @{ $issue->errors };
- print 'Would you like to re-edit (y/n): ';
- $edit = <STDIN>;
- chomp $edit;
- print "\n";
- }
- }
-
- # if the issue is still invalid, they quit without correcting it
- return unless $issue->is_valid( $cil );
-
- # we've got the issue, so let's name it
- my $unique_str = $issue->Inserted . $issue->Summary . $issue->Description;
- $issue->set_name( substr(md5_hex($unique_str), 0, 8) );
- $issue->save($cil);
- display_issue($cil, $issue);
+ add_issue_loop($cil, undef, $issue);
}
sub cmd_edit {
my ($cil, undef, $issue_name) = @_;
- my $issue = CIL::Issue->new_from_name($cil, $issue_name);
- unless ( defined $issue ) {
- fatal("Couldn't load issue '$issue_name'");
- }
+ my $issue = load_issue_fuzzy( $cil, $issue_name );
CIL::Utils->ensure_interactive();
@@ -289,10 +257,7 @@ sub cmd_edit {
}
else {
msg($_) foreach @{ $issue->errors };
- print 'Would you like to re-edit (y/n): ';
- $edit = <STDIN>;
- chomp $edit;
- print "\n";
+ $edit = ans('Would you like to re-edit (y/n): ');
}
}
@@ -307,63 +272,23 @@ sub cmd_edit {
sub cmd_comment {
my ($cil, undef, $issue_name) = @_;
- my $issue = CIL::Issue->new_from_name($cil, $issue_name);
- unless ( defined $issue ) {
- fatal("couldn't load issue '$issue_name'");
- }
+ my $issue = load_issue_fuzzy( $cil, $issue_name );
CIL::Utils->ensure_interactive();
# create the new comment
my $comment = CIL::Comment->new('tmpname');
$comment->Issue( $issue->name );
- $comment->CreatedBy("$gan <$gae>");
+ $comment->CreatedBy( user($cil) );
$comment->Description("Description ...");
- my $edit = $y;
-
- # keep going until we get a valid issue or we want to quit
- while ( $edit eq $y ) {
- # read in the new comment text
- my $fh = CIL::Utils->solicit( $comment->as_output );
- $comment = CIL::Comment->new_from_fh( 'tmp', $fh );
-
- # check if the comment is valid
- if ( $comment->is_valid($cil) ) {
- $edit = 'n';
- }
- else {
- msg($_) foreach @{ $issue->errors };
- print 'Would you like to re-edit (y/n): ';
- $edit = <STDIN>;
- chomp $edit;
- print "\n";
- }
- }
-
- # if the comment is still invalid, they quit without correcting it
- return unless $comment->is_valid( $cil );
-
- # we've got the comment, so let's name it
- my $unique_str = $comment->Inserted . $issue->Description;
- $comment->set_name( substr(md5_hex($unique_str), 0, 8) );
-
- # finally, save it
- $comment->save($cil);
-
- # add the comment to the issue, update it's timestamp and save it out
- $issue->add_comment( $comment );
- $issue->save($cil);
- display_issue_full($cil, $issue);
+ add_comment_loop($cil, undef, $issue, $comment);
}
sub cmd_attach {
my ($cil, undef, $issue_name, $filename) = @_;
- my $issue = CIL::Issue->new_from_name($cil, $issue_name);
- unless ( defined $issue ) {
- fatal("couldn't load issue '$issue_name'");
- }
+ my $issue = load_issue_fuzzy( $cil, $issue_name );
# check to see if the file exists
unless ( -r $filename ) {
@@ -371,10 +296,11 @@ sub cmd_attach {
}
my $basename = basename( $filename );
+ my $user = user($cil);
my $add_attachment_text = <<"EOF";
Filename : $basename
-CreatedBy : $gan <$gae>
+CreatedBy : $user
File goes here ... this will be overwritten.
EOF
@@ -397,7 +323,7 @@ EOF
$attachment->Size( $size );
# we've got the attachment, so let's name it
- my $unique_str = $attachment->Inserted . $attachment->File;
+ my $unique_str = time . $attachment->Inserted . $attachment->File;
$attachment->set_name( substr(md5_hex($unique_str), 0, 8) );
# finally, tell it who it's parent is and then save
@@ -413,10 +339,7 @@ EOF
sub cmd_extract {
my ($cil, $args, $attachment_name) = @_;
- my $attachment = CIL::Attachment->new_from_name($cil, $attachment_name);
- unless ( defined $attachment ) {
- fatal("Couldn't load attachment '$attachment_name'");
- }
+ my $attachment = load_attachment_fuzzy($cil, $attachment_name);
my $filename = $args->{f} || $attachment->Filename();
write_file( $filename, $attachment->as_binary );
@@ -452,8 +375,9 @@ sub cmd_fsck {
$attachment->{$a->name} = $a;
}
+ # ------
+ # issues
my $errors = {};
-
if ( @$issues ) {
foreach my $i ( sort { $a->Inserted cmp $b->Inserted } @$issues ) {
my $name = $i->name;
@@ -465,7 +389,7 @@ sub cmd_fsck {
}
# check that all it's comments are there and that they have this parent
- my $comments = $i->Comments;
+ my $comments = $i->CommentList;
foreach my $c ( @$comments ) {
# see if this comment exists at all
if ( exists $comment->{$c} ) {
@@ -479,7 +403,7 @@ sub cmd_fsck {
}
# check that all it's attachments are there and that they have this parent
- my $attachments = $i->Attachments;
+ my $attachments = $i->AttachmentList;
foreach my $a ( @$attachments ) {
# see if this attachment exists at all
if ( exists $attachment->{$a} ) {
@@ -491,14 +415,43 @@ sub cmd_fsck {
push @{$errors->{$name}}, "attachment '$a' listed in issue '" . $i->name . "' does not exist";
}
}
+
+ # check that all it's 'DependsOn' are there and that they have this under 'Precedes'
+ my $depends_on = $i->DependsOnList;
+ foreach my $d ( @$depends_on ) {
+ # see if this issue exists at all
+ if ( exists $issue->{$d} ) {
+ # check the 'Precedes' is this issue
+ my %precedes = map { $_ => 1 } @{$issue->{$d}->PrecedesList};
+ push @{$errors->{$name}}, "issue '$d' should precede '" . $i->name . "' but doesn't"
+ unless exists $precedes{$i->name};
+ }
+ else {
+ push @{$errors->{$name}}, "issue '$d' listed as a dependency of issue '" . $i->name . "' does not exist";
+ }
+ }
+
+ # check that all it's 'Precedes' are there and that they have this under 'DependsOn'
+ my $precedes = $i->PrecedesList;
+ foreach my $p ( @$precedes ) {
+ # see if this issue exists at all
+ if ( exists $issue->{$p} ) {
+ # check the 'DependsOn' is this issue
+ my %depends_on = map { $_ => 1 } @{$issue->{$p}->DependsOnList};
+ push @{$errors->{$name}}, "issue '$p' should depend on '" . $i->name . "' but doesn't"
+ unless exists $depends_on{$i->name};
+ }
+ else {
+ push @{$errors->{$name}}, "issue '$p' listed as preceding issue '" . $i->name . "' does not exist";
+ }
+ }
}
}
-
print_fsck_errors('Issue', $errors);
+ # --------
# comments
$errors = {};
-
# loop through all the comments
if ( @$comments ) {
# check that their parent issues exist
@@ -509,12 +462,11 @@ sub cmd_fsck {
}
}
}
-
print_fsck_errors('Comment', $errors);
+ # -----------
# attachments
$errors = {};
-
# loop through all the attachments
if ( @$attachments ) {
# check that their parent issues exist
@@ -525,26 +477,276 @@ sub cmd_fsck {
}
}
}
-
print_fsck_errors('Attachment', $errors);
- # nothing more to do
+ # ------------
+ # nothing left
separator();
}
-sub print_fsck_errors {
- my ($entity, $errors) = @_;
+sub cmd_am {
+ my ($cil, $args, $email_filename) = @_;
- separator();
- foreach my $issue_name ( keys %$errors ) {
- title( "$entity $issue_name ");
- foreach my $error ( @{$errors->{$issue_name}} ) {
- msg("* $error");
+ unless ( -f $email_filename ) {
+ fatal("couldn't load email '$email_filename'");
+ }
+
+ my $msg_text = read_file($email_filename);
+
+ my $email = Email::Simple->new($msg_text);
+ unless ( defined $email ) {
+ fatal("email file '$email_filename' didn't look like an email");
+ }
+
+ # extract some fields
+ my $subject = $email->header('Subject');
+ my $from = $email->header('From');
+ my $date = find_date($email)->datetime;
+ my $body = $email->body;
+
+ # see if we can find any issue names in either the subject or the body
+ my @issue_names;
+ foreach my $text ( $subject, $body ) {
+ my @new = ( $text =~ /\b\#?([0-9a-f]{8})\b/gxms );
+ push @issue_names, @new;
+ }
+
+ msg("Found possible issue names in email: ", ( join(' ', @issue_names) || '[none]' ));
+
+ my %issue;
+ foreach ( @issue_names ) {
+ my $i = eval { CIL::Issue->new_from_name($cil, $_) };
+ next unless defined $i;
+
+ $issue{$i->name} = $i;
+ }
+
+ if ( keys %issue ) {
+ msg( "Found actual issues: " . (join(' ', keys %issue)) );
+
+ # create the new comment
+ my $comment = CIL::Comment->new('tmpname');
+ $comment->Issue( '...' );
+ $comment->CreatedBy( $from );
+ $comment->Inserted( $date );
+ # $comment->Updated( $date );
+ $comment->Description( $body );
+
+ # display
+ display_comment($cil, $comment);
+
+ # found at least one issue, so this might be a comment
+ my $issue;
+ if ( keys %issue == 1 ) {
+ $issue = (values %issue)[0];
+ }
+ else {
+ my $ans = ans('To which issue would you like to add this comment: ');
+
+ # ToDo: decide whether we let them choose an arbitrary issue, for
+ # now quit unless they choose one from the list
+ return unless exists $issue{$ans};
+
+ # got a valid issue_name, so set the parent name
+ $issue = $issue{$ans};
}
+
+ # set the parent issue
+ $comment->Issue( $issue->name );
+
+ add_comment_loop($cil, undef, $issue, $comment);
+ }
+ else {
+ msg("Couldn't find reference to any issues in the email.");
+
+ # no issue found so make up the issue first
+ my $issue = CIL::Issue->new('tmpname');
+ $issue->Summary( $subject );
+ $issue->Status( 'New' );
+ $issue->CreatedBy( $from );
+ $issue->AssignedTo( user($cil) );
+ $issue->Inserted( $date );
+ $issue->Updated( $date );
+ $issue->Description( $body );
+
+ # display
+ display_issue_full($cil, $issue);
+
+ # then ask if the user would like to add it
+ msg("Couldn't find any likely issues, so this might be a new one.");
+ my $ans = ans('Would you like to add this message as an issue shown above (y/n): ');
+ return unless $ans eq 'y';
+
+ add_issue_loop($cil, undef, $issue);
}
}
+sub cmd_depends_on {
+ my ($cil, undef, $issue_name, $depends_name) = @_;
+
+ my $issue = load_issue_fuzzy($cil, $issue_name);
+ my $depends = load_issue_fuzzy($cil, $depends_name);
+
+ $issue->add_depends_on( $depends->name );
+ $depends->add_precedes( $issue->name );
+
+ $issue->save($cil);
+ $depends->save($cil);
+}
+
+sub cmd_precedes {
+ my ($cil, undef, $issue_name, $depends_name) = @_;
+
+ # switch them round and call 'DependsOn'
+ cmd_depends_on($cil, undef, $depends_name, $issue_name);
+}
+
## ----------------------------------------------------------------------------
+# helpers
+
+sub load_issue_fuzzy {
+ my ($cil, $partial_name) = @_;
+
+ my $issues = $cil->list_issues_fuzzy( $partial_name );
+ unless ( defined $issues ) {
+ fatal("Couldn't find any issues using '$partial_name'");
+ }
+
+ if ( @$issues > 1 ) {
+ fatal('found multiple issues which match that name: ' . join(' ', map { $_->{name} } @$issues));
+ }
+
+ my $issue_name = $issues->[0]->{name};
+ my $issue = CIL::Issue->new_from_name($cil, $issue_name);
+ unless ( defined $issue ) {
+ fatal("Couldn't load issue '$issue_name'");
+ }
+
+ return $issue;
+}
+
+sub load_comment_fuzzy {
+ my ($cil, $partial_name) = @_;
+
+ my $comments = $cil->list_comments_fuzzy( $partial_name );
+ unless ( defined $comments ) {
+ fatal("Couldn't find any comments using '$partial_name'");
+ }
+
+ if ( @$comments > 1 ) {
+ fatal('found multiple comments which match that name: ' . join(' ', map { $_->{name} } @$comments));
+ }
+
+ my $comment_name = $comments->[0]->{name};
+ my $comment = CIL::comment->new_from_name($cil, $comment_name);
+ unless ( defined $comment ) {
+ fatal("Couldn't load comment '$comment_name'");
+ }
+
+ return $comment;
+}
+
+sub load_attachment_fuzzy {
+ my ($cil, $partial_name) = @_;
+
+ my $attachments = $cil->list_attachments_fuzzy( $partial_name );
+ unless ( defined $attachments ) {
+ fatal("Couldn't find any attachments using '$partial_name'");
+ }
+
+ if ( @$attachments > 1 ) {
+ fatal('found multiple attachments which match that name: ' . join(' ', map { $_->{name} } @$attachments));
+ }
+
+ my $attachment_name = $attachments->[0]->{name};
+ my $attachment = CIL::Attachment->new_from_name($cil, $attachment_name);
+ unless ( defined $attachment ) {
+ fatal("Couldn't load attachment '$partial_name'");
+ }
+
+ return $attachment;
+}
+
+sub ans {
+ my ($msg) = @_;
+ print $msg;
+ my $ans = <STDIN>;
+ chomp $ans;
+ print "\n";
+ return $ans;
+}
+
+sub add_issue_loop {
+ my ($cil, undef, $issue) = @_;
+
+ my $edit = $y;
+
+ # keep going until we get a valid issue or we want to quit
+ while ( $edit eq $y ) {
+ # read in the new issue text
+ my $fh = CIL::Utils->solicit( $issue->as_output );
+ $issue = CIL::Issue->new_from_fh( 'tmp', $fh );
+
+ # check if the issue is valid
+ if ( $issue->is_valid($cil) ) {
+ $edit = 'n';
+ }
+ else {
+ msg($_) foreach @{ $issue->errors };
+ $edit = ans('Would you like to re-edit (y/n): ');
+ }
+ }
+
+ # if the issue is still invalid, they quit without correcting it
+ return unless $issue->is_valid( $cil );
+
+ # we've got the issue, so let's name it
+ my $unique_str = time . $issue->Inserted . $issue->Summary . $issue->Description;
+ $issue->set_name( substr(md5_hex($unique_str), 0, 8) );
+ $issue->save($cil);
+ display_issue($cil, $issue);
+
+ return $issue;
+}
+
+sub add_comment_loop {
+ my ($cil, undef, $issue, $comment) = @_;
+
+ my $edit = $y;
+
+ # keep going until we get a valid issue or we want to quit
+ while ( $edit eq $y ) {
+ # read in the new comment text
+ my $fh = CIL::Utils->solicit( $comment->as_output );
+ $comment = CIL::Comment->new_from_fh( 'tmp', $fh );
+
+ # check if the comment is valid
+ if ( $comment->is_valid($cil) ) {
+ $edit = 'n';
+ }
+ else {
+ msg($_) foreach @{ $issue->errors };
+ $edit = ans('Would you like to re-edit (y/n): ');
+ }
+ }
+
+ # if the comment is still invalid, they quit without correcting it
+ return unless $comment->is_valid( $cil );
+
+ # we've got the comment, so let's name it
+ my $unique_str = time . $comment->Inserted . $issue->Description;
+ $comment->set_name( substr(md5_hex($unique_str), 0, 8) );
+
+ # finally, save it
+ $comment->save($cil);
+
+ # add the comment to the issue, update it's timestamp and save it out
+ $issue->add_comment( $comment );
+ $issue->save($cil);
+ display_issue_full($cil, $issue);
+
+ return $comment;
+}
sub check_paths {
my ($cil) = @_;
@@ -561,6 +763,11 @@ sub filter_issues {
# don't filter if we haven't been given anything
return $issues unless %$args;
+ # check that they aren't filtering on both --assigned-to and --is-mine
+ if ( defined $args->{a} and defined $args->{'is-mine'} ) {
+ fatal("the --assigned-to and --is-mine filters are mutually exclusive");
+ }
+
# take a copy of the whole lot first (so we don't destroy the input list)
my @new_issues = @$issues;
@@ -574,7 +781,7 @@ sub filter_issues {
my @tmp;
foreach my $issue ( @new_issues ) {
push @tmp, $issue
- if grep { $_ eq $args->{l} } @{$issue->Labels};
+ if grep { $_ eq $args->{l} } @{$issue->LabelList};
}
@new_issues = @tmp;
}
@@ -595,6 +802,8 @@ sub filter_issues {
}
# filter out 'assigned to'
+ $args->{a} = $cil->UserEmail
+ if defined $args->{'is-mine'};
if ( defined $args->{a} ) {
@new_issues = grep { $args->{a} eq $_->assigned_to_email } @new_issues;
}
@@ -602,6 +811,19 @@ sub filter_issues {
return \@new_issues;
}
+sub print_fsck_errors {
+ my ($entity, $errors) = @_;
+ return unless keys %$errors;
+
+ separator();
+ foreach my $issue_name ( keys %$errors ) {
+ title( "$entity $issue_name ");
+ foreach my $error ( @{$errors->{$issue_name}} ) {
+ msg("* $error");
+ }
+ }
+}
+
## ----------------------------------------------------------------------------
# input/output
@@ -626,7 +848,9 @@ sub display_issue_headers {
field( 'AssignedTo', $issue->AssignedTo() );
field( 'Inserted', $issue->Inserted() );
field( 'Status', $issue->Status() );
- field( 'Labels', join(' ', @{$issue->Label()}) );
+ field( 'Labels', join(' ', @{$issue->LabelList()}) );
+ field( 'DependsOn', join(' ', @{$issue->DependsOnList()}) );
+ field( 'Precedes', join(' ', @{$issue->PrecedesList()}) );
}
sub display_issue {
@@ -639,11 +863,15 @@ sub display_issue {
field( 'CreatedBy', $issue->CreatedBy() );
field( 'AssignedTo', $issue->AssignedTo() );
field( 'Label', $_ )
- foreach sort @{$issue->Label()};
+ foreach sort @{$issue->LabelList()};
field( 'Comment', $_ )
- foreach sort @{$issue->Comment()};
+ foreach sort @{$issue->CommentList()};
field( 'Attachment', $_ )
- foreach sort @{$issue->Attachment()};
+ foreach sort @{$issue->AttachmentList()};
+ field( 'DependsOn', $_ )
+ foreach sort @{$issue->DependsOnList()};
+ field( 'Precedes', $_ )
+ foreach sort @{$issue->PrecedesList()};
field( 'Inserted', $issue->Inserted() );
field( 'Updated', $issue->Inserted() );
text('Description', $issue->Description());
@@ -661,32 +889,53 @@ sub display_issue_full {
field( 'AssignedTo', $issue->AssignedTo() );
field( 'Label', $_ )
foreach sort @{$issue->Label()};
+ field( 'DependsOn', $_ )
+ foreach sort @{$issue->DependsOnList()};
+ field( 'Precedes', $_ )
+ foreach sort @{$issue->PrecedesList()};
field( 'Inserted', $issue->Inserted() );
field( 'Updated', $issue->Inserted() );
text('Description', $issue->Description());
my $comments = $cil->get_comments_for( $issue );
foreach my $comment ( @$comments ) {
- title( 'Comment ' . $comment->name() );
- field( 'CreatedBy', $comment->CreatedBy() );
- field( 'Inserted', $comment->Inserted() );
- field( 'Updated', $comment->Inserted() );
- text('Description', $comment->Description());
+ display_comment( $cil, $comment );
}
my $attachments = $cil->get_attachments_for( $issue );
foreach my $attachment ( @$attachments ) {
- title( 'Attachment ' . $attachment->name() );
- field( 'Filename', $attachment->Filename() );
- field( 'CreatedBy', $attachment->CreatedBy() );
- field( 'Inserted', $attachment->Inserted() );
- field( 'Updated', $attachment->Inserted() );
+ display_attachment( $cil, $attachment );
msg();
}
separator();
}
+sub display_comment {
+ my ($cil, $comment) = @_;
+
+ title( 'Comment ' . $comment->name() );
+ field( 'CreatedBy', $comment->CreatedBy() );
+ field( 'Inserted', $comment->Inserted() );
+ field( 'Updated', $comment->Inserted() );
+ text('Description', $comment->Description());
+}
+
+sub display_attachment {
+ my ($cil, $attachment) = @_;
+
+ title( 'Attachment ' . $attachment->name() );
+ field( 'Filename', $attachment->Filename() );
+ field( 'CreatedBy', $attachment->CreatedBy() );
+ field( 'Inserted', $attachment->Inserted() );
+ field( 'Updated', $attachment->Inserted() );
+}
+
+sub user {
+ my ($cil) = @_;
+ return $cil->UserName . ' <' . $cil->UserEmail . '>';
+}
+
## ----------------------------------------------------------------------------
# helper functions for this command line tool
@@ -768,8 +1017,8 @@ Usage: $0 COMMAND [options]
Commands:
init [--path=PATH]
add
- summary [--status=STATUS] [--label=LABEL] [--is-open] [--is-closed]
- list [--status=STATUS] [--label=LABEL] [--is-open] [--is-closed]
+ summary [FILTERS...]
+ list [FILTERS...]
show ISSUE
status ISSUE NEW_STATUS
edit ISSUE
@@ -778,8 +1027,16 @@ Commands:
extract ATTACHMENT [--filename=FILENAME]
fsck
+Filters:
+ --status=?
+ --is-open
+ --is-closed
+ --label=?
+ --assigned-to=?
+ --is-mine
+
See <http://kapiti.geek.nz/software/cil.html> for further information.
-Report bugs to <andychilton -at- gmail -dot- com>.
+Report bugs to <andychilton\@gmail.com>.
END_USAGE
}
@@ -813,6 +1070,8 @@ cil - the command-line issue list
$ cil extract decaf7ea
$ cil extract decaf7ea --filename=other_filename.txt
+ $ cil am email.txt
+
$ cil fsck
=head1 DESCRIPTION
@@ -827,12 +1086,12 @@ and attachments as local files which you can check in to your repository.
Creates a local '.cil' file and an 'issues' directory. If PATH is specified,
the config file and directory will be created in the destination directory.
-=item summary [--status=STATUS] [--label=LABEL] [--is-open] [--is-closed]
+=item summary [filters]
Displays a one line summary for each issue. You may filter on both the Status
and Label fields.
-=item list [--status=STATUS] [--label=LABEL] [--is-open] [--is-closed]
+=item list [filters]
Shows each issue with more information. You may filter on both the Status and
Label fields.
@@ -850,6 +1109,21 @@ Shows the issue name with more detail.
Shortcut so that you can set a new status on an issue without having to edit
it.
+=item depends-on ISSUE1 ISSUE2
+
+Shortcut so that cil will add a 'DependsOn' from issue 1 to issue
+2. Conversley, issue 2 will also then contain a 'Precedes' pointer to issue 1.
+
+=item precedes ISSUE1 ISSUE2
+
+This is the exact opposite of C<depends-on> and is here for convenience and
+completeness. ie. issue 1 has to be completed before issue 2.
+
+=item status ISSUE NEW_STATUS
+
+Shortcut so that you can set a new status on an issue without having to edit
+it.
+
=item edit ISSUE
Edits the issue. If it changes, set the updates time to now.
@@ -867,6 +1141,59 @@ Adds that particular filename to an existing issue.
Extracts the file from the attachment number. If filename if given uses that,
otherwise it will use the original one saved along with the attachment.
+=item fsck
+
+Tries to help you organise your issues if any aren't valid or have broken
+relationships.
+
+=item am
+
+Applies an email message to the issue list. It tries to figure out the type of
+email it is, whether it is a new issue or a comment on an already existing
+issue. For example, if it can find valid issue names in the subject or body of
+the message, it adds it as a comment to that issue. If it can't find any valid
+issue names, it presumes it's a new issue and adds that.
+
+Note: this command will deal with Mailbox format files later on.
+
+=back
+
+=head1 FILTERS
+
+Filters can be used on both the C<summary> and C<list> commands. Most can be
+combined. See each individual filter for details.
+
+=over
+
+=item --status=STATUS
+
+You can choose any of the Statuses which might appear in your issues. This
+status does not have to be defined in your C<.cil> file, even if you have
+C<StatusStrict> turned on.
+
+=item --label=LABEL
+
+You can choose any of the Labels which might appear in your issues. This
+label does not have to be defined in your C<.cil> file, even if you have
+C<LabelStrict> turned on.
+
+=item --is-open, --is-closed
+
+These check both C<StatusOpenList> and C<StatusClosedList> from your C<.cil>
+file. If both are specified, you're likely to get no issues unless you
+explicitely defined a status as being in both lists (for whatever reason you
+have).
+
+=item --assigned-to=EMAIL_ADDRESS, --is-mine
+
+These items are mutually exclusive. The C<--assigned-to> just checks the email
+address in the AssignedTo field. It does not match anything else in that field,
+including any preceding name or any angle brackets.
+
+The C<--is-mine> filter is a shortcut to asking if AssignedTo is you. Cil knows
+your email address if you define it in your user's C<~/.cilrc> file as
+C<UserEmail>.
+
=back
=head1 .cil
@@ -935,6 +1262,33 @@ This determines which labels are allowed if you have turned on LabelStrict.
=back
+=head1 ~/.cilrc
+
+The C<~/.cilrc> file is read to configure the user's preferences for all cil
+lists they're using. It is of the same format as the C<.cil> file and contains
+the following options:
+
+ UserName: Andrew Chilton
+ UserEmail: andychilton@gmail.com
+
+=over
+
+=item UserName
+
+Default: 'Name', Type: String
+
+This is used as a default in the C<CreatedBy> and C<AssignedTo> fields in any
+issues/comments/attachments you add.
+
+=item UserEmail
+
+Default: 'Email', Type: String
+
+This is used as a default in the C<CreatedBy> and C<AssignedTo> fields in any
+issues/comments/attachments you add.
+
+=back
+
=head1 BUGS
Probably. Let me know :-)