import rt 2.0.14
[freeside.git] / rt / bin / rt
diff --git a/rt/bin/rt b/rt/bin/rt
new file mode 100755 (executable)
index 0000000..41220bb
--- /dev/null
+++ b/rt/bin/rt
@@ -0,0 +1,1391 @@
+#!!!PERL!! -w
+#
+# $Header: /home/cvs/cvsroot/freeside/rt/bin/Attic/rt,v 1.1 2002-08-12 06:17:07 ivan Exp $
+# RT is (c) 1996-2001 Jesse Vincent <jesse@bestpractical.com>
+
+use strict;
+use Carp;
+use Getopt::Long;
+
+use lib "!!RT_LIB_PATH!!";
+use lib "!!RT_ETC_PATH!!";
+
+use RT::Interface::CLI  qw(CleanEnv LoadConfig DBConnect 
+                          GetCurrentUser GetMessageContent);
+
+#Clean out all the nasties from the environment
+CleanEnv();
+
+#Load etc/config.pm and drop privs
+LoadConfig();
+
+#Connect to the database and get RT::SystemUser and RT::Nobody loaded
+DBConnect();
+
+#Drop setgid permissions
+RT::DropSetGIDPermissions();
+
+#Get the current user all loaded
+my $CurrentUser = GetCurrentUser();
+
+unless ($CurrentUser->Id) {
+       print "No RT user found. Please consult your RT administrator.\n";
+       exit(1);
+}
+
+
+# {{{ commandline flags 
+
+my ( @id,
+     @limit_queue,
+     @limit_status,
+     @limit_owner,
+     @limit_priority,
+     @limit_final_priority,
+     @limit_requestor,
+     @limit_subject,
+     @limit_body,
+     @limit_created,
+     @limit_resolved,
+     @limit_lastupdated,
+     @limit_dependson,
+     @limit_dependedonby,
+     @limit_memberof,
+     @limit_hasmember,
+     @limit_refersto,
+     @limit_referredtoby,
+     @limit_keyword,
+     
+     @limit_due,
+     @limit_starts,
+     @limit_started,
+     $limit_first,
+     $limit_rows,
+     $history,
+     $summary,
+     $create,
+     @requestors,
+     @cc,
+     @admincc,
+     $status,
+     $subject,
+     $owner,
+     $steal,
+     $queue,
+     $time_left,
+     $priority,
+     $final_priority,
+     $due,
+     $starts,
+     $started,
+     $contacted,
+     $comment,
+     $reply,
+     $source,
+     $edit,
+     @dependson,
+     @memberof, 
+     @refersto,
+     $mergeinto,
+     @keywords,
+     $time_taken,
+     $verbose,
+     $debug,
+   $help,
+   $version);
+
+# }}}
+
+# Set defaults for cli args
+
+$edit = 1; # Assume the user wants to edit replies and comments 
+           # unless they specify --noedit
+
+# {{{    args
+
+my @args =("id=s" => \@id,
+          "limit-queue=s" => \@limit_queue,
+          "limit-status=s" => \@limit_status,
+          "limit-owner=s" => \@limit_owner,
+          "limit-priority=s" => \@limit_priority,
+          "limit-final-priority=s" => \@limit_final_priority,
+          "limit-requestor=s" => \@limit_requestor,
+          "limit-subject=s" => \@limit_subject,
+          "limit-body=s",      \@limit_body,
+          "limit-created=s" => \@limit_created,
+          "limit-due=s" =>     \@limit_due,
+          "limit-last-updated=s" => \@limit_lastupdated,
+          "limit-keyword=s" => \@limit_keyword,
+
+          "limit-member-of=s" => \@limit_memberof,
+          "limit-has-member=s" => \@limit_hasmember,
+          "limit-depended-on-by=s" => \@limit_dependedonby,
+          "limit-depends-on=s" => \@limit_dependson,
+          "limit-referred-to-by=s" => \@limit_referredtoby,
+          "limit-refers-to=s" => \@limit_refersto,
+
+          "limit-starts=s" => \@limit_starts,
+          "limit-started=s" => \@limit_started,
+          "limit-first=i" => \$limit_first,
+          "limit-rows=i" => \$limit_rows,
+          "history|show" => \$history,
+          "summary:s" => \$summary,
+          "create" => \$create,
+          "keywords=s" => \@keywords,
+          "requestor|requestors=s" => \@requestors,
+          "cc=s" => \@cc,
+          "admincc=s" => \@admincc,
+          "status=s" => \$status,
+          "subject=s" => \$subject,
+          "owner=s" => \$owner,
+          "steal" => \$steal,
+          "queue=s" => \$queue,
+
+          
+          "priority=i" => \$priority,
+          "final-priority=i" => \$final_priority,
+          "due=s" => \$due,
+          "starts=s" => \$starts,
+          "started=s" => \$started,
+          "contacted=s" => \$contacted,
+          "comment", \$comment,
+          "reply|respond", \$reply,
+          "source=s" => \$source,
+          "edit!" => \$edit,
+          "depends-on=s" => \@dependson,
+          "member-of=s" => \@memberof, 
+          "merge-into=s" => \$mergeinto, 
+          "refers-to=s" => \@refersto,
+          "time-left=i" => \$time_left,
+          "time-taken=i" => \$time_taken,
+          "verbose+" => \$verbose,
+          "debug" => \$debug,
+          "version" => \$version,
+          "help|h|usage" => \$help
+         );
+
+# }}}
+
+
+
+GetOptions(@args);
+
+print join(':',@keywords);
+# {{{ If they want it, print a usage message and get out
+
+if ($help) {
+
+
+print <<EOUSAGE;
+
+Limit the set of records returned:
+
+--id=[first][-][last]
+  Specify a single ticket, a range, or to start with (n-) or end with (-n)
+a specific ticket.
+  
+  --limit-queue=<queue>
+         --limit-status=[!](new|open|stalled|resolved)
+
+         --limit-owner=[!]<userid>
+         --limit-priority=[starts][-][ends]
+         --limit-final-priority=[starts][-][ends]
+           starts is less than ends
+         --limit-requestor=[!]<userid>|<email>
+         --limit-subject=[!]<text>
+         --limit-body=[!]<text>
+         --limit-keyword=[!]<select>/<keyword>
+        
+       Links
+          --limit-member-of=<ticketid>
+          --limit-has-member=<ticketid>
+          --limit-refers-to=<ticketid>
+          --limit-referred-to-by=<ticketid>
+          --limit-depends-on=<ticketid>
+          --limit-depended-on-by=<ticketid>
+
+
+       Dates
+         --limit-created=[starts][-][ends]
+         --limit-due=[starts][-][ends]
+         --limit-starts=[starts][-][ends]
+         --limit-started=[starts][-][ends]
+          --limit-resolved=[starts][-][ends]
+          --limit-last-updated=[starts][-][ends]
+           starts and ends are dates.  starts can not be less than ends
+
+         --limit-first=<first row returned>
+         --limit-rows=<row count>
+
+         --history | --show
+            show a history of the tickets found
+
+         --summary [format-string]
+             show a listing-style summary of the tickets found. If format string
+             is ommitted, uses \$RT_SUMMARY_FORMAT or an internal default
+            
+
+             #TODO: doc summary 
+             format: <atom>%<format>
+             atom:   <name><size>
+             size: <integer>
+             name:  (grep for # {{{ attribs for the array of ok values)
+
+
+         --create
+            create a new ticket. Any attributes that you can modify on an existing ticket
+            can also be used for ticket creation.
+
+
+
+Attributes
+  Basics
+         --status=<new|open|stalled|resolved|dead>
+           sets status
+          --subject=<subject>
+           sets subject
+          --owner=<userid>
+           set owner to 
+           --steal
+           Become the owner, even if someone else owns the ticket
+          --queue=<queueid>
+           set queue to
+          
+          --priority=<int>
+         
+           --final-priority=<int>
+
+  Watchers
+         --requestors=[+|-]<userid|email address>
+          add or remove this user as a ticket requestor 
+         --cc=[+|-]<userid|email address>
+          add or remove this user as a ticket cc
+         --admincc=[+|-]<userid|email address>
+          add or remove this user as a ticket admincc
+
+       (When creating tickets, just leave off the + or - )
+
+  Keywords
+         --keywords[+|-]<keyword_select>/<keyword>
+          Add or remove a keyword.
+
+
+
+  Dates
+          --due=<date>
+          --starts=<date>
+          --started=<date>
+          --contacted=<date>
+
+          --time-left=<int>
+            
+          --time-taken=<int>
+
+
+   Link related manipulation:
+
+          --depends-on=[+|-]<ticketid>
+          --member-of=[+|-]<ticketid>
+          --refers-to=[+|-]<ticketid>
+           --merge-into=<ticketid>
+
+Comments and replies
+
+          --comment
+          --reply|respond
+            --source <path>
+                Specify the path to the source file for this ticket update
+
+             --noedit
+                Don't invoke \$EDITOR to edit the content of this update
+
+
+
+
+   Condiments
+
+          --verbose
+          --debug
+          --version
+          --help|h|usage
+             You're reading it.
+
+EOUSAGE
+
+    exit(0);
+}
+
+# Print version, and leave
+if ($version) {
+       print "RT $RT::VERSION for $RT::rtname. Copyright 1996-2001 Jesse Vincent <jesse\@fsck.com>\n";
+       exit(0);
+}
+
+# }}}
+
+# {{{ Validate any options that were passed in. normalize them.
+
+#if a queue was specified
+if ($queue) {
+    # make sure that $queue is a valid queue and load it into $queue_obj
+}
+
+#For each date in: $due, $starts, $started
+
+# load up an RT::Date object and parse it into a normalized form
+# if it can't parse it, log an error and null out the variable
+
+# }}}
+
+# {{{ Check if we're creating, if so, create the ticket and be done
+
+if ($create) {
+    $RT::Logger->debug("Creating a new ticket");
+
+    #Make sure the current user can create tickets in this queue
+    
+    #Make sure that the owner specified can own tickets in this queue
+
+
+           
+    my $linesref = GetMessageContent( Edit => $edit, Source => $source,
+                                     CurrentUser => $CurrentUser
+                                   );
+    
+    require MIME::Entity;
+    my $MIMEObj;
+    
+    if ($linesref) {
+       $MIMEObj = MIME::Entity->build(Data => $linesref);
+    }  
+    
+    use RT::Ticket;
+    my $Ticket=new RT::Ticket($CurrentUser);
+    my ($ticket, $trans, $msg) =
+      $Ticket->Create(Queue => $queue,
+                     Owner => $owner,
+                     Status => $status || 'new' ,
+                     Subject => $subject,
+                     Requestor => \@requestors,
+                     Cc => \@cc,
+                     AdminCc => \@admincc,
+                     Due => $due,
+                     Starts => $starts,
+                     Started => $started,
+                     TimeLeft => $time_left,
+                     InitialPriority => $priority,
+                     FinalPriority => $final_priority,
+                     MIMEObj => $MIMEObj
+                    );
+    print $msg . "\n";
+}
+
+# }}}
+
+else {
+    #Apply restrictions
+    use RT::Tickets;
+    my $Tickets = new RT::Tickets($CurrentUser);
+    
+    # {{{ Limit our search
+    my $value;                 #to use when iterating through restrictions
+    my $queue_id;              #to use when limiting by keyword
+    
+    # {{{ limit on id
+
+    foreach $value (@id) {
+       if ($value =~ /^(\d+)$/) {
+           $Tickets->LimitId ( VALUE => $1,
+                               OPERATOR => '=');
+       }       
+       elsif ($value =~ /^(\d*)\D?(\d*)$/) {
+           my $start = $1;
+           my $end = $2;
+           $Tickets->LimitId(
+                             VALUE => "$start",
+                             OPERATOR => '>=') if ($start);
+           $Tickets->LimitId(
+                             VALUE => "$end",
+                             OPERATOR => '<=') if ($end);
+       }       
+    }
+
+
+    # }}}
+    
+    # {{{ limit on status
+
+    foreach $value (@limit_status) {
+       if ($value =~ /^(=|!=|!|)(.*)$/) {
+           my $op = $1;
+           my $val = $2;
+                
+
+           $op = ParseBooleanOp($op);
+           $Tickets->LimitStatus(VALUE => "$val",
+                                 OPERATOR => "$op");
+       }       
+    }
+
+    # }}}
+
+
+
+    # {{{ limit on queue
+    foreach $value (@limit_queue) {
+       if ($value =~ /^(\W?)(.*?)$/i) {
+           my $op = $1;
+           my $val = $2;
+               
+           $op = ParseBooleanOp($op);
+
+           my $queue_obj = new RT::Queue($RT::SystemUser);
+               
+           unless ($queue_obj->Load($val)) {
+               $RT::Logger->debug("Queue '$val' not found");
+               print STDERR "Queue '$val' not found\n";        
+               exit(-1);
+           }
+           $RT::Logger->debug ("Limiting queue to $op ".$queue_obj->Name);
+           $Tickets->LimitQueue(VALUE => $queue_obj->Name,
+                                OPERATOR => $op);
+           $queue_id=$queue_obj->id;
+       }       
+    }  
+
+    # {{{ limit on keyword
+    foreach $value (@limit_keyword) {
+       if ($value =~ /^(\W?)(.*?)\/(.*)$/i) {
+           my $op = $1;
+           my $select = $2;
+           my $keyword = $3;
+
+           $op = ParseBooleanOp($op);
+
+           # load the keyword select
+           my $keyselect = RT::KeywordSelect->new($RT::SystemUser);
+           unless ($keyselect->LoadByName(Name=>$select, Queue=>$queue_id)) {
+               $RT::Logger->debug("KeywordSelect '$select' not found");
+               print STDERR "KeywordSelect '$select' not fount\n";
+               exit(-1);
+           }
+
+           # load the keyword
+           my $k = RT::Keyword->new($RT::SystemUser);
+           unless ($k->LoadByNameAndParentId($keyword, $keyselect->Keyword)) {
+               $RT::Logger->debug("Keyword '$keyword' not found");
+               print STDERR "Keyword '$keyword' not found\n";
+               exit(-1);
+           }
+           $Tickets->LimitKeyword(OPERATOR => $op,
+                                  KEYWORDSELECT => $keyselect->id,
+                                  KEYWORD => $k->id);
+           $RT::Logger->debug ("Limiting keyword to $op ".$k->Path);
+       }
+    }
+    # }}}
+    # {{{ limit on owner
+    foreach $value (@limit_owner) {
+       if ($value =~ /^(\W?)(.*?)$/i) {
+           my $op = $1;
+           my $val = $2;
+               
+           $op = ParseBooleanOp($op);
+
+           my $user_obj = new RT::User($RT::SystemUser);
+               
+           unless ($user_obj->Load($val)) {
+               $RT::Logger->debug("User '$val' not found");
+               print STDERR "User '$val' not found\n"; 
+               exit(-1);
+           }
+           $val = $user_obj->id();
+               
+           $RT::Logger->debug ("Limiting owner to $op $val");
+           $Tickets->LimitOwner(VALUE => "$val",
+                                OPERATOR => "$op");
+       }       
+    }  
+    # }}}
+    # {{{ limt on priority
+
+    foreach $value (@limit_priority) {
+       my ($start, $end) = ParseRange($value);
+       if ($start == $end) {
+           $Tickets->LimitPriority( VALUE => $start,
+                                    OPERATOR => '=');
+       } elsif ($start) {
+           $Tickets->LimitPriority( VALUE => $start,
+                                    OPERATOR => '>=');
+       } elsif ($end) {
+           $Tickets->LimitPriority( VALUE => $end,
+                                    OPERATOR => '<=');
+       }       
+           
+    }
+    foreach $value (@limit_final_priority) {
+       my ($start, $end) = ParseRange($value);
+       if ($start == $end) {
+           $Tickets->LimitFinalPriority( VALUE => $start,
+                                         OPERATOR => '=');
+       } elsif ($start) {
+           $Tickets->LimitFinalPriority( VALUE => $start,
+                                         OPERATOR => '>=');
+       } elsif ($end) {
+           $Tickets->LimitFinalPriority( VALUE => $end,
+                                         OPERATOR => '<=');
+       }       
+    }
+    # }}}
+
+    foreach $value (@limit_requestor) {
+       if ($value =~ /^(\W?)(.*?)$/i) {
+           my $op = $1;
+           my $val = $2;
+               
+           $op = ParseBooleanOp($op);
+           $Tickets->LimitRequestor(VALUE => $val,
+                                    OPERATOR => $op );
+       }
+           
+    }
+    foreach $value (@limit_subject) {
+       
+       if ($value =~ /^(\W?)(.*?)$/i) {
+           my $op = $1;
+           my $val = $2;
+           
+           $op = ParseLikeOp($op);
+           
+           $Tickets->LimitSubject(VALUE => $val,
+                                  OPERATOR => $op );
+           }
+    }
+    
+    foreach $value (@limit_body) {
+       if ($value =~ /^(\W?)(.*?)$/i) {
+           my $op = $1;
+           my $val = $2;
+           
+           $op = ParseLikeOp($op);
+           
+               $Tickets->LimitBody(VALUE => $val,
+                                   OPERATOR => $op );
+       }       
+       
+    }
+    
+    
+    
+    # Dates
+    foreach my $date (@limit_created) {
+       my ($start, $end) = ParseDateRange($date);
+       $Tickets->LimitCreated ( VALUE => $start,
+                                OPERATOR => '>=' ) if ($start);
+       $Tickets->LimitCreated ( VALUE => $end,
+                                OPERATOR => '<=' ) if ($end);
+    }
+
+    foreach my $date (@limit_due) {
+       my ($start, $end) = ParseDateRange($date);
+       $Tickets->LimitDue ( VALUE => $start,
+                                OPERATOR => '>=' ) if ($start);
+       $Tickets->LimitDue ( VALUE => $end,
+                                OPERATOR => '<=' ) if ($end);
+    }
+
+    foreach my $date (@limit_starts) {
+       my ($start, $end) = ParseDateRange($date);
+       $Tickets->LimitStarts ( VALUE => $start,
+                                OPERATOR => '>=' ) if ($start);
+       $Tickets->LimitStarts ( VALUE => $end,
+                                OPERATOR => '<=' ) if ($end);
+    }
+
+    foreach my $date (@limit_started) {
+       my ($start, $end) = ParseDateRange($date);
+       $Tickets->LimitStarted ( VALUE => $start,
+                                OPERATOR => '>=' ) if ($start);
+       $Tickets->LimitStarted ( VALUE => $end,
+                                OPERATOR => '<=' ) if ($end);
+    }
+
+    foreach my $date (@limit_resolved) {
+       my ($start, $end) = ParseDateRange($date);
+       $Tickets->LimitResolved ( VALUE => $start,
+                                OPERATOR => '>=' ) if ($start);
+       $Tickets->LimitResolved ( VALUE => $end,
+                                OPERATOR => '<=' ) if ($end);
+    }
+
+    foreach my $date (@limit_lastupdated) {
+       my ($start, $end) = ParseDateRange($date);
+       $Tickets->LimitLastUpdated( VALUE => $start,
+                                OPERATOR => '>=' ) if ($start);
+       $Tickets->LimitLastUpdated ( VALUE => $end,
+                                OPERATOR => '<=' ) if ($end);
+    }
+
+    foreach my $link (@limit_memberof) {
+       $Tickets->LimitMemberOf($link);
+    }  
+
+    foreach my $link (@limit_hasmember) {
+       $Tickets->LimitHasMember($link);
+    }  
+
+    foreach my $link (@limit_dependson) {
+       $Tickets->LimitDependsOn($link);
+    }  
+
+    foreach my $link (@limit_dependedonby) {
+       $Tickets->LimitDependedOnBy($link);
+    }
+    foreach my $link (@limit_refersto) {
+       $Tickets->LimitRefersTo($link);
+    }  
+    
+    foreach my $link (@limit_referredtoby) {
+       $Tickets->LimitReferredToBy($link);
+    }  
+
+    
+    if ($limit_first){
+    }
+    if ($limit_rows){
+    }
+
+# }}}
+    
+    # {{{ Iterate through all tickets we found
+
+
+    my ($format, $titles, $code);
+    
+    #Set up the summary format if we need to
+    if (defined $summary) {
+       my $format_string = $summary || $ENV{'RT_SUMMARY_FORMAT'} || "%id4%status4%queue7%subject40%requestor16";
+
+       ($format, $titles, $code) = BuildListingFormat($format_string);
+        printf "$format\n", eval "$titles";
+   }   
+
+
+    while (my $Ticket = $Tickets->Next()) {
+       $RT::Logger->debug ("Now working on ticket ". $Ticket->id);
+    
+       #Run through all the ticket modifications we might want to do
+       #TODO: these are all insufficiently lazy and should be replaced with some 
+       # nice foreaches.
+
+
+       # {{{ deal with watchers
+       
+       # add / delete requestors
+       foreach $value (@requestors) {
+           if ($value =~ /^(\W?)(.*)$/) {
+               my $op = $1;
+               my $addr = $2;
+               
+               $Ticket->AddRequestor(Email => $addr) if ($op eq '+');
+               $Ticket->DeleteRequestor( $addr) if ($op eq '-');
+           }   
+       }
+       
+       # add / delete ccs
+       foreach $value (@cc) {
+           if ($value =~ /^(\W?)(.*)$/) {
+               my $op = $1;
+               my $addr = $2;
+               $Ticket->AddCc(Email => $addr) if ($op eq '+');
+               $Ticket->DeleteCc($addr) if ($op eq '-');
+           }   
+       }       
+       
+       # add / delete adminccs
+        $RT::Logger->debug("Looking at admin ccs");
+       foreach $value (@admincc) {
+           if ($value =~ /^(\W?)(.*)$/) {
+               my $op = $1;
+               my $addr = $2;
+               $Ticket->AddAdminCc(Email => $addr) if ($op eq '+');
+               $Ticket->DeleteAdminCc($addr) if ($op eq '-');
+           }   
+       }       
+
+       # }}}
+       
+       # {{{ Deal with ticket keywords
+
+       my $KeywordSelects = $Ticket->QueueObj->KeywordSelects();
+        $RT::Logger->debug ("Looking at keywords");
+       foreach $value (@keywords) {
+           $RT::Logger->debug("Looking at --keyword=$value");
+           if ($value =~ /^(\W?)(.*?)\/(.*)$/) {
+               my $op = $1;
+               my $select = $2;
+               my $keyword = $3;
+               
+               $RT::Logger->debug("Going to $op Keyword $select / $keyword");  
+               while (my $ks = $KeywordSelects->Next) {
+                    $RT::Logger->debug("$select is select ".$ks->Name." is found");
+                   next unless ($ks->Name =~ /$select/i);
+                   $RT::Logger->debug ("Found a match for $select\n"); 
+                   my $kids = $ks->KeywordObj->Descendents;
+    
+                    my ($kid);
+                   foreach $kid (keys %{$kids}) {
+                        $RT::Logger->debug("Now comparing $keyword with ".$kids->{$kid}. "\n");
+                       next unless ($kids->{$kid} =~ /^$keyword$/i);
+                       $RT::Logger->debug("Going to $op $select / $keyword (".$kids->{$kid} .")");     
+                       $Ticket->DeleteKeyword(KeywordSelect => $ks->id,
+                                           Keyword => $kid) if ($op eq '-');
+                       
+                       $Ticket->AddKeyword(KeywordSelect => $ks->id,
+                                           Keyword => $kid) if ($op eq '+');
+                   }
+                   
+               }
+           }
+       }
+       # }}}
+       
+       # {{{ deal with links
+
+       # Deal with merging {
+       if ($mergeinto) {
+               my ($trans, $msg) =$Ticket->MergeInto($mergeinto);
+               print $msg."\n";
+       }       
+       # add /delete depends-ons
+
+       foreach my $value (@dependson) {
+           if ($value =~ /^(\W?)(.*)$/) {
+               my $op = $1;
+               my $ticket = $2;
+               if (!$op or ($op eq '+')) {
+                   my ($trans, $msg) =
+                     $Ticket->AddLink(Type => 'DependsOn', Target => $ticket);
+                   print $msg."\n";
+               }
+               elsif ($op eq '-') {
+                   my ($trans, $msg) = 
+                     $Ticket->DeleteLink(Type => 'DependsOn', Target => $ticket);
+                   print $msg."\n";
+               }
+
+           }
+       }
+       # add /delete member-of
+       foreach my $value (@memberof) {
+           if ($value =~ /^(\W?)(.*)$/) {
+               my $op = $1;
+               my $ticket = $2;
+               if ($op eq '+') {
+                   my ($trans, $msg) =
+                     $Ticket->AddLink(Type => 'MemberOf', Target => $ticket);
+                   print $msg;
+               }
+               elsif ($op eq '-') {
+                   my ($trans, $msg) = 
+                     $Ticket->DeleteLink(Type => 'MemberOf', Target => $ticket);
+                   print $msg;
+               }
+
+           }
+       }       
+       # add / delete refers-to
+               foreach my $value (@refersto) {
+           if ($value =~ /^(\W?)(.*)$/) {
+               my $op = $1;
+               my $ticket = $2;
+               if ($op eq '+') {
+                   my ($trans, $msg) =
+                     $Ticket->AddLink(Type => 'RefersTo', Target => $ticket);
+                   print $msg;
+               }
+               elsif ($op eq '-') {
+                   my ($trans, $msg) = 
+                     $Ticket->DeleteLink(Type => 'RefersTo', Target => $ticket);
+                   print $msg;
+               }
+
+           }
+       }
+
+       # }}}
+       
+       # {{{ deal with dates
+       
+       #set due 
+       if ($due) {
+           my $iso = ParseDateToISO($due);
+           if ($iso) {
+               $RT::Logger->debug("Setting due date to $iso ($due)");
+               my ($trans, $msg) = 
+                 $Ticket->SetDue($iso);
+               print $msg;
+           }
+           else {
+               print "Due date '$due' could not be parsed";
+           }
+       }
+
+       #set starts
+       if ($starts) {
+           my $iso = ParseDateToISO($due);
+           if ($iso) {
+               my ($trans, $msg) = 
+                 $Ticket->SetStarts($iso);
+               print $msg."\n";
+           }
+           else {
+               print "Starts date '$starts' could not be parsed";
+           }
+       }
+       #set started
+               if ($started) {
+           my $iso = ParseDateToISO($started);
+           if ($iso) {
+               my ($trans, $msg) = 
+                 $Ticket->SetStarted($iso);
+               print $msg."\n";
+           }
+           else {
+               print "Started date '$started' could not be parsed";
+           }
+       }
+       #set contacted
+               if ($contacted) {
+           my $iso = ParseDateToISO($contacted);
+           if ($iso) {
+               my ($trans, $msg) = 
+                 $Ticket->SetContacted($iso);
+               print $msg."\n";
+           }
+           else {
+               print "Contacted date '$contacted' could not be parsed";
+           }
+       }
+
+    # }}}
+       
+       # {{{ set other attributes
+
+       #Set subject
+       if ($subject) {
+           my ($trans, $msg) = $Ticket->SetSubject($subject);
+           print $msg."\n";
+       }
+       
+       #Set priority
+       if ($priority) {
+           my ($trans, $msg) = 
+             $Ticket->SetPriority($priority);
+           print $msg."\n";
+       }
+       
+       #Set final priority
+       if ($final_priority) {
+           my ($trans, $msg) =
+             $Ticket->SetFinalPriority($final_priority);
+           print $msg."\n";
+       }
+
+       #Set status
+       if ($status) {
+           my ($trans, $msg) = 
+             $Ticket->SetStatus($status);
+           print $msg."\n";
+       }
+       
+       #Set time left
+       if ($time_left) {
+           my ($trans, $msg) = 
+             $Ticket->SetTimeLeft($time_left);
+           print $msg."\n";
+       }
+
+       #Set time_taken 
+       if ($time_taken) {
+           my ($trans, $msg) = 
+             $Ticket->SetTimeTaken($time_taken);
+           print $msg."\n";
+       }
+       
+       #Set owner
+       if ($owner) {
+           my ($trans, $msg) =
+             $Ticket->SetOwner($owner);
+           print $msg."\n";
+       }
+
+        # Steal
+        if ($steal) {
+                my ($trans, $msg) =
+                 $Ticket->Steal();
+                 print $msg . "\n";
+        }
+       #Set queue 
+       if ($queue) {
+           my ($trans, $msg) = 
+             $Ticket->SetQueue($queue);
+           print $msg."\n";
+       }
+
+    # }}}
+       
+
+
+       # {{{ Perform ticket comments/replies
+       if ($reply) {
+           $RT::Logger->debug("Replying to ticket ".$Ticket->Id);
+           
+           my $linesref = GetMessageContent( Edit => $edit, Source => $source,
+                                            CurrentUser => $CurrentUser
+                                          );
+           
+           #TODO build this entity
+           require MIME::Entity;
+           my $MIMEObj = MIME::Entity->build(Data => $linesref);
+           
+           $Ticket->Correspond( MIMEObj => $MIMEObj ,
+                                TimeTaken => $time_taken);
+       }       
+       
+       elsif ($comment) {
+           $RT::Logger->debug("Commenting on ticket ".$Ticket->Id);
+       
+           my $linesref =GetMessageContent(Edit => $edit, Source => $source,
+                                           CurrentUser => $CurrentUser);
+           #TODO build this entity
+           require MIME::Entity;
+           my $MIMEObj = MIME::Entity->build(Data => $linesref);
+           
+           $Ticket->Comment( MIMEObj => $MIMEObj,
+                             TimeTaken => $time_taken);
+       }
+
+    # }}}
+       
+       # {{{ Display whatever we need to display
+
+       # {{{ Display a full ticket listing and history
+       if ($history) {
+           #Display the history
+           $RT::Logger->debug("Show history for ".$Ticket->id);
+           
+           if ($Ticket->CurrentUserHasRight("ShowTicket")) {
+               &ShowSummary($Ticket);
+               print "\n";
+               &ShowHistory($Ticket);
+           }
+           else {
+               print "You don't have permission to view that ticket.\n";
+           }
+       }       
+
+       # }}}
+       
+       # {{{ Display a summary if we need to
+       if (defined $summary) {
+           $RT::Logger->debug ("Show ticket summary with format $format");
+           
+           printf $format."\n", eval $code;
+           
+       }       
+       # }}}
+
+       # }}}
+       
+    }
+
+    # }}}
+    
+}
+
+
+$RT::Handle->Disconnect();
+
+
+
+
+
+
+
+# {{{ sub ParseBooleanOp
+
+=head2 ParseBooleanOp
+
+  Takes an option modifier. returns the apropriate SQL operator.
+  If it's handed ! or -, returns !=.  Otherwise returns =.
+
+=cut
+
+sub ParseBooleanOp {
+    
+    my $op = shift;
+    
+    #so that !new limits to not new, etc
+    if ($op =~ /^(\!|-)/) {
+       $op = "!=";
+    }
+    else {
+       $op = "=";
+    }
+    
+    return($op);
+}
+
+# }}}
+
+# {{{ sub ParseLikeOp
+=head2 ParseLikeOp
+
+  Takes an option modifier. returns the apropriate SQL operator.
+  If it's handed ! or -, returns NOT  LIKE.  Otherwise returns LIKE
+
+=cut
+
+sub ParseLikeOp {
+    
+    my $op = shift;
+    
+    #so that !new limits to not new, etc
+    if ($op =~ /^(\!|-)/) {
+       $op = "NOT LIKE";
+    }
+    else {
+       $op = "LIKE";
+    }
+    
+    return($op);
+}
+# }}}
+
+# {{{ sub ParseDateToISO
+
+=head2 ParseDateToISO
+
+Takes a date in an arbitrary format.
+Returns an ISO date and time in GMT
+
+=cut
+
+sub ParseDateToISO {
+    my $date = shift;
+
+       my $date_obj = new RT::Date($CurrentUser);
+       $date_obj->Set( Format => 'unknown',
+                       Value => $date
+                     );
+       return ($date_obj->ISO);
+}
+
+# }}}
+
+# {{{ sub ParseDateRange
+
+=head2 ParseDateRange [RANGE]
+
+Takes a range of dates of the form [<date>][-][<date>] and returns 
+starting and ending dates (as ISOs) If a date is specified as neither a starting nor ending 
+date, we parse it it as "midnight tonight to midnight tomorrow"
+
+=cut
+
+sub ParseDateRange {
+    my $in = shift;
+    my ($start, $end);
+    
+    
+    use RT::Date;
+    my $start_obj = new RT::Date($CurrentUser);
+    my $end_obj = new RT::Date($CurrentUser);
+    
+    if ($in =~ /^(.*?)-(.*?)$/) {
+       $start = $1;
+       $end = $2;
+
+       if ($start) {
+           $start_obj->Set(Format => 'unknown', 
+                           Value => $start);
+       }
+       if ($end) {
+           $end_obj->Set(Format => 'unknown', 
+                         Value => $end);
+       }
+    }
+    else {
+       $start = $in;
+       $end = $in;
+
+       $start_obj->Set(Format => 'unknown', 
+                       Value => $start);
+       
+       $end_obj->Set(Format => 'unknown', 
+                     Value => $end);
+       
+       $start_obj->SetToMidnight();
+       $end_obj->SetToMidnight();
+       $end_obj->AddDay();
+    }  
+    
+    if ($start) {
+       $start = $start_obj->ISO;
+    }
+    if ($end) {
+       $end = $end_obj->ISO;
+    }
+
+    return ($start, $end);
+}
+
+# }}}
+
+# {{{ ParseRange
+=head2 ParseRange [RANGE]
+
+Takes a range of the form [<int>][-][<int>] and returns 
+a first and a last value. If the - is omitted, both $start and $end are the same.
+=cut
+
+sub ParseRange {
+    my $in = shift;
+    my ($start, $end);
+    
+    if ($in =~ /(.*?)-(.*?)/) {
+       $start = $1;
+       $end = $2;
+    }
+    else {
+       $start = $in;
+       $end = $in;
+    }  
+    
+    return ($start, $end);
+    
+
+    
+}
+
+# }}}
+         
+# {{{ sub ShowSummary 
+
+sub ShowSummary  {
+    my $Ticket = shift;
+
+
+    print <<EOFORM;
+Serial Number: @{[$Ticket->Id]}   Status:@{[$Ticket->Status]} Worked: @{[$Ticket->TimeWorked]} minutes  Queue:@{[$Ticket->QueueObj->Name]}
+      Subject: @{[$Ticket->Subject]}
+   Requestors: @{[$Ticket->RequestorsAsString]}
+           Cc: @{[$Ticket->CcAsString]}
+     Admin Cc: @{[$Ticket->AdminCcAsString]}
+        Owner: @{[$Ticket->OwnerObj->Name]}
+     Priority: @{[$Ticket->Priority]} / @{[$Ticket->FinalPriority]}
+          Due: @{[$Ticket->DueAsString]}
+      Created: @{[$Ticket->CreatedAsString]} (@{[$Ticket->AgeAsString]})
+ Last Contact: @{[$Ticket->ToldAsString]} (@{[$Ticket->LongSinceToldAsString]})
+  Last Update: @{[$Ticket->LastUpdatedAsString]} by @{[$Ticket->LastUpdatedByObj->Name]}
+                
+EOFORM
+
+my $selects = $Ticket->QueueObj->KeywordSelects();
+    #get the keyword selects
+    print "Keywords:\n";
+    while (my $select = $selects->Next) {
+       print "\t" .$select->Name .": ";
+       my $keys = $Ticket->KeywordsObj($select->id);   
+       while (my $key = $keys->Next) {
+           print $key->KeywordObj->RelativePath($select->KeywordObj) . "  ";
+           
+       }       
+       print "\n";
+    }
+    
+#iterate through the keyword selects.
+#print the keyword select and all the related keywords
+
+
+
+#TODO: finish link  descriptions
+print "Dependencies: \n";
+   while (my $l=$Ticket->DependedOnBy->Next) {
+       print $l->BaseObj->id," (",$l->BaseObj->Subject,") ",$l->Type," this ticket\n";
+   }
+   while (my $l=$Ticket->DependsOn->Next) {
+       print "This ticket ",$l->Type," ",$l->TargetObj->Id," (",$l->TargetObj->Subject,")\n";
+   }
+}
+
+# }}}
+
+# {{{ sub ShowHistory 
+sub ShowHistory  {
+    my $Ticket = shift;
+    my $Transaction;    
+    my $Transactions = $Ticket->Transactions;
+
+    while ($Transaction = $Transactions->Next) {
+      &ShowTransaction($Transaction);
+    }   
+  }
+# }}}
+
+# {{{ sub ShowTransaction 
+sub ShowTransaction  {
+  my $transaction = shift;
+  
+print <<EOFORM;
+==========================================================================
+Date: @{[$transaction->CreatedAsString]} (@{[$transaction->TimeTaken]} minutes)
+@{[$transaction->Description]}
+EOFORM
+    ;
+  my $attachments=$transaction->Attachments();
+  while (my $message=$attachments->Next) {
+    print <<EOFORM;
+--------------------------------------------------------------------------
+@{[$message->Headers]}
+EOFORM
+
+    if ($message->ContentType =~ m{^(text/plain|message|text$)}) {
+       print $message->Content;
+    } else {
+       print $message->ContentType, " not shown";
+    }
+  }
+  print "\n";
+  return();
+}
+# }}}
+
+
+# {{{ sub BuildListingFormat
+
+sub BuildListingFormat {
+    my $format_string = shift;
+
+    my ($id, @format, @code, @titles);
+    my ($field,$titles,$length, $format);
+
+    my $code = "";
+
+    # {{{ attribs
+    my $attribs = { id => { chars => '4',
+                           justify => 'r',
+                           title => 'id',
+                           value => '$Ticket->id',
+                         },
+                   
+                   queue => { chars => '8',
+                              justify => 'l',
+                              title => 'Queue',
+                              value => '$Ticket->QueueObj->Name' 
+                            },
+                   subject => { chars => '30',
+                                justify => 'l',
+                                title => 'Subject',
+                                value => '$Ticket->Subject',
+                              },
+                   priority => { chars => '2',
+                                 justify => 'r',
+                                 title => 'Pri',
+                                 value => '$Ticket->Priority',
+                               },
+                   final_priority => {  chars => '2',
+                                        justify => 'r',
+                                        title => 'Fin',
+                                        value => '$Ticket->FinalPriority',
+                                     },
+                   time_worked => { chars => '6',
+                                    justify => 'r',
+                                    title => 'Worked',
+                                    value => '$Ticket->TimeWorked',
+                                  },
+                   time_left => { chars => '5',
+                                  justify => 'r',
+                                  title => 'Left',
+                                  value => '$Ticket->TimeLeft',
+                              
+                                },
+               
+                   status => {  chars => '6',
+                                justify => 'r',
+                                title => 'Status',
+                                value => '$Ticket->Status',
+                             },
+                   owner => {  chars => '10',
+                               justify => 'r',
+                               title => 'Owner',
+                               value => '$Ticket->OwnerObj->Name'
+                            },
+                   requestor => {  chars => '10',
+                                   justify => 'r',
+                                   title => 'Requestor',
+                                   value => '$Ticket->RequestorsAsString'
+                                },
+                   created => {  chars => '12',
+                                 justify => 'r',
+                                 title => 'Created',
+                                 value => '$Ticket->CreatedAsString'
+                              },
+                   updated => {  chars => '12',
+                                 justify => 'r',
+                                 title => 'Updated',
+                                 value => '$Ticket->LastUpdatedAsString'
+                              },
+                   due => {  chars => '12',
+                             justify => 'r',
+                             title => 'Due',
+                             value => '$Ticket->DueAsString'
+                          },
+                   told => {  chars => '12',
+                              justify => 'r',
+                              title => 'Told',
+                              value => '$Ticket->ToldAsString'
+                           },
+               
+               
+               
+                 };
+
+    # }}}
+    
+
+    foreach $field (split ('%',$format_string)) {
+       
+       if ($field =~ /^(\D*?)(\d*?)$/) {
+           $id = $1;
+           $length = $2;
+       }
+       else {  
+           $RT::Logger->debug ("Error parsing $field\n");
+       }
+       if ($length) {
+           push (@format, "%".$length.".".$length."s ");
+           
+           push (@code,  $attribs->{"$id"}->{'value'});
+                 
+           push (@titles, "'". $attribs->{"$id"}->{title}. "'");
+       }
+       
+       
+    }
+     $code = join (',', @code);
+     $format = join (" ", @format);
+     $titles = join (', ', @titles);
+    
+  
+    return ($format, $titles, $code);
+}
+
+# }}}
+
+
+
+1;