—#!perl -w
#
# PTKSH 2.0
#
# A graphical user interface for testing Perl/Tk commands and scripts.
#
# VERSION HISTORY:
# ...truncated earlier stuff...
# 4/23/98 V1.7 Achim Bohnet -- some fixes to "o" command
# 6/08/98 V2.01 M. Beller -- merge in GUI code for "wish"-like interface
#
# 2.01d1 6/6/98 First development version
#
# 2.01d2 6/7/98
# - apply A.B. patch for pod and -option
# - fix "use of uninitialized variable" in END{ } block (for -c option)
# - support h and ? only for help
# - misc. pod fixes (PITFALLS)
# - use default fonts and default colors ## NOT YET--still working on it
# - get rid of Data::Dumper for history
#
# 2.01d3 6/8/98
# - Remove "use Data::Dumper" line
# - Put in hack for unix vs. win32 window manager focus problem
# - Achim's pod and histfile patch
#
# 2.01d4 6/18/98
# - Slaven's patch to make <Home> work properly
# - Add help message to banner (per Steve Lydie)
# - Fix horizontal scrolling (turn off wrapping in console window)
# - Clarify <Up> in docs and help means "up arrow"
# - Use HOMEDRIVE/HOMEPATH on Win32
#
=head1 NAME
ptksh - Perl/Tk script to provide a graphical user interface for testing Perl/Tk
commands and scripts.
=head1 SYNOPSIS
% ptksh ?scriptfile?
... version information ...
ptksh> $b=$mw->Button(-text=>'Hi',-command=>sub{print 'Hi'})
ptksh> $b->pack
ptksh> o $b
... list of options ...
ptksh> help
... help information ...
ptksh> exit
%
=head1 DESCRIPTION
ptksh is a perl/Tk shell to enter perl commands
interactively. When one starts ptksh a L<MainWindow|Tk::MainWindow>
is automaticly created, along with a ptksh command window.
One can access the main window by typing commands using the
variable $mw at the 'ptksh> ' prompt of the command window.
ptksh supports command line editing and history. Just type "<Up>" at
the command prompt to see a history list. The last 50 commands entered
are saved, then reloaded into history list the next time you start ptksh.
ptksh supports some convenient commands for inspecting Tk widgets. See below.
To exit ptksh use: C<exit>.
ptksh is B<*not*> a full symbolic debugger.
To debug perl/Tk programs at a low level use the more powerful
L<perl debugger|perldebug>. (Just enter ``O tk'' on debuggers
command line to start the Tk eventloop.)
=head1 FEATURES
=head2 History
Press <Up> (the Up Arrow) in the perlwish window to obtain a gui-based history list.
Press <Enter> on any history line to enter it into the perlwish window.
Then hit return. So, for example, repeat last command is <Up><Enter><Enter>.
You can quit the history window with <Escape>. NOTE: history is only saved
if exit is "graceful" (i.e. by the "exit" command from the console or by
quitting all main windows--NOT by interrupt).
=head2 Debugging Support
ptksh provides some convenience function to make browsing
in perl/Tk widget easier:
=over 4
=item B<?>, or B<h>
displays a short help summary.
=item B<d> ?I<args>, ...?
Dumps recursively arguments to stdout. (see L<Data::Dumper>).
You must have <Data::Dumper> installed to support this feature.
=item B<p> ?I<arg>, ...?
appends "|\n" to each of it's arguments and prints it.
If value is B<undef>, '(undef)' is printed to stdout.
=item B<o> I<$widget> ?I<-option> ...?
prints the option(s) of I<$widget> one on each line.
If no options are given all options of the widget are
listed. See L<Tk::options> for more details on the
format and contents of the returned list.
=item B<o> I<$widget> B</>I<regexp>B</>
Lists options of I<$widget> matching the
L<regular expression|perlre> I<regexp>.
=item B<u> ?I<class>?
If no argument is given it lists the modules loaded
by the commands you executed or since the last time you
called C<u>.
If argument is the empty string lists all modules that are
loaded by ptksh.
If argument is a string, ``text'' it tries to do a ``use Tk::Text;''.
=back
=head2 Packages
Ptksh compiles into package Tk::ptksh. Your code is eval'ed into package
main. The coolness of this is that your eval code should not interfere with
ptksh itself.
=head2 Multiline Commands
ptksh will accept multiline commands. Simply put a "\" character immediately
before the newline, and ptksh will continue your command onto the next line.
=head2 Source File Support
If you have a perl/Tk script that you want to do debugging on, try running the
command
ptksh> do 'myscript';
-- or (at shell command prompt) --
% ptksh myscript
Then use the perl/Tk commands to try out different operations on your script.
=head1 ENVIRONMENT
Looks for your .ptksh_history in the directory specified by
the $HOME environment variable ($HOMEPATH on Win32 systems).
=head1 FILES
=over 4
=item F<.ptksh_init>
If found in current directory it is read in an evaluated
after the mainwindow I<$mw> is created. F<.ptksh_init>
can contain any valid perl code.
=item F<~/.ptksh_history>
Contains the last 50 lines entered in ptksh session(s).
=back
=head1 PITFALLS
It is best not to use "my" in the commands you type into ptksh.
For example "my $v" will make $v local just to the command or commands
entered until <Return> is pressed.
For a related reason, there are no file-scopy "my" variables in the
ptksh code itself (else the user might trounce on them by accident).
=head1 BUGS
B<Tk::MainLoop> function interactively entered or sourced in a
init or script file will block ptksh.
=head1 SEE ALSO
L<Tk|Tk>
L<perldebug|perldebug>
=head1 VERSION
VERSION 2.02
=head1 AUTHORS
Mike Beller <beller@penvision.com>,
Achim Bohnet <ach@mpe.mpg.de>
Copyright (c) 1996 - 1998 Achim Bohnet and Mike Beller. All rights reserved.
This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut
package
Tk::ptksh;
require
5.004;
use
strict;
use
Tk;
##### Constants
$NAME
=
'ptksh'
;
$VERSION
=
'2.02'
;
$WIN32
= 1
if
$^O =~ /Win32/;
$HOME
=
$WIN32
? (
$ENV
{HOMEDRIVE} .
$ENV
{HOMEPATH}) ||
'C:\\'
:
$ENV
{HOME} .
"/"
;
@FONT
= (
$WIN32
? (
-font
=>
'systemfixed'
) : () );
#@FONT = ($WIN32 ? (-font => ['courier', 9, 'normal']) : () );
$HISTFILE
=
"${HOME}.${NAME}_history"
;
$HISTSAVE
= 50;
$INITFILE
=
".${NAME}_init"
;
$PROMPT
=
"$NAME> "
;
sub
Win32Fix {
my
$p
=
shift
;
$p
=~ s
'\\'
/
'g; $p =~ s'
/$
''
;
return
$p
}
# NOTE: mainwindow creation order seems to impact who gets focus, and
# order is different on Win32 & *nix!! So hack is to create the windows
# in an order dependent on the OS!
$mw
= Tk::MainWindow->new
unless
$WIN32
;
# &&& hack to work around focus problem
##### set up user's main window
package
main;
$main::mw
= Tk::MainWindow->new;
$main::mw
->title(
'$mw'
);
$main::mw
->geometry(
"+1+1"
);
package
Tk::ptksh;
##### Set up ptksh windows
$mw
= Tk::MainWindow->new
if
$WIN32
;
# &&& hack to work around focus problem
$mw
->title(
$NAME
);
$st
=
$mw
->Scrolled(
'Text'
,
-scrollbars
=>
'osoe'
,
-wrap
=>
'none'
,
-width
=> 80,
-height
=> 25,
@FONT
);
$t
=
$st
->Subwidget(
'scrolled'
);
$st
->
pack
(
-fill
=>
'both'
,
-expand
=>
'true'
);
$mw
->
bind
(
'<Map>'
,
sub
{Center(
$mw
);} );
# Event bindings
$t
->bindtags([
$t
,
ref
(
$t
),
$t
->toplevel,
'all'
]);
# take first crack at events
$t
->
bind
(
'<Return>'
, \
&EvalInput
);
$t
->
bind
(
'<BackSpace>'
, \
&BackSpace
);
$t
->
bind
(
'<Escape>'
, \
&HistKill
);
$t
->
bind
(
'<Up>'
, \
&History
);
$t
->
bind
(
'<Control-a>'
, \
&BeginLine
);
$t
->
bind
(
'<Home>'
, \
&BeginLine
);
$t
->
bind
(
'<Any-KeyPress>'
, [\
&Key
, Tk::Ev(
'K'
), Tk::Ev(
'A'
)]);
# Set up different colors for the various window outputs
#$t->tagConfigure('prompt', -underline => 'true');
$t
->tagConfigure(
'prompt'
,
-foreground
=>
'blue'
);
$t
->tagConfigure(
'result'
,
-foreground
=>
'purple'
);
$t
->tagConfigure(
'error'
,
-foreground
=>
'red'
);
$t
->tagConfigure(
'output'
,
-foreground
=>
'blue'
);
# The tag 'limit' is the beginning of the input command line
$t
->markSet(
'limit'
,
'insert'
);
$t
->markGravity(
'limit'
,
'left'
);
# redirect stdout
#tie (*STDOUT, 'Tk::Text', $t);
tie
(
*STDOUT
,
'Tk::ptksh'
);
#tie (*STDERR, 'Tk::ptksh');
# Print banner
"$NAME V$VERSION"
;
" perl V$] Tk V$Tk::VERSION MainWindow -> \$mw\n"
;
"\n\t\@INC:\n"
;
foreach
(
@INC
) {
"\t $_\n"
};
"Type 'h<Return>' at the prompt for help\n"
;
##### Read .ptkshinit
if
( -r
$INITFILE
)
{
"Reading $INITFILE ...\n"
;
do
$Tk::ptksh::INITFILE
;
}
###### Source the file if given as argument 0
if
(
defined
(
$ARGV
[0]) && -r
$ARGV
[0])
{
"Reading $ARGV[0] ...\n"
;
do
$ARGV
[0];
}
##### Read history
@hist
= ();
if
( -r
$HISTFILE
and
open
(HIST,
$HISTFILE
) ) {
"Reading history ...\n"
;
my
$c
=
""
;
while
(<HIST>) {
chomp
;
$c
.=
$_
;
if
(
$_
!~ /\\$/) {
#end of command if no trailing "\"
push
@hist
,
$c
;
$c
=
""
;
}
else
{
chop
$c
;
# kill trailing "\"
$c
.=
"\n"
;
}
}
close
HIST;
}
##### Initial prompt
Prompt(
$PROMPT
);
$Tk::ptksh::mw
->focus;
$t
->focus;
#$mw->after(1000, sub {print STDERR "now\n"; $mw->focus; $t->focus;});
##### Now enter main loop
MainLoop();
####### Callbacks/etc.
# EvalInput -- Eval the input area (between 'limit' and 'insert')
# in package main;
sub
EvalInput {
# If return is hit when not inside the command entry range, reprompt
if
(
$t
->compare(
'insert'
,
'<='
,
'limit'
)) {
$t
->markSet(
'insert'
,
'end'
);
Prompt(
$PROMPT
);
Tk->break;
}
# Support multi-line commands
if
(
$t
->get(
'insert-1c'
,
'insert'
) eq
"\\"
) {
$t
->insert(
'insert'
,
"\n"
);
$t
->insert(
'insert'
,
"> "
,
'prompt'
);
# must use this pattern for continue
$t
->see(
'insert'
);
Tk->break;
}
# Get the command and strip out continuations
$command
=
$t
->get(
'limit'
,
'end'
);
$t
->markSet(
'insert'
,
'end'
);
$command
=~ s/\\\n>\s/\n/mg;
# Eval it
if
(
$command
!~ /^\s*$/) {
chomp
$command
;
push
(
@hist
,
$command
)
unless
@hist
&& (
$command
eq
$hist
[
$#hist
]); #could elim more redundancy
$t
->insert(
'insert'
,
"\n"
);
$isStartOfCommand
= 1;
$command
= PtkshCommand(
$command
);
exit
if
(
$command
eq
'exit'
);
no
strict;
$Tk::ptksh::result
=
eval
"local \$^W=0; $Tk::ptksh::command;"
;
if
(
$t
->compare(
'insert'
,
'!='
,
'insert linestart'
)) {
$t
->insert(
'insert'
,
"\n"
);
}
if
($@) {
$t
->insert(
'insert'
,
'## '
. $@,
'error'
);
}
else
{
$result
=
""
if
!
defined
(
$result
);
$t
->insert(
'insert'
,
'# '
.
$result
,
'result'
);
}
}
Prompt(
$PROMPT
);
Tk->break;
}
sub
Prompt {
my
$pr
=
shift
;
if
(
$t
->compare(
'insert'
,
'!='
,
'insert linestart'
)) {
$t
->insert(
'insert'
,
"\n"
);
}
$t
->insert(
'insert'
,
$pr
,
'prompt'
);
$t
->see(
'insert'
);
$t
->markSet(
'limit'
,
'insert'
);
}
sub
BackSpace {
if
(
$t
->tagNextrange(
'sel'
,
'1.0'
,
'end'
)) {
$t
->
delete
(
'sel.first'
,
'sel.last'
);
}
elsif
(
$t
->compare(
'insert'
,
'>'
,
'limit'
)) {
$t
->
delete
(
'insert-1c'
);
$t
->see(
'insert'
);
}
Tk->break;
}
sub
BeginLine {
$t
->SetCursor(
'limit'
);
$t
->break;
}
sub
Key {
my
(
$self
,
$k
,
$a
) =
@_
;
#print "key event: ", $k, "\n";
if
(
$t
->compare(
'insert'
,
'<'
,
'limit'
)) {
$t
->markSet(
'insert'
,
'end'
);
}
#$t->break; #for testing bindtags
}
sub
History {
Tk->break
if
defined
(
$hist
);
$hist
=
$mw
->Toplevel;
$hist
->title(
'History'
);
$list
=
$hist
->ScrlListbox(
-scrollbars
=>
'oe'
,
-width
=> 30,
-height
=> 10,
@FONT
)->
pack
;
Center(
$hist
);
$list
->insert(
'end'
,
@hist
);
$list
->see(
'end'
);
$list
->activate(
'end'
);
$hist
->
bind
(
'<Double-1>'
, \
&HistPick
);
$hist
->
bind
(
'<Return>'
, \
&HistPick
);
$hist
->
bind
(
'<Escape>'
, \
&HistKill
);
$hist
->
bind
(
'<Map>'
,
sub
{Center(
$hist
);} );
$hist
->
bind
(
'<Destroy>'
, \
&HistDestroy
);
$hist
->focus;
$list
->focus;
$hist
->grab;
Tk->break;
}
sub
HistPick {
my
$item
=
$list
->get(
'active'
);
return
if
(!
$item
);
$t
->markSet(
'insert'
,
'end'
);
$t
->insert(
'insert'
,
$item
);
$t
->see(
'insert'
);
$mw
->focus;
$t
->focus;
HistKill();
}
sub
HistKill {
if
(
$hist
) {
$hist
->grabRelease;
$hist
->destroy;
}
}
# Called from destroy event mapping
sub
HistDestroy {
if
(
defined
(
$hist
) && (
shift
==
$hist
)) {
$hist
=
undef
;
$mw
->focus;
$t
->focus;
}
}
sub
LastCommand {
if
(
$t
->compare(
'insert'
,
'=='
,
'limit'
)) {
$t
->insert(
'insert'
,
$hist
[
$#hist
]);
$t
->break;
}
}
# Center a toplevel on screen or above parent
sub
Center {
my
$w
=
shift
;
my
(
$x
,
$y
);
if
(
$w
->parent) {
#print STDERR $w->screenwidth, " ", $w->width, "\n";
$x
=
$w
->parent->x + (
$w
->parent->width -
$w
->width)/2;
$y
=
$w
->parent->y + (
$w
->parent->height -
$w
->height)/2;
}
else
{
#print STDERR $w->screenwidth, " ", $w->width, "\n";
$x
= (
$w
->screenwidth -
$w
->width)/2;
$y
= (
$w
->screenheight -
$w
->height)/2;
}
$x
=
int
(
$x
);
$y
=
int
(
$y
);
my
$g
=
"+$x+$y"
;
#print STDERR "Setting geometry to $g\n";
$w
->geometry(
$g
);
}
# To deal with "TIE".
# We have to make sure the prints don't go into the command entry range.
sub
TIEHANDLE {
# just to capture the tied calls
my
$self
= [];
return
bless
$self
;
}
sub
PRINT {
my
(
$bogus
) =
shift
;
$t
->markSet(
'insert'
,
'end'
);
if
(
$isStartOfCommand
) {
# Then no prints have happened in this command yet so...
if
(
$t
->compare(
'insert'
,
'!='
,
'insert linestart'
)) {
$t
->insert(
'insert'
,
"\n"
);
}
# set flag so we know at least one print happened in this eval
$isStartOfCommand
= 0;
}
while
(
@_
) {
$t
->insert(
'end'
,
shift
,
'output'
);
}
$t
->see(
'insert'
);
$t
->markSet(
'limit'
,
'insert'
);
# don't interpret print as an input command
}
sub
PRINTF
{
my
$w
=
shift
;
$w
->PRINT(
sprintf
(
shift
,
@_
));
}
###
### Utility function
###
sub
_o
{
my
$w
=
shift
;
my
$what
=
shift
;
$what
=~ s/^\s+//;
$what
=~ s/\s+$//;
my
(
@opt
) =
split
" "
,
$what
;
'o('
,
join
(
'|'
,
@opt
),
")\n"
;
# check for regexp
if
(
$opt
[0] =~ s|^/(.*)/$|$1|)
{
"options matching /$opt[0]/:\n"
;
foreach
(
$w
->configure())
{
Tk::Pretty::Pretty(
$_
),
"\n"
if
$_
->[0] =~ /\Q
$opt
[0]\E/;
}
return
;
}
# list of options (allow as bar words)
foreach
(
@opt
)
{
s/^['"]//;
s/,$//;
s/['"]$//;
s/^([^-])/-$1/;
}
if
(
length
$what
)
{
foreach
(
@opt
)
{
Tk::Pretty::Pretty(
$w
->configure(
$_
)),
"\n"
;
}
}
else
{
foreach
(
$w
->configure()) {
Tk::Pretty::Pretty(
$_
),
"\n"
}
}
}
sub
_p {
foreach
(
@_
) {
$_
,
"|\n"
; }
}
$u_init
= 0;
%u_last
= ();
sub
_u {
my
$module
=
shift
;
if
(
defined
(
$module
) and
$module
ne
''
) {
$module
=
"Tk/"
.
ucfirst
(
$module
).
".pm"
unless
$module
=~ /^Tk/;
" --- Loading $module ---\n"
;
require
"$module"
;
$@
if
$@;
}
else
{
%u_last
= ()
if
defined
$module
;
$u_cnt
= 0;
foreach
(
sort
keys
%INC
) {
next
if
exists
$u_last
{
$_
};
$u_cnt
++;
$u_last
{
$_
} = 1;
#next if m,^/, and m,\.ix$,; # Ignore autoloader files
#next if m,\.ix$,; # Ignore autoloader files
if
(
length
(
$_
) < 20 ) {
printf
"%-20s -> %s\n"
,
$_
,
$INC
{
$_
};
}
else
{
"$_ -> $INC{$_}\n"
;
}
}
STDERR
"No modules loaded since last 'u' command (or startup)\n"
unless
$u_cnt
;
}
}
sub
_d
{
local
$Data::Dumper::Deparse
;
$Data::Dumper::Deparse
= 1;
Data::Dumper::Dumper(
@_
);
}
sub
_h
{
<<'EOT';
? or h print this message
d arg,... calls Data::Dumper::Dumper
p arg,... print args, each on a line and "|\n"
o $w /regexp/ print options of widget matching regexp
o $w [opt ...] print (all) options of widget
u xxx xxx = string : load Tk::Xxx
= '' : list all modules loaded
= undef : list modules loaded since last u call
(or after ptksh startup)
Press <Up> (the "up arrow" key) for command history
Press <Escape> to leave command history window
Type "exit" to quit (saves history)
Type \<Return> for continuation of command to following line
EOT
}
# Substitute our special commands into the command line
sub
PtkshCommand {
$_
=
shift
;
foreach
(
$_
) {
last
if
s/^\?\s*$/Tk::ptksh::_h /;
last
if
s/^h\s*$/Tk::ptksh::_h /;
last
if
s/^u(\s+|$)/Tk::ptksh::_u /;
last
if
s/^d\s+/Tk::ptksh::_d /;
last
if
s/^u\s+(\S+)/Tk::ptksh::_u(
'$1'
)/;
last
if
s/^p\s+(.*)$/Tk::ptksh::_p $1;/;
last
if
s/^o\s+(\S+)\s*?$/Tk::ptksh::_o $1;/;
last
if
s/^o\s+(\S+)\s*,?\s+(.*)?$/Tk::ptksh::_o $1,
'$2'
;/;
}
%u_last
=
%INC
unless
$u_init
++;
# print STDERR "Command is: $_\n";
$_
;
}
###
### Save History -- use Data::Dumper to preserve multiline commands
###
END {
if
(
$HISTFILE
) {
# because this is probably perl -c if $HISTFILE is not set
$#hist
--
if
$hist
[-1] =~ /^(
q$|x$
|\s
*exit
\b)/; #
chop
off the
exit
command
@hist
=
@hist
[(
$#hist
-
$HISTSAVE
)..(
$#hist
)]
if
$#hist
>
$HISTSAVE
;
if
(
open
HIST,
">$HISTFILE"
) {
while
(
$_
=
shift
(
@hist
)) {
s/\n/\\\n/mg;
HIST
"$_\n"
;
}
close
HIST;
}
else
{
STDERR
"Error: Unable to open history file '$HISTFILE'\n"
;
}
}
}
1;
# just in case we decide to be "use"'able in the future.