1#!/usr/bin/perl 2 3use strict; 4use warnings; 5use 5.008001; 6 7use Cwd qw(abs_path getcwd); 8use File::Find; 9use File::Spec qw(devnull); 10use File::Temp; 11use IO::Handle; 12use Getopt::Long; 13 14# Update for pg_bsd_indent version 15my $INDENT_VERSION = "2.0"; 16 17# Our standard indent settings 18my $indent_opts = 19"-bad -bap -bbb -bc -bl -cli1 -cp33 -cdb -nce -d0 -di12 -nfc1 -i4 -l79 -lp -lpl -nip -npro -sac -tpg -ts4"; 20 21my $devnull = File::Spec->devnull; 22 23my ($typedefs_file, $typedef_str, $code_base, $excludes, $indent, $build); 24 25my %options = ( 26 "typedefs=s" => \$typedefs_file, 27 "list-of-typedefs=s" => \$typedef_str, 28 "code-base=s" => \$code_base, 29 "excludes=s" => \$excludes, 30 "indent=s" => \$indent, 31 "build" => \$build,); 32GetOptions(%options) || die "bad command line argument\n"; 33 34run_build($code_base) if ($build); 35 36# command line option wins, then first non-option arg, 37# then environment (which is how --build sets it) , 38# then locations. based on current dir, then default location 39$typedefs_file ||= shift if @ARGV && $ARGV[0] !~ /\.[ch]$/; 40$typedefs_file ||= $ENV{PGTYPEDEFS}; 41 42# build mode sets PGINDENT 43$indent ||= $ENV{PGINDENT} || $ENV{INDENT} || "pg_bsd_indent"; 44 45# no non-option arguments given. so do everything in the current directory 46$code_base ||= '.' unless @ARGV; 47 48# if it's the base of a postgres tree, we will exclude the files 49# postgres wants excluded 50$excludes ||= "$code_base/src/tools/pgindent/exclude_file_patterns" 51 if $code_base && -f "$code_base/src/tools/pgindent/exclude_file_patterns"; 52 53# globals 54my @files; 55my $filtered_typedefs_fh; 56 57 58sub check_indent 59{ 60 system("$indent -? < $devnull > $devnull 2>&1"); 61 if ($? >> 8 != 1) 62 { 63 print STDERR 64 "You do not appear to have $indent installed on your system.\n"; 65 exit 1; 66 } 67 68 if (`$indent --version` !~ m/ $INDENT_VERSION$/) 69 { 70 print STDERR 71"You do not appear to have $indent version $INDENT_VERSION installed on your system.\n"; 72 exit 1; 73 } 74 75 system("$indent -gnu < $devnull > $devnull 2>&1"); 76 if ($? == 0) 77 { 78 print STDERR 79 "You appear to have GNU indent rather than BSD indent.\n"; 80 exit 1; 81 } 82} 83 84 85sub load_typedefs 86{ 87 88 # try fairly hard to find the typedefs file if it's not set 89 90 foreach my $try ('.', 'src/tools/pgindent', '/usr/local/etc') 91 { 92 $typedefs_file ||= "$try/typedefs.list" 93 if (-f "$try/typedefs.list"); 94 } 95 96 # try to find typedefs by moving up directory levels 97 my $tdtry = ".."; 98 foreach (1 .. 5) 99 { 100 $typedefs_file ||= "$tdtry/src/tools/pgindent/typedefs.list" 101 if (-f "$tdtry/src/tools/pgindent/typedefs.list"); 102 $tdtry = "$tdtry/.."; 103 } 104 die "cannot locate typedefs file \"$typedefs_file\"\n" 105 unless $typedefs_file && -f $typedefs_file; 106 107 open(my $typedefs_fh, '<', $typedefs_file) 108 || die "cannot open typedefs file \"$typedefs_file\": $!\n"; 109 my @typedefs = <$typedefs_fh>; 110 close($typedefs_fh); 111 112 # add command-line-supplied typedefs? 113 if (defined($typedef_str)) 114 { 115 foreach my $typedef (split(m/[, \t\n]+/, $typedef_str)) 116 { 117 push(@typedefs, $typedef . "\n"); 118 } 119 } 120 121 # remove certain entries 122 @typedefs = 123 grep { !m/^(FD_SET|date|interval|timestamp|ANY)\n?$/ } @typedefs; 124 125 # write filtered typedefs 126 my $filter_typedefs_fh = new File::Temp(TEMPLATE => "pgtypedefXXXXX"); 127 print $filter_typedefs_fh @typedefs; 128 $filter_typedefs_fh->close(); 129 130 # temp file remains because we return a file handle reference 131 return $filter_typedefs_fh; 132} 133 134 135sub process_exclude 136{ 137 if ($excludes && @files) 138 { 139 open(my $eh, '<', $excludes) 140 || die "cannot open exclude file \"$excludes\"\n"; 141 while (my $line = <$eh>) 142 { 143 chomp $line; 144 my $rgx = qr!$line!; 145 @files = grep { $_ !~ /$rgx/ } @files if $rgx; 146 } 147 close($eh); 148 } 149} 150 151 152sub read_source 153{ 154 my $source_filename = shift; 155 my $source; 156 157 open(my $src_fd, '<', $source_filename) 158 || die "cannot open file \"$source_filename\": $!\n"; 159 local ($/) = undef; 160 $source = <$src_fd>; 161 close($src_fd); 162 163 return $source; 164} 165 166 167sub write_source 168{ 169 my $source = shift; 170 my $source_filename = shift; 171 172 open(my $src_fh, '>', $source_filename) 173 || die "cannot open file \"$source_filename\": $!\n"; 174 print $src_fh $source; 175 close($src_fh); 176} 177 178 179sub pre_indent 180{ 181 my $source = shift; 182 183 ## Comments 184 185 # Convert // comments to /* */ 186 $source =~ s!^([ \t]*)//(.*)$!$1/* $2 */!gm; 187 188 # Adjust dash-protected block comments so indent won't change them 189 $source =~ s!/\* +---!/*---X_X!g; 190 191 ## Other 192 193 # Prevent indenting of code in 'extern "C"' blocks. 194 # we replace the braces with comments which we'll reverse later 195 my $extern_c_start = '/* Open extern "C" */'; 196 my $extern_c_stop = '/* Close extern "C" */'; 197 $source =~ 198s!(^#ifdef[ \t]+__cplusplus.*\nextern[ \t]+"C"[ \t]*\n)\{[ \t]*$!$1$extern_c_start!gm; 199 $source =~ s!(^#ifdef[ \t]+__cplusplus.*\n)\}[ \t]*$!$1$extern_c_stop!gm; 200 201 # Protect backslashes in DATA() and wrapping in CATALOG() 202 $source =~ s!^((DATA|CATALOG)\(.*)$!/*$1*/!gm; 203 204 return $source; 205} 206 207 208sub post_indent 209{ 210 my $source = shift; 211 my $source_filename = shift; 212 213 # Restore DATA/CATALOG lines 214 $source =~ s!^/\*((DATA|CATALOG)\(.*)\*/$!$1!gm; 215 216 # Put back braces for extern "C" 217 $source =~ s!^/\* Open extern "C" \*/$!{!gm; 218 $source =~ s!^/\* Close extern "C" \*/$!}!gm; 219 220 ## Comments 221 222 # Undo change of dash-protected block comments 223 $source =~ s!/\*---X_X!/* ---!g; 224 225 # Fix run-together comments to have a tab between them 226 $source =~ s!\*/(/\*.*\*/)$!*/\t$1!gm; 227 228 ## Functions 229 230 # Use a single space before '*' in function return types 231 $source =~ s!^([A-Za-z_]\S*)[ \t]+\*$!$1 *!gm; 232 233 # Move prototype names to the same line as return type. Useful 234 # for ctags. Indent should do this, but it does not. It formats 235 # prototypes just like real functions. 236 237 my $ident = qr/[a-zA-Z_][a-zA-Z_0-9]*/; 238 my $comment = qr!/\*.*\*/!; 239 240 $source =~ s! 241 (\n$ident[^(\n]*)\n # e.g. static void 242 ( 243 $ident\(\n? # func_name( 244 (.*,([ \t]*$comment)?\n)* # args b4 final ln 245 .*\);([ \t]*$comment)?$ # final line 246 ) 247 !$1 . (substr($1,-1,1) eq '*' ? '' : ' ') . $2!gmxe; 248 249 return $source; 250} 251 252 253sub run_indent 254{ 255 my $source = shift; 256 my $error_message = shift; 257 258 my $cmd = "$indent $indent_opts -U" . $filtered_typedefs_fh->filename; 259 260 my $tmp_fh = new File::Temp(TEMPLATE => "pgsrcXXXXX"); 261 my $filename = $tmp_fh->filename; 262 print $tmp_fh $source; 263 $tmp_fh->close(); 264 265 $$error_message = `$cmd $filename 2>&1`; 266 267 return "" if ($? || length($$error_message) > 0); 268 269 unlink "$filename.BAK"; 270 271 open(my $src_out, '<', $filename); 272 local ($/) = undef; 273 $source = <$src_out>; 274 close($src_out); 275 276 return $source; 277 278} 279 280 281# for development diagnostics 282sub diff 283{ 284 my $pre = shift; 285 my $post = shift; 286 my $flags = shift || ""; 287 288 print STDERR "running diff\n"; 289 290 my $pre_fh = new File::Temp(TEMPLATE => "pgdiffbXXXXX"); 291 my $post_fh = new File::Temp(TEMPLATE => "pgdiffaXXXXX"); 292 293 print $pre_fh $pre; 294 print $post_fh $post; 295 296 $pre_fh->close(); 297 $post_fh->close(); 298 299 system( "diff $flags " 300 . $pre_fh->filename . " " 301 . $post_fh->filename 302 . " >&2"); 303} 304 305 306sub run_build 307{ 308 eval "use LWP::Simple;"; ## no critic (ProhibitStringyEval); 309 310 my $code_base = shift || '.'; 311 my $save_dir = getcwd(); 312 313 # look for the code root 314 foreach (1 .. 5) 315 { 316 last if -d "$code_base/src/tools/pgindent"; 317 $code_base = "$code_base/.."; 318 } 319 320 die "cannot locate src/tools/pgindent directory in \"$code_base\"\n" 321 unless -d "$code_base/src/tools/pgindent"; 322 323 chdir "$code_base/src/tools/pgindent"; 324 325 my $typedefs_list_url = 326 "https://buildfarm.postgresql.org/cgi-bin/typedefs.pl"; 327 328 my $rv = getstore($typedefs_list_url, "tmp_typedefs.list"); 329 330 die "cannot fetch typedefs list from $typedefs_list_url\n" 331 unless is_success($rv); 332 333 $ENV{PGTYPEDEFS} = abs_path('tmp_typedefs.list'); 334 335 my $indentrepo = "https://git.postgresql.org/git/pg_bsd_indent.git"; 336 system("git clone $indentrepo >$devnull 2>&1"); 337 die "could not fetch pg_bsd_indent sources from $indentrepo\n" 338 unless $? == 0; 339 340 chdir "pg_bsd_indent" || die; 341 system("make all check >$devnull"); 342 die "could not build pg_bsd_indent from source\n" 343 unless $? == 0; 344 345 $ENV{PGINDENT} = abs_path('pg_bsd_indent'); 346 347 chdir $save_dir; 348} 349 350 351sub build_clean 352{ 353 my $code_base = shift || '.'; 354 355 # look for the code root 356 foreach (1 .. 5) 357 { 358 last if -d "$code_base/src/tools/pgindent"; 359 $code_base = "$code_base/.."; 360 } 361 362 die "cannot locate src/tools/pgindent directory in \"$code_base\"\n" 363 unless -d "$code_base/src/tools/pgindent"; 364 365 chdir "$code_base"; 366 367 system("rm -rf src/tools/pgindent/pg_bsd_indent"); 368 system("rm -f src/tools/pgindent/tmp_typedefs.list"); 369} 370 371 372# main 373 374# get the list of files under code base, if it's set 375File::Find::find( 376 { wanted => sub { 377 my ($dev, $ino, $mode, $nlink, $uid, $gid); 378 (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_)) 379 && -f _ 380 && /^.*\.[ch]\z/s 381 && push(@files, $File::Find::name); 382 } 383 }, 384 $code_base) if $code_base; 385 386process_exclude(); 387 388$filtered_typedefs_fh = load_typedefs(); 389 390check_indent(); 391 392# any non-option arguments are files to be processed 393push(@files, @ARGV); 394 395foreach my $source_filename (@files) 396{ 397 398 # Automatically ignore .c and .h files that correspond to a .y or .l 399 # file. indent tends to get badly confused by Bison/flex output, 400 # and there's no value in indenting derived files anyway. 401 my $otherfile = $source_filename; 402 $otherfile =~ s/\.[ch]$/.y/; 403 next if $otherfile ne $source_filename && -f $otherfile; 404 $otherfile =~ s/\.y$/.l/; 405 next if $otherfile ne $source_filename && -f $otherfile; 406 407 my $source = read_source($source_filename); 408 my $orig_source = $source; 409 my $error_message = ''; 410 411 $source = pre_indent($source); 412 413 $source = run_indent($source, \$error_message); 414 if ($source eq "") 415 { 416 print STDERR "Failure in $source_filename: " . $error_message . "\n"; 417 next; 418 } 419 420 $source = post_indent($source, $source_filename); 421 422 write_source($source, $source_filename) if $source ne $orig_source; 423} 424 425build_clean($code_base) if $build; 426