X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Fbin%2Frt.in;h=b75e9e7dd69bb96e36b0509dcc19f6b381c1468a;hp=d12460b06f9ed1899bdc52c4dda2b3f2d103d562;hb=ef20b2b6b1feb47ad02b5ff7525f1a0fd11d0fa4;hpb=a513c0bef534d05f03c1242831b6f3be19b97dae diff --git a/rt/bin/rt.in b/rt/bin/rt.in index d12460b06..b75e9e7dd 100644 --- a/rt/bin/rt.in +++ b/rt/bin/rt.in @@ -3,7 +3,7 @@ # # COPYRIGHT: # -# This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC +# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) @@ -23,7 +23,9 @@ # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software -# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301 or visit their web page on the internet at +# http://www.gnu.org/copyleft/gpl.html. # # # CONTRIBUTION SUBMISSION POLICY: @@ -44,7 +46,6 @@ # those contributions and any derivatives thereof. # # END BPS TAGGED BLOCK }}} - # Designed and implemented for Best Practical Solutions, LLC by # Abhijit Menon-Sen @@ -57,6 +58,7 @@ use Cwd; use LWP; use Text::ParseWords; use HTTP::Request::Common; +use Term::ReadLine; # We derive configuration information from hardwired defaults, dotfiles, # and the RT* environment variables (in increasing order of precedence). @@ -71,7 +73,7 @@ my %config = ( debug => 0, user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME}, passwd => undef, - server => 'http://localhost/rt/', + server => 'http://localhost/', query => undef, orderby => undef, ), @@ -81,6 +83,8 @@ my %config = ( my $session = new Session("$HOME/.rt_sessions"); my $REST = "$config{server}/REST/1.0"; +my $prompt = 'rt> '; + sub whine; sub DEBUG { warn @_ if $config{debug} >= shift } @@ -113,6 +117,8 @@ my %handlers = ( link => ["link", "ln"], merge => ["merge"], grant => ["grant", "revoke"], + take => ["take", "steal", "untake"], + quit => ["quit", "exit"], ); my %actions; @@ -127,10 +133,16 @@ foreach my $fn (keys %handlers) { sub handler { my $action; + push @ARGV, 'shell' if (!@ARGV); # default to shell mode + shift @ARGV if ($ARGV[0] eq 'rt'); # ignore a leading 'rt' if (@ARGV && exists $actions{$ARGV[0]}) { $action = shift @ARGV; + $actions{$action}->($action); + } + else { + print STDERR "rt: Unknown command '@ARGV'.\n"; + print STDERR "rt: For help, run 'rt help'.\n"; } - $actions{$action || "help"}->($action || ()); } handler(); @@ -143,16 +155,13 @@ exit; sub shell { $|=1; - print "rt> "; - while (<>) { - chomp; + my $term = new Term::ReadLine 'RT CLI'; + while ( defined ($_ = $term->readline($prompt)) ) { next if /^#/ || /^\s*$/; @ARGV = shellwords($_); handler(); - print "rt> "; } - print "\n"; } sub version { @@ -163,6 +172,11 @@ sub logout { submit("$REST/logout") if defined $session->cookie; } +sub quit { + logout(); + exit; +} + my %help; sub help { my ($action, $type) = @_; @@ -268,7 +282,8 @@ sub list { whine "No $item specified."; $bad = 1; } - return help("list", $type) if $bad; + #return help("list", $type) if $bad; + return suggest_help("list", $type) if $bad; my $r = submit("$REST/search/$type", { query => $q, %data }); print $r->content; @@ -325,10 +340,18 @@ sub show { whine "No objects specified."; $bad = 1; } - return help("show", $type) if $bad; + #return help("show", $type) if $bad; + return suggest_help("show", $type) if $bad; my $r = submit("$REST/show", { id => \@objects, %data }); - print $r->content; + my $c = $r->content; + # if this isn't a text reply, remove the trailing newline so we + # don't corrupt things like tarballs when people do + # show ticket/id/attachments/id/content > foo.tar.gz + if ($r->content_type !~ /^text\//) { + chomp($c); + } + print $c; } # To create a new object, we ask the server for a form with the defaults @@ -432,18 +455,23 @@ sub edit { } @objects = ("$type/new"); } - return help($action, $type) if $bad; + #return help($action, $type) if $bad; + return suggest_help($action, $type) if $bad; # We need a form to make changes to. We usually ask the server for # one, but we can avoid that if we are fed one on STDIN, or if the # user doesn't want to edit the form by hand, and the command line - # specifies only simple variable assignments. + # specifies only simple variable assignments. We *should* get a + # form if we're creating a new ticket, so that the default values + # get filled in properly. + + my @new_objects = grep /\/new$/, @objects; if ($input) { local $/ = undef; $text = ; } - elsif ($edit || %add || %del || !$cl) { + elsif ($edit || %add || %del || !$cl || @new_objects) { my $r = submit("$REST/show", { id => \@objects, format => 'l' }); $text = $r->content; } @@ -599,7 +627,8 @@ sub comment { whine "No object specified."; $bad = 1; } - return help($action, "ticket") if $bad; + #return help($action, "ticket") if $bad; + return suggest_help($action, "ticket") if $bad; my $form = [ "", @@ -652,7 +681,7 @@ sub comment { } $data{content} = $text; - my $r = submit("$REST/ticket/comment/$id", \%data); + my $r = submit("$REST/ticket/$id/comment", \%data); print $r->content; } @@ -679,9 +708,10 @@ sub merge { whine "Too $evil arguments specified."; $bad = 1; } - return help("merge", "ticket") if $bad; + #return help("merge", "ticket") if $bad; + return suggest_help("merge", "ticket") if $bad; - my $r = submit("$REST/ticket/merge/$id[0]", {into => $id[1]}); + my $r = submit("$REST/ticket/$id[0]/merge/$id[1]"); print $r->content; } @@ -722,12 +752,51 @@ sub link { whine "Too $bad arguments specified."; $bad = 1; } - return help("link", "ticket") if $bad; + #return help("link", "ticket") if $bad; + return suggest_help("link", "ticket") if $bad; my $r = submit("$REST/ticket/link", \%data); print $r->content; } +# Take/steal a ticket +sub take { + my ($cmd) = @_; + my ($bad, %data) = (0, ()); + + my $id; + + # get the ticket id + if (@ARGV == 1) { + ($id) = @ARGV; + unless ($id =~ /^\d+$/) { + whine "Invalid ticket ID $id specified."; + $bad = 1; + } + my $form = [ + "", + [ "Ticket", "Action" ], + { + Ticket => $id, + Action => $cmd, + Status => '', + } + ]; + + my $text = Form::compose([ $form ]); + $data{content} = $text; + } + else { + $bad = @ARGV < 1 ? "few" : "many"; + whine "Too $bad arguments specified."; + $bad = 1; + } + return suggest_help("take", "ticket") if $bad; + + my $r = submit("$REST/ticket/$id/take", \%data); + print $r->content; +} + # Grant/revoke a user's rights. sub grant { @@ -839,7 +908,7 @@ sub submit { # For anything else, we just die. elsif ($res->code != 409) { warn "rt: ", $res->content; - exit; + #exit; } } } @@ -1268,17 +1337,29 @@ sub vsplit { return \@words; } +# WARN: this code is duplicated in lib/RT/Interface/REST.pm +# change both functions at once sub expand_list { my ($list) = @_; - my ($elt, @elts, %elts); - foreach $elt (split /,/, $list) { - if ($elt =~ /^(\d+)-(\d+)$/) { push @elts, ($1..$2) } - else { push @elts, $elt } + my @elts; + foreach (split /,/, $list) { + push @elts, /^(\d+)-(\d+)$/? ($1..$2): $_; } - @elts{@elts}=(); - return sort {$a<=>$b} keys %elts; + return map $_->[0], # schwartzian transform + sort { + defined $a->[1] && defined $b->[1]? + # both numbers + $a->[1] <=> $b->[1] + :!defined $a->[1] && !defined $b->[1]? + # both letters + $a->[2] cmp $b->[2] + # mix, number must be first + :defined $a->[1]? -1: 1 + } + map [ $_, (defined( /^(\d+)$/ )? $1: undef), lc($_) ], + @elts; } sub get_type_argument { @@ -1328,16 +1409,23 @@ sub is_object_spec { return; } +sub suggest_help { + my ($action, $type) = @_; + + print STDERR "rt: For help, run 'rt help $action'.\n" if defined $action; + print STDERR "rt: For help, run 'rt help $type'.\n" if defined $type; +} + __DATA__ Title: intro Title: introduction Text: - ** THIS IS AN UNSUPPORTED PREVIEW RELEASE ** - ** PLEASE REPORT BUGS TO rt-bugs@fsck.com ** + ** THIS IS AN UNSUPPORTED PREVIEW RELEASE ** + ** PLEASE REPORT BUGS TO rt-bugs@bestpractical.com ** - This is a command-line interface to RT 3. + This is a command-line interface to RT 3.0 or newer It allows you to interact with an RT server over HTTP, and offers an interface to RT's functionality that is better-suited to automation @@ -1349,9 +1437,10 @@ Text: For more information: - - rt help actions (a list of possible actions) - - rt help objects (how to specify objects) - rt help usage (syntax information) + - rt help objects (how to specify objects) + - rt help actions (a list of possible actions) + - rt help types (a list of object types) - rt help config (configuration details) - rt help examples (a few useful examples) @@ -1366,6 +1455,8 @@ Text: Syntax: rt [options] [arguments] + or + rt shell Each invocation of this program must specify an action (e.g. "edit", "create"), options to modify behaviour, and other arguments required @@ -1376,6 +1467,10 @@ Text: "rt help ". Some actions may be referred to by more than one name ("create" is the same as "new", for example). + You may also call "rt shell", which will give you an 'rt>' prompt at + which you can issue commands of the form " [options] + [arguments]". See "rt help shell" for details. + Objects are identified by a type and an ID (which can be a name or a number, depending on the type). For some actions, the object type is implied (you can only comment on tickets); for others, the user must @@ -1390,6 +1485,7 @@ Text: - rt help objects (how to specify objects) - rt help actions (a list of actions) - rt help types (a list of object types) + - rt help shell (how to use the shell) -- @@ -1673,13 +1769,15 @@ Text: -t type Specifies object type. -f a,b,c Restrict the display to the specified fields. -S var=val Submits the specified variable with the request. - + -v Verbose display Examples: rt show -t ticket -f id,subject,status 1-3 rt show ticket/3/attachments/29 rt show ticket/3/attachments/29/content rt show ticket/1-3/links + rt show ticket/3/history + rt show -v ticket/3/history rt show -t user 2 -- @@ -1740,7 +1838,7 @@ Text: rt ls -t tickets -i 'Priority > 5' | rt edit - set status=resolved rt edit ticket/4 set priority=3 owner=bar@example.com \ add cc=foo@example.com bcc=quux@example.net - rt create -t ticket subject='new ticket' priority=10 \ + rt create -t ticket set subject='new ticket' priority=10 \ add cc=foo@example.com -- @@ -1786,7 +1884,7 @@ Text: rt merge - Merges the two specified tickets. + Merges the first ticket specified into the second ticket specified. -- @@ -1861,7 +1959,11 @@ Text: Title: topics Text: - Use "rt help " for help on any of the following subjects: + Syntax: + + rt help + + Get help on any of the following subjects: - tickets, users, groups, queues. - show, edit, ls/list/search, new/create. @@ -1888,3 +1990,71 @@ Text: For the moment, please consult examples provided with each action. -- + +Title: shell +Text: + + Syntax: + + rt shell + + Opens an interactive shell, at which you can issue commands of + the form " [options] [arguments]". + + To exit the shell, type "quit" or "exit". + + Commands can be given at the shell in the same form as they would + be given at the command line without the leading 'rt' invocation. + + Example: + $ rt shell + rt> create -t ticket set subject='new' add cc=foo@example.com + # Ticket 8 created. + rt> quit + $ + +-- + +Title: take +Title: untake +Title: steal +Text: + + Syntax: + + rt + + Sets the owner of the specified ticket to the current user, + assuming said user has the bits to do so, or releases the + ticket. + + 'Take' is used on tickets which are not currently owned + (Owner: Nobody), 'steal' is used on tickets which *are* + currently owned, and 'untake' is used to "release" a ticket + (reset its Owner to Nobody). 'Take' cannot be used on + tickets which are currently owned. + + Example: + alice$ rt create -t ticket set subject="New ticket" + # Ticket 7 created. + alice$ rt take 7 + # Owner changed from Nobody to alice + alice$ su bob + bob$ rt steal 7 + # Owner changed from alice to bob + bob$ rt untake 7 + # Owner changed from bob to Nobody + +-- + +Title: quit +Title: exit +Text: + + Use "quit" or "exit" to leave the shell. Only valid within shell + mode. + + Example: + $ rt shell + rt> quit + $