Replace pgindent shell script with Perl script. Update perltidy
authorBruce Momjian <[email protected]>
Sat, 4 Aug 2012 16:41:21 +0000 (12:41 -0400)
committerBruce Momjian <[email protected]>
Sat, 4 Aug 2012 16:41:21 +0000 (12:41 -0400)
instructions to perltidy Perl files that lack Perl file extensions.

pgindent Perl coding by Andrew Dunstan, restructured by me.

src/tools/pgindent/README
src/tools/pgindent/pgindent
src/tools/pgindent/pgindent.man [new file with mode: 0644]

index fa64390baafc2adf61d0757e36e7ce62e14610a3..a89c5c214fb5e7ba7e5b9cec36d54b0733e93ba7 100644 (file)
@@ -1,5 +1,3 @@
-src/tools/pgindent/README
-
 pgindent
 ========
 
@@ -26,9 +24,7 @@ This can format all PostgreSQL *.c and *.h files, but excludes *.y, and
 
 6) Run pgindent:
 
-       find . -name '*.[ch]' -type f -print | \
-       egrep -v -f src/tools/pgindent/exclude_file_patterns | \
-       xargs -n100 src/tools/pgindent/pgindent src/tools/pgindent/typedefs.list
+       pgindent
 
 7) Remove any files that generate errors and restore their original
    versions.
@@ -46,7 +42,14 @@ This can format all PostgreSQL *.c and *.h files, but excludes *.y, and
 
 9) Indent the Perl code:
 
-       find . -name \*.pl -o -name \*.pm | 
+       (
+               find . -name \*.pl -o -name \*.pm
+
+               find . -type f -exec file {} \; |
+               egrep -i ':.*perl[0-9]*\>' |
+               cut -d: -f1
+       ) |
+       sort -u |
        xargs perltidy --profile=src/tools/pgindent/perltidyrc
 
 ---------------------------------------------------------------------------
index 188803750abbf75010eb85afff8309cc72264be9..00c9ac4755e8749a2d8c4e17465b012a47c3295a 100755 (executable)
-#!/bin/sh
-
-# src/tools/pgindent/pgindent
-
-# Known bugs:
-#
-# Blank line is added after parentheses; seen as a function definition, no space
-# after *:
-#      y = (int) x *y;
-#
-# Structure/union pointers in function prototypes and definitions have an extra
-# space after the asterisk:
-#
-#      void x(struct xxc * a);
-
-if [ "$#" -lt 2 ]
-then   echo "Usage:  $(basename $0) typedefs file [...]" 1>&2
-       exit 1
-fi
-
-TYPEDEFS="$1"
-shift
-
-[ -z "$INDENT" ] && INDENT=pg_bsd_indent
-INDENT_VERSION="1.1"
-
-trap "rm -f /tmp/$$ /tmp/$$a" 0 1 2 3 15
-
-# check the environment
-
-entab </dev/null >/dev/null
-if [ "$?" -ne 0 ]
-then   echo "Go to the src/tools/entab directory and do a 'make' and 'make install'." >&2
-       echo "This will put the 'entab' command in your path." >&2
-       echo "Then run $0 again."
-       exit 1
-fi
-$INDENT -? </dev/null >/dev/null 2>&1
-if [ "$?" -ne 1 ]
-then   echo "You do not appear to have '$INDENT' installed on your system." >&2
-       exit 1
-fi
-if [ "`$INDENT -V`" != "$INDENT $INDENT_VERSION" ]
-then   echo "You do not appear to have $INDENT version $INDENT_VERSION installed on your system." >&2
-       exit 1
-fi
-$INDENT -gnu </dev/null >/dev/null 2>&1
-if [ "$?" -eq 0 ]
-then   echo "You appear to have GNU indent rather than BSD indent." >&2
-       echo "See the pgindent/README file for a description of its problems." >&2
-       EXTRA_OPTS="-cdb -bli0 -npcs -cli4 -sc"
-else
-       EXTRA_OPTS="-cli1"
-fi
-
-for FILE
-do
-       cat "$FILE" |
-
-# Convert // comments to /* */
-       sed 's;^\([     ]*\)//\(.*\)$;\1/* \2 */;g' |
-
-# Mark some comments for special treatment later
-       sed 's;/\*  *---;/*---X_X;g' |
-
-# 'else' followed by a single-line comment, followed by
-# a brace on the next line confuses BSD indent, so we push
-# the comment down to the next line, then later pull it
-# back up again.  Add space before _PGMV or indent will add
-# it for us.
-       sed 's;\([}     ]\)else[        ]*\(/\*\)\(.*\*/\)[     ]*$;\1else\
-    \2 _PGMV\3;g' |
-
-# Indent multi-line after-'else' comment so BSD indent will move it properly.
-# We already moved down single-line comments above.  Check for '*' to make
-# sure we are not in a single-line comment that has other text on the line.
-       sed 's;\([}     ]\)else[        ]*\(/\*[^\*]*\)[        ]*$;\1else\
-    \2;g' |
-       detab -t4 -qc |
-
-# Work around bug where function that defines no local variables misindents
-# switch() case lines and line after #else.  Do not do for struct/enum.
-       awk '   BEGIN   {line1 = ""; line2 = ""}
-               {
-                       line2 = $0;
-                       if (NR >= 2)
-                               print line1;
-                       if (NR >= 2 &&
-                           line2 ~ /^{[        ]*$/ &&
-                           line1 !~ /^struct/ &&
-                           line1 !~ /^enum/ &&
-                           line1 !~ /^typedef/ &&
-                           line1 !~ /^extern[  ][      ]*"C"/ &&
-                           line1 !~ /=/ &&
-                           line1 ~ /\)/)
-                               print "int      pgindent_func_no_var_fix;";
-                       line1 = line2;
-               }
-               END {
-                       if (NR >= 1)
-                               print line1;
-               }' |
+#!/usr/bin/perl
 
-# Prevent indenting of code in 'extern "C"' blocks.
-       awk '   BEGIN   {line1 = ""; line2 = ""; skips = 0}
-               {
-                       line2 = $0;
-                       if (skips > 0)
-                               skips--;
-                       if (line1 ~ /^#ifdef[   ]*__cplusplus/ &&
-                           line2 ~ /^extern[   ]*"C"[  ]*$/)
-                       {
-                               print line1;
-                               print line2;
-                               if (getline && $0 ~ /^{[        ]*$/)
-                                       print "/* Open extern \"C\" */";
-                               else    print $0;
-                               line2 = "";
-                               skips = 2;
-                       }
-                       else if (line1 ~ /^#ifdef[      ]*__cplusplus/ &&
-                           line2 ~ /^}[        ]*$/)
-                       {
-                               print line1;
-                               print "/* Close extern \"C\" */";
-                               line2 = "";
-                               skips = 2;
-                       }
-                       else
-                               if (skips == 0 && NR >= 2)
-                                       print line1;
-                       line1 = line2;
-               }
-               END {
-                       if (NR >= 1 && skips <= 1)
-                               print line1;
-               }' |
+use strict;
+use warnings;
+
+use Cwd qw(abs_path getcwd);
+use File::Find;
+use File::Spec qw(devnull);
+use File::Temp;
+use IO::Handle;
+use Getopt::Long;
+use Readonly;
 
-# Protect backslashes in DATA().
-       sed 's;^DATA(.*$;/*&*/;' |
+# Update for pg_bsd_indent version
+Readonly my $INDENT_VERSION => "1.1";
+Readonly my $devnull        => File::Spec->devnull;
 
-# Protect wrapping in CATALOG().
-       sed 's;^CATALOG(.*$;/*&*/;' >/tmp/$$a
+# Common indent settings
+my $indent_opts =
+  "-bad -bap -bc -bl -d0 -cdb -nce -nfc1 -di12 -i4 -l79 -lp -nip -npro -bbb";
 
-       egrep -v '^(FD_SET|date|interval|timestamp|ANY)$' "$TYPEDEFS" | sed -e '/^$/d' > /tmp/$$b
+# indent-dependant settings
+my $extra_opts = "";
 
-# We get the list of typedef's from /src/tools/find_typedef
-       $INDENT -bad -bap -bc -bl -d0 -cdb -nce -nfc1 -di12 -i4 -l79 \
-               -lp -nip -npro -bbb $EXTRA_OPTS -U/tmp/$$b \
-               /tmp/$$a >/tmp/$$ 2>&1
+my ($typedefs_file, $code_base, $excludes, $indent, $build);
 
-       if [ "$?" -ne 0 -o -s /tmp/$$ ]
-       then    echo
-               echo "$FILE"
-               cat /tmp/$$
-       fi
-       cat /tmp/$$a |
+my %options = (
+       "typedefs=s"  => \$typedefs_file,
+       "code-base=s" => \$code_base,
+       "excludes=s"  => \$excludes,
+       "indent=s"    => \$indent,
+       "build"       => \$build,);
+GetOptions(%options) || die "bad command line";
 
-# Restore DATA/CATALOG lines.
-       sed 's;^/\*\(DATA(.*\)\*/$;\1;' |
-       sed 's;^/\*\(CATALOG(.*\)\*/$;\1;' |
+run_build($code_base) if ($build);
 
-# Remove tabs and retab with four spaces.
-       detab -t8 -qc |
-       entab -t4 -qc |
-       sed 's;^/\* Open extern \"C\" \*/$;{;' |
-       sed 's;^/\* Close extern \"C\" \*/$;};' |
-       sed 's;/\*---X_X;/* ---;g' |
+# command line option wins, then first non-option arg,
+# then environment (which is how --build sets it) ,
+# then locations. based on current dir, then default location
+$typedefs_file ||= shift if @ARGV && $ARGV[0] !~ /\\.[ch]$/;
+$typedefs_file ||= $ENV{PGTYPEDEFS};
 
-# Workaround indent bug for 'static'.
-       sed 's;^static[         ][      ]*;static ;g' |
+# build mode sets PGINDENT and PGENTAB
+$indent ||= $ENV{PGINDENT} || $ENV{INDENT} || "pg_bsd_indent";
+my $entab = $ENV{PGENTAB} || "entab";
 
-# Remove too much indenting after closing brace.
-       sed 's;^}       [       ]*;}    ;' |
+# no non-option arguments given. so do everything in the current directory
+$code_base ||= '.' unless @ARGV;
 
-# Indent single-line after-'else' comment by only one tab.
-       sed 's;\([}     ]\)else[        ]*\(/\*.*\*/\)[         ]*$;\1else      \2;g' |
+# if it's the base of a postgres tree, we will exclude the files
+# postgres wants excluded
+$excludes ||= "$code_base/src/tools/pgindent/exclude_file_patterns"
+  if $code_base && -f "$code_base/src/tools/pgindent/exclude_file_patterns";
 
-# Pull in #endif comments.
-       sed 's;^#endif[         ][      ]*/\*;#endif   /*;' |
+# globals
+my @files;
+my $filtered_typedefs_fh;
 
-# Work around misindenting of function with no variables defined.
-       awk '
+
+sub check_indent
+{
+       system("entab < $devnull");
+       if ($?)
        {
-               if ($0 ~ /^[    ]*int[  ]*pgindent_func_no_var_fix;/)
-               {
-                       if (getline && $0 != "")
-                               print $0;
-               }
-               else    print $0;
-       }' |
-
-# Add space after comments that start on tab stops.
-       sed 's;\([^     ]\)\(/\*.*\*/\)$;\1     \2;' |
-
-# Move trailing * in function return type.
-       sed 's;^\([A-Za-z_][^   ]*\)[   ][      ]*\*$;\1 *;' |
-
-# Remove un-needed braces around single statements.
-# Do not use because it uglifies PG_TRY/PG_CATCH blocks and probably
-# isn't needed for general use.
-#      awk '
-#      {
-#                      line3 = $0;
-#                      if (skips > 0)
-#                              skips--;
-#                      if (line1 ~ /           *{$/ &&
-#                          line2 ~ /           *[^;{}]*;$/ &&
-#                          line3 ~ /           *}$/)
-#                      {
-#                              print line2;
-#                              line2 = "";
-#                              line3 = "";
-#                              skips = 3;
-#                      }
-#                      else
-#                              if (skips == 0 && NR >= 3)
-#                                      print line1;
-#                      line1 = line2;
-#                      line2 = line3;
-#              }
-#              END {
-#                      if (NR >= 2 && skips <= 1)
-#                              print line1;
-#                      if (NR >= 1 && skips <= 2)
-#                              print line2;
-#              }' |
-
-# Remove blank line between opening brace and block comment.
-       awk '
+               print STDERR
+"Go to the src/tools/entab directory and do 'make' and 'make install'.\n",
+                 "This will put the 'entab' command in your path.\n",
+                 "Then run $0 again.\n";
+               exit 1;
+       }
+
+       system("$indent -? < $devnull > $devnull 2>&1");
+       if ($? >> 8 != 1)
        {
-                       line3 = $0;
-                       if (skips > 0)
-                               skips--;
-                       if (line1 ~ /   *{$/ &&
-                           line2 ~ /^$/ &&
-                           line3 ~ /           *\/[*]$/)
-                       {
-                               print line1;
-                               print line3;
-                               line2 = "";
-                               line3 = "";
-                               skips = 3;
-                       }
-                       else
-                               if (skips == 0 && NR >= 3)
-                                       print line1;
-                       line1 = line2;
-                       line2 = line3;
-               }
-               END {
-                       if (NR >= 2 && skips <= 1)
-                               print line1;
-                       if (NR >= 1 && skips <= 2)
-                               print line2;
-               }' |
-
-# Pull up single-line comment after 'else' that was pulled down above
-       awk '
-               {
-                       if (NR != 1)
-                       {
-                               if ($0 ~ "/[*] _PGMV")
-                               {
-                                       # remove tag
-                                       sub(" _PGMV", "", $0);
-                                       # remove leading whitespace
-                                       sub("^[         ]*", "", $0);
-                                       # add comment with single tab prefix
-                                       print prev_line"        "$0;
-                                       # throw away current line
-                                       getline;
-                               }
-                               else
-                                       print prev_line;
-                       }
-                       prev_line = $0;
-               }
-               END {
-                       if (NR >= 1)
-                               print prev_line;
-               }' |
+               print STDERR
+                 "You do not appear to have 'indent' installed on your system.\n";
+               exit 1;
+       }
 
-# Remove trailing blank lines, helps with adding blank before trailing #endif.
-       awk '   BEGIN   {blank_lines = 0;}
-               {
-                       line1 = $0;
-                       if (line1 ~ /^$/)
-                               blank_lines++;
-                       else
-                       {
-                               for (; blank_lines > 0; blank_lines--)
-                                       printf "\n";
-                               print line1;
-                       }
-               }' |
-
-# Remove blank line before #else, #elif, and #endif.
-       awk '   BEGIN   {line1 = ""; line2 = ""; skips = 0}
-               {
-                       line2 = $0;
-                       if (skips > 0)
-                               skips--;
-                       if (line1 ~ /^$/ &&
-                           (line2 ~ /^#else/ ||
-                            line2 ~ /^#elif/ ||
-                            line2 ~ /^#endif/))
-                       {
-                               print line2;
-                               line2 = "";
-                               skips = 2;
-                       }
-                       else
-                               if (skips == 0 && NR >= 2)
-                                       print line1;
-                       line1 = line2;
-               }
-               END {
-                       if (NR >= 1 && skips <= 1)
-                               print line1;
-               }' |
+       if (`$indent -V` !~ m/ $INDENT_VERSION$/)
+       {
+               print STDERR
+"You do not appear to have $indent version $INDENT_VERSION installed on your system.\n";
+               exit 1;
+       }
 
-# Add blank line before #endif if it is the last line in the file.
-       awk '   BEGIN   {line1 = ""; line2 = ""}
-               {
-                       line2 = $0;
-                       if (NR >= 2)
-                               print line1;
-                       line1 = line2;
-               }
-               END {
-                       if (NR >= 1 && line2 ~ /^#endif/)
-                               printf "\n";
-                       print line1;
-               }' |
-
-#  Move prototype names to the same line as return type.  Useful for ctags.
-#  Indent should do this, but it does not.  It formats prototypes just
-#  like real functions.
-       awk '   BEGIN   {paren_level = 0}
+       system("$indent -gnu < $devnull > $devnull 2>&1");
+       if ($? == 0)
        {
-               if ($0 ~ /^[a-zA-Z_][a-zA-Z_0-9]*[^\(]*$/)
+               print STDERR
+                 "You appear to have GNU indent rather than BSD indent.\n",
+                 "See the pgindent/README file for a description of its problems.\n";
+               $extra_opts = "-cdb -bli0 -npcs -cli4 -sc";
+       }
+       else
+       {
+               $extra_opts = "-cli1";
+       }
+}
+
+
+sub load_typedefs
+{
+
+       # try fairly hard to find the typedefs file if it's not set
+
+       foreach my $try ('.', 'src/tools/pgindent', '/usr/local/etc')
+       {
+               $typedefs_file ||= "$try/typedefs.list"
+                 if (-f "$try/typedefs.list");
+       }
+
+       # try to find typedefs by moving up directory levels
+       my $tdtry = "..";
+       foreach (1 .. 5)
+       {
+               $typedefs_file ||= "$tdtry/src/tools/pgindent/typedefs.list"
+                 if (-f "$tdtry/src/tools/pgindent/typedefs.list");
+               $tdtry = "$tdtry/..";
+       }
+       die "no typedefs file" unless $typedefs_file && -f $typedefs_file;
+
+       open(my $typedefs_fh, '<', $typedefs_file)
+         || die "opening $typedefs_file: $!";
+       my @typedefs = <$typedefs_fh>;
+       close($typedefs_fh);
+
+       # remove certain entries
+       @typedefs =
+         grep { !m/^(FD_SET|date|interval|timestamp|ANY)\n?$/ } @typedefs;
+
+       # write filtered typedefs
+       my $filter_typedefs_fh = new File::Temp(TEMPLATE => "pgtypedefXXXXX");
+       print $filter_typedefs_fh @typedefs;
+       $filter_typedefs_fh->close();
+
+       # temp file remains because we return a file handle reference
+       return $filter_typedefs_fh;
+}
+
+
+sub process_exclude
+{
+       if ($excludes && @files)
+       {
+               open(my $eh, '<', $excludes) || die "opening $excludes";
+               while (my $line = <$eh>)
                {
-                       saved_len = 0;
-                       saved_lines[++saved_len] = $0;
-                       if ((getline saved_lines[++saved_len]) == 0)
-                               print saved_lines[1];
-                       else
-                       if (saved_lines[saved_len] !~ /^[a-zA-Z_][a-zA-Z_0-9]*\(/ ||
-                           saved_lines[saved_len] ~  /^[a-zA-Z_][a-zA-Z_0-9]*\(.*\)$/ ||
-                           saved_lines[saved_len] ~  /^[a-zA-Z_][a-zA-Z_0-9]*\(.*\);$/)
-                       {
-                               print saved_lines[1];
-                               print saved_lines[2];
-                       }
-                       else
-                       {
-                               while (1)
-                               {
-                                       if ((getline saved_lines[++saved_len]) == 0)
-                                               break;
-                                       if (saved_lines[saved_len] ~ /^[^       ]/ ||
-                                           saved_lines[saved_len] !~ /,$/)
-                                               break;
-                               }
-                               for (i=1; i <= saved_len; i++)
-                               {
-                                       if (i == 1 && saved_lines[saved_len] ~ /\);$/)
-                                       {
-                                               printf "%s", saved_lines[i];
-                                               if (substr(saved_lines[i], length(saved_lines[i]),1) != "*")
-                                                       printf " ";
-                                       }
-                                       else    print saved_lines[i];
-                               }
-                       }
+                       chomp $line;
+                       my $rgx;
+                       eval " \$rgx = qr!$line!;";
+                       @files = grep { $_ !~ /$rgx/ } @files if $rgx;
                }
-               else    print $0;
-       }' |
-
-# Fix indenting of typedef caused by __cplusplus in libpq-fe.h.
-       (
-               if echo "$FILE" | grep -q 'libpq-fe.h$'
-               then    sed 's/^[       ]*typedef enum/typedef enum/'
-               else    cat
-               fi
-       ) |
-# end
-       cat >/tmp/$$ && cat /tmp/$$ >"$FILE"
-done
-
-# The 'for' loop makes these backup files useless so delete them
-rm -f *a.BAK
+               close($eh);
+       }
+}
+
+
+sub read_source
+{
+       my $source_filename = shift;
+       my $source;
+
+       open(my $src_fd, '<', $source_filename)
+         || die "opening $source_filename: $!";
+       local ($/) = undef;
+       $source = <$src_fd>;
+       close($src_fd);
+
+       return $source;
+}
+
+
+sub write_source
+{
+       my $source          = shift;
+       my $source_filename = shift;
+
+       open(my $src_fh, '>', $source_filename)
+         || die "opening $source_filename: $!";
+       print $src_fh $source;
+       close($src_fh);
+}
+
+
+sub pre_indent
+{
+       my $source = shift;
+
+       # remove trailing whitespace
+       $source =~ s/\h+$//gm;
+
+       ## Comments
+
+       # Convert // comments to /* */
+       $source =~ s!^(\h*)//(.*)$!$1/* $2 */!gm;
+
+       # 'else' followed by a single-line comment, followed by
+       # a brace on the next line confuses BSD indent, so we push
+       # the comment down to the next line, then later pull it
+       # back up again.  Add space before _PGMV or indent will add
+       # it for us.
+       # AMD: A symptom of not getting this right is that you see errors like:
+       # FILE: ../../../src/backend/rewrite/rewriteHandler.c
+       # Error@2259:
+       # Stuff missing from end of file
+       $source =~ s!(\}|\h)else\h*(/\*)(.*\*/)\h*$!$1else\n    $2 _PGMV$3!gm;
+
+       # Indent multi-line after-'else' comment so BSD indent will move it
+       # properly. We already moved down single-line comments above.
+       # Check for '*' to make sure we are not in a single-line comment that
+       # has other text on the line.
+       $source =~ s!(\}|\h)else\h*(/\*[^*]*)\h*$!$1else\n    $2!gm;
+
+       # Mark some comments for special treatment later
+       $source =~ s!/\* +---!/*---X_X!g;
+
+       ## Other
+
+       # Work around bug where function that defines no local variables
+       # misindents switch() case lines and line after #else.  Do not do
+       # for struct/enum.
+       my @srclines = split(/\n/, $source);
+       foreach my $lno (1 .. $#srclines)
+       {
+               my $l2 = $srclines[$lno];
+
+               # Line is only a single open brace in column 0
+               next unless $l2 =~ /^\{\h*$/;
+
+               # previous line has a closing paren
+               next unless $srclines[ $lno - 1 ] =~ /\)/;
+
+               # previous line was struct, etc.
+               next
+                 if $srclines[ $lno - 1 ] =~
+                         m!=|^(struct|enum|\h*typedef|extern\h+"C")!;
+
+               $srclines[$lno] = "$l2\nint pgindent_func_no_var_fix;";
+       }
+       $source = join("\n", @srclines) . "\n";    # make sure there's a final \n
+
+       # Prevent indenting of code in 'extern "C"' blocks.
+       # we replace the braces with comments which we'll reverse later
+       my $extern_c_start = '/* Open extern "C" */';
+       my $extern_c_stop  = '/* Close extern "C" */';
+       $source =~
+s!(^#ifdef\h+__cplusplus.*\nextern\h+"C"\h*\n)\{\h*$!$1$extern_c_start!gm;
+       $source =~ s!(^#ifdef\h+__cplusplus.*\n)\}\h*$!$1$extern_c_stop!gm;
+
+       return $source;
+}
+
+
+sub post_indent
+{
+       my $source          = shift;
+       my $source_filename = shift;
+
+       # put back braces for extern "C"
+       $source =~ s!^/\* Open extern "C" \*/$!{!gm;
+       $source =~ s!^/\* Close extern "C" \*/$!}!gm;
+
+       ## Comments
+
+       # remove special comment marker
+       $source =~ s!/\*---X_X!/* ---!g;
+
+       # Pull up single-line comment after 'else' that was pulled down above
+       $source =~ s!else\n\h+/\* _PGMV!else\t/*!g;
+
+       # Indent single-line after-'else' comment by only one tab.
+       $source =~ s!(\}|\h)else\h+(/\*.*\*/)\h*$!$1else\t$2!gm;
+
+       # Add tab before comments with no whitespace before them (on a tab stop)
+       $source =~ s!(\S)(/\*.*\*/)$!$1\t$2!gm;
+
+       # Remove blank line between opening brace and block comment.
+       $source =~ s!(\t*\{\n)\n(\h+/\*)$!$1$2!gm;
+
+       # cpp conditionals
+
+       # Reduce whitespace between #endif and comments to one tab
+       $source =~ s!^\#endif\h+/\*!#endif   /*!gm;
+
+       # Remove blank line(s) before #else, #elif, and #endif
+       $source =~ s!\n\n+(\#else|\#elif|\#endif)!\n$1!g;
+
+       # Add blank line before #endif if it is the last line in the file
+       $source =~ s!\n(#endif.*)\n\z!\n\n$1\n!;
+
+       ## Functions
+
+       # Work around misindenting of function with no variables defined.
+       $source =~ s!^\h*int\h+pgindent_func_no_var_fix;\h*\n{1,2}!!gm;
+
+       # Use a single space before '*' in function return types
+       $source =~ s!^([A-Za-z_]\S*)\h+\*$!$1 *!gm;
+
+       #  Move prototype names to the same line as return type.  Useful
+       # for ctags.  Indent should do this, but it does not.  It formats
+       # prototypes just like real functions.
+
+       my $ident   = qr/[a-zA-Z_][a-zA-Z_0-9]*/;
+       my $comment = qr!/\*.*\*/!;
+
+       $source =~ s!
+                       (\n$ident[^(\n]*)\n                  # e.g. static void
+                       (
+                               $ident\(\n?                      # func_name( 
+                               (.*,(\h*$comment)?\n)*           # args b4 final ln
+                               .*\);(\h*$comment)?$             # final line
+                       )
+               !$1 . (substr($1,-1,1) eq '*' ? '' : ' ') . $2!gmxe;
+
+       ## Other
+
+       # Remove too much indenting after closing brace.
+       $source =~ s!^\}\t\h+!}\t!gm;
+
+       # Workaround indent bug that places excessive space before 'static'.
+       $source =~ s!^static\h+!static !gm;
+
+       # Remove leading whitespace from typedefs
+       $source =~ s!^\h+typedef enum!typedef enum!gm
+         if $source_filename =~ 'libpq-(fe|events).h$';
+
+       # Remove trailing blank lines
+       $source =~ s!\n+\z!\n!;
+
+       return $source;
+}
+
+
+sub run_indent
+{
+       my $source        = shift;
+       my $error_message = shift;
+
+       my $cmd =
+         "$indent $indent_opts $extra_opts -U" . $filtered_typedefs_fh->filename;
+
+       my $tmp_fh = new File::Temp(TEMPLATE => "pgsrcXXXXX");
+       my $filename = $tmp_fh->filename;
+       print $tmp_fh $source;
+       $tmp_fh->close();
+
+       $$error_message = `$cmd $filename 2>&1`;
+
+       return "" if ($? || length($$error_message) > 0);
+
+       unlink "$filename.BAK";
+
+       open(my $src_out, '<', $filename);
+       local ($/) = undef;
+       $source = <$src_out>;
+       close($src_out);
+
+       return $source;
+
+}
+
+# XXX Ideally we'd implement entab/detab in pure perl.
+
+sub detab
+{
+       my $source = shift;
+
+       my $tmp_fh = new File::Temp(TEMPLATE => "pgdetXXXXX");
+       print $tmp_fh $source;
+       $tmp_fh->close();
+
+       open(my $entab, '-|', "$entab -d -t4 -qc " . $tmp_fh->filename);
+       local ($/) = undef;
+       $source = <$entab>;
+       close($entab);
+
+       return $source;
+}
+
+
+sub entab
+{
+       my $source = shift;
+
+       my $tmp_fh = new File::Temp(TEMPLATE => "pgentXXXXX");
+       print $tmp_fh $source;
+       $tmp_fh->close();
+
+       open(my $entab, '-|',
+               "$entab -d -t8 -qc " . $tmp_fh->filename . " | $entab -t4 -qc");
+       local ($/) = undef;
+       $source = <$entab>;
+       close($entab);
+
+       return $source;
+}
+
+
+# for development diagnostics
+sub diff
+{
+       my $pre   = shift;
+       my $post  = shift;
+       my $flags = shift || "";
+
+       print STDERR "running diff\n";
+
+       my $pre_fh  = new File::Temp(TEMPLATE => "pgdiffbXXXXX");
+       my $post_fh = new File::Temp(TEMPLATE => "pgdiffaXXXXX");
+
+       print $pre_fh $pre;
+       print $post_fh $post;
+
+       $pre_fh->close();
+       $post_fh->close();
+
+       system( "diff $flags "
+                 . $pre_fh->filename . " "
+                 . $post_fh->filename
+                 . " >&2");
+}
+
+
+sub run_build
+{
+       eval "use LWP::Simple;";
+
+       my $code_base = shift || '.';
+       my $save_dir = getcwd();
+
+       # look for the code root
+       foreach (1 .. 5)
+       {
+               last if -d "$code_base/src/tools/pgindent";
+               $code_base = "$code_base/..";
+       }
+
+       die "no src/tools/pgindent directory in $code_base"
+         unless -d "$code_base/src/tools/pgindent";
+
+       chdir "$code_base/src/tools/pgindent";
+
+       my $rv = getstore("http://buildfarm.postgresql.org/cgi-bin/typedefs.pl",
+               "tmp_typedefs.list");
+
+       die "fetching typedefs.list" unless is_success($rv);
+
+       $ENV{PGTYPEDEFS} = abs_path('tmp_typedefs.list');
+
+       $rv =
+         getstore("ftp://ftp.postgresql.org/pub/dev/indent.netbsd.patched.tgz",
+               "indent.netbsd.patched.tgz");
+
+       die "fetching indent.netbsd.patched.tgz" unless is_success($rv);
+
+       # XXX add error checking here
+
+       mkdir "bsdindent";
+       chdir "bsdindent";
+       system("tar -z -xf ../indent.netbsd.patched.tgz");
+       system("make > $devnull 2>&1");
+
+       $ENV{PGINDENT} = abs_path('indent');
+
+       chdir "../../entab";
+
+       system("make > $devnull 2>&1");
+
+       $ENV{PGENTAB} = abs_path('entab');
+
+       chdir $save_dir;
+
+}
+
+
+sub build_clean
+{
+       my $code_base = shift || '.';
+
+       # look for the code root
+       foreach (1 .. 5)
+       {
+               last if -d "$code_base/src/tools/pgindent";
+               $code_base = "$code_base/..";
+       }
+
+       die "no src/tools/pgindent directory in $code_base"
+         unless -d "$code_base/src/tools/pgindent";
+
+       chdir "$code_base";
+
+       system("rm -rf src/tools/pgindent/bsdindent");
+       system("git clean -q -f src/tools/entab src/tools/pgindent");
+}
+
+
+# main
+
+# get the list of files under code base, if it's set
+File::Find::find(
+       {   wanted => sub {
+                       my ($dev, $ino, $mode, $nlink, $uid, $gid);
+                       (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))
+                         && -f _
+                         && /^.*\.[ch]\z/s
+                         && push(@files, $File::Find::name);
+                 }
+       },
+       $code_base) if $code_base;
+
+process_exclude();
+
+$filtered_typedefs_fh = load_typedefs();
+
+check_indent();
+
+# make sure we process any non-option arguments.
+push(@files, @ARGV);
+
+foreach my $source_filename (@files)
+{
+       my $source        = read_source($source_filename);
+       my $error_message = '';
+
+       $source = pre_indent($source);
+
+       # Protect backslashes in DATA() and wrapping in CATALOG()
+
+       $source = detab($source);
+       $source =~ s!^((DATA|CATALOG)\(.*)$!/*$1*/!gm;
+
+       $source = run_indent($source, \$error_message);
+       if ($source eq "")
+       {
+               print STDERR "Failure in $source_filename: " . $error_message . "\n";
+               next;
+       }
+
+ # Restore DATA/CATALOG lines; must be done here so tab alignment is preserved
+       $source =~ s!^/\*((DATA|CATALOG)\(.*)\*/$!$1!gm;
+       $source = entab($source);
+
+       $source = post_indent($source, $source_filename);
+
+       write_source($source, $source_filename);
+}
+
+build_clean($code_base) if $build;
diff --git a/src/tools/pgindent/pgindent.man b/src/tools/pgindent/pgindent.man
new file mode 100644 (file)
index 0000000..cff092c
--- /dev/null
@@ -0,0 +1,45 @@
+pgindent will indent .c and .h files according to the coding standards of
+the PostgreSQL project. It needs several things to run, and tries to locate
+or build them if possible. They can also be specified via command line switches
+or the environment.
+
+In its simplest form, if all the required objects are installed, simply run
+it without any parameters at the top of the source tree you want to process.
+
+       pgindent 
+
+If you don't have all the requirements installed, pgindent will fetch and build 
+them for you, if you're in a PostgreSQL source tree:
+
+
+       pgindent --build
+
+If your indent program is not installed in your path, you can specify it
+by setting the environment variable INDENT, or PGINDENT, or by giving the
+command line option --indent:
+
+       pgindent --indent=/opt/extras/bsdindent
+
+Similarly, the entab program can be specified using the PGENTAB environment
+variable, or using the --entab command line option.
+
+pgindent also needs a file containing a list of typedefs. This can be 
+specified using the PGTYPEDEFS environment variable, or via the command line
+--typedefs option. If neither is used, it will look for it within the
+current source tree, or in /usr/local/etc/typedefs.list.
+
+If you want to indent a source tree other than the current working directory,
+you can specify it via the --code-base command line option.
+
+We don't want to indent certain files in the PostgreSQL source. pgindent
+will honor a file containing a list of patterns of files to avoid. This
+file can be specified using the --excludes command line option. If indenting
+a PostgreSQL source tree, this option isn't necessary, as it will find the file
+src/tools/pgindent/exclude_file_patterns.
+
+Any non-option arguments are taken as the names of files to be indented. In this
+case only these files will be changed, and nothing else will be touched. If the
+first non-option argument is not a .c or .h file, it is treated as the name
+of a typedefs file for legacy reasons, but this use is deprecated - use the 
+--typedefs option instead.
+