Rework hostname expansion

Didn't like relying on the bash shell for expansions - instead recoded it to be pure-perl so it is more cross-platform.

Github issue #53
This commit is contained in:
Duncan Ferguson 2016-04-29 18:33:50 +01:00
parent 08d7fead03
commit 8b8a692b6c
8 changed files with 142 additions and 37 deletions

View file

@ -1,6 +1,7 @@
4.07_1 0000-00-00 Duncan Ferguson <duncan_ferguson@user.sf.net>
- Fixed tests on systems where bash is not installed in /bin/bash (Github issue #60)
- Include link to travis-ci site in release emails for automated build and test reports
- Rework hostname expansion to be pure-perl rather than relying on the bash shell (Github issue #53)
4.06 2016-03-26 Duncan Ferguson <duncan_ferguson@user.sf.net>
- Failure to find the terminal binary should not be fatal

View file

@ -11,6 +11,7 @@ use Try::Tiny;
use English qw( -no_match_vars );
use base qw/ App::ClusterSSH::Base /;
use App::ClusterSSH::Range;
our $master_object_ref;
@ -220,23 +221,18 @@ sub expand_glob {
my ( $self, $type, $name, @items ) = @_;
my @expanded;
my $range = App::ClusterSSH::Range->new();
# skip expanding anything that appears to have nasty metachars
if ( !grep {m/[\`\!\$;]/} @items ) {
if ( grep {m/[{]/} @items ) {
#@expanded = split / /, `/bin/bash -c 'shopt -s extglob\n echo @items'`;
my $shell = $self->config->find_binary(
$self->parent->config->{shell} );
my $cmd
= $shell . q{ } . $self->parent->config->{shell_expansion};
$cmd =~ s/%items%/@items/;
@expanded = split / /, `$cmd`;
chomp(@expanded);
}
else {
@expanded = map { glob $_ } @items;
}
$self->debug( 4, "Non-expanded: @items" );
@items = $range->expand(@items);
# run glob over anything left incase there are numeric and textual ranges
@expanded = map { glob $_ } @items;
$self->debug( 4, "Final expansion: @expanded" );
}
else {
warn(

View file

@ -102,9 +102,6 @@ my %default_config = (
send_menu_xml_file => $ENV{HOME} . '/.clusterssh/send_menu',
shell => "bash",
shell_expansion => "-c 'shopt -s extglob\n echo %items%'",
# don't set username here as takes precendence over ssh config
user => '',
);

View file

@ -563,7 +563,7 @@ would replace the <Alt-n> with the client's name in each window.}
output $self->loc(
q{All comments (marked by a #) and blank lines are ignored. Tags may be nested, but be aware of using recursive tags as they are not checked for.}
);
output $self->loc(q{Servers can be defined using bash shell expansion:});
output $self->loc(q{Servers can be defined using expansion macros:});
output 'C<< webservers websvr{a,b,c} >>';
output $self->loc(q{would be expanded to});
output 'C<< webservers websvra websvrb websvrc >>';
@ -571,10 +571,7 @@ would replace the <Alt-n> with the client's name in each window.}
output 'C<< webservers websvr{6..9} >>';
output $self->loc(q{would be expanded to});
output 'C<< webservers websvr6 websvr7 websvr8 websvr9 >>';
output $self->loc(
q{B<NOTE:> this requires [_1] to be installed on your system (see [_2] configuration option },
'bash', 'C<shell_expansion>'
);
output $self->loc(
q{Extra cluster files may also be specified either as an option on the command line (see [_1]) or in the user's [_2] file (see [_3] configuration option).},
'C<cluster-file>',
@ -772,13 +769,6 @@ B<NOTE:> Any "generic" change to the method (e.g., specifying the ssh port to us
q{Number of pixels from the screen's side to reserve when calculating screen geometry for tiling. Setting this to something like 50 will help keep cssh from positioning windows over your window manager's menu bar if it draws one at that side of the screen.}
);
output q{=item shell = bash};
output q{=item shell_expansion = -c 'shopt -s extglob\n echo %items%'"};
output $self->loc(
q{Command used to expand a given string (provided by the macro [_1]) - used for expanding host names when a [_2] is in the name. See [_3]},
'C<%items%>', 'C<{>', 'L<bash/EXPANSION>'
);
output '=item terminal = /path/to/xterm';
output $self->loc(q{Path to the X-Windows terminal used for the client.});

View file

@ -0,0 +1,64 @@
use strict;
use warnings;
package App::ClusterSSH::Range;
# ABSTRACT: Expand ranges such as {0..1} as well as other bsd_glob specs
=head1 SYNOPSIS
use App::ClusterSSH::Range;
my $range=App::ClusterSSH::Range->new();
my @list = $range->expand('range{0..5}');
=head1 DESCRIPTION
This module adds in the numbered range specification as found in Bash
EXPANSIONS (see the bash S<man> page) before putting the same string
through C<bsd_glob>.
=cut
use File::Glob ':bsd_glob';
sub new {
my ( $class, %args ) = @_;
my $self = {%args};
return bless $self, $class;
}
sub expand {
my ( $self, @items ) = @_;
my $range_regexp = qr/^\w+\{[\w\.,]+\}$/;
my @newlist;
foreach my $item (@items) {
if ( $item !~ m/$range_regexp/ ) {
push( @newlist, $item );
next;
}
my ( $base, $spec ) = $item =~ m/^(.*)?\{(.*)\}$/;
for my $section ( split( /,/, $spec ) ) {
my ( $start, $end );
if ( $section =~ m/\.\./ ) {
( $start, $end ) = split( /\.\./, $section, 2 );
}
$start //= $section;
$end //= $start;
foreach my $number ( $start .. $end ) {
push( @newlist, "$base$number" );
}
}
}
my @text = map { bsd_glob($_) } @newlist;
return wantarray ? @text : "@text";
}
1;

View file

@ -116,9 +116,6 @@ Readonly::Hash my %default_config => {
send_menu_xml_file => $ENV{HOME} . '/.clusterssh/send_menu',
shell => "bash",
shell_expansion => "-c 'shopt -s extglob\n echo %items%'",
# other bits inheritted from App::ClusterSSH::Base
debug => 0,
lang => 'en',
@ -576,8 +573,6 @@ screen_reserve_top=0
send_menu_xml_file=} . $ENV{HOME} . qq{/.clusterssh/send_menu
sftp=sftp
sftp_args=
shell=bash
shell_expansion=-c \'shopt -s extglob\n echo %items%\'
show_history=0
ssh=ssh
ssh_args=

View file

@ -56,10 +56,7 @@ BEGIN {
use_ok("App::ClusterSSH::Config") || BAIL_OUT('failed to use module');
}
my $mock_object = Test::ClusterSSH::Mock->new(
shell => which("bash"),
shell_expansion => "-c 'shopt -s extglob\n echo %items%'",
);
my $mock_object = Test::ClusterSSH::Mock->new();
my $cluster1 = App::ClusterSSH::Cluster->new( parent => $mock_object );
isa_ok( $cluster1, 'App::ClusterSSH::Cluster' );

65
t/range.t Normal file
View file

@ -0,0 +1,65 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Test::More;
use Test::Trap;
use Data::Dump;
require_ok('App::ClusterSSH::Range')
|| BAIL_OUT('Failed to use App::ClusterSSH::Range');
my %tests = (
'a' => 'a',
'c{a,b}' => 'ca cb',
'd{a,b,c}' => 'da db dc',
'e{0}' => 'e0',
'f{0..3}' => 'f0 f1 f2 f3',
'g{0..2,4}' => 'g0 g1 g2 g4',
'h{0..2,4..6}' => 'h0 h1 h2 h4 h5 h6',
'i{0..1,a}' => 'i0 i1 ia',
'j{0..2,a,b,c}' => 'j0 j1 j2 ja jb jc',
'k{4..6,a..c}' => 'k4 k5 k6 ka kb kc',
'l{0..2,7..9,e..g}' => 'l0 l1 l2 l7 l8 l9 le lf lg',
'm{0,1}' => 'm0 m1',
'n0..2}' => 'n0..2}',
# NOTE: the following are not "as expected" in line with above tests
# due to bsd_glob functionality. See output from:
# print join(q{ }, bsd_glob("o{a,b,c")).$/
'o{a,b,c' => 'o',
'p{0..2' => 'p',
);
my $range = App::ClusterSSH::Range->new();
isa_ok( $range, 'App::ClusterSSH::Range', 'object created correctly' );
for my $key ( sort keys %tests ) {
my $expected = $tests{$key};
my @expected = split / /, $tests{$key};
my $got;
trap {
$got = $range->expand($key);
};
is( $trap->stdout, '', "No stdout for scalar $key" );
is( $trap->stderr, '', "No stderr for scalar $key" );
is( $trap->leaveby, 'return', "correct leaveby for scalar $key" );
is( $trap->die, undef, "die is undef for scalar $key" );
is( $got, "$expected", "expected return for scalar $key" );
my @got;
trap {
@got = $range->expand($key);
};
is( $trap->stdout, '', "No stdout for array $key" );
is( $trap->stderr, '', "No stderr for array $key" );
is( $trap->leaveby, 'return', "correct leaveby for array $key" );
is( $trap->die, undef, "die is undef for array $key" );
is_deeply( \@got, \@expected, "expected return for array $key" )
|| diag explain \@got;
}
done_testing();