Author: spadkins
Date: Thu Mar 26 09:48:42 2009
New Revision: 12636

Added:
   p5ee/trunk/App-Repository/lib/App/RepositoryShell.pm

Log:
new

Added: p5ee/trunk/App-Repository/lib/App/RepositoryShell.pm
==============================================================================
--- (empty file)
+++ p5ee/trunk/App-Repository/lib/App/RepositoryShell.pm        Thu Mar 26 
09:48:42 2009
@@ -0,0 +1,593 @@
+
+#############################################################################
+## $Id: RepositoryShell.pm 12569 2009-03-05 05:08:58Z spadkins $
+#############################################################################
+
+package App::RepositoryShell;
+$VERSION = (q$Revision: 12569 $ =~ /(\d[\d\.]*)/)[0];  # VERSION numbers 
generated by svn
+
+use strict;
+
+use App;
+use File::Temp qw(tempfile);
+use Fcntl;   # For O_RDWR, O_CREAT, etc.
+use SDBM_File;
+
+$| = 1;  # autoflush stdout
+
+=head1 NAME
+
+App::RepositoryShell - The core logic for "dash", the Data Access Shell
+
+=head1 SYNOPSIS
+
+    use App::RepositoryShell;
+
+    my $shell = App::RepositoryShell->new();
+    $shell->run();
+
+=cut
+
+=head1 DESCRIPTION
+
+The core logic for "dash", the Data Access Shell.
+
+=cut
+
+sub new {
+    &App::sub_entry if ($App::trace);
+    my ($this) = @_;
+    my $class = ref($this) || $this;
+    my $self = bless {}, $class;
+
+    my $context = App->context();
+    $self->{context} = $context;
+    my $options = $context->{options};
+    my $prefix = $options->{prefix};
+
+    $self->{interactive} = (-t STDIN) ? 1 : 0;
+
+    if ($self->{interactive}) {
+        eval { use Term::ReadLine; };
+        if ($@) {
+            $self->{loaded_term_readline} = 0;
+        }
+        else {
+            $self->{loaded_term_readline} = 1;
+            my $terminal = Term::ReadLine->new($options->{app});
+            # us,ue,md,me
+            #$terminal->ornaments(",,,");
+            #my @foo = $terminal->ornaments();
+            #print "ornaments:[...@foo]\n";
+            #$terminal->ornaments(0);
+            $self->{terminal} = $terminal;
+        }
+        my $silent = $options->{silent};
+        $silent = 0 if (!defined $silent);
+        $self->{silent} = $silent;
+    }
+    else {
+        $self->{loaded_term_readline} = 0;
+
+        my $silent = $options->{silent};
+        $silent = 1 if (!defined $silent);
+        $self->{silent} = $silent;
+    }
+
+    
################################################################################
+    # tempdir
+    
################################################################################
+    my ($tempdir);
+    if ($ENV{HOME}) {
+        my $homedir = $ENV{HOME};
+        mkdir("$homedir/.dash")         if (! -d "$homedir/.dash");
+        mkdir("$homedir/.dash/tmp")     if (! -d "$homedir/.dash/tmp");
+        mkdir("$homedir/.dash/command") if (! -d "$homedir/.dash/command");
+        mkdir("$homedir/.dash/report")  if (! -d "$homedir/.dash/report");
+        $tempdir = "$homedir/.dash/tmp" if (-d "$homedir/.dash/tmp");
+    }
+    if (!$tempdir) {
+        mkdir("$prefix/tmp") if (! -d "$prefix/tmp");
+        $tempdir = "$prefix/tmp" if (-d "$prefix/tmp");
+    }
+    if (!$tempdir) {
+        mkdir("/tmp") if (! -d "/tmp");
+        $tempdir = "/tmp" if (-d "/tmp");
+    }
+    if (!$tempdir) {
+        die "Can't create a temporary directory";
+    }
+    $self->{tempdir} = $tempdir;
+
+    
################################################################################
+    # DBM Database to Store Index Info
+    
################################################################################
+    my (%dbm);
+    my $dbmfile = "$ENV{HOME}/.dash/info";
+    my $dbmfile_needs_initialization = (! -f "$dbmfile.dir");
+    tie(%dbm, 'SDBM_File', $dbmfile, O_RDWR|O_CREAT, 0664)
+        or die "Couldn't tie SDBM file '$dbmfile': $!; aborting";
+    $self->{dbm} = \%dbm;
+    $self->init_dbmfile() if ($dbmfile_needs_initialization);
+
+    &App::sub_exit($self) if ($App::trace);
+    return($self);
+}
+
+sub run {
+    &App::sub_entry if ($App::trace);
+    my ($self) = @_;
+    my $context  = $self->{context};
+    my $options  = $context->{options};
+    my $prefix   = $context->{prefix};
+    my $silent   = $self->{silent};
+    my $done = 0;
+    my ($command, $command_entry);
+    my $repository = $options->{repository} || "default";
+
+    $self->{repository} = $repository;
+    $self->{pager}      = "default";
+    $self->{results}    = [];
+    $self->{params}     = {};
+
+    my ($prompt);
+
+    if (!$silent) {
+        print 
"#####################################################################################\n";
+        print "# Welcome to 'dash', the Data Access Shell\n";
+        print "# >>> Type 'help' at the command line for help. <<<\n";
+        print 
"#####################################################################################\n";
+    }
+    while (!$done) {
+        $repository = $self->{repository};
+        $prompt = $repository;
+
+        $command_entry = $self->get_command_entry($prompt, $options);
+        $done = $self->execute($command_entry, $options);
+    }
+    $self->shutdown();
+    print "Goodbye\n" if (!$silent);
+    &App::sub_exit() if ($App::trace);
+}
+
+sub shutdown {
+    &App::sub_entry if ($App::trace);
+    my ($self) = @_;
+
+    my $dbm = $self->{dbm};
+    untie %$dbm;
+
+    &App::sub_exit() if ($App::trace);
+}
+
+sub init_dbmfile {
+    &App::sub_entry if ($App::trace);
+    my ($self) = @_;
+
+    my $dbm = $self->{dbm};
+    # ... do stuff here ...
+
+    &App::sub_exit() if ($App::trace);
+}
+
+sub execute {
+    &App::sub_entry if ($App::trace);
+    my ($self, $command_entry, $options) = @_;
+    my $done = 0;
+    my $command = $self->get_command_from_command_entry($command_entry);
+    if (!$command) {
+        #print "I didn't understand that.\n";
+        #print "Please try 'help' for help on the available commands and their 
use\n";
+    }
+    elsif ($command eq "help" || $command eq "?") {
+        $self->execute_help_command($command_entry, $options);
+    }
+    elsif ($command eq "repository") {
+        $self->execute_repository_command($command_entry, $options);
+    }
+    elsif ($command eq "select") {
+        $self->execute_select_command($command_entry, $options);
+        $self->save_sql($command_entry);
+    }
+    elsif ($command eq "edit") {
+        $self->execute_edit_command($command_entry, $options);
+        $self->execute_run_command("run", $options);
+    }
+    elsif ($command eq "exit") {
+        $done = 1;
+    }
+    else {
+        print "I don't know the '$command' command.\n";
+        print "Please try 'help' for help on the available commands and their 
use\n";
+    }
+    &App::sub_exit($done) if ($App::trace);
+    return($done);
+}
+
+sub get_command_entry {
+    &App::sub_entry if ($App::trace);
+    my ($self, $prompt, $options) = @_;
+    my ($command_entry);
+    if ($self->{loaded_term_readline}) {
+        $command_entry = $self->get_command_entry_readline($prompt, $options);
+    }
+    else {
+        $command_entry = $self->get_command_entry_std($prompt, $options);
+    }
+    &App::sub_exit($command_entry) if ($App::trace);
+    return($command_entry);
+}
+
+sub get_multiline_command_entry {
+    &App::sub_entry if ($App::trace);
+    my ($self, $prompt, $ml_command_entry, $terminator, $options) = @_;
+    $prompt = "" if ($options->{silent});
+    my ($line);
+    while ($ml_command_entry !~ s/\s*$terminator\s*$//) {
+        $line = $self->get_command_entry($prompt, $options);
+        if (!$line || $line eq "exit") {
+            last;
+        }
+        else {
+            $ml_command_entry .= "\n" . $line;
+        }
+    }
+    &App::sub_exit($ml_command_entry) if ($App::trace);
+    return($ml_command_entry);
+}
+
+sub get_command_entry_std {
+    &App::sub_entry if ($App::trace);
+    my ($self, $prompt, $options) = @_;
+    print "$prompt> " if (!$options->{silent});
+    my $command_entry = <STDIN>;
+    $command_entry = "exit" if (!$command_entry && eof(STDIN));
+    &App::sub_exit($command_entry) if ($App::trace);
+    return($command_entry);
+}
+
+sub get_command_entry_readline {
+    &App::sub_entry if ($App::trace);
+    my ($self, $prompt, $options) = @_;
+    my $readline_prompt = $options->{silent} ? "" : "$prompt> ";
+    my $command_entry = $self->{terminal}->readline($readline_prompt);
+    &App::sub_exit($command_entry) if ($App::trace);
+    return($command_entry);
+}
+
+sub get_command_from_command_entry {
+    &App::sub_entry if ($App::trace);
+    my ($self, $command_entry) = @_;
+    my ($command);
+    if ($command_entry =~ /^([a-zA-Z_\?]+)/) {
+        $command = lc($1);
+    }
+    &App::sub_exit($command) if ($App::trace);
+    return($command);
+}
+
+sub execute_help_command {
+    &App::sub_entry if ($App::trace);
+    my ($self, $command_entry, $options) = @_;
+    print "============================================================\n";
+    print "COMMANDS:\n";
+    print "============================================================\n";
+    print "help       - [synonym: ?] show this list of commands\n";
+    print "select ... - run a select statement and make it the active 
statement\n";
+    print "repository [<name>] - set or show the current repository\n";
+    #print "table [<name>]      - set or show the current table\n";
+    #print "param [<name>]      - set or show the current params\n";
+    #print "save [<name>]       - save the active statement to a file\n";
+    #print "load [<name>]       - load a file as the active statement\n";
+    #print "result [<name>]     - redisplay a recently shown result set\n";
+    #print "show [<name>]       - redisplay a recently shown result set\n";
+    #print "run [<name>]        - (re-)run the active statement\n";
+    print "edit                - edit and run the last command on the command 
stack\n";
+    print "exit       - quit the program\n";
+    print ">>> for more help on any command, type 'help <command>'\n";
+    &App::sub_exit() if ($App::trace);
+}
+
+sub execute_repository_command {
+    &App::sub_entry if ($App::trace);
+    my ($self, $command_entry, $options) = @_;
+    if ($command_entry =~ /^\s*repository\s+(\w+)/s) {
+        $self->{repository} = $1;
+    }
+    &App::sub_exit() if ($App::trace);
+}
+
+sub execute_edit_command {
+    &App::sub_entry if ($App::trace);
+    my ($self, $command_entry, $options) = @_;
+    my $last_command_entry = $self->{last_command_entry} || "";
+    my $modified_command_entry = $self->edit($last_command_entry);
+    $self->{last_command_entry} = $modified_command_entry;
+    &App::sub_exit() if ($App::trace);
+}
+
+sub execute_run_command {
+    &App::sub_entry if ($App::trace);
+    my ($self, $command_entry, $options) = @_;
+    my $last_command_entry = $self->{last_command_entry} || "";
+    $self->execute($last_command_entry);
+    &App::sub_exit() if ($App::trace);
+}
+
+sub execute_select_command {
+    &App::sub_entry if ($App::trace);
+    my ($self, $command_entry, $options) = @_;
+    my $sql = $self->get_multiline_command_entry("sql", $command_entry, ";", 
$options);
+    my $context  = $self->{context};
+    my $db       = $context->repository($self->{repository});
+    my $columns  = [];
+    my ($rows);
+    eval {
+        $rows = $db->_do($sql, {columns => $columns});
+    };
+    if ($@) {
+        # [23480] 2009-03-10 23:48:55 DBI Exception (fail) in _do(): 
DBD::Oracle::db prepare failed: ORA-00942: table or view does not exist (DBD 
ERROR: error possibly near <*> indicator at char 16 in 'select foo from 
<*>boo') [for Statement "select foo from boo"] at 
/usr/rubicon/spadkins/src/p5ee/App-Repository/lib/App/Repository/DBI.pm line 
2556. select foo from booDBD::Oracle::db prepare failed: ORA-00942: table or 
view does not exist (DBD ERROR: error possibly near <*> indicator at char 16 in 
'select foo from <*>boo') [for Statement "select foo from boo"] at 
/usr/rubicon/spadkins/src/p5ee/App-Repository/lib/App/Repository/DBI.pm line 
2556.
+        my $e = $@;
+        #print "EXCEPTION: [$e]\n";
+        $e =~ s/ at (\S+) line .*//;
+        #print "EXCEPTION: (remove at*) [$e]\n";
+        $e =~ s/.* _do\(\): //;
+        #print "EXCEPTION: (remove _do()*) [$e]\n";
+        $e =~ s/(\(DBD ERROR:)/\n$1/;
+        #print "EXCEPTION: (newline before DBD ERROR) [$e]\n";
+        $e =~ s/(\[for Statement)/\n$1/;
+        #print "EXCEPTION: (newline before for Statement) [$e]\n";
+        print $e;
+    }
+    else {
+        my $results  = $self->{results};
+        push(@$results, $rows);
+        my $formats  = [];
+        my $table = "";
+        if ($sql =~ /\bfrom\s+(\w+)/is) {
+            $table = $1;
+        }
+        my $fh = $self->open_tempfile("result");
+        my $headings = $self->get_headings($db, $table, $columns, $options);
+        $self->print_table($fh, $rows, $columns, $formats, { compact => 
$options->{compact}, headings => $headings, silent => $options->{silent} });
+        system("less -Sin $self->{result}");
+    }
+    $self->{last_command_entry} = $command_entry;
+    &App::sub_exit() if ($App::trace);
+}
+
+sub edit {
+    &App::sub_entry if ($App::trace);
+    my ($self, $command_entry) = @_;
+    my $filename = $self->write_tempfile("edit", $command_entry);
+    system("vi $filename");
+    $command_entry = $self->read_tempfile("edit");
+    &App::sub_exit($command_entry) if ($App::trace);
+    return($command_entry);
+}
+
+sub open_tempfile {
+    &App::sub_entry if ($App::trace);
+    my ($self, $file_use) = @_;
+    my ($fh);
+    my $filename = $self->{$file_use};
+    if ($filename) {
+        open(App::RepositoryShell::FILE, "> $filename") || die "Unable to open 
temporary $file_use: $!\n";
+        $fh = \*App::RepositoryShell::FILE;
+    }
+    else {
+        my $tempdir = $self->{tempdir};
+        ($fh, $filename) = tempfile("${file_use}-XXXXXX", DIR => $tempdir, 
SUFFIX => ".txt", UNLINK => 1);
+        $self->{$file_use} = $filename;
+    }
+    &App::sub_exit($fh) if ($App::trace);
+    return($fh);
+}
+
+sub read_tempfile {
+    &App::sub_entry if ($App::trace);
+    my ($self, $file_use) = @_;
+    my ($fh);
+    my $data = "";
+    my $filename = $self->{$file_use};
+    if ($filename) {
+        open(App::RepositoryShell::FILE, "< $filename") || die "Unable to 
write $file_use file: $!\n";
+        local($/) = undef;
+        $data = <App::RepositoryShell::FILE>;
+        close(App::RepositoryShell::FILE);
+    }
+    &App::sub_exit($data) if ($App::trace);
+    return($data);
+}
+
+sub write_tempfile {
+    &App::sub_entry if ($App::trace);
+    my ($self, $file_use, $data) = @_;
+    my $fh = $self->open_tempfile($file_use);
+    print $fh $data;
+    close($fh);
+    my $filename = $self->{$file_use};
+    &App::sub_exit($filename) if ($App::trace);
+    return($filename);
+}
+
+sub save_sql {
+    &App::sub_entry if ($App::trace);
+    my ($self, $sql) = @_;
+    # ...
+    &App::sub_exit() if ($App::trace);
+}
+
+sub get_headings {
+    &App::sub_entry if ($App::trace);
+    my ($self, $db, $table, $columns, $options) = @_;
+    my $table_def = $db->get_table_def($table);
+    my $headings = [];
+    my ($alias);
+    foreach my $column (@$columns) {
+        if (!defined $table_def->{column}{$column}) {
+            if ($columns =~ /_/) {
+                $column = $self->symbol2abbr($column);
+            }
+            push(@$headings, $column);
+        }
+        elsif ($columns =~ /^\w+$/) {
+            $alias = $table_def->{column}{$column}{alias} || "";
+            if (!$alias || $alias =~ /^c\d+$/) {
+                $alias = $self->symbol2abbr($column);
+            }
+            push(@$headings, $alias);
+        }
+        else {
+            push(@$headings, $column);
+        }
+    }
+    &App::sub_exit($headings) if ($App::trace);
+    return($headings);
+}
+
+sub symbol2abbr {
+    &App::sub_entry if ($App::trace);
+    my ($self, $symbol) = @_;
+    my ($abbr);
+    if ($symbol =~ /_/) {
+        $symbol = $self->symbol2abbr($symbol);
+        my @abbr = grep { s/^(.).*//; } split(/_/, $symbol);
+        $abbr = join("", @abbr);
+    }
+    else {
+        $abbr = $symbol;
+    }
+    &App::sub_exit($abbr) if ($App::trace);
+    return($abbr);
+}
+
+sub print_table {
+    &App::sub_entry if ($App::trace);
+    my ($self, $fh, $rows, $columns, $formats, $options) = @_;
+    my ($row, $r, $c, $elem, $format, $len, $f, $heading);
+    my (@autoformat);
+    my $headings = $options->{headings};
+
+    # compute the number of columns as the max columns of any row
+    my $max_columns = 0;
+    for ($r = 0; $r <= $#$rows; $r++) {
+        $row = $rows->[$r];
+        if ($max_columns < $#$row + 1) {
+            $max_columns = $#$row + 1;
+        }
+    }
+
+    # compute automatic sprintf formats
+    for ($c = 0; $c < $max_columns; $c++) {
+
+        if (! defined $autoformat[$c]) {
+            $autoformat[$c] = {
+                max_length => 0,
+                type => 2,        # 0=string, 1=float, 2=integer
+                min => undef,
+                max => undef,
+            };
+        }
+        $f = $autoformat[$c];
+
+        # set the length of the column by the length of its heading
+        $heading = ($headings && $headings->[$c]) ? $headings->[$c] : "";
+        if ($heading) { 
+            $len = length($heading);
+            if ($len > $f->{max_length}) {
+                $f->{max_length} = $len;
+            }
+        }
+        elsif (! $options->{compact}) { 
+            $len = length($columns->[$c]);
+            if ($len > $f->{max_length}) {
+                $f->{max_length} = $len;
+            }
+        }
+
+        for ($r = 0; $r <= $#$rows; $r++) {
+            $row = $rows->[$r];
+            if ($c <= $#$row && defined $row->[$c]) {
+                $elem = $row->[$c];
+                $len = length($elem);
+                if ($elem =~ /^-?[0-9]*\.[0-9]+$/) {  # float
+                    $len = length(sprintf("%.$App::options{decimals}f",$elem));
+                    $f->{type} = 1 if ($f->{type} > 1);
+                    if (!defined $f->{min} || $elem < $f->{min}) {
+                        $f->{min} = $elem;
+                    }
+                    if (!defined $f->{max} || $elem < $f->{max}) {
+                        $f->{max} = $elem;
+                    }
+                }
+                elsif ($elem =~ /^-?[0-9]+$/) { # integer
+                    if (!defined $f->{min} || $elem < $f->{min}) {
+                        $f->{min} = $elem;
+                    }
+                    if (!defined $f->{max} || $elem < $f->{max}) {
+                        $f->{max} = $elem;
+                    }
+                }
+                else {
+                    $f->{type} = 0;
+                }
+                $f->{max_length} = $len if ($len > $f->{max_length});
+            }
+        }
+        $self->determine_sprintf_fmt($f);
+    }
+    if (!$options->{silent}) {
+        for ($c = 0; $c <= $#$columns; $c++) {
+            $format = $autoformat[$c]->{title_fmt} || "%s";
+            print $fh " " if ($c > 0);
+            $heading = ($headings && $headings->[$c]) ? $headings->[$c] : 
$columns->[$c];
+            printf($fh $format, $heading);
+        }
+        print $fh "\n";
+    }
+    for ($r = 0; $r <= $#$rows; $r++) {
+        $row = $rows->[$r];
+        for ($c = 0; $c <= $#$row; $c++) {
+            $format = $autoformat[$c]->{fmt} || "%s";
+            print $fh " " if ($c > 0);
+            printf($fh $format, $row->[$c]);
+        }
+        print $fh "\n";
+    }
+    &App::sub_exit() if ($App::trace);
+}
+
+sub determine_sprintf_fmt {
+    &App::sub_entry if ($App::trace);
+    my ($self, $f) = @_;
+    my ($width, $int_len, $fract_len);
+    if ($f->{type} == 1) {     # float
+        $f->{title_fmt} = "%$f->{max_length}.$f->{max_length}s";
+        $f->{fmt} = "%$f->{max_length}.$App::options{decimals}f";
+    }
+    elsif ($f->{type} == 2) {  # integer
+        $f->{title_fmt} = "%$f->{max_length}.$f->{max_length}s";
+        $f->{fmt} = "%$f->{max_length}d";
+    }
+    else {                     # string
+        $f->{title_fmt} = "%-$f->{max_length}.$f->{max_length}s";
+        $f->{fmt} = "%-$f->{max_length}s";
+    }
+    &App::sub_exit($f->{fmt}) if ($App::trace);
+}
+
+=head1 ACKNOWLEDGEMENTS
+
+ * Author:  Stephen Adkins <[email protected]>
+ * License: This is free software. It is licensed under the same terms as Perl 
itself.
+
+=head1 SEE ALSO
+
+L<C<App::Context>|App::Context>,
+L<C<App::Repository>|App::Repository>
+
+=cut
+
+1;
+

Reply via email to