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