Test-Pod-LinkCheck-Lite
view release on metacpan or search on metacpan
eg/manpath.PL view on Meta::CPAN
#!/usr/bin/env perl
use 5.008;
use strict;
use warnings;
use Config;
use Cwd qw{ abs_path };
use Getopt::Long 2.33 qw{ :config auto_version };
use File::Glob qw{ bsd_glob };
use File::Spec;
use List::Util qw{ uniq };
use Pod::Usage;
our $VERSION = '0.014';
use constant PATH_SEP => qr/ $Config{path_sep} /smx;
use constant MANPATH_APPEND => qr/ (?<= $Config{path_sep} \z ) /smx;
use constant MANPATH_EMBED =>
qr/ (?<= $Config{path_sep} ) (?= $Config{path_sep} ) /smx;
use constant MANPATH_PREPEND => qr/ \A (?= $Config{path_sep} ) /smx;
use constant MAN_CONF => {
darwin => [ '/private/etc/man.conf' ],
freebsd => [ '/etc/man.conf', '/usr/local/etc/man.d/*.conf' ],
haiku => [ '/packages/man-*/.settings/man.conf' ],
linux => [ '/etc/manpath.config' ],
openbsd => [ '/etc/man.conf' ],
}->{$^O} || [];
my %opt = (
1 => ! -t STDOUT,
);
GetOptions( \%opt,
qw{ 1! debug! },
help => sub { pod2usage( { -verbose => 2 } ) },
) or pod2usage( { -verbose => 0 } );
my $exit_status = 0;
if ( @ARGV ) {
my @manpath = man_path();
while ( @ARGV ) {
my $section = ( @ARGV > 1 && $ARGV[0] =~ m/ \A [1-9] \z /smx ) ?
shift @ARGV : undef;
my $prog = shift @ARGV;
if ( my $path = find_man( $prog, $section ) ) {
print "$path\n";
$exit_status = 0;
} elsif ( defined $section ) {
warn "No entry for $prog in section $section of the manual\n";
$exit_status = 1;
} else {
warn "No manual entry for $prog\n";
$exit_status = 1;
}
}
} else {
local $\ = "\n";
if ( $opt{1} ) {
print for man_path();
} else {
print join $Config{path_sep}, man_path();
}
}
exit $exit_status;
sub find_man {
my ( $prog, $section ) = @_;
defined $section
or $section = '?';
# NOTE: Experimentation with Linux shows that locale is applied here
# rather than in man_path. Furthermore,
# env LC_MESSAGES=es_ES.UTF-8 man -w man
# prints '/usr/share/man/es/man1/man.1.gz'
# Even further more, environment variables appear to be taken in the
eg/manpath.PL view on Meta::CPAN
# Or maybe the territory gets delimited by [_-] rather than [_],
# though I think not.
# https://www.shellhacks.com/linux-define-locale-language-settings/
# was helpful here. See also
# https://en.wikipedia.org/wiki/Locale_(computer_software)
# Unfortunately the referenced ISO/IEC 15897 costs CHF 158 per
# https://www.iso.org/standard/50707.html
my @lang_dir;
foreach my $env_name ( qw{ LC_MESSAGES LC_ALL LANG LANGUAGE } ) {
defined $ENV{$env_name}
or next;
$opt{debug}
and warn "Debug - translating $env_name";
my ( $language, $territory, $codeset, $modifiers ) =
$ENV{$env_name} =~ m/ \A
( [[:alpha:][:digit:]]+ ) # language
( _ \w+ )? # territory
( [.] [\w-]+ )? # codeset
( \@ .+)? # modifier
\z /smx
or die "Invalid $env_name '$ENV{$env_name}";
foreach ( $territory, $codeset, $modifiers ) {
defined $_
or $_ = '';
}
push @lang_dir, "/$ENV{$env_name}", "/$language$territory",
"/$language";
last;
}
@lang_dir = uniq( @lang_dir, '' );
if ( $opt{debug} ) {
require Data::Dumper;
no warnings qw{ once };
local $Data::Dumper::Indent = 1;
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Sortkeys = 1;
warn 'Debug - lang_dir is ', Data::Dumper::Dumper( \@lang_dir );
}
foreach my $dir ( man_path () ) {
foreach my $lang ( @lang_dir ) {
foreach my $suffix ( '', "man$section/" ) {
foreach my $path (
bsd_glob( "$dir$lang/$suffix$prog.$section*" )
) {
return $path;
}
}
}
}
return;
}
sub man_path {
my $manpath = $ENV{MANPATH};
if ( defined $manpath ) {
$manpath =~ MANPATH_PREPEND
or $manpath =~ MANPATH_EMBED
or $manpath =~ MANPATH_APPEND
or return split PATH_SEP, $ENV{MANPATH};
}
my $auto_path = ! {
openbsd => 1,
}->{$^O};
my @man_path;
my %man_path_map = map { $_ => undef } File::Spec->path();
my @mandatory;
foreach my $man_conf ( map { bsd_glob( $_ ) } @{ ( MAN_CONF ) } ) {
open my $fh, '<', $man_conf
or next;
local $_ = undef; # while (<>) ... does not localize $_.
while ( <$fh> ) {
m/ \A \s* (?: \z | [#] ) /smx
and next;
s/ \A \s+ //smx;
s/ \s+ \z //smx;
my ( $verb, @arg ) = split qr< \s+ >smx;
my $code = {
AUTOPATH => sub {
$auto_path = 0;
},
MANDATORY_MANPATH => sub {
-d $arg[0]
and push @mandatory, $arg[0];
},
MANPATH => sub {
-d $arg[0]
and push @man_path, $arg[0];
},
manpath => sub {
-d $arg[0]
and push @man_path, $arg[0];
},
MANPATH_MAP => sub {
exists $man_path_map{$arg[0]}
and @arg > 1
and -d $arg[1]
and push @{ $man_path_map{$arg[0]} }, $arg[1];
},
}->{$verb}
or next;
$code->();
}
}
push @man_path, @mandatory;
push @man_path, grep { -d } @{
{
darwin => [ qw{
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/share/man
/Applications/Xcode.app/Contents/Developer/usr/share/man
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man
}
]
}->{$^O} || []
};
unless ( @man_path || $auto_path ) {
push @man_path, grep { -d } @{
{
openbsd => [ qw{ /usr/share/man /usr/X11R6/man
/usr/local/man } ],
}->{$^O} || [] };
}
{
my @auto;
foreach my $path ( File::Spec->path() ) {
if ( $man_path_map{$path} ) {
push @auto, @{ $man_path_map{$path} };
} elsif ( $auto_path ) {
foreach my $suffix (
qw{ share/man share/openssl/man man }
) {
my $man = abs_path( "$path/../$suffix" )
or next;
-d $man
or next;
push @auto, $man;
}
}
}
splice @man_path, 0, 0, @auto;
if ( defined $manpath ) {
my $dflt = join $Config{path_sep}, @man_path;
foreach my $re ( MANPATH_PREPEND, MANPATH_EMBED,
MANPATH_APPEND ) {
$manpath =~ s/ $re /$dflt/smx
and last;
}
@man_path = split PATH_SEP, $manpath;
}
}
return uniq( @man_path );
}
__END__
=head1 TITLE
manpath.PL - Perl implementation of the manpath (1) command
=head1 SYNOPSIS
manpath.PL
manpath.PL -help
manpath.PL -version
=head1 OPTIONS
=head2 -1
If this Boolean option is asserted, output is one path per line. If
negated, output is all on one line, separated by the system's path
separator. Yes, the name of the option is the digit C<1>, not a
lower-case letter "ell". Think L<ls (1)>.
The default is C<-no-1> if output is to a terminal, or C<-1> otherwise.
=head2 -debug
If this Boolean option is asserted, debug information is sent to STDERR.
The default is C<-nodebug>.
=head2 -help
This option displays the documentation for this script. The script then
exits.
=head2 -version
This option displays the version of this script. The script then exits.
=head1 DETAILS
This Perl script attempts to implement the L<manpath (1)> command.
Originally it was a study in whether it was practical to look up
L<man (1)> pages directly rather than spawn the L<man (1)> command.
By value of C<$^O>, here are the results so far:
=over
=item darwin
( run in 1.427 second using v1.01-cache-2.11-cpan-71847e10f99 )