App-AltSQL
view release on metacpan or search on metacpan
lib/App/AltSQL.pm view on Meta::CPAN
package App::AltSQL;
=encoding utf-8
=head1 NAME
App::AltSQL - A drop in replacement to the MySQL prompt with a pluggable Perl interface
=head1 SYNOPSIS
./altsql -h <host> -u <username> -D <database> -p<password>
altsql> select * from film limit 4;
âââââââââââ¤âââââââââââââââââââ¤ââââââââââââââââââââââââââââ
â film_id â title â description
âââââââââââªâââââââââââââââââââªââââââââââââââââââââââââââââ
â 1 â ACADEMY DINOSAUR â A Epic Drama of a Feminist
â 2 â ACE GOLDFINGER â A Astounding Epistle of a D
â 3 â ADAPTATION HOLES â A Astounding Reflection of
â 4 â AFFAIR PREJUDICE â A Fanciful Documentary of a
âââââââââââ§âââââââââââââââââââ§ââââââââââââââââââââââââââââ
4 rows in set (0.00 sec)
=head1 DESCRIPTION
AltSQL is a way to improve your user experience with C<mysql>, C<sqlite3>, C<psql> and other tools that Perl has L<DBI> drivers for. Currently written for MySQL only, the long term goal of this project is to provide users of the various SQL-based da...
There are a few key issues that this programmer has had with using the mysql client every day. After looking for alternatives and other ways to fix the problems, reimplementing the client in Perl seemed like the easiest approach, and lent towards th...
=over 4
=item Ctrl-C kills the program
All of the shells that we used on a daily basis allow you to abandon the half-written statement on the prompt by typing Ctrl-C. Spending all day in shells, you expect this behavior to be consistent, but you do this in mysql and you will be thrown to...
=item Wide output wraps
We are grateful that mysql at least uses ASCII art for table formatting (unlike C<sqlite3> for some reason). But there are some tables that I work with that have many columns, with long names (it's often easier to keep adding columns to a table over...
Suffice it to say, it's a much better experience if, just like with C<git diff>, wide output is left wide, and you are optionally able to scroll horizontally with your arrow keys like you wanted in the first place.
=item Color
Most other modern programs we developers use on a daily basis (vim, ls, top, git, tmux, screen) offer to provide additional context to you via color. By consistently setting colors on a variable type or file type, programs can convey to us additiona...
=item Unicode Box characters
The usage of '|', '+' and '-' for drawing tables and formatting data seems a bit antiquated. Other tools are adopting Unicode characters, and most programmers are now using terminal programs that support Unicode and UTF8 encoding natively. The Unic...
=back
I've thought of a number of other features, but so too have my coworkers and friends. Most people I've spoken with have ideas for future features. Next time you're using your DB shell and find yourself irritated at a feature or bug in the software ...
=head1 CONFIGURATION
The command line arguments inform how to connect to the database, whereas the configuration file(s) provide behavior and features of the UI.
=head2 Command Line
The following options are available.
=over 4
=item -h HOSTNAME | --host HOSTNAME
=item -u USERNAME | --user USERNAME
=item -p | --password=PASSWORD | -pPASSWORD
=item --port PORT
=item -D DATABASE | --database DATABASE
Basic connection parameters to the MySQL database.
=item --A | --no-auto-rehash
By default, upon startup and whenever the database is changed, the C<information_schema> tables will be read to perform tab completion. Disable this behavior to get a faster startup time (but no tab complete).
=back
=head2 Config File
We are using L<Config::Any> for finding and parsing the configuration file. You may use any format you'd like to write it so long as it's support in C<Config::Any>.
=over 4
=item /etc/altsql.(yml|cnf|ini|js|pl)
=item ~/.altsql.(yml|cnf|ini|js|pl)
Write your configuration file to either the system or the local configuration locations. The local file will inherit from the global configuration but with local modifications. For purposes of this example I'll be writing out the config in YAML, bu...
=back
---
prompt: 'altsql> '
plugins:
- Tail
- Dump
view_plugins:
- Color
- UnicodeBox
App::AltSQL::View::Plugin::Color:
header_text:
default: red
cell_text:
is_null: blue
is_primary_key: bold
is_number: yellow
App::AltSQL::View::Plugin::UnicodeBox:
style: heavy_header
split_lines: 1
plain_ascii: 0
This is the default configuration, and currently encompasses all the configurable settings. This should be future safe; as you can see, plugins may use this file for their own variables as there are namespaced sections.
=over 4
=item B<prompt>
prompt: "%u@%h[%d]> "
# 'username@hostname[database]> '
prompt: "%c{red}%u%c{reset} %t{%F %T}> '
lib/App/AltSQL.pm view on Meta::CPAN
$self->args->{view_args} = \%args;
}
else {
$self->{$subclass} = $subclass_name->new({
app => $self,
%args,
});
}
}
# Call setup on each subclass now that they're all created
foreach my $subclass (qw(term model)) {
$self->{$subclass}->setup();
}
$self->model->db_connect();
}
=head2 parse_cli_args \@ARGV
Called in C<bin/altsql> to collect command line arguments and return a hashref
=cut
sub parse_cli_args {
my ($class, $argv, %args) = @_;
my @argv = defined $argv ? @$argv : ();
# Read in the args_spec() from each subclass we'll be using
my %opts_spec;
$args{term_class} ||= $_default_classes{term};
$args{view_class} ||= $_default_classes{view};
$args{model_class} ||= $_default_classes{model};
foreach my $args_class ('main', 'view', 'term', 'model') {
if ($args_class eq 'main') {
my %args_spec = $class->args_spec();
foreach my $arg (keys %args_spec) {
next unless $args_spec{$arg}{cli};
$opts_spec{ $args_spec{$arg}{cli} } = \$args{$arg};
}
}
else {
my $args_classname = $args{"${args_class}_class"};
eval "require $args_classname";
die $@ if $@;
my %args_spec = $args_classname->args_spec();
foreach my $key (keys %args_spec) {
next unless $args_spec{$key}{cli};
$opts_spec{ $args_spec{$key}{cli} } = \$args{"_${args_class}_$key"};
if (my $default = $args_spec{$key}{default}) {
$args{"_${args_class}_$key"} = $default;
}
}
}
}
# Password is a special case
foreach my $i (0..$#argv) {
my $arg = $argv[$i];
next unless $arg =~ m{^(?:-p|--password=)(.*)$};
splice @argv, $i, 1;
if (length $1) {
$args{_model_password} = $1;
# Remove the password from the program name so people can't see it in process listings
$0 = join ' ', $0, @argv;
}
else {
# Prompt the user for the password
require Term::ReadKey;
Term::ReadKey::ReadMode('noecho');
print "Enter password: ";
$args{_model_password} = Term::ReadKey::ReadLine(0);
Term::ReadKey::ReadMode('normal');
print "\n";
chomp $args{_model_password};
}
last; # I've found what I was looking for
}
GetOptionsFromArray(\@argv, %opts_spec);
# Database is a special case; if left over arguments, that's the database name
if (@argv && int @argv == 1) {
$args{_model_database} = $argv[0];
}
return \%args;
}
=head2 resolve_namespace_config_value $namespace, $key | [ $key1, $key2, ... ], \%default_config
$self->resolve_namespace_config_value('MyApp', 'timeout', { timeout => 60 });
# Will search $self->config->{MyApp}{timeout} and will return that or the default 60 if not present
Provides plugin authors with easy access to the configuration file. Provide either an arrayref of keys for deep hash matching or a single key for a two dimensional hash.
=cut
sub resolve_namespace_config_value {
my ($self, $namespace, $key_or_keys, $default_config) = @_;
my $return;
my $cache_key = join ':', $namespace, ref $key_or_keys ? @$key_or_keys : $key_or_keys;
if (exists $self->{_resolve_namespace_config_value_cache}{$cache_key}) {
return $self->{_resolve_namespace_config_value_cache}{$cache_key};
}
if (ref $key_or_keys && int @$key_or_keys > 1) {
my @keys = @$key_or_keys;
my $first_key = shift @keys;
my $default_hash = $default_config->{$first_key};
my $defined_hash = $self->get_namespace_config_value($namespace, $first_key) || {};
my $config = union([ $default_hash, $defined_hash ]);
$return = _find_hash_value($config, @keys);
}
else {
my $default = $default_config->{$key_or_keys};
my $defined = $self->get_namespace_config_value($namespace, $key_or_keys) || undef;
$return = defined $defined ? $defined : $default;
}
$self->{_resolve_namespace_config_value_cache}{$cache_key} = $return;
return $return;
}
sub _find_hash_value {
my ($config, @keys) = @_;
my $key = shift @keys;
return undef if ! defined $key;
return undef if ! exists $config->{$key};
my $value = $config->{$key};
if (ref $value && ref $value eq 'HASH') {
return _find_hash_value($value, @keys);
}
return $value;
( run in 1.399 second using v1.01-cache-2.11-cpan-d7f47b0818f )