use Data::Dumper;
use CIL::Issue;
use Term::CallEditor;
+use File::Touch;
+use YAML;
+use File::Glob ':glob';
+use File::Basename;
my $COMMANDS = {
+ init => 1,
+ list => 1,
add => 1,
show => 1,
- comment => 1,
- update => 1
};
+my $new_issue_text = <<"EOF";
+[Issue]
+
+Summary =
+Status =New
+CreatedBy =$ENV{GIT_AUTHOR_NAME} <$ENV{GIT_AUTHOR_EMAIL}>
+Labels =
+Description = <<END_OF_DESCRIPTION
+
+END_OF_DESCRIPTION
+EOF
+
## ----------------------------------------------------------------------------
{
my ($command) = shift;
unless ( defined $command and exists $COMMANDS->{$command} ) {
usage();
- exit 2;
+ exit 1;
}
- if ( $command eq 'show' ) {
+ if ( $command eq 'init' ) {
+ my ($path) = @ARGV;
+ $path ||= '.';
+ init($path);
+ }
+ elsif ( $command eq 'list' ) {
+ my ($issue_name) = @ARGV;
+ list();
+
+ }
+ elsif ( $command eq 'show' ) {
my ($issue_name) = @ARGV;
show($issue_name);
}
+## ----------------------------------------------------------------------------
+# commands
+
+sub init {
+ my ($path) = @_;
+
+ # 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: $!");
+ }
+
+ # create a .cil file here also
+ unless ( touch $config ) {
+ rmdir $issues_dir;
+ fatal("couldn't create a '$config' file");
+ }
+
+ # $path/issues/ and $path/.cil create correctly
+ msg("initialised empty issue list inside '$path/'");
+}
+
+sub list {
+ check_paths();
+
+ my @issues;
+
+ # find all the issues
+ my @filenames = <issues/*.yaml>;
+ foreach my $filename ( sort @filenames ) {
+ push @issues, CIL::Issue->new_load_issue( basename($filename, '.yaml') );
+ }
+ @issues = sort { $a->Inserted cmp $b->Inserted } @issues;
+ separator();
+ foreach my $issue ( @issues ) {
+ display_issue_short($issue);
+ }
+ separator();
+}
+
sub show {
my ($issue_name) = @_;
# firstly, read the issue in
- my $issue = CIL::Issue->load_issue('2008-04-17T22:05:02.000Z');
-
+ my $issue = CIL::Issue->new_load_issue($issue_name);
unless ( defined $issue ) {
print STDERR "Couldn't load issue '$issue'\n";
return;
}
+ display_issue_full( $issue );
+}
+
+sub add {
+ my ($issue_name) = @_;
+
+ # read in the new issue text
+ my $fh = solicit( $new_issue_text );
+
+ my $issue;
+ eval {
+ $issue = CIL::Issue->new_parse_issue( $fh );
+ };
+ if ( $@ ) {
+ fatal("couldn't parse issue: $@");
+ }
+ unless ( defined $issue ) {
+ fatal("couldn't parse issue (program error)");
+ }
+
+ $issue->inserted;
+ $issue->set_no_update( 'Name', $issue->Inserted );
+ $issue->save();
+ display_issue_full( $issue );
+}
+
+## ----------------------------------------------------------------------------
+
+sub check_paths {
+ # make sure an issue directory is available
+ unless ( -d 'issues' ) {
+ fatal("couldn't find 'issues' directory");
+ }
+}
+
+## ----------------------------------------------------------------------------
+# input/output
+
+sub display_issue_short {
+ my ($issue) = @_;
+
+ title( "Issue " . $issue->Name );
+ field( 'Summary', $issue->Summary() );
+ field( 'Name', $issue->Name() );
+ field( 'CreatedBy', $issue->CreatedBy() );
+ field( 'Inserted', $issue->Inserted() );
+ field( 'Status', $issue->Status() );
+ field( 'Labels', $issue->Labels() );
+}
+
+sub display_issue_full {
+ my ($issue) = @_;
separator();
title('Details');
field( 'Summary', $issue->Summary() );
field( 'Name', $issue->Name() );
+ field( 'Status', $issue->Status() );
field( 'CreatedBy', $issue->CreatedBy() );
field( 'Inserted', $issue->Inserted() );
- field( 'Status', $issue->Status() );
+ field( 'Updated', $issue->Inserted() );
field( 'Labels', $issue->Labels() );
separator();
text('Description', $issue->Description());
separator();
}
-sub add {
- my ($issue_name) = @_;
- my $fh = solicit('FOO: please replace this text');
- die "$Term::CallEditor::errstr\n" unless $fh;
- my $text;
- $text .= $_ while <$fh>;
- separator();
- text('You said:', $text);
- separator();
-}
-
-## ----------------------------------------------------------------------------
-
-sub usage {
- print <<"END_USAGE";
-Usage: $0 <command> [options]
-
-Commands:
- add
- show <issue>
- comment <issue>
- update <issue>
-
-See <http://github.com/andychilton/cil/> for further information.
-Report bugs to <andychilton -at- gmail -dot- com>.
-END_USAGE
-}
-
-sub say {
- print $_[0], "\n";
+sub msg {
+ print ( defined $_[0] ? $_[0] : '' );
+ print "\n";
}
sub separator {
- say('=' x 79);
+ msg('=' x 79);
}
sub title {
my ($title) = @_;
my $msg = "--- $title ";
$msg .= '-' x (74 - length($title));
- say($msg);
+ msg($msg);
}
sub field {
my ($field, $value) = @_;
my $msg = "$field";
$msg .= " " x (12 - length($field));
- say("$msg: " . (defined $value ? $value : '') );
+ msg("$msg: " . (defined $value ? $value : '') );
}
sub text {
my ($field, $value) = @_;
title($field);
- say "";
- say($value);
- say "";
+ msg "";
+ msg($value);
+ msg "";
+}
+
+sub fatal {
+ my ($msg) = @_;
+ chomp $msg;
+ print STDERR $msg, "\n";
+ exit 2;
+}
+
+## ----------------------------------------------------------------------------
+# program info
+
+sub usage {
+ print <<"END_USAGE";
+Usage: $0 <command> [options]
+
+Commands:
+ init <path>
+ add
+ list
+ show <issue>
+
+See <http://kapiti.geek.nz/software/cil.html> for further information.
+Report bugs to <andychilton -at- gmail -dot- com>.
+END_USAGE
}
## ----------------------------------------------------------------------------
use strict;
use warnings;
+use Carp;
use DateTime;
use base qw(Class::Accessor);
__PACKAGE__->mk_accessors(qw(Description CreatedBy Inserted Updated));
+# override Class::Accessor's set
+sub set {
+ my ($self, $key, $value) = @_;
+ croak "provide a key name" unless defined $key;
+
+ my $orig = $self->get($key);
+
+ # get out if both are defined and they're the same
+ if ( defined $orig and defined $value ) {
+ return if $orig eq $value
+ }
+
+ # get out if neither are defined
+ if ( !defined $orig and !defined $value ) {
+ return;
+ }
+
+ # since we're actually changing the key, say we updated something
+ $self->{data}{$key} = $value;
+ $self->updated;
+}
+
+sub set_no_update {
+ my ($self, $key, $value) = @_;
+ croak "provide a key name" unless defined $key;
+ $self->{data}{$key} = $value;
+}
+
+# override Class::Accessor's get
+sub get {
+ my ($self, $key) = @_;
+ $self->{data}{$key};
+}
+
sub inserted {
my ($self) = @_;
- $self->{Inserted} = DateTime->new()->iso8601;
+ my $time = time;
+ $self->{data}{Inserted} = $time;
+ $self->{data}{Updated} = $time;
+ $self->{Changed} = '1';
}
sub updated {
my ($self) = @_;
- $self->{Updated} = DateTime->new()->iso8601;
+ my $time = time;
+ $self->{data}{Updated} = $time;
+ $self->{Changed} = '1';
}
## ----------------------------------------------------------------------------
use strict;
use warnings;
+use Carp;
use Data::Dumper;
use Config::IniFiles;
+use YAML qw(LoadFile DumpFile);
use base qw(CIL::Base);
__PACKAGE__->mk_accessors(qw(Name Summary Status Labels Comments));
+my @ATTRS = ( qw(Name Summary Description CreatedBy Status Labels Comments) );
+
## ----------------------------------------------------------------------------
-sub load_issue {
- my ($self, $name) = @_;
- return unless defined $name;
+sub new {
+ my ($proto) = @_;
+ my $class = ref $proto || $proto;
+ my $self = {};
+ bless $self, $class;
+ $self->inserted;
+ return $self;
+}
+
+sub new_load_issue {
+ my ($class, $name) = @_;
- my $filename = "issues/$name.ini";
+ unless ( defined $name ) {
+ croak "provide an issue name to load";
+ }
+
+ my $filename = "issues/$name.yaml";
+ unless ( -f $filename ) {
+ croak "filename '$filename' does no exist";
+ }
- return unless -f $filename;
my $issue = CIL::Issue->new();
- $issue->read_issue( $filename );
- $issue->Name( $name );
+ $issue->{data} = LoadFile( $filename );
return $issue;
-}
-
-sub read_issue {
- my ($self, $filename) = @_;
my $cfg = Config::IniFiles->new( -file => $filename );
+ unless ( defined $cfg ) {
+ croak("not a valid inifile");
+ }
- foreach my $attr ( qw(Summary Description) ) {
- $self->{$attr} = $cfg->val( 'Issue', $attr );
+ # my $issue = CIL::Issue->new();
+ foreach my $attr ( qw(Summary Name Description CreatedBy Status Labels Inserted Updated) ) {
+ # modify the data directly, otherwise Updated will kick in
+ $issue->{data}{$attr} = $cfg->val( 'Issue', $attr );
}
+ $issue->{data}{Comments} = [];
+
+ # set the issue Name
+ $issue->{data}{Name} = $name;
+
+ return $issue;
}
-sub reset {
- my ($self) = @_;
+sub new_parse_issue {
+ my ($class, $file) = @_;
- foreach my $attr ( qw(changed) ) {
- delete $self->{$attr};
+ # $file may be a string ($filename) or a file handle ($fh)
+ my $cfg = Config::IniFiles->new( -file => $file );
+
+ unless ( defined $cfg ) {
+ croak("not a valid inifile");
+ }
+
+ my $issue = CIL::Issue->new();
+ foreach my $attr ( qw(Summary Name Description CreatedBy Status Labels Inserted Updated) ) {
+ # modify the data directly, otherwise Updated will kick in
+ $issue->set_no_update($attr, $cfg->val( 'Issue', $attr ));
}
+ $issue->set_no_update('Comments', []);
+ return $issue;
}
-sub inserted {
+sub save {
my ($self) = @_;
- $self->{data}{inserted} = time;
+ my $name = $self->Name;
+ my $filename = "issues/$name.yaml";
+ DumpFile($filename, $self->{data});
}
-sub updated {
+sub reset {
my ($self) = @_;
- $self->{changed} = '1';
- $self->{data}{updated} = time;
+
+ foreach my $attr ( @ATTRS ) {
+ delete $self->{$attr};
+ }
}
sub add_comment {