Skip to content
Snippets Groups Projects
Commit c45d363a authored by Gerhard Gonter's avatar Gerhard Gonter :speech_balloon:
Browse files

more functions for the Redmine CLI

parent 574fcab0
Branches
No related tags found
No related merge requests found
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
=head1 NAME =head1 NAME
REdmine::CLI Redmine::CLI
=head1 DESCRIPTION =head1 DESCRIPTION
...@@ -15,17 +15,66 @@ package Redmine::CLI; ...@@ -15,17 +15,66 @@ package Redmine::CLI;
use strict; use strict;
use Data::Dumper;
use Pod::Simple::Text;
use FileHandle;
autoflush STDOUT 1;
use Util::JSON; use Util::JSON;
use Util::Simple_CSV; use Util::Simple_CSV;
use Util::Matrix; use Util::Matrix;
use Redmine::Wrapper; use Redmine::Wrapper;
use Data::Dumper; =head1 HELP TOPICS
=cut
my %HELP=
(
'topics' => <<EOPOD,
=head2 TOPICS
The following help topics are available
=over 1
=item overview
=item environment
=back
=cut
EOPOD
'overview' => <<EOPOD,
=head2 Overview
help [topic] (this overview)
list
show ticket
=cut
EOPOD
'env' => <<EOPOD,
=head2 Enivironment
=head3 default attributes
project_name
ticket_number
=cut
EOPOD
);
my $default_config_fnm= 'redmine.json'; my $default_config_fnm= 'redmine.json';
my @default_home_dirs= ('etc', undef, 'bin'); my @default_home_dirs= ('etc', undef, 'bin');
my @env_vars= qw(project_name ticket_number);
my %env_vars= map { $_ => 1 } @env_vars;
sub new sub new
{ {
my $class= shift; my $class= shift;
...@@ -34,8 +83,8 @@ sub new ...@@ -34,8 +83,8 @@ sub new
{ {
# defaults # defaults
'cfg_stanza' => 'Redmine', 'cfg_stanza' => 'Redmine',
'op_mode' => 'list', 'op_mode' => undef,
'project_name' => undef, # 'project_name' => undef,
}; };
my @cfg_fnm= ( my @cfg_fnm= (
...@@ -51,7 +100,7 @@ sub new ...@@ -51,7 +100,7 @@ sub new
# print "NOTE: trying [$f] as config filen name\n"; # print "NOTE: trying [$f] as config filen name\n";
if (-f $f) if (-f $f)
{ {
print "NOTE: picked [$f] as config filen name\n"; # print "NOTE: picked [$f] as config filen name\n";
$obj->{'cfg_fnm'}= $f; $obj->{'cfg_fnm'}= $f;
last; last;
} }
...@@ -99,22 +148,22 @@ sub parse_args ...@@ -99,22 +148,22 @@ sub parse_args
{ {
my ($opt, $val)= split ('=', $1, $2); my ($opt, $val)= split ('=', $1, $2);
if ($opt eq 'help') { usage(); } if ($opt eq 'help') { usage('help', 'usage'); exit(0); }
elsif ($opt eq 'config') { $self->{cfg_fnm}= $val || shift (@ARGV); } elsif ($opt eq 'config') { $self->{cfg_fnm}= $val || shift (@ARGV); }
elsif ($opt eq 'stanza') { $self->{cfg_stanza}= $val || shift (@ARGV); } elsif ($opt eq 'stanza') { $self->{cfg_stanza}= $val || shift (@ARGV); }
elsif ($opt eq 'project') { $self->{project_name}= $val || shift (@ARGV); } elsif ($opt eq 'project') { $self->{project_name}= $val || shift (@ARGV); }
elsif ($opt eq 'show') { $self->{op_mode}= 'show'; } # elsif ($opt eq 'show') { $self->{op_mode}= 'show'; }
elsif ($opt eq 'list') { $self->{op_mode}= 'list'; } # elsif ($opt eq 'list') { $self->{op_mode}= 'list'; }
# TODO: allow extra arguments # TODO: allow extra arguments
else { usage(); } else { usage('error', "unknown option --${arg}"); exit(0); }
} }
elsif ($arg =~ /^-(.+)/) elsif ($arg =~ /^-(.+)/)
{ {
foreach my $opt (split ('', $1)) foreach my $opt (split ('', $1))
{ {
if ($opt eq 'h') { usage(); exit (0); } if ($opt eq 'h') { usage('help', 'usage'); exit (0); exit(0); }
# elsif ($opt eq 'x') { $x_flag= 1; } # elsif ($opt eq 'x') { $x_flag= 1; }
else { usage(); } else { usage('error', "unknown option -{$arg}"); }
} }
} }
else else
...@@ -123,6 +172,11 @@ sub parse_args ...@@ -123,6 +172,11 @@ sub parse_args
} }
} }
unless (defined ($self->{op_mode}))
{
$self->{op_mode}= (@PARS) ? shift (@PARS) : 'help';
}
$self->{_pars}= \@PARS; $self->{_pars}= \@PARS;
1; 1;
...@@ -137,6 +191,17 @@ sub init ...@@ -137,6 +191,17 @@ sub init
$self->{_rm_cfg}= my $rm_cfg= $cfg->{$self->{cfg_stanza}}; $self->{_rm_cfg}= my $rm_cfg= $cfg->{$self->{cfg_stanza}};
# TODO: set defaults?
foreach my $an (@env_vars)
{
if (!defined ($self->{$an}) && exists ($rm_cfg->{$an}))
{
my $av= $self->{$an}= $rm_cfg->{$an};
# print "transcribing attribute='$an' ($av)\n";
}
}
$self->{_rm_wrapper}= my $mRM= new Redmine::Wrapper ('cfg' => $rm_cfg); $self->{_rm_wrapper}= my $mRM= new Redmine::Wrapper ('cfg' => $rm_cfg);
($cfg, $mRM); ($cfg, $mRM);
...@@ -148,17 +213,15 @@ sub main_part2 ...@@ -148,17 +213,15 @@ sub main_part2
# print __LINE__, " self: ", Dumper ($self); # print __LINE__, " self: ", Dumper ($self);
my ($mRM, $rm_cfg, $op_mode, $pars)= map { $self->{$_} } qw(_rm_wrapper _rm_cfg op_mode _pars); my ($op_mode, $pars)= map { $self->{$_} } qw(op_mode _pars);
unless (defined ($mRM))
{ # print "op_mode=[$op_mode]\n";
print "ATTN: Redmine::Wrapper not defined!\n";
return undef; =begin comment
}
# print __LINE__, " mRM: ", Dumper ($mRM); # print __LINE__, " mRM: ", Dumper ($mRM);
print "op_mode=[$op_mode]\n"; my $project_name= $self->{'project_name'} || $rm_cfg->{'project_name'};
my $project_name= $self->{'project_name'} || $rm_cfg->{'project-name'};
unless (defined ($project_name)) unless (defined ($project_name))
{ # TODO: look up project id in Redmine itself { # TODO: look up project id in Redmine itself
print "ATTN: no project name found in configuration!\n"; print "ATTN: no project name found in configuration!\n";
...@@ -173,20 +236,149 @@ sub main_part2 ...@@ -173,20 +236,149 @@ sub main_part2
print "ATTN: no project_id found in config for project_name=[$project_name]\n"; print "ATTN: no project_id found in config for project_name=[$project_name]\n";
} }
if ($op_mode eq 'show') =end comment
=cut
interpret($self, $op_mode, $pars);
}
sub interpret
{
my $self= shift;
my $op_mode= shift;
my $pars= shift;
my $mRM= $self->{_rm_wrapper};
unless (defined ($mRM))
{ {
my $rm= $mRM->attach(); print "ATTN: Redmine::Wrapper not defined!\n";
foreach my $ticket_number (@$pars) return undef;
}
if ($op_mode eq 'help') { usage('help', (@$pars) ? shift (@$pars) : 'overview'); }
elsif ($op_mode eq 'exit') { return 0; }
elsif ($op_mode eq 'interact' || $op_mode eq 'i')
{
$self->interact ();
}
elsif ($op_mode eq 'env')
{
foreach my $an (@env_vars)
{ {
Redmine::CLI::show_issue ($rm, $ticket_number); printf ("%12s = '%s'\n", $an, $self->{$an});
}
}
elsif ($op_mode eq 'set')
{
my $an= shift (@$pars);
if (exists ($env_vars{$an}))
{
$self->{$an}= join (' ', @$pars);
}
else
{
usage ('error', "unknown environment variable '$an'", 'help', 'environment');
} }
} }
elsif ($op_mode eq 'list') elsif ($op_mode eq 'list')
{ {
my $rm= $mRM->attach(); my $rm= $mRM->attach();
my $project_name= (@$pars) ? shift (@$pars) : $self->{project_name};
print "project_name=[$project_name]\n"; print "project_name=[$project_name]\n";
Redmine::CLI::show_issues ($rm, $project_name); Redmine::CLI::show_issues ($rm, $project_name);
} }
elsif ($op_mode eq 'show')
{
my $rm= $mRM->attach();
push (@$pars, $self->{ticket_number}) if (!@$pars && exists ($self->{ticket_number}));
foreach my $ticket_number (@$pars)
{
Redmine::CLI::show_issue ($rm, $ticket_number);
$self->{ticket_number}= $ticket_number;
}
}
elsif ($op_mode eq 'browse' || $op_mode eq 'display')
{
my $rm_cfg= $self->{_rm_cfg};
my $base_url= sprintf ("%s://%s/issues/", map { $rm_cfg->{$_} } qw(protocol host));
push (@$pars, $self->{ticket_number}) if (!@$pars && exists ($self->{ticket_number}));
foreach my $ticket_number (@$pars)
{
my $url= $base_url . $ticket_number;
system ('xdg-open', $url);
}
}
elsif ($op_mode eq 'parent')
{
my $rm= $mRM->attach();
my $ticket_number= (@$pars) ? shift (@$pars) : $self->{ticket_number};
print "ticket_number: $ticket_number\n";
my $issue= $rm->issue( $ticket_number );
if (defined ($issue) && exists ($issue->{issue}->{parent}))
{
print "issue: ", join (' ', sort keys %{$issue->{issue}}), "\n";
my $parent= $issue->{issue}->{parent};
print "parent issue: $parent ", Dumper ($parent);
my $parent_issue= $parent->{id};
$self->interpret ('show', [ $parent_issue ]);
}
else
{
print "no parent issue found for $ticket_number\n";
}
}
=begin comment
not needed?
elsif ($op_mode eq 'related')
{
my $rm= $mRM->attach();
my $ticket_number= (@$pars) ? shift (@$pars) : $self->{ticket_number};
print "ticket_number: $ticket_number\n";
my $issue= $rm->issue( $ticket_number, { include => 'relations,changesets' } );
if (defined ($issue))
{
print "issue: ", Dumper ($issue);
}
}
=end comment
=cut
return 1;
}
sub interact
{
my $self= shift;
my $last_line;
LINE: while (1)
{
print "rcli> ";
my $l= <STDIN>;
last unless (defined ($l));
chop ($l);
if ($l eq '.') { $l= $last_line }
elsif ($l eq '') { next LINE; }
else { $last_line= $l }
my ($op, @pars)= split (' ', $l);
print "op=[$op]\n";
my $continue= interpret ($self, $op, \@pars);
last unless ($continue);
}
} }
sub show_issues sub show_issues
...@@ -266,15 +458,64 @@ sub show_issue ...@@ -266,15 +458,64 @@ sub show_issue
my $rm= shift; my $rm= shift;
my $ticket_number= shift; my $ticket_number= shift;
my $issues= $rm->issue( $ticket_number ); my $issue= $rm->issue( $ticket_number, { include => 'children,attachments,relations,changesets,journals' } );
print "issues: ", Dumper ($issues); print "issue: ", Dumper ($issue);
$issue;
} }
sub usage sub usage
{ {
system ('perldoc', __FILE__); my $type= shift || 'help';
exit (0); my $message= shift || 'usage';
my $pod= new Pod::Simple::Text();
my $y= $pod->output_fh (*STDOUT);
# print "usage: type=[$type] message=[$message]\n";
my $was_error= 0;
while (1)
{
if ($type eq 'error')
{
$was_error= 1;
print "ERROR: ", $message, "\n\n";
}
elsif ($type eq 'help')
{
$message= 'overview' unless (exists ($HELP{$message}));
$pod->parse_string_document ($HELP{$message});
}
last unless (@_);
}
# system ('perldoc', __FILE__);
} }
1; 1;
__END__
=head1 TODOS
=over 1
=item There command line options need to be evolved
=back
=head1 AUTHOR
Gerhard Gonter E<lt>ggonter@cpan.orgE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2016 by Gerhard Gonter
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.10.0 or,
at your option, any later version of Perl 5 you may have available.
=cut
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment