#!@PERL@ -w
-# BEGIN LICENSE BLOCK
+# {{{ BEGIN BPS TAGGED BLOCK
#
-# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2004 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
#
-# (Except where explictly superceded by other copyright notices)
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
#
# This work is made available to you under the terms of Version 2 of
# the GNU General Public License. A copy of that license should have
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
-# Unless otherwise specified, all modifications, corrections or
-# extensions to this work which alter its source code become the
-# property of Best Practical Solutions, LLC when submitted for
-# inclusion in the work.
+# 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.
#
#
-# END LICENSE BLOCK
-
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# }}} END BPS TAGGED BLOCK
use strict;
# This program is intentionally written to have as few non-core module
use Cwd;
use LWP;
+use Text::ParseWords;
use HTTP::Request::Common;
# We derive configuration information from hardwired defaults, dotfiles,
user => eval{(getpwuid($<))[0]} || $ENV{USER} || $ENV{USERNAME},
passwd => undef,
server => 'http://localhost/rt/',
+ query => undef,
+ orderby => undef,
),
config_from_file($ENV{RTCONFIG} || ".rtrc"),
config_from_env()
my %handlers = (
# handler => [ ...aliases... ],
version => ["version", "ver"],
+ shell => ["shell"],
logout => ["logout"],
help => ["help", "man"],
show => ["show", "cat"],
grant => ["grant", "revoke"],
);
-# Once we find and call an appropriate handler, we're done.
-
-my (%actions, $action);
+my %actions;
foreach my $fn (keys %handlers) {
foreach my $alias (@{ $handlers{$fn} }) {
$actions{$alias} = \&{"$fn"};
}
}
-if (@ARGV && exists $actions{$ARGV[0]}) {
- $action = shift @ARGV;
+
+# Once we find and call an appropriate handler, we're done.
+
+sub handler {
+ my $action;
+
+ if (@ARGV && exists $actions{$ARGV[0]}) {
+ $action = shift @ARGV;
+ }
+ $actions{$action || "help"}->($action || ());
}
-$actions{$action || "help"}->($action || ());
+
+handler();
exit;
# Handler functions.
#
# The following subs are handlers for each entry in %actions.
+sub shell {
+ $|=1;
+ print "rt> ";
+ while (<>) {
+ chomp;
+ next if /^#/ || /^\s*$/;
+
+ @ARGV = shellwords($_);
+ handler();
+ print "rt> ";
+ }
+ print "\n";
+}
+
sub version {
print "rt $VERSION\n";
}
submit("$REST/logout") if defined $session->cookie;
}
+my %help;
sub help {
my ($action, $type) = @_;
- my (%help, $key);
+ my $key;
# What help topics do we know about?
- local $/ = undef;
- foreach my $item (@{ Form::parse(<DATA>) }) {
- my $title = $item->[2]{Title};
- my @titles = ref $title eq 'ARRAY' ? @$title : $title;
+ if (!%help) {
+ local $/ = undef;
+ foreach my $item (@{ Form::parse(<DATA>) }) {
+ my $title = $item->[2]{Title};
+ my @titles = ref $title eq 'ARRAY' ? @$title : $title;
- foreach $title (grep $_, @titles) {
- $help{$title} = $item->[2]{Text};
+ foreach $title (grep $_, @titles) {
+ $help{$title} = $item->[2]{Text};
+ }
}
}
# Displays a list of objects that match some specified condition.
sub list {
- my ($q, $type, %data, $orderby);
+ my ($q, $type, %data);
+ my $orderby = $config{orderby};
+
+ if ($config{orderby}) {
+ $data{orderby} = $config{orderby};
+ }
my $bad = 0;
while (@ARGV) {
$bad = 1, last unless get_var_argument(\%data);
}
elsif (/^-o$/) {
- $orderby = shift @ARGV;
+ $data{'orderby'} = shift @ARGV;
}
elsif (/^-([isl])$/) {
$data{format} = $1;
$bad = 1; last;
}
}
-
+ if (!defined $q) {
+ $q = $config{query};
+ }
+
$type ||= "ticket";
unless ($type && defined $q) {
my $item = $type ? "query string" : "object type";
}
return help("list", $type) if $bad;
- my $r = submit("$REST/search/$type", { query => $q, %data, orderby => $orderby || "" });
+ my $r = submit("$REST/search/$type", { query => $q, %data });
print $r->content;
}
if ($output) {
print $text;
- exit;
+ return;
}
my $synerr = 0;
}
else {
print $r->content;
- exit -1;
+ return;
}
}
print $r->content;
if (/-a/) {
unless (-f $ARGV[0] && -r $ARGV[0]) {
whine "Cannot read attachment: '$ARGV[0]'.";
- exit -1;
+ return;
}
push @files, shift @ARGV;
}
my $a = $_ eq "-b" ? \@bcc : \@cc;
@$a = split /\s*,\s*/, shift @ARGV;
}
- elsif (/-m/) { $msg = shift @ARGV }
+ elsif (/-m/) {
+ $msg = shift @ARGV;
+ if ( $msg =~ /^-$/ ) {
+ undef $msg;
+ while (<STDIN>) { $msg .= $_ }
+ }
+ }
+
elsif (/-w/) { $wtime = shift @ARGV }
}
elsif (!$id && m|^(?:ticket/)?($idlist)$|) {
Attachment => [ @files ],
TimeWorked => $wtime || '',
Text => $msg || '',
+ Status => ''
}
];
do {
my $ntext = vi($text);
- exit if ($error && $ntext eq $text);
+ return if ($error && $ntext eq $text);
$text = $ntext;
$form = Form::parse($text);
$error = 0;
goto NEXT;
}
elsif (!@$o) {
- exit;
+ return;
}
@files = @{ vsplit($k->{Attachment}) };
$bad = 1;
}
unless (exists $ltypes{lc $rel}) {
- whine "Invalid relationship '$rel' specified.";
+ whine "Invalid link '$rel' specified.";
$bad = 1;
}
%data = (id => $from, rel => $rel, to => $to, del => $del);
my ($head, $text) = split /\n\n/, $res->content, 2;
my ($status, @headers) = split /\n/, $head;
- $text =~ s/\n*$/\n/;
+ $text =~ s/\n*$/\n/ if ($text);
# "RT/3.0.1 401 Credentials required"
- if ($status !~ m#^RT/\d+(?:\.\d+)+(?:-?\w+)? (\d+) ([\w\s]+)$#) {
+ if ($status !~ m#^RT/\d+(?:\S+) (\d+) ([\w\s]+)$#) {
warn "rt: Malformed RT response from $config{server}.\n";
warn "(Rerun with RTDEBUG=3 for details.)\n" if $config{debug} < 3;
exit -1;
sub cookie {
my ($self) = @_;
my $cookie = $self->{sids}{$s}{$u};
- return defined $cookie ? "RT_SID=$cookie" : undef;
+ return defined $cookie ? "RT_SID_$cookie" : undef;
}
# Deletes the current session cookie.
my ($self, $response) = @_;
my $cookie = $response->header("Set-Cookie");
- if (defined $cookie && $cookie =~ /^RT_SID=([0-9A-Fa-f]+);/) {
+ if (defined $cookie && $cookie =~ /^RT_SID_(.[^;,\s]+=[0-9A-Fa-f]+);/) {
$self->{sids}{$s}{$u} = $1;
}
}
while (<F>) {
chomp;
next if /^$/ || /^#/;
- next unless m#^https?://[^ ]+ \w+ [0-9A-Fa-f]+$#;
+ next unless m#^https?://[^ ]+ \w+ [^;,\s]+=[0-9A-Fa-f]+$#;
my ($server, $user, $cookie) = split / /, $_;
$sids->{$server}{$user} = $cookie;
}
sub config_from_env {
my %env;
- foreach my $k ("DEBUG", "USER", "PASSWD", "SERVER") {
+ foreach my $k ("DEBUG", "USER", "PASSWD", "SERVER", "QUERY", "ORDERBY") {
if (exists $ENV{"RT$k"}) {
$env{lc $k} = $ENV{"RT$k"};
}
sub parse_config_file {
my %cfg;
my ($file) = @_;
+ local $_; # $_ may be aliased to a constant, from line 1163
open(CFG, $file) && do {
while (<CFG>) {
chomp;
next if (/^#/ || /^\s*$/);
- if (/^(user|passwd|server)\s+([^ ]+)$/) {
+ if (/^(user|passwd|server|query|orderby)\s+(.*)\s?$/) {
$cfg{$1} = $2;
}
else {
- server <URL> URL to RT server.
- user <username> RT username.
- passwd <passwd> RT user's password.
+ - query <RT Query> Default RT Query for list action
+ - orderby <order> Default RT order for list action
Blank and #-commented lines are ignored.
- RTDEBUG Numeric debug level. (Set to 3 for full logs.)
- RTCONFIG Specifies a name other than ".rtrc" for the
configuration file.
+ - RTQUERY Default RT Query for rt list
+ - RTORDERBY Default order for rt list
--
be used to specify more than one object of the same type. Note that
the list must be a single argument (i.e., no spaces). For example,
"user/root,1-3,5,7-10,ams" is a list of ten users; the same list
- can also be written as "user/ams,root,1,2,3,5,7,8-20".
+ can also be written as "user/ams,root,1,2,3,5,7,8-10".
Examples:
Syntax:
- rt link [-d] <id-A> <relationship> <id-B>
+ rt link [-d] <id-A> <link> <id-B>
Creates (or, with -d, deletes) a link between the specified tickets.
- The relationship can (irrespective of case) be any of:
+ The link can (irrespective of case) be any of:
DependsOn/DependedOnBy: A depends upon B (or vice versa).
RefersTo/ReferredToBy: A refers to B (or vice versa).
MemberOf/HasMember: A is a member of B (or vice versa).
- To view a ticket's relationships, use "rt show ticket/3/links". (See
+ To view a ticket's links, use "rt show ticket/3/links". (See
"rt help ticket" and "rt help show".)
Options: