7 use Cwd qw(abs_path getcwd);
9 use File::Spec qw(devnull);
14 # Update for pg_bsd_indent version
15 my $INDENT_VERSION = "1.3";
16 my $devnull = File::Spec->devnull;
18 # Common indent settings
20 "-bad -bap -bc -bl -d0 -cdb -nce -nfc1 -di12 -i4 -l79 -lp -nip -npro -bbb";
22 # indent-dependant settings
25 my ($typedefs_file, $typedef_str, $code_base, $excludes, $indent, $build);
28 "typedefs=s" => \$typedefs_file,
29 "list-of-typedefs=s" => \$typedef_str,
30 "code-base=s" => \$code_base,
31 "excludes=s" => \$excludes,
32 "indent=s" => \$indent,
34 GetOptions(%options) || die "bad command line argument\n";
36 run_build($code_base) if ($build);
38 # command line option wins, then first non-option arg,
39 # then environment (which is how --build sets it) ,
40 # then locations. based on current dir, then default location
41 $typedefs_file ||= shift if @ARGV && $ARGV[0] !~ /\.[ch]$/;
42 $typedefs_file ||= $ENV{PGTYPEDEFS};
44 # build mode sets PGINDENT and PGENTAB
45 $indent ||= $ENV{PGINDENT} || $ENV{INDENT} || "pg_bsd_indent";
46 my $entab = $ENV{PGENTAB} || "entab";
48 # no non-option arguments given. so do everything in the current directory
49 $code_base ||= '.' unless @ARGV;
51 # if it's the base of a postgres tree, we will exclude the files
52 # postgres wants excluded
53 $excludes ||= "$code_base/src/tools/pgindent/exclude_file_patterns"
54 if $code_base && -f "$code_base/src/tools/pgindent/exclude_file_patterns";
58 my $filtered_typedefs_fh;
63 system("$entab < $devnull");
67 "Go to the src/tools/entab directory and do 'make' and 'make install'.\n",
68 "This will put the 'entab' command in your path.\n",
69 "Then run $0 again.\n";
73 system("$indent -? < $devnull > $devnull 2>&1");
77 "You do not appear to have 'indent' installed on your system.\n";
81 if (`$indent -V` !~ m/ $INDENT_VERSION$/)
84 "You do not appear to have $indent version $INDENT_VERSION installed on your system.\n";
88 system("$indent -gnu < $devnull > $devnull 2>&1");
92 "You appear to have GNU indent rather than BSD indent.\n",
93 "See the pgindent/README file for a description of its problems.\n";
94 $extra_opts = "-cdb -bli0 -npcs -cli4 -sc";
98 $extra_opts = "-cli1";
106 # try fairly hard to find the typedefs file if it's not set
108 foreach my $try ('.', 'src/tools/pgindent', '/usr/local/etc')
110 $typedefs_file ||= "$try/typedefs.list"
111 if (-f "$try/typedefs.list");
114 # try to find typedefs by moving up directory levels
118 $typedefs_file ||= "$tdtry/src/tools/pgindent/typedefs.list"
119 if (-f "$tdtry/src/tools/pgindent/typedefs.list");
120 $tdtry = "$tdtry/..";
122 die "cannot locate typedefs file \"$typedefs_file\"\n"
123 unless $typedefs_file && -f $typedefs_file;
125 open(my $typedefs_fh, '<', $typedefs_file)
126 || die "cannot open typedefs file \"$typedefs_file\": $!\n";
127 my @typedefs = <$typedefs_fh>;
130 # add command-line-supplied typedefs?
131 if (defined($typedef_str))
133 foreach my $typedef (split(m/[, \t\n]+/, $typedef_str))
135 push(@typedefs, $typedef . "\n");
139 # remove certain entries
141 grep { !m/^(FD_SET|date|interval|timestamp|ANY)\n?$/ } @typedefs;
143 # write filtered typedefs
144 my $filter_typedefs_fh = new File::Temp(TEMPLATE => "pgtypedefXXXXX");
145 print $filter_typedefs_fh @typedefs;
146 $filter_typedefs_fh->close();
148 # temp file remains because we return a file handle reference
149 return $filter_typedefs_fh;
155 if ($excludes && @files)
157 open(my $eh, '<', $excludes)
158 || die "cannot open exclude file \"$excludes\"\n";
159 while (my $line = <$eh>)
163 eval " \$rgx = qr!$line!;";
164 @files = grep { $_ !~ /$rgx/ } @files if $rgx;
173 my $source_filename = shift;
176 open(my $src_fd, '<', $source_filename)
177 || die "cannot open file \"$source_filename\": $!\n";
189 my $source_filename = shift;
191 open(my $src_fh, '>', $source_filename)
192 || die "cannot open file \"$source_filename\": $!\n";
193 print $src_fh $source;
202 # remove trailing whitespace
203 $source =~ s/[ \t]+$//gm;
207 # Convert // comments to /* */
208 $source =~ s!^([ \t]*)//(.*)$!$1/* $2 */!gm;
210 # 'else' followed by a single-line comment, followed by
211 # a brace on the next line confuses BSD indent, so we push
212 # the comment down to the next line, then later pull it
213 # back up again. Add space before _PGMV or indent will add
215 # AMD: A symptom of not getting this right is that you see errors like:
216 # FILE: ../../../src/backend/rewrite/rewriteHandler.c
218 # Stuff missing from end of file
220 s!(\}|[ \t])else[ \t]*(/\*)(.*\*/)[ \t]*$!$1else\n $2 _PGMV$3!gm;
222 # Indent multi-line after-'else' comment so BSD indent will move it
223 # properly. We already moved down single-line comments above.
224 # Check for '*' to make sure we are not in a single-line comment that
225 # has other text on the line.
226 $source =~ s!(\}|[ \t])else[ \t]*(/\*[^*]*)[ \t]*$!$1else\n $2!gm;
228 # Mark some comments for special treatment later
229 $source =~ s!/\* +---!/*---X_X!g;
233 # Work around bug where function that defines no local variables
234 # misindents switch() case lines and line after #else. Do not do
236 my @srclines = split(/\n/, $source);
237 foreach my $lno (1 .. $#srclines)
239 my $l2 = $srclines[$lno];
241 # Line is only a single open brace in column 0
242 next unless $l2 =~ /^\{[ \t]*$/;
244 # previous line has a closing paren
245 next unless $srclines[ $lno - 1 ] =~ /\)/;
247 # previous line was struct, etc.
249 if $srclines[ $lno - 1 ] =~
250 m!=|^(struct|enum|[ \t]*typedef|extern[ \t]+"C")!;
252 $srclines[$lno] = "$l2\nint pgindent_func_no_var_fix;";
254 $source = join("\n", @srclines) . "\n"; # make sure there's a final \n
256 # Prevent indenting of code in 'extern "C"' blocks.
257 # we replace the braces with comments which we'll reverse later
258 my $extern_c_start = '/* Open extern "C" */';
259 my $extern_c_stop = '/* Close extern "C" */';
261 s!(^#ifdef[ \t]+__cplusplus.*\nextern[ \t]+"C"[ \t]*\n)\{[ \t]*$!$1$extern_c_start!gm;
262 $source =~ s!(^#ifdef[ \t]+__cplusplus.*\n)\}[ \t]*$!$1$extern_c_stop!gm;
271 my $source_filename = shift;
273 # put back braces for extern "C"
274 $source =~ s!^/\* Open extern "C" \*/$!{!gm;
275 $source =~ s!^/\* Close extern "C" \*/$!}!gm;
279 # remove special comment marker
280 $source =~ s!/\*---X_X!/* ---!g;
282 # Pull up single-line comment after 'else' that was pulled down above
283 $source =~ s!else\n[ \t]+/\* _PGMV!else\t/*!g;
285 # Indent single-line after-'else' comment by only one tab.
286 $source =~ s!(\}|[ \t])else[ \t]+(/\*.*\*/)[ \t]*$!$1else\t$2!gm;
288 # Add tab before comments with no whitespace before them (on a tab stop)
289 $source =~ s!(\S)(/\*.*\*/)$!$1\t$2!gm;
291 # Remove blank line between opening brace and block comment.
292 $source =~ s!(\t*\{\n)\n([ \t]+/\*)$!$1$2!gm;
296 # Reduce whitespace between #endif and comments to one tab
297 $source =~ s!^\#endif[ \t]+/\*!#endif /*!gm;
301 # Work around misindenting of function with no variables defined.
302 $source =~ s!^[ \t]*int[ \t]+pgindent_func_no_var_fix;[ \t]*\n{1,2}!!gm;
304 # Use a single space before '*' in function return types
305 $source =~ s!^([A-Za-z_]\S*)[ \t]+\*$!$1 *!gm;
307 # Move prototype names to the same line as return type. Useful
308 # for ctags. Indent should do this, but it does not. It formats
309 # prototypes just like real functions.
311 my $ident = qr/[a-zA-Z_][a-zA-Z_0-9]*/;
312 my $comment = qr!/\*.*\*/!;
315 (\n$ident[^(\n]*)\n # e.g. static void
317 $ident\(\n? # func_name(
318 (.*,([ \t]*$comment)?\n)* # args b4 final ln
319 .*\);([ \t]*$comment)?$ # final line
321 !$1 . (substr($1,-1,1) eq '*' ? '' : ' ') . $2!gmxe;
325 # Remove too much indenting after closing brace.
326 $source =~ s!^\}\t[ \t]+!}\t!gm;
328 # Workaround indent bug that places excessive space before 'static'.
329 $source =~ s!^static[ \t]+!static !gm;
331 # Remove leading whitespace from typedefs
332 $source =~ s!^[ \t]+typedef enum!typedef enum!gm
333 if $source_filename =~ 'libpq-(fe|events).h$';
335 # Remove trailing blank lines
336 $source =~ s!\n+\z!\n!;
345 my $error_message = shift;
348 "$indent $indent_opts $extra_opts -U" . $filtered_typedefs_fh->filename;
350 my $tmp_fh = new File::Temp(TEMPLATE => "pgsrcXXXXX");
351 my $filename = $tmp_fh->filename;
352 print $tmp_fh $source;
355 $$error_message = `$cmd $filename 2>&1`;
357 return "" if ($? || length($$error_message) > 0);
359 unlink "$filename.BAK";
361 open(my $src_out, '<', $filename);
363 $source = <$src_out>;
370 # XXX Ideally we'd implement entab/detab in pure perl.
376 my $tmp_fh = new File::Temp(TEMPLATE => "pgdetXXXXX");
377 print $tmp_fh $source;
380 open(my $entab, '-|', "$entab -d -t4 -qc " . $tmp_fh->filename);
393 my $tmp_fh = new File::Temp(TEMPLATE => "pgentXXXXX");
394 print $tmp_fh $source;
402 . " | $entab -t4 -qc | $entab -d -t4 -m");
411 # for development diagnostics
416 my $flags = shift || "";
418 print STDERR "running diff\n";
420 my $pre_fh = new File::Temp(TEMPLATE => "pgdiffbXXXXX");
421 my $post_fh = new File::Temp(TEMPLATE => "pgdiffaXXXXX");
424 print $post_fh $post;
429 system( "diff $flags "
430 . $pre_fh->filename . " "
438 eval "use LWP::Simple;";
440 my $code_base = shift || '.';
441 my $save_dir = getcwd();
443 # look for the code root
446 last if -d "$code_base/src/tools/pgindent";
447 $code_base = "$code_base/..";
450 die "cannot locate src/tools/pgindent directory in \"$code_base\"\n"
451 unless -d "$code_base/src/tools/pgindent";
453 chdir "$code_base/src/tools/pgindent";
455 my $typedefs_list_url =
456 "http://buildfarm.postgresql.org/cgi-bin/typedefs.pl";
458 my $rv = getstore($typedefs_list_url, "tmp_typedefs.list");
460 die "cannot fetch typedefs list from $typedefs_list_url\n"
461 unless is_success($rv);
463 $ENV{PGTYPEDEFS} = abs_path('tmp_typedefs.list');
465 my $pg_bsd_indent_url =
466 "ftp://ftp.postgresql.org/pub/dev/pg_bsd_indent-"
470 $rv = getstore($pg_bsd_indent_url, "pg_bsd_indent.tgz");
472 die "cannot fetch BSD indent tarfile from $pg_bsd_indent_url\n"
473 unless is_success($rv);
475 # XXX add error checking here
477 system("tar -z -xf pg_bsd_indent.tgz");
478 chdir "pg_bsd_indent";
479 system("make > $devnull 2>&1");
481 $ENV{PGINDENT} = abs_path('pg_bsd_indent');
484 system("make > $devnull 2>&1");
486 $ENV{PGENTAB} = abs_path('entab');
494 my $code_base = shift || '.';
496 # look for the code root
499 last if -d "$code_base/src/tools/pgindent";
500 $code_base = "$code_base/..";
503 die "cannot locate src/tools/pgindent directory in \"$code_base\"\n"
504 unless -d "$code_base/src/tools/pgindent";
508 system("rm -rf src/tools/pgindent/bsdindent");
509 system("git clean -q -f src/tools/entab src/tools/pgindent");
515 # get the list of files under code base, if it's set
518 my ($dev, $ino, $mode, $nlink, $uid, $gid);
519 (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))
522 && push(@files, $File::Find::name);
525 $code_base) if $code_base;
529 $filtered_typedefs_fh = load_typedefs();
533 # make sure we process any non-option arguments.
536 foreach my $source_filename (@files)
538 my $source = read_source($source_filename);
539 my $error_message = '';
541 $source = pre_indent($source);
543 # Protect backslashes in DATA() and wrapping in CATALOG()
545 $source = detab($source);
546 $source =~ s!^((DATA|CATALOG)\(.*)$!/*$1*/!gm;
548 $source = run_indent($source, \$error_message);
551 print STDERR "Failure in $source_filename: " . $error_message . "\n";
555 # Restore DATA/CATALOG lines; must be done here so tab alignment is preserved
556 $source =~ s!^/\*((DATA|CATALOG)\(.*)\*/$!$1!gm;
557 $source = entab($source);
559 $source = post_indent($source, $source_filename);
561 write_source($source, $source_filename);
564 build_clean($code_base) if $build;