From ddb4637a270edb55b4466ede2def461ed6247c8d Mon Sep 17 00:00:00 2001 From: Andrew Chilton Date: Tue, 23 Sep 2008 00:05:58 +1200 Subject: [PATCH] Follow Yanick's lead and push all the command out into pluggable modules --- bin/cil | 809 ----------------------------------- lib/CIL.pm | 131 +----- lib/CIL/Command/Add.pm | 51 +++ lib/CIL/Command/Am.pm | 131 ++++++ lib/CIL/Command/Attach.pm | 85 ++++ lib/CIL/Command/Comment.pm | 50 +++ lib/CIL/Command/DependsOn.pm | 47 ++ lib/CIL/Command/Edit.pm | 71 +++ lib/CIL/Command/Extract.pm | 44 ++ lib/CIL/Command/Fsck.pm | 187 ++++++++ lib/CIL/Command/Init.pm | 111 +++++ lib/CIL/Command/List.pm | 43 +- lib/CIL/Command/Precedes.pm | 48 +++ lib/CIL/Command/Show.pm | 42 ++ lib/CIL/Command/Status.pm | 51 +++ lib/CIL/Command/Steal.pm | 47 ++ lib/CIL/Command/Summary.pm | 55 +++ lib/CIL/Command/Track.pm | 54 +++ lib/CIL/Utils.pm | 383 +++++++++++++++++ 19 files changed, 1493 insertions(+), 947 deletions(-) create mode 100644 lib/CIL/Command/Add.pm create mode 100644 lib/CIL/Command/Am.pm create mode 100644 lib/CIL/Command/Attach.pm create mode 100644 lib/CIL/Command/Comment.pm create mode 100644 lib/CIL/Command/DependsOn.pm create mode 100644 lib/CIL/Command/Edit.pm create mode 100644 lib/CIL/Command/Extract.pm create mode 100644 lib/CIL/Command/Fsck.pm create mode 100644 lib/CIL/Command/Init.pm create mode 100644 lib/CIL/Command/Precedes.pm create mode 100644 lib/CIL/Command/Show.pm create mode 100644 lib/CIL/Command/Status.pm create mode 100644 lib/CIL/Command/Steal.pm create mode 100644 lib/CIL/Command/Summary.pm create mode 100644 lib/CIL/Command/Track.pm diff --git a/bin/cil b/bin/cil index 54f136a..dff5789 100755 --- a/bin/cil +++ b/bin/cil @@ -21,7 +21,6 @@ use strict; use warnings; use Getopt::Mixed "nextOption"; -use Digest::MD5 qw(md5_hex); use File::Touch; use File::Glob ':glob'; use File::Basename; @@ -37,8 +36,6 @@ use CIL::Attachment; ## ---------------------------------------------------------------------------- # constants -my $y = 'y'; - my @IN_OPTS = ( # strings 'p=s', # p = path @@ -112,798 +109,11 @@ my %BOOLEAN_ARGS = ( $command->run($cil, $args, @ARGV); } -## ---------------------------------------------------------------------------- -# commands - -sub cmd_init { - my ($cil, $args) = @_; - - my $path = $args->{p} || '.'; # default path is right here - - # error if $path doesn't exist - unless ( -d $path ) { - fatal("path '$path' doesn't exist"); - } - - # error if issues/ already exists - my $issues_dir = "$path/issues"; - if ( -d $issues_dir ) { - fatal("issues directory '$issues_dir' already exists, not initialising issues"); - } - - # error if .cil already exists - my $config = "$path/.cil"; - if ( -f $config ) { - fatal("config file '$config' already exists, not initialising issues"); - } - - # try to create the issues/ dir - unless ( mkdir $issues_dir ) { - fatal("Couldn't create '$issues_dir' directory: $!"); - } - - # are we in a Git repository? - my $VCSconfig = ''; - if ( -d '.git' ) { - msg( 'git repository detected, setting VCS accordingly' ); - $VCSconfig = 'VCS: git'; - my $vcs = CIL::VCS::Factory->new( 'Git' ); - } - - # create a .cil file here also - if ( $args->{bare} ) { - unless ( touch $config ) { - rmdir $issues_dir; - fatal("couldn't create a '$config' file"); - } - } - else { - # write a default .cil file - write_file($config, <<"CONFIG"); -$VCSconfig -StatusStrict: 1 -StatusAllowedList: New -StatusAllowedList: InProgress -StatusAllowedList: Finished -StatusOpenList: New -StatusOpenList: InProgress -StatusClosedList: Finished -LabelStrict: 1 -LabelAllowedList: Type-Enhancement -LabelAllowedList: Type-Defect -LabelAllowedList: Priority-High -LabelAllowedList: Priority-Medium -LabelAllowedList: Priority-Low -CONFIG - } - - # add a README.txt so people know what this is about - unless ( -f "$issues_dir/README.txt" ) { - write_file("$issues_dir/README.txt", <<'README'); -This directory is used by CIL to track issues and feature requests. - -The home page for CIL is at http://kapiti.geek.nz/software/cil.html -README - } - - # $path/issues/ and $path/.cil create correctly - msg("initialised empty issue list inside '$path/'"); -} - -sub cmd_summary { - my ($cil, $args) = @_; - - check_paths($cil); - - # find all the issues - my $issues = $cil->get_issues(); - $issues = filter_issues( $cil, $issues, $args ); - if ( @$issues ) { - separator(); - foreach my $issue ( @$issues ) { - display_issue_summary($cil, $issue); - } - separator(); - } - else { - msg('no issues found'); - } -} - -sub cmd_show { - my ($cil, undef, $issue_name) = @_; - - # firstly, read the issue in - my $issue = load_issue_fuzzy( $cil, $issue_name ); - display_issue_full($cil, $issue); -} - -sub cmd_status { - my ($cil, undef, $issue_name, $status) = @_; - - unless ( defined $status ) { - fatal("provide a status to set this issue to"); - } - - # firstly, read the issue in - my $issue = load_issue_fuzzy( $cil, $issue_name ); - - # set the status for this issue - $issue->Status( $status ); - $issue->save($cil); - - display_issue($cil, $issue); -} - -sub cmd_steal { - my ($cil, undef, $issue_name) = @_; - - # firstly, read the issue in - my $issue = load_issue_fuzzy( $cil, $issue_name ); - - # set the AssignedTo for this issue to you (because you're stealing it) - $issue->AssignedTo( user($cil) ); - $issue->save($cil); - - display_issue($cil, $issue); -} - -sub cmd_add { - my ($cil, undef, @argv ) = @_; - - CIL::Utils->ensure_interactive(); - - my $user = user($cil); - - my $issue = CIL::Issue->new('tmpname'); - $issue->Summary( join ' ', @argv ); - $issue->Status('New'); - $issue->CreatedBy( $user ); - $issue->Description("Description ..."); - - add_issue_loop($cil, undef, $issue); -} - -sub cmd_edit { - my ($cil, undef, $issue_name) = @_; - - my $issue = load_issue_fuzzy( $cil, $issue_name ); - - CIL::Utils->ensure_interactive(); - - 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( $issue->name, $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 ); - - # save it - $issue->save($cil); - display_issue($cil, $issue); -} - -sub cmd_comment { - my ($cil, undef, $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( user($cil) ); - $comment->Description("Description ..."); - - add_comment_loop($cil, undef, $issue, $comment); -} - -sub cmd_attach { - my ($cil, undef, $issue_name, $filename) = @_; - - my $issue = load_issue_fuzzy( $cil, $issue_name ); - - # check to see if the file exists - unless ( -r $filename ) { - fatal("couldn't read file '$filename'"); - } - - my $basename = basename( $filename ); - my $user = user($cil); - - my $add_attachment_text = <<"EOF"; -Filename : $basename -CreatedBy : $user - -File goes here ... this will be overwritten. -EOF - - # read in the new issue text - CIL::Utils->ensure_interactive(); - my $fh = CIL::Utils->solicit( $add_attachment_text ); - - my $attachment = CIL::Attachment->new_from_fh( 'tmp', $fh ); - unless ( defined $attachment ) { - fatal("could not create new attachment"); - } - - # now add the file itself - my $contents = read_file( $filename ); - $attachment->set_file_contents( $contents ); - - # set the size - my ($size) = (stat($filename))[7]; - $attachment->Size( $size ); - - # we've got the attachment, so let's name it - 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 - $attachment->Issue( $issue->name ); - $attachment->save($cil); - - # add the comment to the issue, update it's timestamp and save it out - $issue->add_attachment( $attachment ); - $issue->save($cil); - display_issue_full($cil, $issue); -} - -sub cmd_extract { - my ($cil, $args, $attachment_name) = @_; - - my $attachment = load_attachment_fuzzy($cil, $attachment_name); - - my $filename = $args->{f} || $attachment->Filename(); - write_file( $filename, $attachment->as_binary ); -} - -sub cmd_track { - my ($cil, $args, $issue_name) = @_; - - fatal("the 'VCS' option in your .cil file is not set") - unless defined $cil->VCS; - - fatal("the 'VCS' option currently only supports values of 'Git'") - unless $cil->VCS eq 'Git'; - - my $issue = load_issue_fuzzy($cil, $issue_name); - - # add the issue to Git - my $issue_dir = $cil->IssueDir(); - my @files; - push @files, "$issue_dir/i_" . $issue->name . '.cil'; - push @files, map { "$issue_dir/c_${_}.cil" } @{ $issue->CommentList }; - push @files, map { "$issue_dir/a_${_}.cil" } @{ $issue->AttachmentList }; - msg("git add @files"); -} - -sub cmd_fsck { - my ($cil, $args) = @_; - - # this looks at all the issues it can find and checks for: - # * validity - # * all the comments are there - # * all the attachments are there - # then it checks each individual comment/attachment for: - # * ToDo: validity - # * it's parent exists - - check_paths($cil); - - # find all the issues, comments and attachments - my $issues = $cil->get_issues(); - my $issue = {}; - foreach my $i ( @$issues ) { - $issue->{$i->name} = $i; - } - my $comments = $cil->get_comments(); - my $comment = {}; - foreach my $c ( @$comments ) { - $comment->{$c->name} = $c; - } - my $attachments = $cil->get_attachments(); - my $attachment = {}; - foreach my $a ( @$attachments ) { - $attachment->{$a->name} = $a; - } - - # ------ - # issues - my $errors = {}; - if ( @$issues ) { - foreach my $i ( sort { $a->Inserted cmp $b->Inserted } @$issues ) { - my $name = $i->name; - - unless ( $i->is_valid($cil) ) { - foreach ( @{ $i->errors } ) { - push @{$errors->{$name}}, $_; - } - } - - # check that all it's comments are there and that they have this parent - my $comments = $i->CommentList; - foreach my $c ( @$comments ) { - # see if this comment exists at all - if ( exists $comment->{$c} ) { - # check the parent is this issue - push @{$errors->{$name}}, "comment '$c' is listed under issue '" . $i->name . "' but does not reciprocate" - unless $comment->{$c}->Issue eq $i->name; - } - else { - push @{$errors->{$name}}, "comment '$c' listed in issue '" . $i->name . "' does not exist"; - } - } - - # check that all it's attachments are there and that they have this parent - my $attachments = $i->AttachmentList; - foreach my $a ( @$attachments ) { - # see if this attachment exists at all - if ( exists $attachment->{$a} ) { - # check the parent is this issue - push @{$errors->{$name}}, "attachment '$a' is listed under issue '" . $i->name . "' but does not reciprocate" - unless $attachment->{$a}->Issue eq $i->name; - } - else { - 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 - foreach my $c ( sort { $a->Inserted cmp $b->Inserted } @$comments ) { - # check that the parent of each comment exists - unless ( exists $issue->{$c->Issue} ) { - push @{$errors->{$c->name}}, "comment '" . $c->name . "' refers to issue '" . $c->Issue . "' but issue does not exist"; - } - } - } - print_fsck_errors('Comment', $errors); - - # ----------- - # attachments - $errors = {}; - # loop through all the attachments - if ( @$attachments ) { - # check that their parent issues exist - foreach my $a ( sort { $a->Inserted cmp $b->Inserted } @$attachments ) { - # check that the parent of each attachment exists - unless ( exists $issue->{$a->Issue} ) { - push @{$errors->{$a->name}}, "attachment '" . $a->name . "' refers to issue '" . $a->Issue . "' but issue does not exist"; - } - } - } - print_fsck_errors('Attachment', $errors); - - # ------------ - # nothing left - separator(); -} - -sub cmd_am { - my ($cil, $args, $email_filename) = @_; - - 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 = ; - 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); - - # should probably be run from with $cil - $cil->run_hook('issue_post_save', $issue); - - 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 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"); - } - } -} - ## ---------------------------------------------------------------------------- # hooks # none yet -## ---------------------------------------------------------------------------- -# input/output - -sub display_issue { - my ($cil, $issue) = @_; - - separator(); - title( 'Issue ' . $issue->name() ); - field( 'Summary', $issue->Summary() ); - field( 'Status', $issue->Status() ); - field( 'CreatedBy', $issue->CreatedBy() ); - field( 'AssignedTo', $issue->AssignedTo() ); - field( 'Label', $_ ) - foreach sort @{$issue->LabelList()}; - field( 'Comment', $_ ) - foreach sort @{$issue->CommentList()}; - field( '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()); - separator(); -} - -sub display_issue_full { - my ($cil, $issue) = @_; - - separator(); - title( 'Issue ' . $issue->name() ); - field( 'Summary', $issue->Summary() ); - field( 'Status', $issue->Status() ); - field( 'CreatedBy', $issue->CreatedBy() ); - 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 ) { - display_comment( $cil, $comment ); - } - - my $attachments = $cil->get_attachments_for( $issue ); - foreach my $attachment ( @$attachments ) { - 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 @@ -933,25 +143,6 @@ sub get_options { return $args; } -sub text { - my ($field, $value) = @_; - msg ""; - msg($value); - msg ""; -} - -sub err { - print STDERR ( defined $_[0] ? $_[0] : '' ); - print STDERR "\n"; -} - -sub fatal { - my ($msg) = @_; - chomp $msg; - print STDERR $msg, "\n"; - exit 2; -} - ## ---------------------------------------------------------------------------- # program info diff --git a/lib/CIL.pm b/lib/CIL.pm index 29a1446..327adc6 100644 --- a/lib/CIL.pm +++ b/lib/CIL.pm @@ -318,8 +318,6 @@ sub register_hook { sub run_hook { my ($self, $hook_name, @rest) = @_; - print "s=$self, hn=$hook_name, r=@rest\n"; - unless ( defined $allowed->{hook}{$hook_name} ) { croak "hook '$hook_name' not allowed"; } @@ -388,132 +386,5 @@ sub UserEmail { } ## ---------------------------------------------------------------------------- - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -sub check_paths { - my ($cil) = @_; - - # make sure an issue directory is available - unless ( $cil->dir_exists($cil->IssueDir) ) { - fatal("couldn't find '" . $cil->IssueDir . "' directory"); - } -} - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -sub filter_issues { - my ($cil, $issues, $args) = @_; - - # 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; - - # firstly, get out the Statuses we want - if ( defined $args->{s} ) { - @new_issues = grep { $_->Status eq $args->{s} } @new_issues; - } - - # then see if we want a particular label (could be a bit nicer) - if ( defined $args->{l} ) { - my @tmp; - foreach my $issue ( @new_issues ) { - push @tmp, $issue - if grep { $_ eq $args->{l} } @{$issue->LabelList}; - } - @new_issues = @tmp; - } - - # filter out dependent on open/closed - if ( defined $args->{'is-open'} ) { - # just get the open issues - @new_issues = grep { $_->is_open($cil) } @new_issues; - } - if ( defined $args->{'is-closed'} ) { - # just get the closed issues - @new_issues = grep { $_->is_closed($cil) } @new_issues; - } - - # filter out 'created by' - if ( defined $args->{c} ) { - @new_issues = grep { $args->{c} eq $_->created_by_email } @new_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; - } - - return \@new_issues; -} - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -sub separator { - msg('=' x 79); -} - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -sub msg { - print ( defined $_[0] ? $_[0] : '' ); - print "\n"; -} - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -sub display_issue_summary { - my ($cil, $issue) = @_; - - my $msg = $issue->name(); - $msg .= "\t"; - $msg .= $issue->Status(); - $msg .= "\t"; - $msg .= $issue->Summary(); - - msg($msg); -} -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -sub display_issue_headers { - my ($cil, $issue) = @_; - - title( 'Issue ' . $issue->name() ); - field( 'Summary', $issue->Summary() ); - field( 'CreatedBy', $issue->CreatedBy() ); - field( 'AssignedTo', $issue->AssignedTo() ); - field( 'Inserted', $issue->Inserted() ); - field( 'Status', $issue->Status() ); - field( 'Labels', join(' ', @{$issue->LabelList()}) ); - field( 'DependsOn', join(' ', @{$issue->DependsOnList()}) ); - field( 'Precedes', join(' ', @{$issue->PrecedesList()}) ); -} - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -sub title { - my ($title) = @_; - my $msg = "--- $title "; - $msg .= '-' x (74 - length($title)); - msg($msg); -} - -#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -sub field { - my ($field, $value) = @_; - my $msg = "$field"; - $msg .= " " x (12 - length($field)); - msg("$msg: " . (defined $value ? $value : '') ); -} - 1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Add.pm b/lib/CIL/Command/Add.pm new file mode 100644 index 0000000..c0a2d15 --- /dev/null +++ b/lib/CIL/Command/Add.pm @@ -0,0 +1,51 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Add; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'add' } + +sub run { + my ($self, $cil, undef, @argv) = @_; + + CIL::Utils->ensure_interactive(); + + my $user = CIL::Utils->user($cil); + + my $issue = CIL::Issue->new('tmpname'); + $issue->Summary( join ' ', @argv ); + $issue->Status('New'); + $issue->CreatedBy( $user ); + $issue->Description("Description ..."); + + CIL::Utils->add_issue_loop($cil, undef, $issue); +} + +1; + +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Am.pm b/lib/CIL/Command/Am.pm new file mode 100644 index 0000000..1fbd749 --- /dev/null +++ b/lib/CIL/Command/Am.pm @@ -0,0 +1,131 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Am; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'am' } + +sub run { + my ($self, $cil, undef, $email_filename) = @_; + + unless ( -f $email_filename ) { + $cil->fatal("couldn't load email '$email_filename'"); + } + + my $msg_text = read_file($email_filename); + + my $email = Email::Simple->new($msg_text); + unless ( defined $email ) { + $cil->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; + } + + $cil->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 ) { + $cil->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 + CIL::Utils->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 { + $cil->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 + CIL::Utils->display_issue_full($cil, $issue); + + # then ask if the user would like to add it + $cil->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'; + + CIL::Utils->add_issue_loop($cil, undef, $issue); + } +} + +1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Attach.pm b/lib/CIL/Command/Attach.pm new file mode 100644 index 0000000..ed354c5 --- /dev/null +++ b/lib/CIL/Command/Attach.pm @@ -0,0 +1,85 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Attach; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'attach' } + +sub run { + my ($self, $cil, undef, $issue_name, $filename) = @_; + + my $issue = load_issue_fuzzy( $cil, $issue_name ); + + # check to see if the file exists + unless ( -r $filename ) { + $cil->fatal("couldn't read file '$filename'"); + } + + my $basename = basename( $filename ); + my $user = user($cil); + + my $add_attachment_text = <<"EOF"; +Filename : $basename +CreatedBy : $user + +File goes here ... this will be overwritten. +EOF + + # read in the new issue text + CIL::Utils->ensure_interactive(); + my $fh = CIL::Utils->solicit( $add_attachment_text ); + + my $attachment = CIL::Attachment->new_from_fh( 'tmp', $fh ); + unless ( defined $attachment ) { + $cil->fatal("could not create new attachment"); + } + + # now add the file itself + my $contents = read_file( $filename ); + $attachment->set_file_contents( $contents ); + + # set the size + my ($size) = (stat($filename))[7]; + $attachment->Size( $size ); + + # we've got the attachment, so let's name it + 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 + $attachment->Issue( $issue->name ); + $attachment->save($cil); + + # add the comment to the issue, update it's timestamp and save it out + $issue->add_attachment( $attachment ); + $issue->save($cil); + CIL::Utils->display_issue_full($cil, $issue); +} + +1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Comment.pm b/lib/CIL/Command/Comment.pm new file mode 100644 index 0000000..c7a8cbb --- /dev/null +++ b/lib/CIL/Command/Comment.pm @@ -0,0 +1,50 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Comment; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'comment' } + +sub run { + my ($self, $cil, undef, $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( user($cil) ); + $comment->Description("Description ..."); + + CIL::Utils->add_comment_loop($cil, undef, $issue, $comment); +} + +1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/DependsOn.pm b/lib/CIL/Command/DependsOn.pm new file mode 100644 index 0000000..11bcec0 --- /dev/null +++ b/lib/CIL/Command/DependsOn.pm @@ -0,0 +1,47 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::DependsOn; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'depends-on' } + +sub run { + my ($self, $cil, $args, $issue_name, $depends_name) = @_; + + my $issue = CIL::Utils->load_issue_fuzzy($cil, $issue_name); + my $depends = CIL::Utils->load_issue_fuzzy($cil, $depends_name); + + $issue->add_depends_on( $depends->name ); + $depends->add_precedes( $issue->name ); + + $issue->save($cil); + $depends->save($cil); +} + +1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Edit.pm b/lib/CIL/Command/Edit.pm new file mode 100644 index 0000000..9c9cd89 --- /dev/null +++ b/lib/CIL/Command/Edit.pm @@ -0,0 +1,71 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Edit; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +my $y = 'y'; + +## ---------------------------------------------------------------------------- + +sub name { 'edit' } + +sub run { + my ($self, $cil, undef, $issue_name) = @_; + + my $issue = CIL::Utils->load_issue_fuzzy( $cil, $issue_name ); + + CIL::Utils->ensure_interactive(); + + 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( $issue->name, $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 ); + + # save it + $issue->save($cil); + CIL::Utils->display_issue($cil, $issue); +} + +1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Extract.pm b/lib/CIL/Command/Extract.pm new file mode 100644 index 0000000..80b158b --- /dev/null +++ b/lib/CIL/Command/Extract.pm @@ -0,0 +1,44 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Extract; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'extract' } + +sub run { + my ($self, $cil, $args, $attachment_name) = @_; + + my $attachment = CIL::Utils->load_attachment_fuzzy($cil, $attachment_name); + + my $filename = $args->{f} || $attachment->Filename(); + write_file( $filename, $attachment->as_binary ); +} + +1; + +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Fsck.pm b/lib/CIL/Command/Fsck.pm new file mode 100644 index 0000000..e362e3e --- /dev/null +++ b/lib/CIL/Command/Fsck.pm @@ -0,0 +1,187 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Fsck; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'fsck' } + +sub run { + my ($self, $cil, $args) = @_; + + + # this looks at all the issues it can find and checks for: + # * validity + # * all the comments are there + # * all the attachments are there + # then it checks each individual comment/attachment for: + # * ToDo: validity + # * it's parent exists + + check_paths($cil); + + # find all the issues, comments and attachments + my $issues = $cil->get_issues(); + my $issue = {}; + foreach my $i ( @$issues ) { + $issue->{$i->name} = $i; + } + my $comments = $cil->get_comments(); + my $comment = {}; + foreach my $c ( @$comments ) { + $comment->{$c->name} = $c; + } + my $attachments = $cil->get_attachments(); + my $attachment = {}; + foreach my $a ( @$attachments ) { + $attachment->{$a->name} = $a; + } + + # ------ + # issues + my $errors = {}; + if ( @$issues ) { + foreach my $i ( sort { $a->Inserted cmp $b->Inserted } @$issues ) { + my $name = $i->name; + + unless ( $i->is_valid($cil) ) { + foreach ( @{ $i->errors } ) { + push @{$errors->{$name}}, $_; + } + } + + # check that all it's comments are there and that they have this parent + my $comments = $i->CommentList; + foreach my $c ( @$comments ) { + # see if this comment exists at all + if ( exists $comment->{$c} ) { + # check the parent is this issue + push @{$errors->{$name}}, "comment '$c' is listed under issue '" . $i->name . "' but does not reciprocate" + unless $comment->{$c}->Issue eq $i->name; + } + else { + push @{$errors->{$name}}, "comment '$c' listed in issue '" . $i->name . "' does not exist"; + } + } + + # check that all it's attachments are there and that they have this parent + my $attachments = $i->AttachmentList; + foreach my $a ( @$attachments ) { + # see if this attachment exists at all + if ( exists $attachment->{$a} ) { + # check the parent is this issue + push @{$errors->{$name}}, "attachment '$a' is listed under issue '" . $i->name . "' but does not reciprocate" + unless $attachment->{$a}->Issue eq $i->name; + } + else { + 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 + foreach my $c ( sort { $a->Inserted cmp $b->Inserted } @$comments ) { + # check that the parent of each comment exists + unless ( exists $issue->{$c->Issue} ) { + push @{$errors->{$c->name}}, "comment '" . $c->name . "' refers to issue '" . $c->Issue . "' but issue does not exist"; + } + } + } + print_fsck_errors('Comment', $errors); + + # ----------- + # attachments + $errors = {}; + # loop through all the attachments + if ( @$attachments ) { + # check that their parent issues exist + foreach my $a ( sort { $a->Inserted cmp $b->Inserted } @$attachments ) { + # check that the parent of each attachment exists + unless ( exists $issue->{$a->Issue} ) { + push @{$errors->{$a->name}}, "attachment '" . $a->name . "' refers to issue '" . $a->Issue . "' but issue does not exist"; + } + } + } + print_fsck_errors('Attachment', $errors); + + # ------------ + # nothing left + separator(); +} + +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"); + } + } +} + +1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Init.pm b/lib/CIL/Command/Init.pm new file mode 100644 index 0000000..e7aa6b0 --- /dev/null +++ b/lib/CIL/Command/Init.pm @@ -0,0 +1,111 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Init; + +use strict; +use warnings; +use File::Slurp qw(read_file write_file); + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'init' } + +sub run { + my ($self, $cil, $args) = @_; + + my $path = $args->{p} || '.'; # default path is right here + + # error if $path doesn't exist + unless ( -d $path ) { + CIL::Utils->fatal("path '$path' doesn't exist"); + } + + # error if issues/ already exists + my $issues_dir = "$path/issues"; + if ( -d $issues_dir ) { + CIL::Utils->fatal("issues directory '$issues_dir' already exists, not initialising issues"); + } + + # error if .cil already exists + my $config = "$path/.cil"; + if ( -f $config ) { + CIL::Utils->fatal("config file '$config' already exists, not initialising issues"); + } + + # try to create the issues/ dir + unless ( mkdir $issues_dir ) { + CIL::Utils->fatal("Couldn't create '$issues_dir' directory: $!"); + } + + # are we in a Git repository? + my $VCSconfig = ''; + if ( -d '.git' ) { + $cil->msg( 'git repository detected, setting VCS accordingly' ); + $VCSconfig = 'VCS: git'; + my $vcs = CIL::VCS::Factory->new( 'Git' ); + } + + # create a .cil file here also + if ( $args->{bare} ) { + unless ( touch $config ) { + rmdir $issues_dir; + CIL::Utils->fatal("couldn't create a '$config' file"); + } + } + else { + # write a default .cil file + write_file($config, <<"CONFIG"); +$VCSconfig +StatusStrict: 1 +StatusAllowedList: New +StatusAllowedList: InProgress +StatusAllowedList: Finished +StatusOpenList: New +StatusOpenList: InProgress +StatusClosedList: Finished +LabelStrict: 1 +LabelAllowedList: Type-Enhancement +LabelAllowedList: Type-Defect +LabelAllowedList: Priority-High +LabelAllowedList: Priority-Medium +LabelAllowedList: Priority-Low +CONFIG + } + + # add a README.txt so people know what this is about + unless ( -f "$issues_dir/README.txt" ) { + write_file("$issues_dir/README.txt", <<'README'); +This directory is used by CIL to track issues and feature requests. + +The home page for CIL is at http://kapiti.geek.nz/software/cil.html +README + } + + # $path/issues/ and $path/.cil create correctly + CIL::Utils->msg("initialised empty issue list inside '$path/'"); +} + +1; + +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/List.pm b/lib/CIL/Command/List.pm index 9842466..cdb5b03 100644 --- a/lib/CIL/Command/List.pm +++ b/lib/CIL/Command/List.pm @@ -1,28 +1,55 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + package CIL::Command::List; use strict; use warnings; -use base 'CIL::Command'; +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'list' } sub run { my ($self, $cil, $args) = @_; - $cil->check_paths; + CIL::Utils->check_paths($cil); # find all the issues my $issues = $cil->get_issues(); - $issues = $cil->filter_issues( $issues, $args ); + $issues = CIL::Utils->filter_issues( $cil, $issues, $args ); if ( @$issues ) { foreach my $issue ( sort { $a->Inserted cmp $b->Inserted } @$issues ) { - $cil->separator(); - $cil->display_issue_headers($issue); + CIL::Utils->separator(); + CIL::Utils->display_issue_headers($issue); } - $cil->separator(); + CIL::Utils->separator(); } else { - $cil->msg('no issues found'); + CIL::Utils->msg('no issues found'); } } -'end of package CIL::Command::List'; +1; + +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Precedes.pm b/lib/CIL/Command/Precedes.pm new file mode 100644 index 0000000..f28c020 --- /dev/null +++ b/lib/CIL/Command/Precedes.pm @@ -0,0 +1,48 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Precedes; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'precedes' } + +sub run { + my ($self, $cil, $args, $issue_name, $precedes_name) = @_; + + my $issue = CIL::Utils->load_issue_fuzzy($cil, $issue_name); + my $precedes = CIL::Utils->load_issue_fuzzy($cil, $precedes_name); + + $issue->add_precedes( $precedes->name ); + $precedes->add_depends_on( $issue->name ); + + $issue->save($cil); + $precedes->save($cil); +} + +1; + +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Show.pm b/lib/CIL/Command/Show.pm new file mode 100644 index 0000000..865fa3a --- /dev/null +++ b/lib/CIL/Command/Show.pm @@ -0,0 +1,42 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Show; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'show' } + +sub run { + my ($self, $cil, undef, $issue_name) = @_; + + # firstly, read the issue in + my $issue = CIL::Utils->load_issue_fuzzy( $cil, $issue_name ); + CIL::Utils->display_issue_full($cil, $issue); +} + +1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Status.pm b/lib/CIL/Command/Status.pm new file mode 100644 index 0000000..b811ec9 --- /dev/null +++ b/lib/CIL/Command/Status.pm @@ -0,0 +1,51 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Status; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'status' } + +sub run { + my ($self, $cil, undef, $issue_name, $status) = @_; + + unless ( defined $status ) { + $cil->fatal("provide a status to set this issue to"); + } + + # firstly, read the issue in + my $issue = CIL::Utils->load_issue_fuzzy( $cil, $issue_name ); + + # set the status for this issue + $issue->Status( $status ); + $issue->save($cil); + + CIL::Utils->display_issue($cil, $issue); +} + +1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Steal.pm b/lib/CIL/Command/Steal.pm new file mode 100644 index 0000000..2d2a9bb --- /dev/null +++ b/lib/CIL/Command/Steal.pm @@ -0,0 +1,47 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Steal; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'steal' } + +sub run { + my ($self, $cil, $args, $issue_name) = @_; + + # firstly, read the issue in + my $issue = CIL::Utils->load_issue_fuzzy( $cil, $issue_name ); + + # set the AssignedTo for this issue to you (because you're stealing it) + $issue->AssignedTo( user($cil) ); + $issue->save($cil); + + CIL::Utils->display_issue($cil, $issue); +} + +1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Summary.pm b/lib/CIL/Command/Summary.pm new file mode 100644 index 0000000..f5a3ab6 --- /dev/null +++ b/lib/CIL/Command/Summary.pm @@ -0,0 +1,55 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Summary; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'summary' } + +sub run { + my ($self, $cil, $args) = @_; + + CIL::Utils->check_paths($cil); + + # find all the issues + my $issues = $cil->get_issues(); + $issues = CIL::Utils->filter_issues( $cil, $issues, $args ); + if ( @$issues ) { + CIL::Utils->separator(); + foreach my $issue ( @$issues ) { + CIL::Utils->display_issue_summary($issue); + } + CIL::Utils->separator(); + } + else { + CIL::Utils->msg('no issues found'); + } +} + +1; + +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Command/Track.pm b/lib/CIL/Command/Track.pm new file mode 100644 index 0000000..fe851b5 --- /dev/null +++ b/lib/CIL/Command/Track.pm @@ -0,0 +1,54 @@ +## ---------------------------------------------------------------------------- +# cil is a Command line Issue List +# Copyright (C) 2008 Andrew Chilton +# +# This file is part of 'cil'. +# +# cil is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program. If not, see . +# +## ---------------------------------------------------------------------------- + +package CIL::Command::Track; + +use strict; +use warnings; + +use base qw(CIL::Command); + +## ---------------------------------------------------------------------------- + +sub name { 'track' } + +sub run { + my ($self, $cil, undef, $issue_name) = @_; + + CIL::Utils->fatal("the 'VCS' option in your .cil file is not set") + unless defined $cil->VCS; + + CIL::Utils->fatal("the 'VCS' option currently only supports values of 'Git'") + unless $cil->VCS eq 'Git'; + + my $issue = CIL::Utils->load_issue_fuzzy($cil, $issue_name); + + # add the issue to Git + my $issue_dir = $cil->IssueDir(); + my @files; + push @files, "$issue_dir/i_" . $issue->name . '.cil'; + push @files, map { "$issue_dir/c_${_}.cil" } @{ $issue->CommentList }; + push @files, map { "$issue_dir/a_${_}.cil" } @{ $issue->AttachmentList }; + CIL::Utils->msg("git add @files"); +} + +1; +## ---------------------------------------------------------------------------- diff --git a/lib/CIL/Utils.pm b/lib/CIL/Utils.pm index 5394e61..a740131 100644 --- a/lib/CIL/Utils.pm +++ b/lib/CIL/Utils.pm @@ -29,11 +29,13 @@ use File::Temp qw(tempfile); use Email::Find; use POSIX qw(getpgrp tcgetpgrp); use Fcntl qw(:DEFAULT :flock); +use Digest::MD5 qw(md5_hex); ## ---------------------------------------------------------------------------- # setup some globals my $editor = $ENV{EDITOR} || 'vi'; +my $y = 'y'; ## ---------------------------------------------------------------------------- @@ -124,6 +126,9 @@ sub write_cil_file { write_file($filename, $lines); } +## ---------------------------------------------------------------------------- +# input + # this method based on Term::CallEditor(v0.11)'s solicit method # original: Copyright 2004 by Jeremy Mates # copied under the terms of the GPL @@ -184,6 +189,379 @@ sub ensure_interactive { return; } +sub add_issue_loop { + my ($class, $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 = $class->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 { + $class->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); + + # should probably be run from with $cil + $cil->run_hook('issue_post_save', $issue); + + $class->display_issue($cil, $issue); + + return $issue; +} + +sub add_comment_loop { + my ($class, $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 { + $class->msg($_) foreach @{ $issue->errors }; + $edit = $class->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); + $class->display_issue_full($cil, $issue); + + return $comment; +} + +## ---------------------------------------------------------------------------- +# loading + +sub load_issue_fuzzy { + my ($class, $cil, $partial_name) = @_; + + my $issues = $cil->list_issues_fuzzy( $partial_name ); + unless ( defined $issues ) { + $class->fatal("Couldn't find any issues using '$partial_name'"); + } + + if ( @$issues > 1 ) { + $class->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 ) { + $class->fatal("Couldn't load issue '$issue_name'"); + } + + return $issue; +} + +sub load_comment_fuzzy { + my ($class, $cil, $partial_name) = @_; + + my $comments = $cil->list_comments_fuzzy( $partial_name ); + unless ( defined $comments ) { + $class->fatal("Couldn't find any comments using '$partial_name'"); + } + + if ( @$comments > 1 ) { + $class->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 ) { + $class->fatal("Couldn't load comment '$comment_name'"); + } + + return $comment; +} + +sub load_attachment_fuzzy { + my ($class, $cil, $partial_name) = @_; + + my $attachments = $cil->list_attachments_fuzzy( $partial_name ); + unless ( defined $attachments ) { + $class->fatal("Couldn't find any attachments using '$partial_name'"); + } + + if ( @$attachments > 1 ) { + $class->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 ) { + $class->fatal("Couldn't load attachment '$partial_name'"); + } + + return $attachment; +} + +## ---------------------------------------------------------------------------- +# display + +sub display_issue { + my ($class, $cil, $issue) = @_; + + $class->separator(); + $class->title( 'Issue ' . $issue->name() ); + $class->field( 'Summary', $issue->Summary() ); + $class->field( 'Status', $issue->Status() ); + $class->field( 'CreatedBy', $issue->CreatedBy() ); + $class->field( 'AssignedTo', $issue->AssignedTo() ); + $class->field( 'Label', $_ ) + foreach sort @{$issue->LabelList()}; + $class->field( 'Comment', $_ ) + foreach sort @{$issue->CommentList()}; + $class->field( 'Attachment', $_ ) + foreach sort @{$issue->AttachmentList()}; + $class->field( 'DependsOn', $_ ) + foreach sort @{$issue->DependsOnList()}; + $class->field( 'Precedes', $_ ) + foreach sort @{$issue->PrecedesList()}; + $class->field( 'Inserted', $issue->Inserted() ); + $class->field( 'Updated', $issue->Inserted() ); + $class->text('Description', $issue->Description()); + $class->separator(); +} + +sub display_issue_full { + my ($class, $cil, $issue) = @_; + + $class->separator(); + $class->title( 'Issue ' . $issue->name() ); + $class->field( 'Summary', $issue->Summary() ); + $class->field( 'Status', $issue->Status() ); + $class->field( 'CreatedBy', $issue->CreatedBy() ); + $class->field( 'AssignedTo', $issue->AssignedTo() ); + $class->field( 'Label', $_ ) + foreach sort @{$issue->Label()}; + $class->field( 'DependsOn', $_ ) + foreach sort @{$issue->DependsOnList()}; + $class->field( 'Precedes', $_ ) + foreach sort @{$issue->PrecedesList()}; + $class->field( 'Inserted', $issue->Inserted() ); + $class->field( 'Updated', $issue->Inserted() ); + $class->text('Description', $issue->Description()); + + my $comments = $cil->get_comments_for( $issue ); + foreach my $comment ( @$comments ) { + $class->display_comment( $cil, $comment ); + } + + my $attachments = $cil->get_attachments_for( $issue ); + foreach my $attachment ( @$attachments ) { + $class->display_attachment( $cil, $attachment ); + $class->msg(); + } + + $class->separator(); +} + +sub display_comment { + my ($class, $cil, $comment) = @_; + + $class->title( 'Comment ' . $comment->name() ); + $class->field( 'CreatedBy', $comment->CreatedBy() ); + $class->field( 'Inserted', $comment->Inserted() ); + $class->field( 'Updated', $comment->Inserted() ); + $class->text('Description', $comment->Description()); +} + +sub display_attachment { + my ($class, $cil, $attachment) = @_; + + $class->title( 'Attachment ' . $attachment->name() ); + $class->field( 'Filename', $attachment->Filename() ); + $class->field( 'CreatedBy', $attachment->CreatedBy() ); + $class->field( 'Inserted', $attachment->Inserted() ); + $class->field( 'Updated', $attachment->Inserted() ); +} + +sub filter_issues { + my ($class, $cil, $issues, $args) = @_; + + # don't filter if we haven't been given anything + return $issues unless defined $args; + 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'} ) { + $class->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; + + # firstly, get out the Statuses we want + if ( defined $args->{s} ) { + @new_issues = grep { $_->Status eq $args->{s} } @new_issues; + } + + # then see if we want a particular label (could be a bit nicer) + if ( defined $args->{l} ) { + my @tmp; + foreach my $issue ( @new_issues ) { + push @tmp, $issue + if grep { $_ eq $args->{l} } @{$issue->LabelList}; + } + @new_issues = @tmp; + } + + # filter out dependent on open/closed + if ( defined $args->{'is-open'} ) { + # just get the open issues + @new_issues = grep { $_->is_open($cil) } @new_issues; + } + if ( defined $args->{'is-closed'} ) { + # just get the closed issues + @new_issues = grep { $_->is_closed($cil) } @new_issues; + } + + # filter out 'created by' + if ( defined $args->{c} ) { + @new_issues = grep { $args->{c} eq $_->created_by_email } @new_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; + } + + return \@new_issues; +} + +sub separator { + my ($class) = @_; + $class->msg('=' x 79); +} + +sub msg { + my ($class, $msg) = @_; + print ( defined $msg ? $msg : '' ); + print "\n"; +} + +sub display_issue_summary { + my ($class, $issue) = @_; + + my $msg = $issue->name(); + $msg .= "\t"; + $msg .= $issue->Status(); + $msg .= "\t"; + $msg .= $issue->Summary(); + + $class->msg($msg); +} + +sub display_issue_headers { + my ($class, $issue) = @_; + + # $class->title( 'Issue ' . $issue->name() ); + $class->field( 'Summary', $issue->Summary() ); + $class->field( 'CreatedBy', $issue->CreatedBy() ); + $class->field( 'AssignedTo', $issue->AssignedTo() ); + $class->field( 'Inserted', $issue->Inserted() ); + $class->field( 'Status', $issue->Status() ); + $class->field( 'Labels', join(' ', @{$issue->LabelList()}) ); + $class->field( 'DependsOn', join(' ', @{$issue->DependsOnList()}) ); + $class->field( 'Precedes', join(' ', @{$issue->PrecedesList()}) ); +} + +sub title { + my ($class, $title) = @_; + my $msg = "--- $title "; + $msg .= '-' x (74 - length($title)); + $class->msg($msg); +} + +sub field { + my ($class, $field, $value) = @_; + my $msg = "$field"; + $msg .= " " x (12 - length($field)); + $class->msg("$msg: " . (defined $value ? $value : '') ); +} + +sub text { + my ($class, $field, $value) = @_; + $class->msg(); + $class->msg($value); + $class->msg(); +} + +## ---------------------------------------------------------------------------- +# system + +sub check_paths { + my ($class, $cil) = @_; + + # make sure an issue directory is available + unless ( $cil->dir_exists($cil->IssueDir) ) { + $class->fatal("couldn't find '" . $cil->IssueDir . "' directory"); + } +} + +sub ans { + my ($msg) = @_; + print $msg; + my $ans = ; + chomp $ans; + print "\n"; + return $ans; +} + +sub err { + my ($class, $msg) = @_; + print STDERR ( defined $msg ? $msg : '' ); + print STDERR "\n"; +} + +sub fatal { + my ($class, $msg) = @_; + chomp $msg; + print STDERR $msg, "\n"; + exit 2; +} + +## ---------------------------------------------------------------------------- +# helpers + sub extract_email_address { my ($class, $text) = @_; @@ -199,6 +577,11 @@ sub extract_email_address { return $email_address; } +sub user { + my ($class, $cil) = @_; + return $cil->UserName . ' <' . $cil->UserEmail . '>'; +} + ## ---------------------------------------------------------------------------- 1; ## ---------------------------------------------------------------------------- -- 2.39.5