| #! /usr/bin/perl |
| |
| ################################################################# |
| # create_help.pl -- converts SGML docs to internal psql help |
| # |
| # Copyright (c) 2000-2023, PostgreSQL Global Development Group |
| # |
| # src/bin/psql/create_help.pl |
| ################################################################# |
| |
| # |
| # This script automatically generates the help on SQL in psql from |
| # the SGML docs. So far the format of the docs was consistent |
| # enough that this worked, but this here is by no means an SGML |
| # parser. |
| # |
| # Call: perl create_help.pl docdir sql_help |
| # The name of the header file doesn't matter to this script, but it |
| # sure does matter to the rest of the source. |
| # |
| |
| use strict; |
| use warnings; |
| use Getopt::Long; |
| |
| my $docdir = ''; |
| my $outdir = '.'; |
| my $depfile = ''; |
| my $hfilebasename = ''; |
| |
| GetOptions( |
| 'docdir=s' => \$docdir, |
| 'outdir=s' => \$outdir, |
| 'basename=s' => \$hfilebasename, |
| 'depfile=s' => \$depfile,) or die "$0: wrong arguments"; |
| |
| $docdir or die "$0: missing required argument: docdir\n"; |
| $hfilebasename or die "$0: missing required argument: basename\n"; |
| |
| my $hfile = $hfilebasename . '.h'; |
| my $cfile = $hfilebasename . '.c'; |
| |
| my $define = $hfilebasename; |
| $define =~ tr/a-z/A-Z/; |
| $define =~ s/\W/_/g; |
| |
| opendir(my $dh, $docdir) |
| or die "$0: could not open documentation source dir '$docdir': $!\n"; |
| open(my $hfile_handle, '>', "$outdir/$hfile") |
| or die "$0: could not open output file '$hfile': $!\n"; |
| open(my $cfile_handle, '>', "$outdir/$cfile") |
| or die "$0: could not open output file '$cfile': $!\n"; |
| |
| my $depfile_handle; |
| if ($depfile) |
| { |
| open($depfile_handle, '>', $depfile) |
| or die "$0: could not open output file '$depfile': $!\n"; |
| } |
| |
| print $hfile_handle "/* |
| * *** Do not change this file by hand. It is automatically |
| * *** generated from the DocBook documentation. |
| * |
| * generated by src/bin/psql/create_help.pl |
| * |
| */ |
| |
| #ifndef $define |
| #define $define |
| |
| #include \"pqexpbuffer.h\" |
| |
| struct _helpStruct |
| { |
| const char *cmd; /* the command name */ |
| const char *help; /* the help associated with it */ |
| const char *docbook_id; /* DocBook XML id (for generating URL) */ |
| void (*syntaxfunc) (PQExpBuffer); /* function that prints the |
| * syntax associated with it */ |
| int nl_count; /* number of newlines in syntax (for pager) */ |
| }; |
| |
| extern const struct _helpStruct QL_HELP[]; |
| "; |
| |
| print $cfile_handle "/* |
| * *** Do not change this file by hand. It is automatically |
| * *** generated from the DocBook documentation. |
| * |
| * generated by src/bin/psql/create_help.pl |
| * |
| */ |
| |
| #define N_(x) (x) /* gettext noop */ |
| |
| #include \"postgres_fe.h\" |
| #include \"$hfile\" |
| |
| "; |
| |
| my $maxlen = 0; |
| |
| my %entries; |
| |
| foreach my $file (sort readdir $dh) |
| { |
| my ($cmdid, @cmdnames, $cmddesc, $cmdsynopsis); |
| $file =~ /\.sgml$/ or next; |
| |
| print $depfile_handle "$outdir/$cfile $outdir/$hfile: $docdir/$file\n" |
| if ($depfile); |
| |
| open(my $fh, '<', "$docdir/$file") or next; |
| my $filecontent = join('', <$fh>); |
| close $fh; |
| |
| # Ignore files that are not for SQL language statements |
| $filecontent =~ |
| m!<refmiscinfo>\s*SQL - Language Statements\s*</refmiscinfo>!i |
| or next; |
| |
| $filecontent =~ m!<refentry id="([a-z-]+)">! |
| and $cmdid = $1; |
| |
| # Collect multiple refnames |
| LOOP: |
| { |
| $filecontent =~ m!\G.*?<refname>\s*([a-z ]+?)\s*</refname>!cgis |
| and push @cmdnames, $1 |
| and redo LOOP; |
| } |
| $filecontent =~ m!<refpurpose>\s*(.+?)\s*</refpurpose>!is |
| and $cmddesc = $1; |
| $filecontent =~ m!<synopsis>\s*(.+?)\s*</synopsis>!is |
| and $cmdsynopsis = $1; |
| |
| if (@cmdnames && $cmddesc && $cmdid && $cmdsynopsis) |
| { |
| s/\"/\\"/g foreach @cmdnames; |
| |
| $cmddesc =~ s/<[^>]+>//g; |
| $cmddesc =~ s/\s+/ /g; |
| $cmddesc =~ s/\"/\\"/g; |
| |
| my @params = (); |
| |
| my $nl_count = () = $cmdsynopsis =~ /\n/g; |
| |
| $cmdsynopsis =~ s/%/%%/g; |
| |
| while ($cmdsynopsis =~ m!<(\w+)[^>]*>(.+?)</\1[^>]*>!) |
| { |
| my $match = $2; |
| $match =~ s/<[^>]+>//g; |
| $match =~ s/%%/%/g; |
| push @params, $match; |
| $cmdsynopsis =~ s!<(\w+)[^>]*>.+?</\1[^>]*>!%s!; |
| } |
| $cmdsynopsis =~ s/\r?\n/\\n/g; |
| $cmdsynopsis =~ s/\"/\\"/g; |
| |
| foreach my $cmdname (@cmdnames) |
| { |
| $entries{$cmdname} = { |
| cmdid => $cmdid, |
| cmddesc => $cmddesc, |
| cmdsynopsis => $cmdsynopsis, |
| params => \@params, |
| nl_count => $nl_count |
| }; |
| $maxlen = |
| ($maxlen >= length $cmdname) ? $maxlen : length $cmdname; |
| } |
| } |
| else |
| { |
| die "$0: parsing file '$file' failed (N='@cmdnames' D='$cmddesc')\n"; |
| } |
| } |
| |
| foreach (sort keys %entries) |
| { |
| my $prefix = "\t" x 5 . ' '; |
| my $id = $_; |
| $id =~ s/ /_/g; |
| my $synopsis = "\"$entries{$_}{cmdsynopsis}\""; |
| $synopsis =~ s/\\n/\\n"\n$prefix"/g; |
| my @args = |
| ("buf", $synopsis, map("_(\"$_\")", @{ $entries{$_}{params} })); |
| print $cfile_handle "static void |
| sql_help_$id(PQExpBuffer buf) |
| { |
| \tappendPQExpBuffer(" . join(",\n$prefix", @args) . "); |
| } |
| |
| "; |
| } |
| |
| print $cfile_handle " |
| const struct _helpStruct QL_HELP[] = { |
| "; |
| foreach (sort keys %entries) |
| { |
| my $id = $_; |
| $id =~ s/ /_/g; |
| print $cfile_handle "\t{\"$_\", |
| \t\tN_(\"$entries{$_}{cmddesc}\"), |
| \t\t\"$entries{$_}{cmdid}\", |
| \t\tsql_help_$id, |
| \t$entries{$_}{nl_count}}, |
| |
| "; |
| } |
| |
| print $cfile_handle " |
| \t{NULL, NULL, NULL}\t\t\t/* End of list marker */ |
| }; |
| "; |
| |
| print $hfile_handle " |
| #define QL_HELP_COUNT " |
| . scalar(keys %entries) . " /* number of help items */ |
| #define QL_MAX_CMD_LEN $maxlen /* largest strlen(cmd) */ |
| |
| |
| #endif /* $define */ |
| "; |
| |
| close $cfile_handle; |
| close $hfile_handle; |
| close $depfile_handle if ($depfile); |
| closedir $dh; |