From c45d363a720b2dba9b09e85908974c5674830c4b Mon Sep 17 00:00:00 2001 From: Gerhard Gonter <ggonter@gmail.com> Date: Sun, 28 Aug 2016 20:05:35 +0200 Subject: [PATCH] more functions for the Redmine CLI --- lib/Redmine/CLI.pm | 295 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 268 insertions(+), 27 deletions(-) diff --git a/lib/Redmine/CLI.pm b/lib/Redmine/CLI.pm index f0b651a..c698ba6 100644 --- a/lib/Redmine/CLI.pm +++ b/lib/Redmine/CLI.pm @@ -3,7 +3,7 @@ =head1 NAME - REdmine::CLI + Redmine::CLI =head1 DESCRIPTION @@ -15,17 +15,66 @@ package Redmine::CLI; use strict; +use Data::Dumper; +use Pod::Simple::Text; +use FileHandle; + +autoflush STDOUT 1; + use Util::JSON; use Util::Simple_CSV; use Util::Matrix; 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_home_dirs= ('etc', undef, 'bin'); +my @env_vars= qw(project_name ticket_number); +my %env_vars= map { $_ => 1 } @env_vars; + sub new { my $class= shift; @@ -34,8 +83,8 @@ sub new { # defaults 'cfg_stanza' => 'Redmine', - 'op_mode' => 'list', - 'project_name' => undef, + 'op_mode' => undef, + # 'project_name' => undef, }; my @cfg_fnm= ( @@ -51,7 +100,7 @@ sub new # print "NOTE: trying [$f] as config filen name\n"; 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; last; } @@ -99,22 +148,22 @@ sub parse_args { 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 'stanza') { $self->{cfg_stanza}= $val || shift (@ARGV); } elsif ($opt eq 'project') { $self->{project_name}= $val || shift (@ARGV); } - elsif ($opt eq 'show') { $self->{op_mode}= 'show'; } - elsif ($opt eq 'list') { $self->{op_mode}= 'list'; } + # elsif ($opt eq 'show') { $self->{op_mode}= 'show'; } + # elsif ($opt eq 'list') { $self->{op_mode}= 'list'; } # TODO: allow extra arguments - else { usage(); } + else { usage('error', "unknown option --${arg}"); exit(0); } } elsif ($arg =~ /^-(.+)/) { 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; } - else { usage(); } + else { usage('error', "unknown option -{$arg}"); } } } else @@ -123,6 +172,11 @@ sub parse_args } } + unless (defined ($self->{op_mode})) + { + $self->{op_mode}= (@PARS) ? shift (@PARS) : 'help'; + } + $self->{_pars}= \@PARS; 1; @@ -137,6 +191,17 @@ sub init $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); ($cfg, $mRM); @@ -148,17 +213,15 @@ sub main_part2 # print __LINE__, " self: ", Dumper ($self); - my ($mRM, $rm_cfg, $op_mode, $pars)= map { $self->{$_} } qw(_rm_wrapper _rm_cfg op_mode _pars); - unless (defined ($mRM)) - { - print "ATTN: Redmine::Wrapper not defined!\n"; - return undef; - } + my ($op_mode, $pars)= map { $self->{$_} } qw(op_mode _pars); + + # print "op_mode=[$op_mode]\n"; + +=begin comment # 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)) { # TODO: look up project id in Redmine itself print "ATTN: no project name found in configuration!\n"; @@ -173,20 +236,149 @@ sub main_part2 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(); - foreach my $ticket_number (@$pars) + print "ATTN: Redmine::Wrapper not defined!\n"; + 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') { my $rm= $mRM->attach(); + + my $project_name= (@$pars) ? shift (@$pars) : $self->{project_name}; + print "project_name=[$project_name]\n"; 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 @@ -266,15 +458,64 @@ sub show_issue my $rm= shift; my $ticket_number= shift; - my $issues= $rm->issue( $ticket_number ); - print "issues: ", Dumper ($issues); + my $issue= $rm->issue( $ticket_number, { include => 'children,attachments,relations,changesets,journals' } ); + print "issue: ", Dumper ($issue); + $issue; } sub usage { - system ('perldoc', __FILE__); - exit (0); + my $type= shift || 'help'; + 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; +__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 + -- GitLab