5 debpaste - http://paste.debian.net/ XML-RPC client
9 # Author: Hanno Hecker <vetinari@ankh-morp.org>
10 # With patches from Richard Hartmann <richih.mailinglist@gmail.com>
11 # Licence: AGPL 3.0 (http://www.fsf.org/licensing/licenses/agpl-3.0.html)
12 # Version: $Id: debpaste 19 2009-04-15 08:15:25Z vetinari $
13 # SVN: http://svn.ankh-morp.org:8080/tools/debpaste/
16 # deb: perl-base perl-modules
17 # libtimedate-perl libfrontier-rpc-perl libtext-iconv-perl
34 B<debpaste> ACTION [OPTIONS] [CODE|ID]
42 Usage: debpaste add [OPTIONS] [CODE]
44 Adds a new paste to L<http://paste.debian.net/>. If no code is given on the
45 command line, it will read from stdin.
47 Your paste infos are saved to I<~/.debpaste_history>
51 Usage: debpaste del [OPTIONS] ID
53 Deletes paste with id ID. This must be an ID which you have pasted before
54 (and is in your history file)
58 Usage: debpaste get [OPTIONS] ID
60 Fetches the paste with id ID from L<http://paste.debian.net>. To C<download>
61 a paste use something like
63 debpaste get --noheader ID > OUTFILE
67 Usage: debpaste lang [OPTIONS]
69 Dumps the list of available languages for syntax highlighting, use the
70 B<--lang=LANG> option when B<add>ing a paste.
74 Usage: debpaste edit [OPTIONS] ID
76 Downloads the paste with id ID, spawns an editor, and sends the edited file
81 Usage: debpaste expire [OPTIONS] [ID]
83 Removes the entry ID from history file. If no ID is given it removes all
84 entries which are expired.
94 paste as USERNAME instead of C<anonymous>
98 use URL instead of http://paste.debian.net/server.pl
102 do not use the http proxy given in the environment variable C<http_proxy>
106 use LANG for syntax highlight ('debpaste lang' for available languages)
110 expires in SEC seconds (default: 259200 = 72h)
114 when adding new paste, use ENC as encoding of file, default: UTF-8
118 when B<get>ting entries, don't print header, just dump the paste to stdout.
122 print version and exit
128 binmode(STDOUT
, ":utf8");
129 binmode(STDERR
, ":utf8");
139 The right place for setting default options like the username or expire values.
140 Format is C<KeyInAnYCase: value>, example:
145 =item ~/.debpaste_history
147 All info about pastes done with B<debpaste> are recorded here. This file
148 is used to keep a record for B<del>eting entries after pasting. Use
149 B<debpaste expire> to remove old entries.
155 my $settings = $ENV{HOME
}."/.debpasterc";
157 ## Don't change, edit $settings file:
158 ## KeYInAnyCaSE: value
159 ## AnoThErKey: other-value
160 my $history = $ENV{HOME
}."/.debpaste_history";
162 server
=> "http://paste.debian.net/server.pl",
165 expires
=> 86400 * 3, #
166 history_file
=> $history,
171 my %help = ( #XXX we probably need to tie this hash so print_help() works nicely
173 ."Usage: $0 add [OPTIONS] [CODE]\n"
174 ." Adds a new paste to http://paste.debian.net/\n"
175 ." If no code is given on the command line, it will read from\n"
177 ." Your paste infos are saved to $history\n",
179 ."Usage: $0 get [OPTIONS] ID\n"
180 ." Fetches the paste with id ID from paste.debian.net\n"
181 ." To 'download' a paste use something like\n"
182 ." $0 get --noheader ID > OUTFILE\n",
184 ."Usage: $0 del [OPTIONS] ID\n"
185 ." Deletes paste with id ID. This must be an ID which you have\n"
186 ." pasted before (and is in your history file)\n",
188 ."Usage: $0 lang [OPTIONS]\n"
189 ." Dumps the list of available languages for syntax highlighting\n",
191 ."Usage: $0 edit [OPTIONS] ID\n"
192 ." Downloads the paste with id ID, spawns an editor (\$EDITOR)\n"
193 ." and sends the edited file as new paste\n",
195 ."Usage: $0 expire [OPTIONS] [ID]\n"
196 ." Removes the entry ID from history file. If no ID is given,\n"
197 ." it removes all entries which are expired.\n",
198 # 'help' => "FIXME: help",
201 if (@ARGV and $ARGV[0] !~ /^-/) {
202 $action = shift @ARGV;
207 # This needs to be here so we can be called with --help
209 print "Short usage help:\n\n";
210 foreach my $key (keys %help) {
211 print "$key $help{$key}\n";
216 "user=s" => \
$config{user
},
217 "server=s" => \
$config{server
},
218 "expires=s" => \
$config{expires
},
219 "lang=s" => \
$config{lang
},
220 "encoding=s"=> \
$config{encoding
},
221 "noheader" => \
$config{no_get_header
},
222 "help" => sub { print_help
(); exit 0 },
223 "usage" => sub { pod2usage
(-exitval
=> 0, -verbose
=> 2); exit 0 },
224 "version" => sub { print "debpaste v$VERSION\n"; exit 0; },
226 or print_help
; #XXX do we _ever_ reach this situation? I think not -- RH 091123
228 if ($action and $action eq "help") {
229 $action = shift @ARGV
230 if (@ARGV and $ARGV[0] !~ /^-/);
235 my $paste = Debpaste
->new(%config);
236 if ($paste->can($action) and $action ne "new" and $action !~ /^_/) {
239 die "$0: err... unknown action $action...\n";
245 while (defined (my $line = <SET
>)) {
246 next unless $line =~ /^(\w+):\s+(.*)$/;
247 my ($key, $value) = (lc $1, $2);
248 unless (exists $config{$key}) {
249 warn "$0: unknown config key '$key' found\n";
252 ($config{$key} = $value) =~ s/^\s*(.*?)\s*$/$1/;
260 if (exists $help{$_[0]}) {
261 ($msg = $help{$_[0]}."\n") if (exists $help{$_[0]});
267 ###################################################################
270 use Frontier
::Client
;
272 use POSIX
qw(strftime);
273 use File
::Temp
qw(tempfile);
279 my $type = ref($me) || $me;
282 foreach (keys %args) {
283 $self->{$_} = $args{$_};
285 unless (exists $self->{editor
}) {
286 $self->{editor
} = $ENV{EDITOR
} ?
287 $ENV{EDITOR
} : ($ENV{VISUAL
} ?
288 $ENV{VISUAL
} : "/usr/bin/editor");
290 $self->{encoding
} = "UTF-8" unless $self->{encoding
};
291 $self->{expires
} += time;
292 my %fc = ( url
=> $self->{server
} );
293 unless ($self->{noproxy
}) {
294 $fc{proxy
} = $ENV{http_proxy
} if $ENV{http_proxy
};
296 $self->{_service
} = Frontier
::Client
->new(%fc);
301 my ($self,$txt) = @_;
302 my $enc = $self->{encoding
};
303 return $txt if $enc eq "UTF-8";
305 my $i = eval { Text
::Iconv
->new($enc, "UTF-8"); };
306 die "$0: unsupported encoding $enc\n" if $@
;
308 my $new = $i->convert($txt);
309 return $txt unless $new;
314 my ($self, $msg) = @_;
315 unlink $self->{_tempfile
} if $self->{_tempfile
};
321 my $rc = $self->{_service
}->call("paste.getLanguages");
322 die $rc->{statusmessage
},"\n" if $rc->{rc
};
323 ## print $rc->{statusmessage},"\n";
324 print "Available syntax highlights:\n";
325 foreach (@
{$rc->{langs
}}) {
332 my $id = shift @ARGV;
333 die "$0: no id given\n" unless $id;
334 my $rc = $self->{_service
}->call("paste.getPaste", $id);
335 die $rc->{statusmessage
},"\n" if $rc->{rc
};
336 # ugly, but dates are ok then...
337 # FIXME: probably only works with paste.d.n's timezone:
338 my $stime = str2time
($rc->{submitdate
}, "CET") - 3600;
339 my $sub_date = strftime
('%Y-%m-%d %H:%M:%S', localtime $stime);
340 my $exp_date = strftime
('%Y-%m-%d %H:%M:%S',
341 localtime($stime + $rc->{expiredate
}));
342 unless ($self->{no_get_header
}) {
343 print "User: ", $rc->{submitter
}, "\n",
345 "Expires: $exp_date\n",
346 "---------------------------------\n";
348 print $rc->{code
},"\n";
353 my $id = shift @ARGV;
354 die "$0: no id given\n" unless $id;
356 my $rc = $self->{_service
}->call("paste.getPaste", $id);
357 die $rc->{statusmessage
},"\n" if $rc->{rc
};
358 my $new = $self->_spawn_editor($rc->{code
});
359 if (!$new or ($new eq $rc->{code
})) {
360 print "$0: not changed, aborting...\n";
363 ## FIXME: text from paste.debian.net is probably UTF-8
364 ## $new = $self->_to_utf8($new);
365 $rc = $self->{_service
}->call("paste.addPaste", $new,
367 $self->{expires
} - time,
369 die $rc->{statusmessage
},"\n"
371 print $rc->{statusmessage
},"\n";
372 print "To delete this entry, use: $0 del $rc->{id}\n";
373 $self->save_entry($rc);
377 my ($self, $txt) = @_;
380 ($fh, $self->{_tempfile
}) = tempfile
("debpaste.XXXXXX", DIR
=> "/tmp");
382 $self->_error("Could not create temp file: $!")
383 unless ($fh and $self->{_tempfile
});
384 print $fh $txt or $self->_error("Could not print to tempfile: $!");
385 close $fh or $self->_error("Failed to close tempfile: $!");
387 if (system($self->{editor
}, $self->{_tempfile
}) != 0) {
388 $self->_error("failed to execute: $!")
391 $self->_error(sprintf('child died with signal %d, %s coredump',
392 ($?
& 127), ($?
& 128) ?
'with' : 'without'))
395 $self->error(sprintf('editor exited with value %d', $?
>> 8));
398 open FH
, $self->{_tempfile
}
399 or $self->_error("Failed to open temp file: $!");
405 unlink $self->{_tempfile
};
409 sub delete { $_[0]->del(); }
413 my $id = shift @ARGV;
414 die "$0: no id given\n" unless $id;
415 open FILE
, $self->{history_file
}
416 or die "$0: failed to open history file: $!\n";
423 %entry = map { /^(\S+):\s*(.*?)\s*$/;
424 ($1, $2 ?
$2 : "") } split /\n/, $_;
425 last if ($entry{Entry
} and $entry{Entry
} eq $id);
429 die "$0: Entry for $id not found...\n" unless $entry{Entry
};
430 die "$0: No Digest for $id\n" unless $entry{Digest
};
431 die "$0: Entry $id expired at ", scalar(localtime($entry{Expires
})),"\n"
432 if ($entry{Expires
} and $entry{Expires
} < time);
434 my $rc = $self->{_service
}->call("paste.deletePaste", $entry{Digest
});
435 die $rc->{statusmessage
},"\n" if $rc->{rc
};
436 print $rc->{statusmessage
},"\n",
437 "$0: deleted paste id ",$rc->{id
},"\n";
438 $self->_expire($rc->{id
});
443 my $id = shift @ARGV;
448 my ($self, $id) = @_;
452 open FILE
, $self->{history_file
}
460 %entry = map { /^(\S+):\s*(.*?)\s*$/;
461 ($1, $2 ?
$2 : "") } split /\n/, $_;
463 ## print "ID: $entry{Entry}\n";
465 if ($entry{Entry
} and $entry{Entry
} eq $id) {
466 push @ids, $entry{Entry
};
469 } elsif ($entry{Expires
} and $entry{Expires
} < time) {
470 push @ids, $entry{Entry
};
473 push @history, { %entry };
477 open FILE
, ">", $self->{history_file
}
478 or die "$0: Failed to open history file: $!\n";
479 foreach my $h (@history) {
480 foreach (keys %{$h}) {
482 print FILE
"$_: $h->{$_}\n";
486 close FILE
or die "$0: failed to write: $!\n";
487 print "$0: expired ", scalar(@ids), " entries from history",
488 (@ids ?
": ".join(", ", @ids) : ""), "\n";
496 $code = join("\n", @ARGV);
498 { local $/ = undef; $code = <STDIN
>; }
500 die "$0: no code given\n"
503 $code = $self->_to_utf8($code);
504 my $rc = $self->{_service
}->call("paste.addPaste", $code,
506 $self->{expires
} - time,
508 die $rc->{statusmessage
},"\n"
510 print $rc->{statusmessage
},"\n";
511 print "To delete this entry, use: $0 del $rc->{id}\n";
512 $self->save_entry($rc);
516 my ($self, $rc) = @_;
517 # return unless $self->{save_pastes};
518 my $file = $self->{history_file
}
520 open FILE
, ">>", $file or die "$0: failed to open $file: $!\n";
521 seek FILE
, 0, 2 or die "$0: Failed to seek: $!\n";
522 print FILE
"Server: ", $self->{server
}, "\n",
523 "Entry: ", $rc->{id
}, "\n",
524 "Lang: ", $self->{lang
}, "\n",
525 "Expires: ", $self->{expires
},"\n",
526 "Digest: ", $rc->{digest
}, "\n\n"
527 or die "$0: Failed to save paste: $!\n";
528 close FILE
or die "$0: Failed to save paste: $!\n";
533 L<http://ankh-morp.org/code/debpaste/debpaste> or
534 L<SVN|http://svn.ankh-morp.org:8080/tools/debpaste/>
538 Hanno Hecker <vetinari@ankh-morp.org>
539 Some patches by Richard Hartmann <richih.mailinglist@gmail.com>
544 # vim: ts=4 sw=4 expandtab syn=perl