view release on metacpan or search on metacpan
Revision history for App::AltSQL
v0.05 Fri, 01 Jun 2012 12:49:25 -0400
- Adding pre-release Term::Plugin::SyntaxHighlight and View::Plugin::VerticalSorted
- Customizable prompt in altsql config with substitutions (and test file)
- Cache autocomplete and fix minor issue with it
- Allow CLI -p to override .my.cnf if empty string
- Refactor Model::MySQL render_prompt into parse_prompt
- Add setup() method to subclasses
- View::Plugin::UnicodeBox header_reminder option (default: 100)
v0.04 Tue, 29 May 2012 08:51:45 -0400
- Fixing build dependency issue
v0.03 Fri, 25 May 2012 15:39:15 -0400
- Adding Dump plugin [contributed by https://github.com/joeshin]
- Parsing of .my.cnf w/ tests [contributed by https://github.com/spikegrobstein]
- Change the prompt with .my.cnf 'prompt' setting
- Adding INSTALL document and some minor build changes
- Use JSON instead of JSON::XS
- Make non-critical dependencies optional
v0.02 Fri May 18 10:33:00 2012 -0400
- Remove unused dependency on Switch
v0.01 Wed May 16 10:07:51 2012 -0400
Build.PL
Makefile.PL
MANIFEST This list of files
MANIFEST.SKIP
META.json
META.yml
README.pod
INSTALL
t/001_arguments.t
t/002_my_cnf.t
t/003_custom_prompt.t
t/sql/sakila-data.sql
t/sql/sakila-schema.sql
lib/App/AltSQL/View/Plugin/VerticalSorted.pm
lib/App/AltSQL/Term/Plugin/SyntaxHighlight.pm
SIGNATURE Added here by Module::Build
{
"abstract" : "A drop in replacement to the MySQL prompt with a pluggable Perl interface",
"author" : [
"Eric Waters <ewaters@gmail.com>"
],
"dynamic_config" : 1,
"generated_by" : "Module::Build version 0.4, CPAN::Meta::Converter version 2.120921",
"license" : [
"perl_5"
],
"meta-spec" : {
"url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec",
---
abstract: 'A drop in replacement to the MySQL prompt with a pluggable Perl interface'
author:
- 'Eric Waters <ewaters@gmail.com>'
build_requires:
Data::Structure::Util: 0
File::Temp: 0
Test::Deep: 0
Test::More: 0
configure_requires:
Module::Build: 0.4
dynamic_config: 1
=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
SHA1 3777b4e109f92b1c23133e2b8d0c0d9d3921a3af lib/App/AltSQL/Plugin/Tail.pm
SHA1 f6f428f3638ce210530ae2f0bd0ca013fca1d5f3 lib/App/AltSQL/Role.pm
SHA1 77e74e69405edaf19d1e754a76f819ace1f4c66c lib/App/AltSQL/Term.pm
SHA1 e2317928d205a77cb468b6b62467309da14c65c0 lib/App/AltSQL/Term/Plugin/SyntaxHighlight.pm
SHA1 6f33fba35b8fba9fc394433b758d5ad75ff93d3e lib/App/AltSQL/View.pm
SHA1 fcedef0e932f6fb7075a3efb972f1d0024ca7d4a lib/App/AltSQL/View/Plugin/Color.pm
SHA1 b9fc3f044721e50774eee7f6d1237372a9ddbdd2 lib/App/AltSQL/View/Plugin/UnicodeBox.pm
SHA1 1ab97c3a0aed03f82381e8f93d28786e58c05008 lib/App/AltSQL/View/Plugin/VerticalSorted.pm
SHA1 9bdf133e2624f3313301047fb9f3fa6cd04a90fe t/001_arguments.t
SHA1 23bfff35f585a5ed0b41663d10a170c394a4e13a t/002_my_cnf.t
SHA1 2dc4a0db89a14e56f423730c5ed7f33d495c847e t/003_custom_prompt.t
SHA1 f766cc9f9823bb28809c7c5110754941b8428599 t/sql/sakila-data.sql
SHA1 4c2ea3fd4b8dfd1b9fb8bf40df59dca068673708 t/sql/sakila-schema.sql
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)
iEYEARECAAYFAk/I8+8ACgkQHHIpFH/pKkIRKgCfYW0r4GZn9VRB/h0lCjsWOMC3
XOAAn06qVRa/QLTufTRIB/Ux+glV+JGr
=6Whb
-----END PGP SIGNATURE-----
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
lib/App/AltSQL.pm view on Meta::CPAN
=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...
lib/App/AltSQL.pm view on Meta::CPAN
=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:
lib/App/AltSQL.pm view on Meta::CPAN
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}> '
# 'username' (in red) ' YYYY-MM-DD HH:MM:SS> '
Provide a custom prompt. The following variables will be interpolated:
=over 4
=item B<%u>
The username used to connect to the model
=item B<%d>
The current database or '(none)'
lib/App/AltSQL/Model/MySQL.pm view on Meta::CPAN
=over 4
=item B<user>
=item B<password>
=item B<host>
=item B<port>
=item B<prompt>
=item B<safe_update>
=item B<select_limit>
=tiem B<no_auto_rehash>
=back
=cut
lib/App/AltSQL/Model/MySQL.pm view on Meta::CPAN
};
if ($@) {
return 0; # when we use this we check for definedness as well as boolean
}
return DBIx::MyParsePP->new();
});
has 'dbh' => (is => 'rw');
has 'current_database' => (is => 'rw');
has [qw(host user password database port)] => ( is => 'ro' );
has [qw(no_auto_rehash select_limit safe_update prompt)] => ( is => 'ro' );
sub args_spec {
return (
host => {
cli => 'host|h=s',
help => '-h HOSTNAME | --host HOSTNAME',
},
user => {
cli => 'user|u=s',
help => '-u USERNAME | --user USERNAME',
lib/App/AltSQL/Model/MySQL.pm view on Meta::CPAN
cli => 'no-auto-rehash|A',
help => "-A --no-auto-rehash -- Don't scan the information schema for tab autocomplete data",
},
);
}
sub setup {
my $self = shift;
$self->find_and_read_configs();
# If the user has configured a custom prompt in .my.cnf and not one in the config, use that in the Term instance
if ($self->prompt && ! $self->app->config->{prompt}) {
$self->app->term->prompt( $self->parse_prompt() );
}
}
sub find_and_read_configs {
my $self = shift;
my @config_paths = (
"$ENV{HOME}/.my.cnf",
);
foreach my $path (@config_paths) {
(-e $path) or next;
$self->read_my_dot_cnf($path);
}
}
sub read_my_dot_cnf {
my $self = shift;
my $path = shift;
my @valid_keys = qw( user password host port database prompt safe_update select_limit no_auto_rehash ); # keys we'll read
my @valid_sections = qw( client mysql ); # valid [section] names
my @boolean_keys = qw( safe_update no_auto_rehash );
open MYCNF, "<$path";
# ignore lines in file until we hit a valid [section]
# then read key=value pairs
my $in_valid_section = 0;
while(<MYCNF>) {
lib/App/AltSQL/Model/MySQL.pm view on Meta::CPAN
sub show_sql_error {
my ($self, $input, $char_number, $line_number) = @_;
my @lines = split /\n/, $input;
my $line = $lines[ $line_number - 1 ];
$self->log_error("There was an error parsing the SQL statement on line $line_number:");
$self->log_error($line);
$self->log_error(('-' x ($char_number - 1)) . '^');
}
my %prompt_substitutions = (
S => ';',
"'" => "'",
'"' => '"',
v => 'TODO-server-version',
p => sub { shift->{self}->port },
'\\' => '\\',
n => "\n",
t => "\t",
'_' => ' ',
' ' => ' ',
lib/App/AltSQL/Model/MySQL.pm view on Meta::CPAN
O => '%t{%b}',
R => '%t{%k}',
r => '%t{%I}',
m => '%t{%M}',
s => '%t{%S}',
P => '%t{%p}',
);
=cut
Take a .my.cnf prompt format and convert it into Term escape options
Reference:
http://www.thegeekstuff.com/2010/02/mysql_ps1-6-examples-to-make-your-mysql-prompt-like-angelina-jolie/
=cut
sub parse_prompt {
my $self = shift;
my $parsed_prompt = $self->prompt;
$parsed_prompt =~ s{\\\\(.)}{
my $substitute = $prompt_substitutions{$1};
if (! $substitute) {
"$1";
}
elsif (ref $substitute) {
$substitute->($self);
}
else {
$substitute;
}
}exg;
return $parsed_prompt;
}
no Moose;
__PACKAGE__->meta->make_immutable;
=head1 COPYRIGHT
Copyright (c) 2012 Eric Waters and Shutterstock Images (http://shutterstock.com). All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the LICENSE file included with this module.
lib/App/AltSQL/Term.pm view on Meta::CPAN
use JSON qw(encode_json decode_json);
use Term::ANSIColor;
with 'App::AltSQL::Role';
with 'MooseX::Object::Pluggable';
has 'term' => (
is => 'ro',
lazy_build => 1,
);
has 'prompt' => (
is => 'rw',
default => 'altsql> ',
);
has 'history_fn' => ( is => 'ro' );
has 'autocomplete_entries' => ( is => 'rw' );
sub args_spec {
return (
history_fn => {
cli => 'history=s',
lib/App/AltSQL/Term.pm view on Meta::CPAN
);
}
sub BUILD {
my $self = shift;
$self->log_info("Ctrl-C to reset the line; Ctrl-D to exit");
}
sub setup {
my $self = shift;
if (my $custom_prompt = $self->app->config->{prompt}) {
$self->prompt($custom_prompt);
}
}
sub _build_term {
my $self = shift;
my $term = Term::ReadLine::Zoid->new("altsql-shell");
$self->{term} = $term;
# Require the tab key to be hit twice before showing the list of autocomplete items
lib/App/AltSQL/Term.pm view on Meta::CPAN
$self->term->accept_line();
}
else {
$self->term->insert_line();
}
}
sub readline {
my $self = shift;
return $self->term->readline($self->render_prompt());
}
sub completion_function {
my ($self, $word, $buffer, $start) = @_;
#$self->log_debug("\ncompletion_function: '$word', '$buffer', '$start'");
my $hash = $self->autocomplete_entries;
return () unless $hash;
lib/App/AltSQL/Term.pm view on Meta::CPAN
my ($width, $height) = $self->term->TermSize();
return $width;
}
sub get_term_height {
my $self = shift;
my ($width, $height) = $self->term->TermSize();
return $height;
}
my %prompt_substitutions = (
u => sub { shift->{self}->app->model->user },
d => sub { shift->{self}->app->model->current_database || '(none)' },
h => sub { shift->{self}->app->model->host },
'%' => '%',
);
my %block_prompt_substitutions = (
c => sub {
my ($context, $block) = @_;
return color($block);
},
e => sub {
my ($context, $block) = @_;
# Make '$self' expected in the current scope so the $block can reference it
my $self = $context->{self};
my $return = eval $block;
if (my $ex = $@) {
lib/App/AltSQL/Term.pm view on Meta::CPAN
t => sub {
my ($context, $format) = @_;
my $now = $context->{date};
if (! $now) {
return 'err';
}
return $now->strftime($format);
},
);
sub render_prompt {
my ($self, $now) = @_;
if (! defined $self->{_has_datetime}) {
eval { require DateTime; };
$self->{_has_datetime} = $@ ? 0 : 1;
}
if (! $now && $self->{_has_datetime}) {
$now = DateTime->now( time_zone => 'local' );
}
my %context = (
self => $self,
date => $now,
);
my $prompt = $self->prompt;
my $output = '';
while (length $prompt) {
my $char = substr $prompt, 0, 1, '';
# We're looking for a closing brace
if ($context{requires_block}) {
if ($char eq '}' && --$context{brace_count} == 0) {
# Block is complete
my $sub = $block_prompt_substitutions{ $context{symbol} };
$output .= $sub->(\%context, delete $context{block});
delete $context{requires_block};
delete $context{brace_count};
next;
}
$context{block} .= $char;
if ($char eq '{') {
$context{brace_count}++;
}
next;
}
if ($char eq '%') {
$context{symbol} = substr $prompt, 0, 1, '';
if ($block_prompt_substitutions{ $context{symbol} } && substr($prompt, 0, 1) eq '{') {
substr $prompt, 0, 1, ''; # shift the '{'
$context{requires_block} = 1;
$context{block} = '';
$context{brace_count} = 1;
}
else {
my $sub = $prompt_substitutions{ $context{symbol} };
if (! $sub) {
$self->log_error("Unrecognized prompt substitution '$context{symbol}'");
$output .= $char;
}
elsif (ref $sub) {
$output .= $sub->(\%context);
}
else {
$output .= $sub;
}
}
next;
t/002_my_cnf.t view on Meta::CPAN
my $filename = write_config(<<ENDFILE);
[client]
user = ewaters
password = 12345
host = localhost
[mysql]
database = sakila
default-character-set = utf8
prompt = \\u@\\h[\\R:\\m:\\s]>
safe-update = false
ENDFILE
$instance->read_my_dot_cnf($filename);
unbless( $instance );
cmp_deeply(
$instance,
superhashof({
user => 'ewaters',
password => '12345',
host => 'localhost',
database => 'sakila',
safe_update => 0,
prompt => '\\u@\\h[\\R:\\m:\\s]>',
}),
'Multi-section my.cnf',
);
unlink $filename;
}
{
my $instance = App::AltSQL::Model::MySQL->new( app => $app );
my $filename = write_config(<<ENDFILE);
[client]
user = ewaters
password = 12345
host = localhost
[mysql]
database = sakila
default-character-set = utf8
prompt = \\u@\\h[\\R:\\m:\\s]>
safe-update = false
ENDFILE
$instance->read_my_dot_cnf($filename);
unbless($instance);
cmp_deeply(
$instance,
superhashof({
user => 'ewaters',
password => '12345',
host => 'localhost',
database => 'sakila',
safe_update => 0,
prompt => '\\u@\\h[\\R:\\m:\\s]>',
}),
'Multi-section my.cnf',
);
unlink $filename;
}
{
my $instance = App::AltSQL::Model::MySQL->new( app => $app );
my $filename = write_config(<<ENDFILE);
t/003_custom_prompt.t view on Meta::CPAN
my $term = App::AltSQL::Term->new(
app => $app,
);
$app->{term} = $term;
#$model->setup();
#$term->setup();
## Testing
$term->prompt('myprompt> ');
is $term->render_prompt(), 'myprompt> ', "Basic, non-special prompt";
$term->prompt('myprompt%%> ');
is $term->render_prompt(), 'myprompt%> ', "Escaped percent sign";
$term->prompt('%u@%h> ');
is $term->render_prompt(), 'testuser@localhost> ', "Some substitutions";
$term->prompt('(%u@%h) [%d]> ');
is $term->render_prompt(), '(testuser@localhost) [(none)]> ', "Issue #28, without database";
$model->current_database('saklia');
$term->prompt('(%u@%h) [%d]> ');
is $term->render_prompt(), '(testuser@localhost) [saklia]> ', "Issue #28, with database";
## Perl eval
$term->prompt('%e{ ++( $self->{_statement_counter} ) }> ');
is $term->render_prompt(), '1> ', "Arbitrary perl";
is $term->render_prompt(), '2> ', "Arbitrary perl, part two";
$term->prompt('%e{ missing end brace');
is $term->render_prompt(), '', "Missing end brace";
$term->prompt('%e{ invalid perl code }');
is $term->render_prompt(), 'err', "Invalid perl code";
## Color
$term->prompt('%c{bold}%u%c{reset}%c{bold red}@%h%c{reset}> ');
is $term->render_prompt(),
colored('testuser', 'bold') . colored('@localhost', 'bold red') . '> ',
"Colored prompt";
## MySQL .my.cnf format
$model->{prompt} = '\\\\u@\\\\h[\\\\d]\\\\_';
is $model->parse_prompt(), '%u@%h[%d] ', "MySQL-style prompt converted into local style";
$model->{prompt} = '(\\\\u@\\\\h) [\\\\d]>\\\\_';
$term->prompt( $model->parse_prompt );
is $term->render_prompt(), '(testuser@localhost) [saklia]> ', "Issue #28 from .my.cnf";
## DateTime testing
eval { require DateTime; };
if ($@) {
print STDERR "Can't continue testing without DateTime\n";
done_testing;
exit;
}
my $now = DateTime->now();
$term->prompt('%u@%h[%t{%H:%M:%S}]> ');
is $term->render_prompt($now), 'testuser@localhost['.$now->strftime('%H:%M:%S').']> ', "Simple DateTime substitution";
done_testing;