Replace pgindent shell script with Perl script. Update perltidy
authorBruce Momjian
Sat, 4 Aug 2012 16:41:21 +0000 (12:41 -0400)
committerBruce Momjian
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
-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 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 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.
+