mirror of
https://github.com/postgres/postgres.git
synced 2025-06-23 14:01:44 +03:00
559 lines
13 KiB
Perl
Executable File
559 lines
13 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
use 5.008001;
|
|
|
|
use Cwd qw(abs_path getcwd);
|
|
use File::Find;
|
|
use File::Spec qw(devnull);
|
|
use File::Temp;
|
|
use IO::Handle;
|
|
use Getopt::Long;
|
|
|
|
# Update for pg_bsd_indent version
|
|
my $INDENT_VERSION = "1.3";
|
|
my $devnull = File::Spec->devnull;
|
|
|
|
# Common indent settings
|
|
my $indent_opts =
|
|
"-bad -bap -bc -bl -d0 -cdb -nce -nfc1 -di12 -i4 -l79 -lp -nip -npro -bbb";
|
|
|
|
# indent-dependant settings
|
|
my $extra_opts = "";
|
|
|
|
my ($typedefs_file, $typedef_str, $code_base, $excludes, $indent, $build);
|
|
|
|
my %options = (
|
|
"typedefs=s" => \$typedefs_file,
|
|
"list-of-typedefs=s" => \$typedef_str,
|
|
"code-base=s" => \$code_base,
|
|
"excludes=s" => \$excludes,
|
|
"indent=s" => \$indent,
|
|
"build" => \$build,);
|
|
GetOptions(%options) || die "bad command line argument\n";
|
|
|
|
run_build($code_base) if ($build);
|
|
|
|
# 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};
|
|
|
|
# build mode sets PGINDENT and PGENTAB
|
|
$indent ||= $ENV{PGINDENT} || $ENV{INDENT} || "pg_bsd_indent";
|
|
my $entab = $ENV{PGENTAB} || "entab";
|
|
|
|
# no non-option arguments given. so do everything in the current directory
|
|
$code_base ||= '.' unless @ARGV;
|
|
|
|
# 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";
|
|
|
|
# globals
|
|
my @files;
|
|
my $filtered_typedefs_fh;
|
|
|
|
|
|
sub check_indent
|
|
{
|
|
system("$entab < $devnull");
|
|
if ($?)
|
|
{
|
|
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)
|
|
{
|
|
print STDERR
|
|
"You do not appear to have 'indent' installed on your system.\n";
|
|
exit 1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
system("$indent -gnu < $devnull > $devnull 2>&1");
|
|
if ($? == 0)
|
|
{
|
|
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 "cannot locate typedefs file \"$typedefs_file\"\n"
|
|
unless $typedefs_file && -f $typedefs_file;
|
|
|
|
open(my $typedefs_fh, '<', $typedefs_file)
|
|
|| die "cannot open typedefs file \"$typedefs_file\": $!\n";
|
|
my @typedefs = <$typedefs_fh>;
|
|
close($typedefs_fh);
|
|
if (defined($typedef_str))
|
|
{
|
|
foreach my $typedef (split(m/[, \t\n]+/, $typedef_str))
|
|
{
|
|
push(@typedefs, $typedef . "\n");
|
|
}
|
|
}
|
|
|
|
# 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 "cannot open exclude file \"$excludes\"\n";
|
|
while (my $line = <$eh>)
|
|
{
|
|
chomp $line;
|
|
my $rgx;
|
|
eval " \$rgx = qr!$line!;";
|
|
@files = grep { $_ !~ /$rgx/ } @files if $rgx;
|
|
}
|
|
close($eh);
|
|
}
|
|
}
|
|
|
|
|
|
sub read_source
|
|
{
|
|
my $source_filename = shift;
|
|
my $source;
|
|
|
|
open(my $src_fd, '<', $source_filename)
|
|
|| die "cannot open file \"$source_filename\": $!\n";
|
|
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 "cannot open file \"$source_filename\": $!\n";
|
|
print $src_fh $source;
|
|
close($src_fh);
|
|
}
|
|
|
|
|
|
sub pre_indent
|
|
{
|
|
my $source = shift;
|
|
|
|
# remove trailing whitespace
|
|
$source =~ s/[ \t]+$//gm;
|
|
|
|
## Comments
|
|
|
|
# Convert // comments to /* */
|
|
$source =~ s!^([ \t]*)//(.*)$!$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!(\}|[ \t])else[ \t]*(/\*)(.*\*/)[ \t]*$!$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!(\}|[ \t])else[ \t]*(/\*[^*]*)[ \t]*$!$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 =~ /^\{[ \t]*$/;
|
|
|
|
# previous line has a closing paren
|
|
next unless $srclines[ $lno - 1 ] =~ /\)/;
|
|
|
|
# previous line was struct, etc.
|
|
next
|
|
if $srclines[ $lno - 1 ] =~
|
|
m!=|^(struct|enum|[ \t]*typedef|extern[ \t]+"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[ \t]+__cplusplus.*\nextern[ \t]+"C"[ \t]*\n)\{[ \t]*$!$1$extern_c_start!gm;
|
|
$source =~ s!(^#ifdef[ \t]+__cplusplus.*\n)\}[ \t]*$!$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[ \t]+/\* _PGMV!else\t/*!g;
|
|
|
|
# Indent single-line after-'else' comment by only one tab.
|
|
$source =~ s!(\}|[ \t])else[ \t]+(/\*.*\*/)[ \t]*$!$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([ \t]+/\*)$!$1$2!gm;
|
|
|
|
# cpp conditionals
|
|
|
|
# Reduce whitespace between #endif and comments to one tab
|
|
$source =~ s!^\#endif[ \t]+/\*!#endif /*!gm;
|
|
|
|
## Functions
|
|
|
|
# Work around misindenting of function with no variables defined.
|
|
$source =~ s!^[ \t]*int[ \t]+pgindent_func_no_var_fix;[ \t]*\n{1,2}!!gm;
|
|
|
|
# Use a single space before '*' in function return types
|
|
$source =~ s!^([A-Za-z_]\S*)[ \t]+\*$!$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(
|
|
(.*,([ \t]*$comment)?\n)* # args b4 final ln
|
|
.*\);([ \t]*$comment)?$ # final line
|
|
)
|
|
!$1 . (substr($1,-1,1) eq '*' ? '' : ' ') . $2!gmxe;
|
|
|
|
## Other
|
|
|
|
# Remove too much indenting after closing brace.
|
|
$source =~ s!^\}\t[ \t]+!}\t!gm;
|
|
|
|
# Workaround indent bug that places excessive space before 'static'.
|
|
$source =~ s!^static[ \t]+!static !gm;
|
|
|
|
# Remove leading whitespace from typedefs
|
|
$source =~ s!^[ \t]+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 "cannot locate src/tools/pgindent directory in \"$code_base\"\n"
|
|
unless -d "$code_base/src/tools/pgindent";
|
|
|
|
chdir "$code_base/src/tools/pgindent";
|
|
|
|
my $typedefs_list_url =
|
|
"http://buildfarm.postgresql.org/cgi-bin/typedefs.pl";
|
|
|
|
my $rv = getstore($typedefs_list_url, "tmp_typedefs.list");
|
|
|
|
die "cannot fetch typedefs list from $typedefs_list_url\n"
|
|
unless is_success($rv);
|
|
|
|
$ENV{PGTYPEDEFS} = abs_path('tmp_typedefs.list');
|
|
|
|
my $pg_bsd_indent_url =
|
|
"ftp://ftp.postgresql.org/pub/dev/pg_bsd_indent-"
|
|
. $INDENT_VERSION
|
|
. ".tar.gz";
|
|
|
|
$rv = getstore($pg_bsd_indent_url, "pg_bsd_indent.tgz");
|
|
|
|
die "cannot fetch BSD indent tarfile from $pg_bsd_indent_url\n"
|
|
unless is_success($rv);
|
|
|
|
# XXX add error checking here
|
|
|
|
system("tar -z -xf pg_bsd_indent.tgz");
|
|
chdir "pg_bsd_indent";
|
|
system("make > $devnull 2>&1");
|
|
|
|
$ENV{PGINDENT} = abs_path('pg_bsd_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 "cannot locate src/tools/pgindent directory in \"$code_base\"\n"
|
|
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;
|