summaryrefslogtreecommitdiff
path: root/build-aux/help-extract.pl
diff options
context:
space:
mode:
Diffstat (limited to 'build-aux/help-extract.pl')
-rw-r--r--build-aux/help-extract.pl275
1 files changed, 275 insertions, 0 deletions
diff --git a/build-aux/help-extract.pl b/build-aux/help-extract.pl
new file mode 100644
index 00000000..0edc793a
--- /dev/null
+++ b/build-aux/help-extract.pl
@@ -0,0 +1,275 @@
+# help-extract -- extract --help and --version output from a script.
+# Copyright (C) 2020 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+# Written by Zack Weinberg.
+
+use strict;
+use warnings;
+
+# File::Spec itself was added in 5.005.
+# File::Spec::Functions was added in 5.6.1 which is just barely too new.
+use File::Spec;
+
+# This script is not intended to be used directly. It's run by
+# help2man via wrappers in man/, e.g. man/autoconf.w, as if it were
+# one of autoconf's executable scripts. It extracts the --help and
+# --version output of that script from its source form, without
+# actually running it. The script to work from is set by the wrapper,
+# and several other parameters are passed down from the Makefile as
+# environment variables; see parse_args below.
+
+# The point of this script is, the preprocessed forms of the
+# executable scripts, and their wrappers for uninstalled use
+# (e.g. <build-dir>/{bin,tests}/autoconf) do not need to exist to
+# generate the corresponding manpages. This is desirable because we
+# can't put those dependencies in the makefiles without breaking
+# people's ability to build autoconf from a release tarball without
+# help2man installed. It also ensures that we will generate manpages
+# from the current source code and not from an older version of the
+# script that has already been installed.
+
+## ----------------------------- ##
+## Extraction from Perl scripts. ##
+## ----------------------------- ##
+
+sub eval_qq_no_interpolation ($)
+{
+ # The argument is expected to be a "double quoted string" including the
+ # leading and trailing delimiters. Returns the text of this string after
+ # processing backslash escapes but NOT interpolation.
+ # / (?<!\\) (?>\\\\)* blah /x means match blah preceded by an
+ # *even* number of backslashes. It would be nice if we could use \K
+ # to exclude the backslashes from the matched text, but that was only
+ # added in Perl 5.10 and we still support back to 5.006.
+ return eval $_[0] =~ s/ (?<!\\) (?>\\\\)* [\$\@] /\\$&/xrg;
+}
+
+sub extract_channeldefs_usage ($)
+{
+ my ($channeldefs_pm) = @_;
+ my $usage = "";
+ my $parse_state = 0;
+ local $_;
+
+ open (my $fh, "<", $channeldefs_pm) or die "$channeldefs_pm: $!\n";
+ while (<$fh>)
+ {
+ if ($parse_state == 0)
+ {
+ $parse_state = 1 if /^sub usage\b/;
+ }
+ elsif ($parse_state == 1)
+ {
+ if (s/^ return "//)
+ {
+ $parse_state = 2;
+ $usage .= $_;
+ }
+ }
+ elsif ($parse_state == 2)
+ {
+ if (s/(?<!\\) ((?>\\\\)*) "; $/$1/x)
+ {
+ $usage .= $_;
+ return $usage;
+ }
+ else
+ {
+ $usage .= $_;
+ }
+ }
+ }
+
+ die "$channeldefs_pm: unexpected EOF in state $parse_state\n";
+}
+
+sub extract_perl_assignment (*$$$)
+{
+ my ($fh, $source, $channeldefs_pm, $what) = @_;
+ my $value = "";
+ my $parse_state = 0;
+ local $_;
+
+ while (<$fh>)
+ {
+ if ($parse_state == 0)
+ {
+ if (s/^\$\Q${what}\E = (?=")//o)
+ {
+ $value .= $_;
+ $parse_state = 1;
+ }
+ }
+ elsif ($parse_state == 1)
+ {
+ if (/^"\s*\.\s*Autom4te::ChannelDefs::usage\s*(?:\(\))?\s*\.\s*"$/)
+ {
+ $value .= extract_channeldefs_usage ($channeldefs_pm);
+ }
+ elsif (/^";$/)
+ {
+ $value .= '"';
+ return eval_qq_no_interpolation ($value);
+ }
+ else
+ {
+ $value .= $_;
+ }
+ }
+ }
+
+ die "$source: unexpected EOF in state $parse_state\n";
+}
+
+
+## ------------------------------ ##
+## Extraction from shell scripts. ##
+## ------------------------------ ##
+
+sub extract_shell_assignment (*$$)
+{
+ my ($fh, $source, $what) = @_;
+ my $value = "";
+ my $parse_state = 0;
+ local $_;
+
+ while (<$fh>)
+ {
+ if ($parse_state == 0)
+ {
+ if (/^\Q${what}\E=\[\"\\$/)
+ {
+ $parse_state = 1;
+ }
+ }
+ elsif ($parse_state == 1)
+ {
+ my $done = s/"\]$//;
+ $value .= $_;
+ if ($done)
+ {
+ # This is not strictly correct but it works acceptably
+ # for the escapes that actually occur in the strings
+ # we're extracting.
+ return eval_qq_no_interpolation ('"'.$value.'"');
+ }
+ }
+ }
+
+ die "$source: unexpected EOF in state $parse_state\n";
+}
+
+
+## -------------- ##
+## Main program. ##
+## -------------- ##
+
+
+sub extract_assignment ($$$)
+{
+ my ($source, $channeldefs_pm, $what) = @_;
+
+ open (my $fh, "<", $source) or die "$source: $!\n";
+
+ my $firstline = <$fh>;
+ if ($firstline =~ /\@PERL\@/ || $firstline =~ /-\*-\s*perl\s*-\*-/i)
+ {
+ return extract_perl_assignment ($fh, $source, $channeldefs_pm, $what);
+ }
+ elsif ($firstline =~ /\bAS_INIT\b/
+ || $firstline =~ /bin\/[a-z0-9]*sh\b/
+ || $firstline =~ /-\*-\s*shell-script\s*-\*-/i)
+ {
+ return extract_shell_assignment ($fh, $source, $what);
+ }
+ else
+ {
+ die "$source: language not recognized\n";
+ }
+}
+
+sub main ()
+{
+ # Most of our arguments come from environment variables, because
+ # help2man doesn't allow for passing additional command line
+ # arguments to the wrappers, and it's easier to write the wrappers
+ # to not mess with the command line.
+ my $usage = "Usage: $0 script-source (--help | --version)
+
+Extract help and version information from a perl or shell script.
+Required environment variables:
+
+ top_srcdir relative path from cwd to the top of the source tree
+ channeldefs_pm relative path from top_srcdir to ChannelDefs.pm
+ PACKAGE_NAME the autoconf PACKAGE_NAME substitution variable
+ VERSION the autoconf VERSION substitution variable
+ RELEASE_YEAR the autoconf RELEASE_YEAR substitution variable
+
+The script-source argument should also be relative to top_srcdir.
+";
+
+ my $source = shift(@ARGV) || die $usage;
+ my $what = shift(@ARGV) || die $usage;
+ my $top_srcdir = $ENV{top_srcdir} || die $usage;
+ my $channeldefs_pm = $ENV{channeldefs_pm} || die $usage;
+ my $package_name = $ENV{PACKAGE_NAME} || die $usage;
+ my $version = $ENV{VERSION} || die $usage;
+ my $release_year = $ENV{RELEASE_YEAR} || die $usage;
+
+ if ($what eq "-h" || $what eq "--help")
+ {
+ $what = "help";
+ }
+ elsif ($what eq "-V" || $what eq "--version")
+ {
+ $what = "version";
+ }
+ else
+ {
+ die $usage;
+ }
+
+ my $cmd_name = $source =~ s{^.*/([^./]+)\.(?:as|in)$}{$1}r;
+ $source = File::Spec->catfile($top_srcdir, $source);
+ $channeldefs_pm = File::Spec->catfile($top_srcdir, $channeldefs_pm);
+
+ my $text = extract_assignment ($source, $channeldefs_pm, $what);
+ $text =~ s/\$0\b/$cmd_name/g;
+ $text =~ s/[@]PACKAGE_NAME@/$package_name/g;
+ $text =~ s/[@]VERSION@/$version/g;
+ $text =~ s/[@]RELEASE_YEAR@/$release_year/g;
+ print $text;
+}
+
+main;
+
+
+### Setup "GNU" style for perl-mode and cperl-mode.
+## Local Variables:
+## perl-indent-level: 2
+## perl-continued-statement-offset: 2
+## perl-continued-brace-offset: 0
+## perl-brace-offset: 0
+## perl-brace-imaginary-offset: 0
+## perl-label-offset: -2
+## cperl-indent-level: 2
+## cperl-brace-offset: 0
+## cperl-continued-brace-offset: 0
+## cperl-label-offset: -2
+## cperl-extra-newline-before-brace: t
+## cperl-merge-trailing-else: nil
+## cperl-continued-statement-offset: 2
+## End: