]> granicus.if.org Git - postgresql/blob - src/tools/pgindent/pgindent
b0c49eb8fc01dd212a4df7c1ba2071f809810d18
[postgresql] / src / tools / pgindent / pgindent
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use 5.008001;
6
7 use Cwd qw(abs_path getcwd);
8 use File::Find;
9 use File::Spec qw(devnull);
10 use File::Temp;
11 use IO::Handle;
12 use Getopt::Long;
13
14 # Update for pg_bsd_indent version
15 my $INDENT_VERSION = "1.1";
16 my $devnull        = File::Spec->devnull;
17
18 # Common indent settings
19 my $indent_opts =
20   "-bad -bap -bc -bl -d0 -cdb -nce -nfc1 -di12 -i4 -l79 -lp -nip -npro -bbb";
21
22 # indent-dependant settings
23 my $extra_opts = "";
24
25 my ($typedefs_file, $code_base, $excludes, $indent, $build);
26
27 my %options = (
28         "typedefs=s"  => \$typedefs_file,
29         "code-base=s" => \$code_base,
30         "excludes=s"  => \$excludes,
31         "indent=s"    => \$indent,
32         "build"       => \$build,);
33 GetOptions(%options) || die "bad command line";
34
35 run_build($code_base) if ($build);
36
37 # command line option wins, then first non-option arg,
38 # then environment (which is how --build sets it) ,
39 # then locations. based on current dir, then default location
40 $typedefs_file ||= shift if @ARGV && $ARGV[0] !~ /\\.[ch]$/;
41 $typedefs_file ||= $ENV{PGTYPEDEFS};
42
43 # build mode sets PGINDENT and PGENTAB
44 $indent ||= $ENV{PGINDENT} || $ENV{INDENT} || "pg_bsd_indent";
45 my $entab = $ENV{PGENTAB} || "entab";
46
47 # no non-option arguments given. so do everything in the current directory
48 $code_base ||= '.' unless @ARGV;
49
50 # if it's the base of a postgres tree, we will exclude the files
51 # postgres wants excluded
52 $excludes ||= "$code_base/src/tools/pgindent/exclude_file_patterns"
53   if $code_base && -f "$code_base/src/tools/pgindent/exclude_file_patterns";
54
55 # globals
56 my @files;
57 my $filtered_typedefs_fh;
58
59
60 sub check_indent
61 {
62         system("entab < $devnull");
63         if ($?)
64         {
65                 print STDERR
66 "Go to the src/tools/entab directory and do 'make' and 'make install'.\n",
67                   "This will put the 'entab' command in your path.\n",
68                   "Then run $0 again.\n";
69                 exit 1;
70         }
71
72         system("$indent -? < $devnull > $devnull 2>&1");
73         if ($? >> 8 != 1)
74         {
75                 print STDERR
76                   "You do not appear to have 'indent' installed on your system.\n";
77                 exit 1;
78         }
79
80         if (`$indent -V` !~ m/ $INDENT_VERSION$/)
81         {
82                 print STDERR
83 "You do not appear to have $indent version $INDENT_VERSION installed on your system.\n";
84                 exit 1;
85         }
86
87         system("$indent -gnu < $devnull > $devnull 2>&1");
88         if ($? == 0)
89         {
90                 print STDERR
91                   "You appear to have GNU indent rather than BSD indent.\n",
92                   "See the pgindent/README file for a description of its problems.\n";
93                 $extra_opts = "-cdb -bli0 -npcs -cli4 -sc";
94         }
95         else
96         {
97                 $extra_opts = "-cli1";
98         }
99 }
100
101
102 sub load_typedefs
103 {
104
105         # try fairly hard to find the typedefs file if it's not set
106
107         foreach my $try ('.', 'src/tools/pgindent', '/usr/local/etc')
108         {
109                 $typedefs_file ||= "$try/typedefs.list"
110                   if (-f "$try/typedefs.list");
111         }
112
113         # try to find typedefs by moving up directory levels
114         my $tdtry = "..";
115         foreach (1 .. 5)
116         {
117                 $typedefs_file ||= "$tdtry/src/tools/pgindent/typedefs.list"
118                   if (-f "$tdtry/src/tools/pgindent/typedefs.list");
119                 $tdtry = "$tdtry/..";
120         }
121         die "no typedefs file" unless $typedefs_file && -f $typedefs_file;
122
123         open(my $typedefs_fh, '<', $typedefs_file)
124           || die "opening $typedefs_file: $!";
125         my @typedefs = <$typedefs_fh>;
126         close($typedefs_fh);
127
128         # remove certain entries
129         @typedefs =
130           grep { !m/^(FD_SET|date|interval|timestamp|ANY)\n?$/ } @typedefs;
131
132         # write filtered typedefs
133         my $filter_typedefs_fh = new File::Temp(TEMPLATE => "pgtypedefXXXXX");
134         print $filter_typedefs_fh @typedefs;
135         $filter_typedefs_fh->close();
136
137         # temp file remains because we return a file handle reference
138         return $filter_typedefs_fh;
139 }
140
141
142 sub process_exclude
143 {
144         if ($excludes && @files)
145         {
146                 open(my $eh, '<', $excludes) || die "opening $excludes";
147                 while (my $line = <$eh>)
148                 {
149                         chomp $line;
150                         my $rgx;
151                         eval " \$rgx = qr!$line!;";
152                         @files = grep { $_ !~ /$rgx/ } @files if $rgx;
153                 }
154                 close($eh);
155         }
156 }
157
158
159 sub read_source
160 {
161         my $source_filename = shift;
162         my $source;
163
164         open(my $src_fd, '<', $source_filename)
165           || die "opening $source_filename: $!";
166         local ($/) = undef;
167         $source = <$src_fd>;
168         close($src_fd);
169
170         return $source;
171 }
172
173
174 sub write_source
175 {
176         my $source          = shift;
177         my $source_filename = shift;
178
179         open(my $src_fh, '>', $source_filename)
180           || die "opening $source_filename: $!";
181         print $src_fh $source;
182         close($src_fh);
183 }
184
185
186 sub pre_indent
187 {
188         my $source = shift;
189
190         # remove trailing whitespace
191         $source =~ s/[ \t]+$//gm;
192
193         ## Comments
194
195         # Convert // comments to /* */
196         $source =~ s!^([ \t]*)//(.*)$!$1/* $2 */!gm;
197
198         # 'else' followed by a single-line comment, followed by
199         # a brace on the next line confuses BSD indent, so we push
200         # the comment down to the next line, then later pull it
201         # back up again.  Add space before _PGMV or indent will add
202         # it for us.
203         # AMD: A symptom of not getting this right is that you see errors like:
204         # FILE: ../../../src/backend/rewrite/rewriteHandler.c
205         # Error@2259:
206         # Stuff missing from end of file
207         $source =~ s!(\}|[ \t])else[ \t]*(/\*)(.*\*/)[ \t]*$!$1else\n    $2 _PGMV$3!gm;
208
209         # Indent multi-line after-'else' comment so BSD indent will move it
210         # properly. We already moved down single-line comments above.
211         # Check for '*' to make sure we are not in a single-line comment that
212         # has other text on the line.
213         $source =~ s!(\}|[ \t])else[ \t]*(/\*[^*]*)[ \t]*$!$1else\n    $2!gm;
214
215         # Mark some comments for special treatment later
216         $source =~ s!/\* +---!/*---X_X!g;
217
218         ## Other
219
220         # Work around bug where function that defines no local variables
221         # misindents switch() case lines and line after #else.  Do not do
222         # for struct/enum.
223         my @srclines = split(/\n/, $source);
224         foreach my $lno (1 .. $#srclines)
225         {
226                 my $l2 = $srclines[$lno];
227
228                 # Line is only a single open brace in column 0
229                 next unless $l2 =~ /^\{[ \t]*$/;
230
231                 # previous line has a closing paren
232                 next unless $srclines[ $lno - 1 ] =~ /\)/;
233
234                 # previous line was struct, etc.
235                 next
236                   if $srclines[ $lno - 1 ] =~
237                           m!=|^(struct|enum|[ \t]*typedef|extern[ \t]+"C")!;
238
239                 $srclines[$lno] = "$l2\nint pgindent_func_no_var_fix;";
240         }
241         $source = join("\n", @srclines) . "\n";    # make sure there's a final \n
242
243         # Prevent indenting of code in 'extern "C"' blocks.
244         # we replace the braces with comments which we'll reverse later
245         my $extern_c_start = '/* Open extern "C" */';
246         my $extern_c_stop  = '/* Close extern "C" */';
247         $source =~
248 s!(^#ifdef[ \t]+__cplusplus.*\nextern[ \t]+"C"[ \t]*\n)\{[ \t]*$!$1$extern_c_start!gm;
249         $source =~ s!(^#ifdef[ \t]+__cplusplus.*\n)\}[ \t]*$!$1$extern_c_stop!gm;
250
251         return $source;
252 }
253
254
255 sub post_indent
256 {
257         my $source          = shift;
258         my $source_filename = shift;
259
260         # put back braces for extern "C"
261         $source =~ s!^/\* Open extern "C" \*/$!{!gm;
262         $source =~ s!^/\* Close extern "C" \*/$!}!gm;
263
264         ## Comments
265
266         # remove special comment marker
267         $source =~ s!/\*---X_X!/* ---!g;
268
269         # Pull up single-line comment after 'else' that was pulled down above
270         $source =~ s!else\n[ \t]+/\* _PGMV!else\t/*!g;
271
272         # Indent single-line after-'else' comment by only one tab.
273         $source =~ s!(\}|[ \t])else[ \t]+(/\*.*\*/)[ \t]*$!$1else\t$2!gm;
274
275         # Add tab before comments with no whitespace before them (on a tab stop)
276         $source =~ s!(\S)(/\*.*\*/)$!$1\t$2!gm;
277
278         # Remove blank line between opening brace and block comment.
279         $source =~ s!(\t*\{\n)\n([ \t]+/\*)$!$1$2!gm;
280
281         # cpp conditionals
282
283         # Reduce whitespace between #endif and comments to one tab
284         $source =~ s!^\#endif[ \t]+/\*!#endif   /*!gm;
285
286         # Remove blank line(s) before #else, #elif, and #endif
287         $source =~ s!\n\n+(\#else|\#elif|\#endif)!\n$1!g;
288
289         # Add blank line before #endif if it is the last line in the file
290         $source =~ s!\n(#endif.*)\n\z!\n\n$1\n!;
291
292         ## Functions
293
294         # Work around misindenting of function with no variables defined.
295         $source =~ s!^[ \t]*int[ \t]+pgindent_func_no_var_fix;[ \t]*\n{1,2}!!gm;
296
297         # Use a single space before '*' in function return types
298         $source =~ s!^([A-Za-z_]\S*)[ \t]+\*$!$1 *!gm;
299
300         #  Move prototype names to the same line as return type.  Useful
301         # for ctags.  Indent should do this, but it does not.  It formats
302         # prototypes just like real functions.
303
304         my $ident   = qr/[a-zA-Z_][a-zA-Z_0-9]*/;
305         my $comment = qr!/\*.*\*/!;
306
307         $source =~ s!
308                         (\n$ident[^(\n]*)\n                  # e.g. static void
309                         (
310                                 $ident\(\n?                      # func_name( 
311                                 (.*,([ \t]*$comment)?\n)*        # args b4 final ln
312                                 .*\);([ \t]*$comment)?$          # final line
313                         )
314                 !$1 . (substr($1,-1,1) eq '*' ? '' : ' ') . $2!gmxe;
315
316         ## Other
317
318         # Remove too much indenting after closing brace.
319         $source =~ s!^\}\t[ \t]+!}\t!gm;
320
321         # Workaround indent bug that places excessive space before 'static'.
322         $source =~ s!^static[ \t]+!static !gm;
323
324         # Remove leading whitespace from typedefs
325         $source =~ s!^[ \t]+typedef enum!typedef enum!gm
326           if $source_filename =~ 'libpq-(fe|events).h$';
327
328         # Remove trailing blank lines
329         $source =~ s!\n+\z!\n!;
330
331         return $source;
332 }
333
334
335 sub run_indent
336 {
337         my $source        = shift;
338         my $error_message = shift;
339
340         my $cmd =
341           "$indent $indent_opts $extra_opts -U" . $filtered_typedefs_fh->filename;
342
343         my $tmp_fh = new File::Temp(TEMPLATE => "pgsrcXXXXX");
344         my $filename = $tmp_fh->filename;
345         print $tmp_fh $source;
346         $tmp_fh->close();
347
348         $$error_message = `$cmd $filename 2>&1`;
349
350         return "" if ($? || length($$error_message) > 0);
351
352         unlink "$filename.BAK";
353
354         open(my $src_out, '<', $filename);
355         local ($/) = undef;
356         $source = <$src_out>;
357         close($src_out);
358
359         return $source;
360
361 }
362
363 # XXX Ideally we'd implement entab/detab in pure perl.
364
365 sub detab
366 {
367         my $source = shift;
368
369         my $tmp_fh = new File::Temp(TEMPLATE => "pgdetXXXXX");
370         print $tmp_fh $source;
371         $tmp_fh->close();
372
373         open(my $entab, '-|', "$entab -d -t4 -qc " . $tmp_fh->filename);
374         local ($/) = undef;
375         $source = <$entab>;
376         close($entab);
377
378         return $source;
379 }
380
381
382 sub entab
383 {
384         my $source = shift;
385
386         my $tmp_fh = new File::Temp(TEMPLATE => "pgentXXXXX");
387         print $tmp_fh $source;
388         $tmp_fh->close();
389
390         open(my $entab, '-|',
391                 "$entab -d -t8 -qc " . $tmp_fh->filename . " | $entab -t4 -qc");
392         local ($/) = undef;
393         $source = <$entab>;
394         close($entab);
395
396         return $source;
397 }
398
399
400 # for development diagnostics
401 sub diff
402 {
403         my $pre   = shift;
404         my $post  = shift;
405         my $flags = shift || "";
406
407         print STDERR "running diff\n";
408
409         my $pre_fh  = new File::Temp(TEMPLATE => "pgdiffbXXXXX");
410         my $post_fh = new File::Temp(TEMPLATE => "pgdiffaXXXXX");
411
412         print $pre_fh $pre;
413         print $post_fh $post;
414
415         $pre_fh->close();
416         $post_fh->close();
417
418         system( "diff $flags "
419                   . $pre_fh->filename . " "
420                   . $post_fh->filename
421                   . " >&2");
422 }
423
424
425 sub run_build
426 {
427         eval "use LWP::Simple;";
428
429         my $code_base = shift || '.';
430         my $save_dir = getcwd();
431
432         # look for the code root
433         foreach (1 .. 5)
434         {
435                 last if -d "$code_base/src/tools/pgindent";
436                 $code_base = "$code_base/..";
437         }
438
439         die "no src/tools/pgindent directory in $code_base"
440           unless -d "$code_base/src/tools/pgindent";
441
442         chdir "$code_base/src/tools/pgindent";
443
444         my $rv = getstore("http://buildfarm.postgresql.org/cgi-bin/typedefs.pl",
445                 "tmp_typedefs.list");
446
447         die "fetching typedefs.list" unless is_success($rv);
448
449         $ENV{PGTYPEDEFS} = abs_path('tmp_typedefs.list');
450
451         $rv =
452           getstore("ftp://ftp.postgresql.org/pub/dev/indent.netbsd.patched.tgz",
453                 "indent.netbsd.patched.tgz");
454
455         die "fetching indent.netbsd.patched.tgz" unless is_success($rv);
456
457         # XXX add error checking here
458
459         mkdir "bsdindent";
460         chdir "bsdindent";
461         system("tar -z -xf ../indent.netbsd.patched.tgz");
462         system("make > $devnull 2>&1");
463
464         $ENV{PGINDENT} = abs_path('indent');
465
466         chdir "../../entab";
467
468         system("make > $devnull 2>&1");
469
470         $ENV{PGENTAB} = abs_path('entab');
471
472         chdir $save_dir;
473
474 }
475
476
477 sub build_clean
478 {
479         my $code_base = shift || '.';
480
481         # look for the code root
482         foreach (1 .. 5)
483         {
484                 last if -d "$code_base/src/tools/pgindent";
485                 $code_base = "$code_base/..";
486         }
487
488         die "no src/tools/pgindent directory in $code_base"
489           unless -d "$code_base/src/tools/pgindent";
490
491         chdir "$code_base";
492
493         system("rm -rf src/tools/pgindent/bsdindent");
494         system("git clean -q -f src/tools/entab src/tools/pgindent");
495 }
496
497
498 # main
499
500 # get the list of files under code base, if it's set
501 File::Find::find(
502         {   wanted => sub {
503                         my ($dev, $ino, $mode, $nlink, $uid, $gid);
504                         (($dev, $ino, $mode, $nlink, $uid, $gid) = lstat($_))
505                           && -f _
506                           && /^.*\.[ch]\z/s
507                           && push(@files, $File::Find::name);
508                   }
509         },
510         $code_base) if $code_base;
511
512 process_exclude();
513
514 $filtered_typedefs_fh = load_typedefs();
515
516 check_indent();
517
518 # make sure we process any non-option arguments.
519 push(@files, @ARGV);
520
521 foreach my $source_filename (@files)
522 {
523         my $source        = read_source($source_filename);
524         my $error_message = '';
525
526         $source = pre_indent($source);
527
528         # Protect backslashes in DATA() and wrapping in CATALOG()
529
530         $source = detab($source);
531         $source =~ s!^((DATA|CATALOG)\(.*)$!/*$1*/!gm;
532
533         $source = run_indent($source, \$error_message);
534         if ($source eq "")
535         {
536                 print STDERR "Failure in $source_filename: " . $error_message . "\n";
537                 next;
538         }
539
540  # Restore DATA/CATALOG lines; must be done here so tab alignment is preserved
541         $source =~ s!^/\*((DATA|CATALOG)\(.*)\*/$!$1!gm;
542         $source = entab($source);
543
544         $source = post_indent($source, $source_filename);
545
546         write_source($source, $source_filename);
547 }
548
549 build_clean($code_base) if $build;