# Copyright (c) 2021-2025, PostgreSQL Global Development Group
# Program to maintain uniform layout style in our C code. # Exit codes: # 0 -- all OK # 1 -- error invoking pgindent, nothing done # 2 -- --check mode and at least one file requires changes # 3 -- pg_bsd_indent failed on at least one file
use strict;
use warnings FATAL => 'all';
use Cwd qw(abs_path getcwd);
use File::Find;
use File::Spec;
use File::Temp;
use IO::Handle;
use Getopt::Long;
# Update for pg_bsd_indent version
my $INDENT_VERSION = "2.1.2";
usage("Cannot use --commit with command line file list") if (@commits && @ARGV);
# command line option wins, then environment, then locations based on current # dir, then default location
$typedefs_file ||= $ENV{PGTYPEDEFS};
# get indent location for environment or default
$indent ||= $ENV{PGINDENT} || $ENV{INDENT} || "pg_bsd_indent";
my $sourcedir = locate_sourcedir();
# if it's the base of a postgres tree, we will exclude the files # postgres wants excluded if ($sourcedir)
{
my $exclude_candidate = "$sourcedir/exclude_file_patterns";
push(@excludes, $exclude_candidate) if -f $exclude_candidate;
}
# The typedef list that's mechanically extracted by the buildfarm may omit # some names we want to treat like typedefs, e.g. "bool" (which is a macro # according to <stdbool.h>), and may include some names we don't want # treated as typedefs, although various headers that some builds include # might make them so. For the moment we just hardwire a list of names # to add and a list of names to exclude; eventually this may need to be # easier to configure. Note that the typedefs need trailing newlines.
my @additional = map { "$_\n" } qw(
bool regex_t regmatch_t regoff
);
my %excluded = map { +"$_\n" => 1 } qw(
FD_SET LookupSet boolean date duration
element_type inquiry iterator other
pointer reference rep string timestamp type wrap
);
# globals
my @files;
my $filtered_typedefs_fh;
sub check_indent
{ if (system("$indent -? < $devnull > $devnull 2>&1") != 0)
{ if ($? >> 8 != 1)
{
print STDERR "You do not appear to have $indent installed on your system.\n";
exit 1;
}
}
if (`$indent --version` !~ m/ $INDENT_VERSION /)
{
print STDERR "You do not appear to have $indent version $INDENT_VERSION installed on your system.\n";
exit 1;
}
if (system("$indent -gnu < $devnull > $devnull 2>&1") == 0)
{
print STDERR "You appear to have GNU indent rather than BSD indent.\n";
exit 1;
}
return;
}
sub locate_sourcedir
{ # try fairly hard to locate the sourcedir
my $sub = "./src/tools/pgindent"; return $sub if -d $sub; # try to find it from an ancestor directory
$sub = "../src/tools/pgindent";
foreach (1 .. 4)
{ return $sub if -d $sub;
$sub = "../$sub";
} return; # undef if nothing found
}
sub load_typedefs
{ # try fairly hard to find the typedefs file if it's not set
foreach my $try ('.', $sourcedir, '/usr/local/etc')
{
last if $typedefs_file;
next unless defined $try;
$typedefs_file = "$try/typedefs.list" if (-f "$try/typedefs.list");
}
# Adjust dash-protected block comments so indent won't change them
$source =~ s!/\* +---!/*---X_X!g;
## Other
# 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;
# Protect wrapping in CATALOG()
$source =~ s!^(CATALOG\(.*)$!/*$1*/!gm;
# Fix run-together comments to have a tab between them
$source =~ s!\*/(/\*.*\*/)$!*/\t$1!gm;
## Functions
# Use a single space before '*' in function return types
$source =~ s!^([A-Za-z_]\S*)[ \t]+\*$!$1 *!gm;
return $source;
}
sub run_indent
{
my $source = shift;
my $error_message = shift;
my $cmd = "$indent $indent_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) || die $!;
local ($/) = undef;
$source = <$src_out>;
close($src_out);
return $source;
}
sub diff
{
my $indented = shift;
my $source_filename = shift;
my $post_fh = new File::Temp(TEMPLATE => "pgdiffXXXXX");
my $post_fh_filename = $post_fh->filename;
print $post_fh $indented;
$post_fh->close();
my $diff = `diff -upd "$source_filename""$post_fh_filename"2>&>1`; return $diff;
}
sub usage
{
my $message = shift;
my $helptext = <<'EOF';
Usage:
pgindent [OPTION]... [FILE|DIR]...
Options:
--help show this message and quit
--commit=gitref use files modified by the named commit
--typedefs=FILE file containing a list of typedefs
--list-of-typedefs=STR string containing typedefs, space separated
--excludes=PATH file containing list of filename patterns to ignore
--indent=PATH path to pg_bsd_indent program
--diff show the changes that would be made
--check exit with status 2if any changes would be made
The --excludes and --commit options can be given more than once.
EOF if ($help)
{
print $helptext;
exit 0;
} else
{
print STDERR "$message\n", $helptext;
exit 1;
}
}
# main
$filtered_typedefs_fh = load_typedefs();
check_indent();
my $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);
};
# any non-option arguments are files or directories to be processed
File::Find::find({ wanted => $wanted }, @ARGV) if @ARGV;
# commit file locations are relative to the source root
chdir "$sourcedir/../../.."if @commits && $sourcedir;
# process named commits by comparing each with their immediate ancestor
foreach my $commit (@commits)
{
my $prev = "$commit~";
my @affected = `git diff --diff-filter=ACMR --name-only $prev $commit`;
die "git error"if $?;
chomp(@affected);
push(@files, @affected);
}
warn "No files to process" unless @files;
# remove excluded files from the file list
process_exclude();
my %processed;
my $status = 0;
foreach my $source_filename (@files)
{ # skip duplicates
next if $processed{$source_filename};
$processed{$source_filename} = 1;
# ignore anything that's not a .c or .h file
next unless $source_filename =~ /\.[ch]$/;
# don't try to indent a file that doesn't exist
unless (-f $source_filename)
{
warn "Could not find $source_filename";
next;
} # Automatically ignore .c and .h files that correspond to a .y or .l # file. indent tends to get badly confused by Bison/flex output, # and there's no value in indenting derived files anyway.
my $otherfile = $source_filename;
$otherfile =~ s/\.[ch]$/.y/;
next if $otherfile ne $source_filename && -f $otherfile;
$otherfile =~ s/\.y$/.l/;
next if $otherfile ne $source_filename && -f $otherfile;
my $source = read_source($source_filename);
my $orig_source = $source;
my $error_message = '';
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.