2 # Time-stamp: <2002-01-28 11:56:19 barre>
4 # Convert VTK headers to doxygen format
6 # roeim : Vetle Roeim <vetler@ifi.uio.no>
7 # barre : Sebastien Barre <sebastien@barre.nom.fr>
10 # - add --stdout : print converted file to standard output
13 # - add --relativeto path : each file/directory to document is considered
14 # relative to 'path', where --to and --relativeto should be absolute
17 # - fix pb if both --to and path to the file to document were absolute
18 # - remove warning when date or revision not found
21 # - update to match the new VTK 4.0 tree
22 # - change default --dirs so that it can be launched from Utilities/Doxygen
23 # - change default --to so that it can be launched from Utilities/Doxygen
24 # - handle more .SECTION syntax
25 # - add group support (at last)
28 # - add 'parallel' to the default set of directories
31 # - change default --to to '../vtk-doxygen' to comply with Kitware's doxyfile
34 # - as doxygen now handles RCS/CVS tags of the form $word:text$, use them
37 # - change doxygen command style from \ to @ to match javadoc, autodoc, etc.
40 # - change default --to to '../vtk-dox'
43 # - fix O_TEXT flag problem
44 # - switch to Unix CR/LF format
51 # - change (warning) default --to to '../vtk2' because I ruined my own
52 # VTK distrib too many times :(
53 # - add automatic creation of missing directory trees
54 # - add check for current OS (if Windows, do not perform tests based
55 # on stat()/idev/ino features)
58 # - better .SECTION handling
59 # - add support for empty lines in documentation block
60 # - fix problem with headers not corresponding to classes
61 # - change name to doc_header2doxygen (removed vtk_)
62 # - change '-s' (silent) to '-v' (verbose)
63 # - add function description reformatting
66 # - change /*! ... */ position upon request
67 # - add 'Date:' support as @date
68 # - add 'Version:' support as @version
69 # - add 'Thanks:' support as @par Thanks
72 # - fix various " // Description" spelling problems :)
75 # - fix problem with classes with no brief documentation
78 # - add Perl syntactic sugar, options...
79 # - add standard output (filter) mode (-c)
80 # - add silent mode (-s)
81 # - add update mode, convert only if newer (-u)
82 # - add conversion to another directory (--to)
83 # - add '.SECTION Caveats' support as @warning
84 # - add/fix '.SECTION whatever' support as @par
85 # - add default directories to process
88 # - first release (thanks to V. Roeim !)
101 my ($VERSION, $PROGNAME, $AUTHOR) = (0.83, $0, "Sebastien Barre et al.");
102 $PROGNAME =~ s/^.*[\\\/]//;
104 # -------------------------------------------------------------------------
105 # Defaults (add options as you want: "verbose" => 1 for default verbose mode)
109 dirs => ["../../Common",
119 temp => "doc_header2doxygen.tmp",
120 to => "../../../VTK-doxygen"
123 # -------------------------------------------------------------------------
127 Getopt::Long::Configure("bundling");
128 GetOptions (\%args, "help", "verbose|v", "update|u", "force|f", "temp=s", "to=s", "stdout", "relativeto=s");
130 print "$PROGNAME $VERSION, by $AUTHOR\n" if ! exists $args{"stdout"};
132 if (exists $args{"help"}) {
134 Usage : $PROGNAME [--help] [--verbose|-v] [--update|-u] [--force|-f] [--temp file] [--to path] [--relativeto path] [files|directories...]
135 --help : this message
136 --verbose|-v : verbose (display filenames while processing)
137 --update|-u : update (convert only if newer, requires --to)
138 --force|-f : force conversion for all files (overrides --update)
139 --stdout : print converted file to standard output
140 --temp file : use 'file' as temporary file (default: $default{temp})
141 --to path : use 'path' as destination directory (default: $default{to})
142 --relativeto path : each file/directory to document is considered relative to 'path', where --to and --relativeto should be absolute (default: $default{relativeto})
145 $PROGNAME --to ../vtk-doxygen
151 $args{"verbose"} = 1 if exists $default{"verbose"};
152 $args{"update"} = 1 if exists $default{"update"};
153 $args{"force"} = 1 if exists $default{"force"};
154 $args{"temp"} = $default{temp} if ! exists $args{"temp"};
155 $args{"to"} = $default{"to"} if ! exists $args{"to"};
156 $args{"to"} =~ s/[\\\/]*$// if exists $args{"to"};
157 $args{"relativeto"} = $default{"relativeto"} if ! exists $args{"relativeto"};
158 $args{"relativeto"} =~ s/[\\\/]*$// if exists $args{"relativeto"};
160 croak "$PROGNAME: --update requires --to\n"
161 if exists $args{"update"} && ! exists $args{"to"};
163 my $os_is_win = ($^O =~ m/(MSWin32|Cygwin)/i);
164 my $open_file_as_text = $os_is_win ? O_TEXT : 0;
165 my $start_time = time();
167 # -------------------------------------------------------------------------
168 # Collect all files and directories
170 push @ARGV, @{$default{dirs}} if !@ARGV;
172 print "Collecting...\n" if ! exists $args{"stdout"};
174 foreach my $file (@ARGV) {
178 find sub { push @files, $File::Find::name; }, $file;
182 # -------------------------------------------------------------------------
183 # Process files corresponding to headers
185 print "Converting...\n" if ! exists $args{"stdout"};
186 my $intermediate_time = time();
189 foreach my $source (@files) {
191 next if $source !~ /vtk[^\\\/]*\.h\Z/;
193 # Figure out destination file now
196 if (! exists $args{"to"}) {
197 $dest = $args{"temp"};
199 # if source has absolute path, just use the basename, unless a
200 # relativeto path has been set
201 if ($source =~ m/^(\/|[a-zA-W]\:[\/\\])/) {
202 if ($args{"relativeto"}) {
203 my ($dir, $absrel) = (abs_path(dirname($source)),
204 abs_path($args{"relativeto"}));
206 $dest = $args{"to"} . $dir . '/' . basename($source);
208 $dest = $args{"to"} . '/' . basename($source);
211 my $source2 = $source;
212 # let's remove the ../ component before the source filename, so
213 # that it might be appended to the "to" directory
214 $source2 =~ s/^(\.\.[\/\\])*//;
215 $dest = $args{"to"} . '/' . $source2;
217 # Ensure both source and target are different
219 my ($i_dev, $i_ino) = stat $source;
220 my ($o_dev, $o_ino) = stat $dest;
221 croak "$PROGNAME: sorry, $source and $dest are the same file\n"
222 if ($i_dev == $o_dev && $i_ino == $o_ino);
226 # Update mode : skip the file if it is not newer than the
227 # previously converted target
229 if (exists $args{"update"} && ! exists $args{"force"}) {
230 next if -e $dest && (stat $source)[9] < (stat $dest)[9];
234 print " $source\n" if exists $args{"verbose"};
236 # Open file, feed it entirely to an array
238 sysopen(HEADERFILE, $source, O_RDONLY|$open_file_as_text)
239 or croak "$PROGNAME: unable to open $source\n";
240 my @headerfile = <HEADERFILE>;
243 my ($date, $revision) = ("", "");
247 # Parse the file until the beginning of the documentation block
248 # is found. The copyright and disclaimer sections are parsed to
249 # extract the 'Date', 'Version' and 'Thanks' values.
252 while ($line = shift @headerfile) {
254 # Quit if the beginning of the documentation block has been reached.
255 # It is supposed to start with:
256 # // .NAME vtkFooBar - foo bar class
258 last if $line =~ /\/\/ \.NAME/;
261 # Date: $Date: 2002/01/29 23:29:28 $
263 if ($line =~ /^\s*Date:\s*(.*)$/) {
267 # Version: $Revision: 1.7 $
269 } elsif ($line =~ /^\s*Version:\s*(.*)$/) {
272 # Thanks (maybe multi-lines). Example:
273 # Thanks: Thanks to Sebastien Barre who developed this class.
275 } elsif ($line =~ /^\s*Thanks:\s*(.*)$/) {
276 push @thanks, " ", $1, "\n";
277 # Handle multi-line thanks
278 while ($line = shift @headerfile) {
279 last if $line =~ /^\s*$/;
281 push @thanks, " ", $line;
283 push @converted, $line;
285 # Everything else goes to the converted file
288 push @converted, $line;
292 # Process the documentation block
293 # Extract the name of the class and its short description
294 # // .NAME vtkFooBar - foo bar class
296 if (defined($line) && $line =~ /\/\/ \.NAME (\w*)( \- (.*))?/) {
298 my ($class_name, $class_short_description) = ($1, $3);
299 $class_name =~ s/\.h//;
301 # Insert class description, date, revision, thanks
303 push @converted, "/*! \@class $class_name\n";
304 push @converted, " \@brief $class_short_description\n"
305 if $class_short_description;
308 push @converted, "\n $date\n";
311 # WARNING : need a blank line between RCS tags and previous dox tag
314 push @converted, "\n" if (!$date);
315 push @converted, " $revision\n";
318 # Do not add thanks anymore. Will be done externally.
319 # push @converted, " \@par Thanks:\n", @thanks if @thanks;
321 # Read until the end of the documentation block is reached
322 # Translate 'See Also', 'Caveats' and whatever .SECTION
323 # As of 24 sep 2001, there are:
324 # 137 // .SECTION Caveats
325 # 1 // .SECTION Credits
326 # 702 // .SECTION Description
329 # 329 // .SECTION See Also
330 # 4 // .SECTION See also
331 # 70 // .SECTION see also
332 # 1 // .SECTION Warning
333 # find . -name vtk\*.h -exec grep "\.SECTION" {} \; | sort | uniq -c
334 # Let's provide support for bugs too:
339 my ($tag, $inblock) = ("", 0);
340 while ($line = shift @headerfile) {
342 # Quit if the end of the documentation block has been reached.
343 # Let'say that it is supposed to end as soon as the usual
344 # inclusion directives are found, for example:
345 # #ifndef __vtkAbstractTransform_h
346 # #define __vtkAbstractTransform_h
348 last if $line =~ /^\#/;
350 # Process and recognize a .SECTION command and convert it to
351 # the corresponding doxygen tag ($tag)
353 if ($line =~ /^\/\/\s+\.SECTION\s+(.+)\s*$/i) {
357 # Bugs (@bugs). Starts with:
361 if ($type =~ /Bugs?/i) {
365 # Caveats or Warnings (@warning). Starts with:
366 # // .SECTION Caveats
367 # // .SECTION Warning
368 # // .SECTION Warnings
370 elsif ($type =~ /(Caveats|Warnings?)/i) {
374 # Description. Starts with:
375 # // .SECTION Description
377 elsif ($type =~ /Description/i) {
379 push @converted, "\n";
382 # Note (@attention). Starts with:
385 elsif ($type =~ /Note/i) {
386 $tag = "\@attention";
389 # See also (@sa). Starts with:
390 # // .SECTION See Also
392 elsif ($type =~ /See Also/i) {
396 # Todo (@todo). Starts with:
399 elsif ($type =~ /Todo/i) {
403 # Any other .SECTION (@par). Starts with:
404 # // .SECTION whatever
407 $tag = "\@par " . $type . ":";
413 # If the line starts with '//', we are still within the tag block.
414 # Remove '//' for non empty lines, eventually put or duplicate
415 # the tag name if an empty comment is found (meaning that a new
416 # 'paragraph' is requested but with the same tag type)
418 # // .SECTION Caveats
423 # Gets translated into:
431 elsif ($line =~ /^\/\/(.*)/) {
433 if ($remaining =~ /\S/) {
434 push @converted, " $tag\n"
435 if $tag ne "" && ! $inblock;
436 push @converted, $remaining, "\n";
439 push @converted, "\n";
443 # Does not starts with // but still within block or just
444 # before the end (#). Probably an empty line.
445 # Hack : let's have a look at the next line... if it begins
446 # with // then the current line is included (was a space).
448 if (my $next_line = shift @headerfile) {
449 push @converted, $line if $next_line =~ /^\/\//;
450 unshift @headerfile, $next_line;
455 # Close the doxygen documentation block describing the class
457 push @converted, "*/\n\n", $line;
460 # Read until the end of the header and translate the description of
461 # each function provided that it is located in a C++ comment
462 # containing the 'Description:' keyword.
465 # // Construct with automatic computation of divisions, averaging
466 # // 25 points per bucket.
467 # static vtkPointLocator2D *New();
469 while ($line = shift @headerfile) {
471 if ($line =~ /^(\s*)\/\/\s*De(s|c)(s|c)?ription/) {
474 $Text::Wrap::columns = 76;
476 # While there are still lines beginning with '//' append them to
477 # the function's description and trim spaces.
479 my @description = ();
480 while ($line = shift @headerfile) {
481 last if $line !~ /^\s*\/\//;
483 $line =~ s/^\s*\/\/\s*//;
485 push @description, $line;
488 # While there are non-empty lines add these lines to the
489 # list of declarations (and/or inline definitions)
490 # pertaining to the same description.
492 my @declarations = ();
493 while ($line && $line =~ /\s+\S/) {
494 push @declarations, $line;
495 $line = shift @headerfile
498 # If there is more than one declaration or at least a macro,
499 # enclose in a group (actually a single multiline declaration will
500 # be enclosed too, but who cares :)...
503 (scalar @declarations > 1 || $declarations[0] =~ /vtk.+Macro/);
505 push @converted, "$indent//@\{\n" if $enclose;
507 wrap("$indent/*! ", "$indent ", @description), " */\n"
509 push @converted, @declarations;
510 push @converted, "$indent//@\}\n" if $enclose;
513 push @converted, $line;
516 # Write the converted header to its destination
517 # or to standard output.
519 if (exists $args{"stdout"}) {
525 # Open the target and create the missing directory if any
527 if (!sysopen(DEST_FILE,
529 O_WRONLY|O_TRUNC|O_CREAT|$open_file_as_text)) {
530 my $dir = dirname($dest);
534 O_WRONLY|O_TRUNC|O_CREAT|$open_file_as_text)
535 or croak "$PROGNAME: unable to open destination file $dest\n";
537 print DEST_FILE @converted;
540 # If in-place conversion was requested, remove source and rename target
541 # (or temp file) to source
543 if (! exists $args{"to"}) {
545 or carp "$PROGNAME: unable to delete original file $source\n";
546 rename($args{"temp"}, $source)
547 or carp "$PROGNAME: unable to rename ", $args{"temp"}, " to $source\n";
552 if (! exists $args{"stdout"}) {
553 print " => $nb_file files converted in ", time() - $intermediate_time, " s. \n";
554 print "Finished in ", time() - $start_time, " s.\n";