Handle multiline output from grep and don't grep for empty strings.
[wine] / tools / c2man.pl
1 #! /usr/bin/perl -w
2 #
3 # Generate API documentation. See documentation/documentation.sgml for details.
4 #
5 # Copyright (C) 2000 Mike McCormack
6 # Copyright (C) 2003 Jon Griffiths
7 #
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 # Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 #
22 # TODO
23 #  SGML gurus - feel free to smarten up the SGML.
24 #  Add any other relevant information for the dll - imports etc
25 #  Read .spec flags such as -i386,-noname and add to docs appropriately
26 #  Generate an alpabetical listing of every API call, with links
27 #  Should we have a special output mode for WineHQ?
28
29 use strict;
30
31 # Options
32 my $opt_output_directory = "man3w"; # All default options are for nroff (man pages)
33 my $opt_manual_section = "3w";
34 my $opt_wine_root_dir = "";
35 my $opt_output_format = "";         # '' = nroff, 'h' = html, 's' = sgml
36 my $opt_output_empty = 0;           # Non-zero = Create 'empty' comments (for every implemented function)
37 my $opt_fussy = 1;                  # Non-zero = Create only if we have a RETURNS section
38 my $opt_verbose = 0;                # >0 = verbosity. Can be given multiple times (for debugging)
39 my @opt_header_file_list = ();
40 my @opt_spec_file_list = ();
41 my @opt_source_file_list = ();
42
43 # All the collected details about all the .spec files being processed
44 my %spec_files;
45 # All the collected details about all the source files being processed
46 my %source_files;
47
48 # useful globals
49 my $pwd = `pwd`."/";
50 $pwd =~ s/\n//;
51 my @datetime = localtime;
52 my @months = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
53             "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
54 my $year = $datetime[5] + 1900;
55 my $date = "$months[$datetime[4]] $year";
56
57 sub usage
58 {
59   print "\nCreate API Documentation from Wine source code.\n\n",
60         "Usage: c2man.pl [options] {-w <spec>} {-I <include>} {<source>}\n",
61         "Where: <spec> is a .spec file giving a DLL's exports.\n",
62         "       <include> is an include directory used by the DLL.\n",
63         "       <source> is a source file of the DLL.\n",
64         "       The above can be given multiple times on the command line, as appropriate.\n",
65         "Options:\n",
66         " -Th      : Output HTML instead of a man page\n",
67         " -Ts      : Output SGML (Docbook source) instead of a man page\n",
68         " -o <dir> : Create output in <dir>, default is \"",$opt_output_directory,"\"\n",
69         " -s <sect>: Set manual section to <sect>, default is ",$opt_manual_section,"\n",
70         " -e       : Output \"FIXME\" documentation from empty comments.\n",
71         " -v       : Verbosity. Can be given more than once for more detail.\n";
72 }
73
74 # Print usage if we're called with no args
75 if( @ARGV == 0)
76 {
77   usage();
78 }
79
80 # Process command line options
81 while(defined($_ = shift @ARGV))
82 {
83   if( s/^-// )
84   {
85     # An option.
86     for ($_)
87     {
88       /^o$/  && do { $opt_output_directory = shift @ARGV; last; };
89       s/^S// && do { $opt_manual_section   = $_;          last; };
90       /^Th$/ && do { $opt_output_format  = "h";           last; };
91       /^Ts$/ && do { $opt_output_format  = "s";           last; };
92       /^v$/  && do { $opt_verbose++;                      last; };
93       /^e$/  && do { $opt_output_empty = 1;               last; };
94       /^L$/  && do { last; };
95       /^w$/  && do { @opt_spec_file_list = (@opt_spec_file_list, shift @ARGV); last; };
96       s/^I// && do { my $include = $_."/*.h";
97                      $include =~ s/\/\//\//g;
98                      my $have_headers = `ls $include >/dev/null 2>&1`;
99                      if ($? >> 8 == 0) { @opt_header_file_list = (@opt_header_file_list, $include); }
100                      last;
101                    };
102       s/^R// && do { if ($_ =~ /^\//) { $opt_wine_root_dir = $_; }
103                      else { $opt_wine_root_dir = `cd $pwd/$_ && pwd`; }
104                      $opt_wine_root_dir =~ s/\n//;
105                      $opt_wine_root_dir =~ s/\/\//\//g;
106                      if (! $opt_wine_root_dir =~ /\/$/ ) { $opt_wine_root_dir = $opt_wine_root_dir."/"; };
107                      last;
108              };
109       die "Unrecognised option $_\n";
110     }
111   }
112   else
113   {
114     # A source file.
115     push (@opt_source_file_list, $_);
116   }
117 }
118
119 # Remove duplicate include directories
120 my %htmp;
121 @opt_header_file_list = grep(!$htmp{$_}++, @opt_header_file_list);
122
123 if ($opt_verbose > 3)
124 {
125   print "Output dir:'".$opt_output_directory."'\n";
126   print "Section   :'".$opt_manual_section."'\n";
127   print "Format  :'".$opt_output_format."'\n";
128   print "Root    :'".$opt_wine_root_dir."'\n";
129   print "Spec files:'@opt_spec_file_list'\n";
130   print "Includes  :'@opt_header_file_list'\n";
131   print "Sources   :'@opt_source_file_list'\n";
132 }
133
134 if (@opt_spec_file_list == 0)
135 {
136   exit 0; # Don't bother processing non-dll files
137 }
138
139 # Read in each .spec files exports and other details
140 while(my $spec_file = shift @opt_spec_file_list)
141 {
142   process_spec_file($spec_file);
143 }
144
145 if ($opt_verbose > 3)
146 {
147     foreach my $spec_file ( keys %spec_files )
148     {
149         print "in '$spec_file':\n";
150         my $spec_details = $spec_files{$spec_file}[0];
151         my $exports = $spec_details->{EXPORTS};
152         for (@$exports)
153         {
154            print @$_[0].",".@$_[1].",".@$_[2].",".@$_[3]."\n";
155         }
156     }
157 }
158
159 # Extract and output the comments from each source file
160 while(defined($_ = shift @opt_source_file_list))
161 {
162   process_source_file($_);
163 }
164
165 # Write the index files for each spec
166 process_index_files();
167
168 # Write the master index file
169 output_master_index_files();
170
171 exit 0;
172
173
174 # Generate the list of exported entries for the dll
175 sub process_spec_file
176 {
177   my $spec_name = shift(@_);
178   my $dll_name  = $spec_name;
179   $dll_name =~ s/\..*//;       # Strip the file extension
180   my $uc_dll_name  = uc $dll_name;
181
182   my $spec_details =
183   {
184     NAME => $spec_name,
185     DLL_NAME => $dll_name,
186     NUM_EXPORTS => 0,
187     NUM_STUBS => 0,
188     NUM_FUNCS => 0,
189     NUM_FORWARDS => 0,
190     NUM_VARS => 0,
191     NUM_DOCS => 0,
192     CONTRIBUTORS => [ ],
193     SOURCES => [ ],
194     DESCRIPTION => [ ],
195     EXPORTS => [ ],
196     EXPORTED_NAMES => { },
197     IMPLEMENTATION_NAMES => { },
198     EXTRA_COMMENTS => [ ],
199     CURRENT_EXTRA => [ ] ,
200   };
201
202   if ($opt_verbose > 0)
203   {
204     print "Processing ".$spec_name."\n";
205   }
206
207   # We allow opening to fail just to cater for the peculiarities of
208   # the Wine build system. This doesn't hurt, in any case
209   open(SPEC_FILE, "<$spec_name") || return;
210
211   while(<SPEC_FILE>)
212   {
213     s/^\s+//;            # Strip leading space
214     s/\s+\n$/\n/;        # Strip trailing space
215     s/\s+/ /g;           # Strip multiple tabs & spaces to a single space
216     s/\s*#.*//;          # Strip comments
217     s/\(.*\)/ /;         # Strip arguments
218     s/ \-[a-z0-9]+//g;   # Strip modifiers
219     s/\s+/ /g;           # Strip multiple tabs & spaces to a single space (again)
220     s/\n$//;             # Strip newline
221
222     if( /^(([0-9]+)|@) / )
223     {
224       # This line contains an exported symbol
225       my ($ordinal, $call_convention, $exported_name, $implementation_name) = split(' ');
226
227       for ($call_convention)
228       {
229         /^(cdecl|stdcall|varargs|pascal|pascal16)$/
230                  && do { $spec_details->{NUM_FUNCS}++;    last; };
231         /^(variable|equate)$/
232                  && do { $spec_details->{NUM_VARS}++;     last; };
233         /^(forward|extern)$/
234                  && do { $spec_details->{NUM_FORWARDS}++; last; };
235         /^stub$/ && do { $spec_details->{NUM_STUBS}++;    last; };
236         if ($opt_verbose > 0)
237         {
238           print "Warning: didn't recognise convention \'",$call_convention,"'\n";
239         }
240         last;
241       }
242
243       # Convert ordinal only names so we can find them later
244       if ($exported_name eq "@")
245       {
246         $exported_name = $uc_dll_name.'_'.$ordinal;
247       }
248       if (!defined($implementation_name))
249       {
250         $implementation_name = $exported_name;
251       }
252       # Add indices for the exported and implementation names
253       $spec_details->{EXPORTED_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
254       if ($implementation_name ne $exported_name)
255       {
256         $spec_details->{IMPLEMENTATION_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
257       }
258
259       # Add the exported entry
260       $spec_details->{NUM_EXPORTS}++;
261       my $documented = 0;
262       my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented);
263       push (@{$spec_details->{EXPORTS}},[@export]);
264     }
265   }
266   close(SPEC_FILE);
267
268   # Add this .spec files details to the list of .spec files
269   $spec_files{$uc_dll_name} = [$spec_details];
270 }
271
272 # Read each source file, extract comments, and generate API documentation if appropriate
273 sub process_source_file
274 {
275   my $source_file = shift(@_);
276   my $source_details =
277   {
278     CONTRIBUTORS => [ ],
279     DEBUG_CHANNEL => "",
280   };
281   my $comment =
282   {
283     FILE => $source_file,
284     COMMENT_NAME => "",
285     ALT_NAME => "",
286     DLL_NAME => "",
287     ORDINAL => "",
288     RETURNS => "",
289     PROTOTYPE => [],
290     TEXT => [],
291   };
292   my $parse_state = 0;
293   my $ignore_blank_lines = 1;
294   my $extra_comment = 0; # 1 if this is an extra comment, i.e its not a .spec export
295
296   if ($opt_verbose > 0)
297   {
298     print "Processing ".$source_file."\n";
299   }
300   open(SOURCE_FILE,"<$source_file") || die "couldn't open ".$source_file."\n";
301
302   # Add this source file to the list of source files
303   $source_files{$source_file} = [$source_details];
304
305   while(<SOURCE_FILE>)
306   {
307     s/\n$//;   # Strip newline
308     s/^\s+//;  # Strip leading space
309     s/\s+$//;  # Strip trailing space
310     if (! /^\*\|/ )
311     {
312       # Strip multiple tabs & spaces to a single space
313       s/\s+/ /g;
314     }
315
316     if ( / +Copyright *(\([Cc]\))*[0-9 \-\,\/]*([[:alpha:][:^ascii:] \.\-]+)/ )
317     {
318       # Extract a contributor to this file
319       my $contributor = $2;
320       $contributor =~ s/ *$//;
321       $contributor =~ s/^by //;
322       $contributor =~ s/\.$//;
323       $contributor =~ s/ (for .*)/ \($1\)/;
324       if ($contributor ne "")
325       {
326         if ($opt_verbose > 3)
327         {
328           print "Info: Found contributor:'".$contributor."'\n";
329         }
330         push (@{$source_details->{CONTRIBUTORS}},$contributor);
331       }
332     }
333     elsif ( /WINE_DEFAULT_DEBUG_CHANNEL\(([A-Za-z]*)\)/ )
334     {
335       # Extract the debug channel to use
336       if ($opt_verbose > 3)
337       {
338         print "Info: Found debug channel:'".$1."'\n";
339       }
340       $source_details->{DEBUG_CHANNEL} = $1;
341     }
342
343     if ($parse_state == 0) # Searching for a comment
344     {
345       if ( /^\/\**$/ )
346       {
347         # Found a comment start
348         $comment->{COMMENT_NAME} = "";
349         $comment->{ALT_NAME} = "";
350         $comment->{DLL_NAME} = "";
351         $comment->{ORDINAL} = "";
352         $comment->{RETURNS} = "";
353         $comment->{PROTOTYPE} = [];
354         $comment->{TEXT} = [];
355         $ignore_blank_lines = 1;
356         $extra_comment = 0;
357         $parse_state = 3;
358       }
359     }
360     elsif ($parse_state == 1) # Reading in a comment
361     {
362       if ( /^\**\// )
363       {
364         # Found the end of the comment
365         $parse_state = 2;
366       }
367       elsif ( s/^\*\|/\|/ )
368       {
369         # A line of comment not meant to be pre-processed
370         push (@{$comment->{TEXT}},$_); # Add the comment text
371       }
372       elsif ( s/^ *\** *// )
373       {
374         # A line of comment, starting with an asterisk
375         if ( /^[A-Z]+$/ || $_ eq "")
376         {
377           # This is a section start, so skip blank lines before and after it.
378           my $last_line = pop(@{$comment->{TEXT}});
379           if (defined($last_line) && $last_line ne "")
380           {
381             # Put it back
382             push (@{$comment->{TEXT}},$last_line);
383           }
384           if ( /^[A-Z]+$/ )
385           {
386             $ignore_blank_lines = 1;
387           }
388           else
389           {
390             $ignore_blank_lines = 0;
391           }
392         }
393
394         if ($ignore_blank_lines == 0 || $_ ne "")
395         {
396           push (@{$comment->{TEXT}},$_); # Add the comment text
397         }
398       }
399       else
400       {
401         # This isn't a well formatted comment: look for the next one
402         $parse_state = 0;
403       }
404     }
405     elsif ($parse_state == 2) # Finished reading in a comment
406     {
407       if ( /(WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ )
408       {
409         # Comment is followed by a function definition
410         $parse_state = 4; # Fall through to read prototype
411       }
412       else
413       {
414         # Allow cpp directives and blank lines between the comment and prototype
415         if ($extra_comment == 1)
416         {
417           # An extra comment not followed by a function definition
418           $parse_state = 5; # Fall through to process comment
419         }
420         elsif (!/^\#/ && !/^ *$/ && !/^__ASM_GLOBAL_FUNC/)
421         {
422           # This isn't a well formatted comment: look for the next one
423           if ($opt_verbose > 1)
424           {
425             print "Info: Function '",$comment->{COMMENT_NAME},"' not followed by prototype.\n";
426           }
427           $parse_state = 0;
428         }
429       }
430     }
431     elsif ($parse_state == 3) # Reading in the first line of a comment
432     {
433       s/^ *\** *//;
434       if ( /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ )
435       {
436         # Found a correctly formed "ApiName (DLLNAME.Ordinal)" line.
437         $comment->{COMMENT_NAME} = $1;
438         $comment->{DLL_NAME} = uc $3;
439         $comment->{ORDINAL} = $4;
440         $comment->{DLL_NAME} =~ s/^KERNEL$/KRNL386/; # Too many of these to ignore, _old_ code
441         $parse_state = 1;
442       }
443       elsif ( /^([A-Za-z0-9_]+) +\{([A-Za-z0-9_]+)\}$/ )
444       {
445         # Found a correctly formed "CommentTitle {DLLNAME}" line (extra documentation)
446         $comment->{COMMENT_NAME} = $1;
447         $comment->{DLL_NAME} = uc $2;
448         $comment->{ORDINAL} = "";
449         $extra_comment = 1;
450         $parse_state = 1;
451       }
452       else
453       {
454         # This isn't a well formatted comment: look for the next one
455         $parse_state = 0;
456       }
457     }
458
459     if ($parse_state == 4) # Reading in the function definition
460     {
461       push (@{$comment->{PROTOTYPE}},$_);
462       # Strip comments from the line before checking for ')'
463       my $stripped_line = $_;
464       $stripped_line =~ s/ *(\/\* *)(.*?)( *\*\/ *)//;
465       if ( $stripped_line =~ /\)/ )
466       {
467         # Strip a blank last line
468         my $last_line = pop(@{$comment->{TEXT}});
469         if (defined($last_line) && $last_line ne "")
470         {
471           # Put it back
472           push (@{$comment->{TEXT}},$last_line);
473         }
474
475         if ($opt_output_empty != 0 && @{$comment->{TEXT}} == 0)
476         {
477           # Create a 'not implemented' comment
478           @{$comment->{TEXT}} = ("fixme: This function has not yet been documented.");
479         }
480         $parse_state = 5;
481       }
482     }
483
484     if ($parse_state == 5) # Processing the comment
485     {
486       # Process it, if it has any text
487       if (@{$comment->{TEXT}} > 0)
488       {
489         if ($extra_comment == 1)
490         {
491           process_extra_comment($comment);
492         }
493         else
494         {
495           @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
496           process_comment($comment);
497         }
498       }
499       elsif ($opt_verbose > 1 && $opt_output_empty == 0)
500       {
501         print "Info: Function '",$comment->{COMMENT_NAME},"' has no documentation.\n";
502       }
503       $parse_state = 0;
504     }
505   }
506   close(SOURCE_FILE);
507 }
508
509 # Standardise a comments text for consistency
510 sub process_comment_text
511 {
512   my $comment = shift(@_);
513
514   for (@{$comment->{TEXT}})
515   {
516     if (! /^\|/ )
517     {
518       # Map I/O values. These come in too many formats to standardise now....
519       s/\[I\]|\[i\]|\[in\]|\[IN\]/\[In\] /g;
520       s/\[O\]|\[o\]|\[out\]\[OUT\]/\[Out\]/g;
521       s/\[I\/O\]|\[I\,O\]|\[i\/o\]|\[in\/out\]|\[IN\/OUT\]/\[In\/Out\]/g;
522       # TRUE/FALSE/NULL are defines, capitilise them
523       s/True|true/TRUE/g;
524       s/False|false/FALSE/g;
525       s/Null|null/NULL/g;
526       # Preferred capitalisations
527       s/ wine| WINE/ Wine/g;
528       s/ API | api / Api /g;
529       s/DLL| Dll /dll /g;
530       s/ URL | url / Url /g;
531       s/WIN16|win16/Win16/g;
532       s/WIN32|win32/Win32/g;
533       s/WIN64|win64/Win64/g;
534       s/ ID | id / Id /g;
535       # Grammar
536       s/([a-z])\.([A-Z])/$1\. $2/g; # Space after full stop
537       s/ \:/\:/g;                   # Colons to the left
538       s/ \;/\;/g;                   # Semi-colons too
539       # Common idioms
540       s/^See ([A-Za-z0-9_]+)\.$/See $1\(\)\./;                # Referring to A version from W
541       s/^Unicode version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Ditto
542       s/^PARAMETERS$/PARAMS/;  # Name of parameter section should be 'PARAMS'
543       # Trademarks
544       s/( |\.)(MS|Microsoft|microsoft)( |\.)/$1Microsoft\(tm\)$3/g;
545       s/( |\.)(Windows|windows|windoze|winblows)( |\.)/$1Windows\(tm\)$3/g;
546       s/( |\.)(DOS|dos|msdos)( |\.)/$1MS-DOS\(tm\)$3/g;
547       s/( |\.)(UNIX|Unix|unix)( |\.)/$1Unix\(tm\)$3/g;
548       # Abbreviations
549       s/( chars)/characters/g;
550       s/( info )/ information /g;
551       s/( app )/ application /g;
552       s/( apps )/ applications /g;
553     }
554   }
555 }
556
557 # Standardise our comment and output it if it is suitable.
558 sub process_comment
559 {
560   my $comment = shift(@_);
561
562   # Don't process this comment if the function isnt exported
563   my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
564
565   if (!defined($spec_details))
566   {
567     if ($opt_verbose > 2)
568     {
569       print "Warning: Function '".$comment->{COMMENT_NAME}."' belongs to '".
570             $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
571     }
572     return;
573   }
574
575   if ($comment->{COMMENT_NAME} eq "@")
576   {
577     # Create an implementation name
578     $comment->{COMMENT_NAME} = $comment->{DLL_NAME}."_".$comment->{ORDINAL};
579   }
580
581   my $exported_names = $spec_details->{EXPORTED_NAMES};
582   my $export_index = $exported_names->{$comment->{COMMENT_NAME}};
583
584   if (!defined($export_index))
585   {
586     # Perhaps the comment uses the implementation name?
587     my $implementation_names = $spec_details->{IMPLEMENTATION_NAMES};
588     $export_index = $implementation_names->{$comment->{COMMENT_NAME}};
589   }
590   if (!defined($export_index))
591   {
592     # This function doesn't appear to be exported. hmm.
593     if ($opt_verbose > 2)
594     {
595       print "Warning: Function '".$comment->{COMMENT_NAME}."' claims to belong to '".
596             $comment->{DLL_NAME}."' but is not exported by it: not processing it.\n";
597     }
598     return;
599   }
600
601   # When the function is exported twice we have the second name below the first
602   # (you see this a lot in ntdll, but also in some other places).
603   my $first_line = ${@{$comment->{TEXT}}}[1];
604
605   if ( $first_line =~ /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ )
606   {
607     # Found a second name - mark it as documented
608     my $alt_index = $exported_names->{$1};
609     if (defined($alt_index))
610     {
611       if ($opt_verbose > 2)
612       {
613         print "Info: Found alternate name '",$1,"\n";
614       }
615       my $alt_export = @{$spec_details->{EXPORTS}}[$alt_index];
616       @$alt_export[4] |= 1;
617       $spec_details->{NUM_DOCS}++;
618       ${@{$comment->{TEXT}}}[1] = "";
619     }
620   }
621
622   if (@{$spec_details->{CURRENT_EXTRA}})
623   {
624     # We have an extra comment that might be related to this one
625     my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
626     my $current_name = $current_comment->{COMMENT_NAME};
627     if ($comment->{COMMENT_NAME} =~ /^$current_name/ && $comment->{COMMENT_NAME} ne $current_name)
628     {
629       if ($opt_verbose > 2)
630       {
631         print "Linking ",$comment->{COMMENT_NAME}," to $current_name\n";
632       }
633       # Add a reference to this comment to our extra comment
634       push (@{$current_comment->{TEXT}}, $comment->{COMMENT_NAME}."()","");
635     }
636   }
637
638   # We want our docs generated using the implementation name, so they are unique
639   my $export = @{$spec_details->{EXPORTS}}[$export_index];
640   $comment->{COMMENT_NAME} = @$export[3];
641   $comment->{ALT_NAME} = @$export[2];
642
643   # Mark the function as documented
644   $spec_details->{NUM_DOCS}++;
645   @$export[4] |= 1;
646
647   # This file is used by the DLL - Make sure we get our contributors right
648   push (@{$spec_details->{SOURCES}},$comment->{FILE});
649
650   # If we have parameter comments in the prototype, extract them
651   my @parameter_comments;
652   for (@{$comment->{PROTOTYPE}})
653   {
654     s/ *\, */\,/g; # Strip spaces from around commas
655
656     if ( s/ *(\/\* *)(.*?)( *\*\/ *)// ) # Strip out comment
657     {
658       my $parameter_comment = $2;
659       if (!$parameter_comment =~ /^\[/ )
660       {
661         # Add [IO] markers so we format the comment correctly
662         $parameter_comment = "[fixme] ".$parameter_comment;
663       }
664       if ( /( |\*)([A-Za-z_]{1}[A-Za-z_0-9]*)(\,|\))/ )
665       {
666         # Add the parameter name
667         $parameter_comment = $2." ".$parameter_comment;
668       }
669       push (@parameter_comments, $parameter_comment);
670     }
671   }
672
673   # If we extracted any prototype comments, add them to the comment text.
674   if (@parameter_comments)
675   {
676     @parameter_comments = ("PARAMS", @parameter_comments);
677     my @new_comment = ();
678     my $inserted_params = 0;
679
680     for (@{$comment->{TEXT}})
681     {
682       if ( $inserted_params == 0 && /^[A-Z]+$/ )
683       {
684         # Found a section header, so this is where we insert
685         push (@new_comment, @parameter_comments);
686         $inserted_params = 1;
687       }
688       push (@new_comment, $_);
689     }
690     if ($inserted_params == 0)
691     {
692       # Add them to the end
693       push (@new_comment, @parameter_comments);
694     }
695     $comment->{TEXT} = [@new_comment];
696   }
697
698   if ($opt_fussy == 1 && $opt_output_empty == 0)
699   {
700     # Reject any comment that doesn't have a description or a RETURNS section.
701     # This is the default for now, 'coz many comments aren't suitable.
702     my $found_returns = 0;
703     my $found_description_text = 0;
704     my $in_description = 0;
705     for (@{$comment->{TEXT}})
706     {
707       if ( /^RETURNS$/ )
708       {
709         $found_returns = 1;
710         $in_description = 0;
711       }
712       elsif ( /^DESCRIPTION$/ )
713       {
714         $in_description = 1;
715       }
716       elsif ($in_description == 1)
717       {
718         if ( !/^[A-Z]+$/ )
719         {
720           if ( /^See ([A-Za-z0-9_]+)\.$/ || /^Unicode version of ([A-Za-z0-9_]+)\.$/)
721           {
722             # Dont reject comments that refer to their A/W couterpart
723             $found_returns = 1;
724           }
725           $found_description_text = 1;
726         }
727         else
728         {
729           $in_description = 0;
730         }
731       }
732     }
733     if ($found_returns == 0 || $found_description_text == 0)
734     {
735       if ($opt_verbose > 2)
736       {
737         print "Info: Function '",$comment->{COMMENT_NAME},"' has no ",
738               "description and/or RETURNS section, skipping\n";
739       }
740       $spec_details->{NUM_DOCS}--;
741       @$export[4] &= ~1;
742       return;
743     }
744   }
745
746   process_comment_text($comment);
747
748   # Strip the prototypes return value, call convention, name and brackets
749   # (This leaves it as a list of types and names, or empty for void functions)
750   my $prototype = join(" ", @{$comment->{PROTOTYPE}});
751   $prototype =~ s/  / /g;
752   $prototype =~ s/^(.*?) (WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16) (.*?)\( *(.*)/$4/;
753   $comment->{RETURNS} = $1;
754   $prototype =~ s/ *\).*//;        # Strip end bracket
755   $prototype =~ s/ *\* */\*/g;     # Strip space around pointers
756   $prototype =~ s/ *\, */\,/g;     # Strip space around commas
757   $prototype =~ s/^(void|VOID)$//; # If void, leave blank
758   $prototype =~ s/\*([A-Za-z_])/\* $1/g; # Seperate pointers from parameter name
759   @{$comment->{PROTOTYPE}} = split ( /,/ ,$prototype);
760
761   # FIXME: If we have no parameters, make sure we have a PARAMS: None. section
762
763   # Find header file
764   my $h_file = "";
765   if ($comment->{COMMENT_NAME} ne "")
766   {
767     my $tmp = "grep -s -l $comment->{COMMENT_NAME} @opt_header_file_list 2>/dev/null";
768     $tmp = `$tmp`;
769     my $exit_value  = $? >> 8;
770     if ($exit_value == 0)
771     {
772       $tmp =~ s/\n.*//g;
773       if ($tmp ne "")
774       {
775         $h_file = `basename $tmp`;
776       }
777     }
778   }
779   elsif ($comment->{ALT_NAME} ne "")
780   {
781     my $tmp = "grep -s -l $comment->{ALT_NAME} @opt_header_file_list"." 2>/dev/null";
782     $tmp = `$tmp`;
783     my $exit_value  = $? >> 8;
784     if ($exit_value == 0)
785     {
786       $tmp =~ s/\n.*//g;
787       if ($tmp ne "")
788       {
789         $h_file = `basename $tmp`;
790       }
791     }
792   }
793   $h_file =~ s/^ *//;
794   $h_file =~ s/\n//;
795   if ($h_file eq "")
796   {
797     $h_file = "Not defined in a Wine header";
798   }
799   else
800   {
801     $h_file = "Defined in \"".$h_file."\"";
802   }
803
804   # Find source file
805   my $c_file = $comment->{FILE};
806   if ($opt_wine_root_dir ne "")
807   {
808     my $cfile = $pwd."/".$c_file;     # Current dir + file
809     $cfile =~ s/(.+)(\/.*$)/$1/;      # Strip the filename
810     $cfile = `cd $cfile && pwd`;      # Strip any relative parts (e.g. "../../")
811     $cfile =~ s/\n//;                 # Strip newline
812     my $newfile = $c_file;
813     $newfile =~ s/(.+)(\/.*$)/$2/;    # Strip all but the filename
814     $cfile = $cfile."/".$newfile;     # Append filename to base path
815     $cfile =~ s/$opt_wine_root_dir//; # Get rid of the root directory
816     $cfile =~ s/\/\//\//g;            # Remove any double slashes
817     $cfile =~ s/^\/+//;               # Strip initial directory slash
818     $c_file = $cfile;
819   }
820   $c_file = "Implemented in \"".$c_file."\"";
821
822   # Add the implementation details
823   push (@{$comment->{TEXT}}, "IMPLEMENTATION","",$h_file,"",$c_file);
824
825   my $source_details = $source_files{$comment->{FILE}}[0];
826   if ($source_details->{DEBUG_CHANNEL} ne "")
827   {
828     push (@{$comment->{TEXT}}, "", "Debug channel \"".$source_details->{DEBUG_CHANNEL}."\"");
829   }
830
831   # Write out the documentation for the API
832   output_comment($comment)
833 }
834
835 # process our extra comment and output it if it is suitable.
836 sub process_extra_comment
837 {
838   my $comment = shift(@_);
839
840   my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
841
842   if (!defined($spec_details))
843   {
844     if ($opt_verbose > 2)
845     {
846       print "Warning: Extra comment '".$comment->{COMMENT_NAME}."' belongs to '".
847             $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
848     }
849     return;
850   }
851
852   # Check first to see if this is documentation for the DLL.
853   if ($comment->{COMMENT_NAME} eq $comment->{DLL_NAME})
854   {
855     if ($opt_verbose > 2)
856     {
857       print "Info: Found DLL documentation\n";
858     }
859     for (@{$comment->{TEXT}})
860     {
861       push (@{$spec_details->{DESCRIPTION}}, $_);
862     }
863     return;
864   }
865
866   # Add the comment to the DLL page as a link
867   push (@{$spec_details->{EXTRA_COMMENTS}},$comment->{COMMENT_NAME});
868
869   # If we have a prototype, process as a regular comment
870   if (@{$comment->{PROTOTYPE}})
871   {
872     $comment->{ORDINAL} = "@";
873
874     # Add an index for the comment name
875     $spec_details->{EXPORTED_NAMES}{$comment->{COMMENT_NAME}} = $spec_details->{NUM_EXPORTS};
876
877     # Add a fake exported entry
878     $spec_details->{NUM_EXPORTS}++;
879     my ($ordinal, $call_convention, $exported_name, $implementation_name, $documented) =
880      ("@", "fake", $comment->{COMMENT_NAME}, $comment->{COMMENT_NAME}, 0);
881     my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented);
882     push (@{$spec_details->{EXPORTS}},[@export]);
883     @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
884     process_comment($comment);
885     return;
886   }
887
888   if ($opt_verbose > 0)
889   {
890     print "Processing ",$comment->{COMMENT_NAME},"\n";
891   }
892
893   if (@{$spec_details->{CURRENT_EXTRA}})
894   {
895     my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
896
897     if ($opt_verbose > 0)
898     {
899       print "Processing old current: ",$current_comment->{COMMENT_NAME},"\n";
900     }
901     # Output the current comment
902     process_comment_text($current_comment);
903     output_open_api_file($current_comment->{COMMENT_NAME});
904     output_api_header($current_comment);
905     output_api_name($current_comment);
906     output_api_comment($current_comment);
907     output_api_footer($current_comment);
908     output_close_api_file();
909   }
910
911   if ($opt_verbose > 2)
912   {
913     print "Setting current to ",$comment->{COMMENT_NAME},"\n";
914   }
915
916   my $comment_copy =
917   {
918     FILE => $comment->{FILE},
919     COMMENT_NAME => $comment->{COMMENT_NAME},
920     ALT_NAME => $comment->{ALT_NAME},
921     DLL_NAME => $comment->{DLL_NAME},
922     ORDINAL => $comment->{ORDINAL},
923     RETURNS => $comment->{RETURNS},
924     PROTOTYPE => [],
925     TEXT => [],
926   };
927
928   for (@{$comment->{TEXT}})
929   {
930     push (@{$comment_copy->{TEXT}}, $_);
931   }
932   # Set this comment to be the current extra comment
933   @{$spec_details->{CURRENT_EXTRA}} = ($comment_copy);
934 }
935
936 # Write a standardised comment out in the appropriate format
937 sub output_comment
938 {
939   my $comment = shift(@_);
940
941   if ($opt_verbose > 0)
942   {
943     print "Processing ",$comment->{COMMENT_NAME},"\n";
944   }
945
946   if ($opt_verbose > 4)
947   {
948     print "--PROTO--\n";
949     for (@{$comment->{PROTOTYPE}})
950     {
951       print "'".$_."'\n";
952     }
953
954     print "--COMMENT--\n";
955     for (@{$comment->{TEXT} })
956     {
957       print $_."\n";
958     }
959   }
960
961   output_open_api_file($comment->{COMMENT_NAME});
962   output_api_header($comment);
963   output_api_name($comment);
964   output_api_synopsis($comment);
965   output_api_comment($comment);
966   output_api_footer($comment);
967   output_close_api_file();
968 }
969
970 # Write out an index file for each .spec processed
971 sub process_index_files
972 {
973   foreach my $spec_file (keys %spec_files)
974   {
975     my $spec_details = $spec_files{$spec_file}[0];
976     if (defined ($spec_details->{DLL_NAME}))
977     {
978       if (@{$spec_details->{CURRENT_EXTRA}})
979       {
980         # We have an unwritten extra comment, write it
981         my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
982         process_extra_comment($current_comment);
983         @{$spec_details->{CURRENT_EXTRA}} = ();
984        }
985        output_spec($spec_details);
986     }
987   }
988 }
989
990 # Write a spec files documentation out in the appropriate format
991 sub output_spec
992 {
993   my $spec_details = shift(@_);
994
995   if ($opt_verbose > 2)
996   {
997     print "Writing:",$spec_details->{DLL_NAME},"\n";
998   }
999
1000   # Use the comment output functions for consistency
1001   my $comment =
1002   {
1003     FILE => $spec_details->{DLL_NAME},
1004     COMMENT_NAME => $spec_details->{DLL_NAME}.".dll",
1005     ALT_NAME => $spec_details->{DLL_NAME},
1006     DLL_NAME => "",
1007     ORDINAL => "",
1008     RETURNS => "",
1009     PROTOTYPE => [],
1010     TEXT => [],
1011   };
1012   my $total_implemented = $spec_details->{NUM_FORWARDS} + $spec_details->{NUM_VARS} +
1013      $spec_details->{NUM_FUNCS};
1014   my $percent_implemented = 0;
1015   if ($total_implemented)
1016   {
1017     $percent_implemented = $total_implemented /
1018      ($total_implemented + $spec_details->{NUM_STUBS}) * 100;
1019   }
1020   $percent_implemented = int($percent_implemented);
1021   my $percent_documented = 0;
1022   if ($spec_details->{NUM_DOCS})
1023   {
1024     # Treat forwards and data as documented funcs for statistics
1025     $percent_documented = $spec_details->{NUM_DOCS} / $spec_details->{NUM_FUNCS} * 100;
1026     $percent_documented = int($percent_documented);
1027   }
1028
1029   # Make a list of the contributors to this DLL. Do this only for the source
1030   # files that make up the DLL, because some directories specify multiple dlls.
1031   my @contributors;
1032
1033   for (@{$spec_details->{SOURCES}})
1034   {
1035     my $source_details = $source_files{$_}[0];
1036     for (@{$source_details->{CONTRIBUTORS}})
1037     {
1038       push (@contributors, $_);
1039     }
1040   }
1041
1042   my %saw;
1043   @contributors = grep(!$saw{$_}++, @contributors); # remove dups, from perlfaq4 manpage
1044   @contributors = sort @contributors;
1045
1046   # Remove duplicates and blanks
1047   for(my $i=0; $i<@contributors; $i++)
1048   {
1049     if ($i > 0 && ($contributors[$i] =~ /$contributors[$i-1]/ || $contributors[$i-1] eq ""))
1050     {
1051       $contributors[$i-1] = $contributors[$i];
1052     }
1053   }
1054   undef %saw;
1055   @contributors = grep(!$saw{$_}++, @contributors);
1056
1057   if ($opt_verbose > 3)
1058   {
1059     print "Contributors:\n";
1060     for (@contributors)
1061     {
1062       print "'".$_."'\n";
1063     }
1064   }
1065   my $contribstring = join (", ", @contributors);
1066
1067   # Create the initial comment text
1068   @{$comment->{TEXT}} = (
1069     "NAME",
1070     $comment->{COMMENT_NAME}
1071   );
1072
1073   # Add the description, if we have one
1074   if (@{$spec_details->{DESCRIPTION}})
1075   {
1076     push (@{$comment->{TEXT}}, "DESCRIPTION");
1077     for (@{$spec_details->{DESCRIPTION}})
1078     {
1079       push (@{$comment->{TEXT}}, $_);
1080     }
1081   }
1082
1083   # Add the statistics and contributors
1084   push (@{$comment->{TEXT}},
1085     "STATISTICS",
1086     "Forwards: ".$spec_details->{NUM_FORWARDS},
1087     "Variables: ".$spec_details->{NUM_VARS},
1088     "Stubs: ".$spec_details->{NUM_STUBS},
1089     "Functions: ".$spec_details->{NUM_FUNCS},
1090     "Exports-Total: ".$spec_details->{NUM_EXPORTS},
1091     "Implemented-Total: ".$total_implemented." (".$percent_implemented."%)",
1092     "Documented-Total: ".$spec_details->{NUM_DOCS}." (".$percent_documented."%)",
1093     "CONTRIBUTORS",
1094     "The following people hold copyrights on the source files comprising this dll:",
1095     "",
1096     $contribstring,
1097     "Note: This list may not be complete.",
1098     "For a complete listing, see the Files \"AUTHORS\" and \"Changelog\" in the Wine source tree.",
1099     "",
1100   );
1101
1102   if ($opt_output_format eq "h")
1103   {
1104     # Add the exports to the comment text
1105     push (@{$comment->{TEXT}},"EXPORTS");
1106     my $exports = $spec_details->{EXPORTS};
1107     for (@$exports)
1108     {
1109       my $line = "";
1110
1111       # @$_ => ordinal, call convention, exported name, implementation name, documented;
1112       if (@$_[1] eq "forward")
1113       {
1114         my $forward_dll = @$_[3];
1115         $forward_dll =~ s/\.(.*)//;
1116         $line = @$_[2]." (forward to ".$1."() in ".$forward_dll."())";
1117       }
1118       elsif (@$_[1] eq "extern")
1119       {
1120         $line = @$_[2]." (extern)";
1121       }
1122       elsif (@$_[1] eq "stub")
1123       {
1124         $line = @$_[2]." (stub)";
1125       }
1126       elsif (@$_[1] eq "fake")
1127       {
1128         # Don't add this function here, it gets listed with the extra documentation
1129       }
1130       elsif (@$_[1] eq "equate" || @$_[1] eq "variable")
1131       {
1132         $line = @$_[2]." (data)";
1133       }
1134       else
1135       {
1136         # A function
1137         if (@$_[4] & 1)
1138         {
1139           # Documented
1140           $line = @$_[2]." (implemented as ".@$_[3]."())";
1141           if (@$_[2] ne @$_[3])
1142           {
1143             $line = @$_[2]." (implemented as ".@$_[3]."())";
1144           }
1145           else
1146           {
1147             $line = @$_[2]."()";
1148           }
1149         }
1150         else
1151         {
1152           $line = @$_[2]." (not documented)";
1153         }
1154       }
1155       if ($line ne "")
1156       {
1157         push (@{$comment->{TEXT}}, $line, "");
1158       }
1159     }
1160
1161     # Add links to the extra documentation
1162     if (@{$spec_details->{EXTRA_COMMENTS}})
1163     {
1164       push (@{$comment->{TEXT}}, "SEE ALSO");
1165       my %htmp;
1166       @{$spec_details->{EXTRA_COMMENTS}} = grep(!$htmp{$_}++, @{$spec_details->{EXTRA_COMMENTS}});
1167       for (@{$spec_details->{EXTRA_COMMENTS}})
1168       {
1169         push (@{$comment->{TEXT}}, $_."()", "");
1170       }
1171     }
1172   }
1173   # Write out the document
1174   output_open_api_file($spec_details->{DLL_NAME});
1175   output_api_header($comment);
1176   output_api_comment($comment);
1177   output_api_footer($comment);
1178   output_close_api_file();
1179
1180   # Add this dll to the database of dll names
1181   my $output_file = $opt_output_directory."/dlls.db";
1182
1183   # Append the dllname to the output db of names
1184   open(DLLDB,">>$output_file") || die "Couldn't create $output_file\n";
1185   print DLLDB $spec_details->{DLL_NAME},"\n";
1186   close(DLLDB);
1187
1188   if ($opt_output_format eq "s")
1189   {
1190     output_sgml_dll_file($spec_details);
1191     return;
1192   }
1193 }
1194
1195 #
1196 # OUTPUT FUNCTIONS
1197 # ----------------
1198 # Only these functions know anything about formatting for a specific
1199 # output type. The functions above work only with plain text.
1200 # This is to allow new types of output to be added easily.
1201
1202 # Open the api file
1203 sub output_open_api_file
1204 {
1205   my $output_name = shift(@_);
1206   $output_name = $opt_output_directory."/".$output_name;
1207
1208   if ($opt_output_format eq "h")
1209   {
1210     $output_name = $output_name.".html";
1211   }
1212   elsif ($opt_output_format eq "s")
1213   {
1214     $output_name = $output_name.".sgml";
1215   }
1216   else
1217   {
1218     $output_name = $output_name.".".$opt_manual_section;
1219   }
1220   open(OUTPUT,">$output_name") || die "Couldn't create file '$output_name'\n";
1221 }
1222
1223 # Close the api file
1224 sub output_close_api_file
1225 {
1226   close (OUTPUT);
1227 }
1228
1229 # Output the api file header
1230 sub output_api_header
1231 {
1232   my $comment = shift(@_);
1233
1234   if ($opt_output_format eq "h")
1235   {
1236       print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1237       print OUTPUT "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
1238       print OUTPUT "<HTML>\n<HEAD>\n";
1239       print OUTPUT "<LINK REL=\"StyleSheet\" href=\"apidoc.css\" type=\"text/css\">\n";
1240       print OUTPUT "<META NAME=\"GENERATOR\" CONTENT=\"tools/c2man.pl\">\n";
1241       print OUTPUT "<META NAME=\"keywords\" CONTENT=\"Win32,Wine,API,$comment->{COMMENT_NAME}\">\n";
1242       print OUTPUT "<TITLE>Wine API: $comment->{COMMENT_NAME}</TITLE>\n</HEAD>\n<BODY>\n";
1243   }
1244   elsif ($opt_output_format eq "s")
1245   {
1246       print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n",
1247                    "<sect1>\n",
1248                    "<title>$comment->{COMMENT_NAME}</title>\n";
1249   }
1250   else
1251   {
1252       print OUTPUT ".\\\" -*- nroff -*-\n.\\\" Generated file - DO NOT EDIT!\n".
1253                    ".TH ",$comment->{COMMENT_NAME}," ",$opt_manual_section," \"",$date,"\" \"".
1254                    "Wine API\" \"Wine API\"\n";
1255   }
1256 }
1257
1258 sub output_api_footer
1259 {
1260   if ($opt_output_format eq "h")
1261   {
1262       print OUTPUT "<hr><p><i class=\"copy\">Copyright &copy ".$year." The Wine Project.".
1263                    "Visit <a HREF=http://www.winehq.org>WineHQ</a> for license details.".
1264                    "Generated $date</i></p>\n</body>\n</html>\n";
1265   }
1266   elsif ($opt_output_format eq "s")
1267   {
1268       print OUTPUT "</sect1>\n";
1269       return;
1270   }
1271   else
1272   {
1273   }
1274 }
1275
1276 sub output_api_section_start
1277 {
1278   my $comment = shift(@_);
1279   my $section_name = shift(@_);
1280
1281   if ($opt_output_format eq "h")
1282   {
1283     print OUTPUT "\n<p><h2 class=\"section\">",$section_name,"</h2></p>\n";
1284   }
1285   elsif ($opt_output_format eq "s")
1286   {
1287     print OUTPUT "<bridgehead>",$section_name,"</bridgehead>\n";
1288   }
1289   else
1290   {
1291     print OUTPUT "\n\.SH ",$section_name,"\n";
1292   }
1293 }
1294
1295 sub output_api_section_end
1296 {
1297   # Not currently required by any output formats
1298 }
1299
1300 sub output_api_name
1301 {
1302   my $comment = shift(@_);
1303
1304   output_api_section_start($comment,"NAME");
1305
1306   my $dll_ordinal = "";
1307   if ($comment->{ORDINAL} ne "")
1308   {
1309     $dll_ordinal = "(".$comment->{DLL_NAME}.".".$comment->{ORDINAL}.")";
1310   }
1311   if ($opt_output_format eq "h")
1312   {
1313     print OUTPUT "<p><b class=\"func_name\">",$comment->{COMMENT_NAME},
1314                  "</b>&nbsp;&nbsp;<i class=\"dll_ord\">",
1315                  ,$dll_ordinal,"</i></p>\n";
1316   }
1317   elsif ($opt_output_format eq "s")
1318   {
1319     print OUTPUT "<para>\n  <command>",$comment->{COMMENT_NAME},"</command>  <emphasis>",
1320                  $dll_ordinal,"</emphasis>\n</para>\n";
1321   }
1322   else
1323   {
1324     print OUTPUT "\\fB",$comment->{COMMENT_NAME},"\\fR ",$dll_ordinal;
1325   }
1326
1327   output_api_section_end();
1328 }
1329
1330 sub output_api_synopsis
1331 {
1332   my $comment = shift(@_);
1333   my @fmt;
1334
1335   output_api_section_start($comment,"SYNOPSIS");
1336
1337   if ($opt_output_format eq "h")
1338   {
1339     print OUTPUT "<p><pre class=\"proto\">\n ", $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1340     @fmt = ("", "\n", "<tt class=\"param\">", "</tt>");
1341   }
1342   elsif ($opt_output_format eq "s")
1343   {
1344     print OUTPUT "<screen>\n ",$comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1345     @fmt = ("", "\n", "<emphasis>", "</emphasis>");
1346   }
1347   else
1348   {
1349     print OUTPUT $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1350     @fmt = ("", "\n", "\\fI", "\\fR");
1351   }
1352
1353   # Since our prototype is output in a pre-formatted block, line up the
1354   # parameters and parameter comments in the same column.
1355
1356   # First caluculate where the columns should start
1357   my $biggest_length = 0;
1358   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1359   {
1360     my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1361     if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1362     {
1363       my $length = length $1;
1364       if ($length > $biggest_length)
1365       {
1366         $biggest_length = $length;
1367       }
1368     }
1369   }
1370
1371   # Now pad the string with blanks
1372   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1373   {
1374     my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1375     if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1376     {
1377       my $pad_len = $biggest_length - length $1;
1378       my $padding = " " x ($pad_len);
1379       ${@{$comment->{PROTOTYPE}}}[$i] = $1.$padding.$2;
1380     }
1381   }
1382
1383   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1384   {
1385     # Format the parameter name
1386     my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1387     my $comma = ($i == @{$comment->{PROTOTYPE}}-1) ? "" : ",";
1388     $line =~ s/(.+?)([A-Za-z_][A-Za-z_0-9]*)$/  $fmt[0]$1$fmt[2]$2$fmt[3]$comma$fmt[1]/;
1389     print OUTPUT $line;
1390   }
1391
1392   if ($opt_output_format eq "h")
1393   {
1394     print OUTPUT " )\n\n</pre></p>\n";
1395   }
1396   elsif ($opt_output_format eq "s")
1397   {
1398     print OUTPUT " )\n</screen>\n";
1399   }
1400   else
1401   {
1402     print OUTPUT " )\n";
1403   }
1404
1405   output_api_section_end();
1406 }
1407
1408 sub output_api_comment
1409 {
1410   my $comment = shift(@_);
1411   my $open_paragraph = 0;
1412   my $open_raw = 0;
1413   my $param_docs = 0;
1414   my @fmt;
1415
1416   if ($opt_output_format eq "h")
1417   {
1418     @fmt = ("<p>", "</p>\n", "<tt class=\"const\">", "</tt>", "<b class=\"emp\">", "</b>",
1419             "<tt class=\"coderef\">", "</tt>", "<tt class=\"param\">", "</tt>",
1420             "<i class=\"in_out\">", "</i>", "<pre class=\"raw\">\n", "</pre>\n",
1421             "<table class=\"tab\"><colgroup><col><col><col></colgroup><tbody>\n",
1422             "</tbody></table>\n","<tr><td>","</td></tr>\n","</td>","</td><td>");
1423   }
1424   elsif ($opt_output_format eq "s")
1425   {
1426     @fmt = ("<para>\n","\n</para>\n","<constant>","</constant>","<emphasis>","</emphasis>",
1427             "<command>","</command>","<constant>","</constant>","<emphasis>","</emphasis>",
1428             "<screen>\n","</screen>\n",
1429             "<informaltable frame=\"none\">\n<tgroup cols=\"3\">\n<tbody>\n",
1430             "</tbody>\n</tgroup>\n</informaltable>\n","<row><entry>","</entry></row>\n",
1431             "</entry>","</entry><entry>");
1432   }
1433   else
1434   {
1435     @fmt = ("\.PP\n", "\n", "\\fB", "\\fR", "\\fB", "\\fR", "\\fB", "\\fR", "\\fI", "\\fR",
1436             "\\fB", "\\fR ", "", "", "", "","","\n.PP\n","","");
1437   }
1438
1439   # Extract the parameter names
1440   my @parameter_names;
1441   for (@{$comment->{PROTOTYPE}})
1442   {
1443     if ( /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ )
1444     {
1445       push (@parameter_names, $2);
1446     }
1447   }
1448
1449   for (@{$comment->{TEXT}})
1450   {
1451     if ($opt_output_format eq "h" || $opt_output_format eq "s")
1452     {
1453       # Map special characters
1454       s/\&/\&amp;/g;
1455       s/\</\&lt;/g;
1456       s/\>/\&gt;/g;
1457       s/\([Cc]\)/\&copy;/g;
1458       s/\(tm\)/&#174;/;
1459     }
1460
1461     if ( s/^\|// )
1462     {
1463       # Raw output
1464       if ($open_raw == 0)
1465       {
1466         if ($open_paragraph == 1)
1467         {
1468           # Close the open paragraph
1469           print OUTPUT $fmt[1];
1470           $open_paragraph = 0;
1471         }
1472         # Start raw output
1473         print OUTPUT $fmt[12];
1474         $open_raw = 1;
1475       }
1476       if ($opt_output_format eq "")
1477       {
1478         print OUTPUT ".br\n"; # Prevent 'man' running these lines together
1479       }
1480       print OUTPUT $_,"\n";
1481     }
1482     else
1483     {
1484       # Highlight strings
1485       s/(\".+?\")/$fmt[2]$1$fmt[3]/g;
1486       # Highlight literal chars
1487       s/(\'.\')/$fmt[2]$1$fmt[3]/g;
1488       s/(\'.{2}\')/$fmt[2]$1$fmt[3]/g;
1489       # Highlight numeric constants
1490       s/( |\-|\+|\.|\()([0-9\-\.]+)( |\-|$|\.|\,|\*|\?|\))/$1$fmt[2]$2$fmt[3]$3/g;
1491
1492       # Leading cases ("xxxx:","-") start new paragraphs & are emphasised
1493       # FIXME: Using bullet points for leading '-' would look nicer.
1494       if ($open_paragraph == 1)
1495       {
1496         s/^(\-)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1497         s/^([[A-Za-z\-]+\:)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1498       }
1499       else
1500       {
1501         s/^(\-)/$fmt[4]$1$fmt[5]/;
1502         s/^([[A-Za-z\-]+\:)/$fmt[4]$1$fmt[5]/;
1503       }
1504
1505       if ($opt_output_format eq "h")
1506       {
1507         # Html uses links for API calls
1508         s/([A-Za-z_]+[A-Za-z_0-9]+)(\(\))/<a href\=\"$1\.html\">$1<\/a>/g;
1509         # And references to COM objects (hey, they'll get documented one day)
1510         s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ <a href\=\"$1\.html\">$1<\/a> $2/g;
1511         # Convert any web addresses to real links
1512         s/(http\:\/\/)(.+?)($| )/<a href\=\"$1$2\">$2<\/a>$3/g;
1513       }
1514       else
1515       {
1516         if ($opt_output_format eq "")
1517         {
1518           # Give the man section for API calls
1519           s/ ([A-Za-z_]+[A-Za-z_0-9]+)\(\)/ $fmt[6]$1\($opt_manual_section\)$fmt[7]/g;
1520         }
1521         else
1522         {
1523           # Highlight API calls
1524           s/ ([A-Za-z_]+[A-Za-z_0-9]+\(\))/ $fmt[6]$1$fmt[7]/g;
1525         }
1526
1527         # And references to COM objects
1528         s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ $fmt[6]$1$fmt[7] $2/g;
1529       }
1530
1531       if ($open_raw == 1)
1532       {
1533         # Finish the raw output
1534         print OUTPUT $fmt[13];
1535         $open_raw = 0;
1536       }
1537
1538       if ( /^[A-Z]+$/ || /^SEE ALSO$/ )
1539       {
1540         # Start of a new section
1541         if ($open_paragraph == 1)
1542         {
1543           if ($param_docs == 1)
1544           {
1545             print OUTPUT $fmt[17],$fmt[15];
1546           }
1547           else
1548           {
1549             print OUTPUT $fmt[1];
1550           }
1551           $open_paragraph = 0;
1552         }
1553         output_api_section_start($comment,$_);
1554         if ( /^PARAMS$/ )
1555         {
1556           print OUTPUT $fmt[14];
1557           $param_docs = 1;
1558         }
1559         else
1560         {
1561           #print OUTPUT $fmt[15];
1562           $param_docs = 0;
1563         }
1564       }
1565       elsif ( /^$/ )
1566       {
1567         # Empty line, indicating a new paragraph
1568         if ($open_paragraph == 1)
1569         {
1570           if ($param_docs == 0)
1571           {
1572             print OUTPUT $fmt[1];
1573             $open_paragraph = 0;
1574           }
1575         }
1576       }
1577       else
1578       {
1579         if ($param_docs == 1)
1580         {
1581           if ($open_paragraph == 1)
1582           {
1583             # For parameter docs, put each parameter into a new paragraph/table row
1584             print OUTPUT $fmt[17];
1585             $open_paragraph = 0;
1586           }
1587           s/(\[.+\])( *)/$fmt[19]$fmt[10]$1$fmt[11]$fmt[19]/; # Format In/Out
1588         }
1589         else
1590         {
1591           # Within paragraph lines, prevent lines running together
1592           $_ = $_." ";
1593         }
1594
1595         # Format parameter names where they appear in the comment
1596         for my $parameter_name (@parameter_names)
1597         {
1598           s/(^|[ \.\,\(\-])($parameter_name)($|[ \.\)\,\-])/$1$fmt[8]$2$fmt[9]$3/g;
1599         }
1600
1601         if ($open_paragraph == 0)
1602         {
1603           if ($param_docs == 1)
1604           {
1605             print OUTPUT $fmt[16];
1606           }
1607           else
1608           {
1609             print OUTPUT $fmt[0];
1610           }
1611           $open_paragraph = 1;
1612         }
1613         # Anything in all uppercase on its own gets emphasised
1614         s/(^|[ \.\,\(\[\|\=])([A-Z]+?[A-Z0-9_]+)($|[ \.\,\*\?\|\)\=\'])/$1$fmt[6]$2$fmt[7]$3/g;
1615
1616         print OUTPUT $_;
1617       }
1618     }
1619   }
1620   if ($open_raw == 1)
1621   {
1622     print OUTPUT $fmt[13];
1623   }
1624   if ($open_paragraph == 1)
1625   {
1626     print OUTPUT $fmt[1];
1627   }
1628 }
1629
1630 # Create the master index file
1631 sub output_master_index_files
1632 {
1633   if ($opt_output_format eq "")
1634   {
1635     return; # No master index for man pages
1636   }
1637
1638   # Use the comment output functions for consistency
1639   my $comment =
1640   {
1641     FILE => "",
1642     COMMENT_NAME => "The Wine Api Guide",
1643     ALT_NAME => "The Wine Api Guide",
1644     DLL_NAME => "",
1645     ORDINAL => "",
1646     RETURNS => "",
1647     PROTOTYPE => [],
1648     TEXT => [],
1649   };
1650
1651   if ($opt_output_format eq "s")
1652   {
1653     $comment->{COMMENT_NAME} = "Introduction";
1654     $comment->{ALT_NAME} = "Introduction",
1655   }
1656   elsif ($opt_output_format eq "h")
1657   {
1658     @{$comment->{TEXT}} = (
1659       "NAME",
1660        $comment->{COMMENT_NAME},
1661        "INTRODUCTION",
1662     );
1663   }
1664
1665   # Create the initial comment text
1666   push (@{$comment->{TEXT}},
1667     "This document describes the Api calls made available",
1668     "by Wine. They are grouped by the dll that exports them.",
1669     "",
1670     "Please do not edit this document, since it is generated automatically",
1671     "from the Wine source code tree. Details on updating this documentation",
1672     "are given in the \"Wine Developers Guide\".",
1673     "CONTRIBUTORS",
1674     "Api documentation is generally written by the person who ",
1675     "implements a given Api call. Authors of each dll are listed in the overview ",
1676     "section for that dll. Additional contributors who have updated source files ",
1677     "but have not entered their names in a copyright statement are noted by an ",
1678     "entry in the file \"Changelog\" from the Wine source code distribution.",
1679       ""
1680   );
1681
1682   # Read in all dlls from the database of dll names
1683   my $input_file = $opt_output_directory."/dlls.db";
1684   my @dlls = `cat $input_file|sort|uniq`;
1685
1686   if ($opt_output_format eq "h")
1687   {
1688     # HTML gets a list of all the dlls. For docbook the index creates this for us
1689     push (@{$comment->{TEXT}},
1690       "DLLS",
1691       "The following dlls are provided by Wine:",
1692       ""
1693     );
1694     # Add the dlls to the comment
1695     for (@dlls)
1696     {
1697       $_ =~ s/(\..*)?\n/\(\)/;
1698       push (@{$comment->{TEXT}}, $_, "");
1699     }
1700     output_open_api_file("index");
1701   }
1702   elsif ($opt_output_format eq "s")
1703   {
1704     # Just write this as the initial blurb, with a chapter heading
1705     output_open_api_file("blurb");
1706     print OUTPUT "<chapter id =\"blurb\">\n<title>Introduction to The Wine Api Guide</title>\n"
1707   }
1708
1709   # Write out the document
1710   output_api_header($comment);
1711   output_api_comment($comment);
1712   output_api_footer($comment);
1713   if ($opt_output_format eq "s")
1714   {
1715     print OUTPUT "</chapter>\n" # finish the chapter
1716   }
1717   output_close_api_file();
1718
1719   if ($opt_output_format eq "s")
1720   {
1721     output_sgml_master_file(\@dlls);
1722     return;
1723   }
1724   if ($opt_output_format eq "h")
1725   {
1726     output_html_stylesheet();
1727     # FIXME: Create an alphabetical index
1728     return;
1729   }
1730 }
1731
1732 # Write the master wine-api.sgml, linking it to each dll.
1733 sub output_sgml_master_file
1734 {
1735   my $dlls = shift(@_);
1736
1737   output_open_api_file("wine-api");
1738   print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1739   print OUTPUT "<!doctype book PUBLIC \"-//OASIS//DTD DocBook V3.1//EN\" [\n\n";
1740   print OUTPUT "<!entity blurb SYSTEM \"blurb.sgml\">\n";
1741
1742   # List the entities
1743   for (@$dlls)
1744   {
1745     $_ =~ s/(\..*)?\n//;
1746     print OUTPUT "<!entity ",$_," SYSTEM \"",$_,".sgml\">\n"
1747   }
1748
1749   print OUTPUT "]>\n\n<book id=\"index\">\n<bookinfo><title>The Wine Api Guide</title></bookinfo>\n\n";
1750   print OUTPUT "  &blurb;\n";
1751
1752   for (@$dlls)
1753   {
1754     print OUTPUT "  &",$_,";\n"
1755   }
1756   print OUTPUT "\n\n</book>\n";
1757
1758   output_close_api_file();
1759 }
1760
1761 # Produce the sgml for the dll chapter from the generated files
1762 sub output_sgml_dll_file
1763 {
1764   my $spec_details = shift(@_);
1765
1766   # Make a list of all the documentation files to include
1767   my $exports = $spec_details->{EXPORTS};
1768   my @source_files = ();
1769   for (@$exports)
1770   {
1771     # @$_ => ordinal, call convention, exported name, implementation name, documented;
1772     if (@$_[1] ne "forward" && @$_[1] ne "extern" && @$_[1] ne "stub" && @$_[1] ne "equate" &&
1773         @$_[1] ne "variable" && @$_[1] ne "fake" && @$_[4] & 1)
1774     {
1775       # A documented function
1776       push (@source_files,@$_[3]);
1777     }
1778   }
1779
1780   push (@source_files,@{$spec_details->{EXTRA_COMMENTS}});
1781
1782   @source_files = sort @source_files;
1783
1784   # create a new chapter for this dll
1785   my $tmp_name = $opt_output_directory."/".$spec_details->{DLL_NAME}.".tmp";
1786   open(OUTPUT,">$tmp_name") || die "Couldn't create $tmp_name\n";
1787   print OUTPUT "<chapter>\n<title>$spec_details->{DLL_NAME}</title>\n";
1788   output_close_api_file();
1789
1790   # Add the sorted documentation, cleaning up as we go
1791   `cat $opt_output_directory/$spec_details->{DLL_NAME}.sgml >>$tmp_name`;
1792   for (@source_files)
1793   {
1794     `cat $opt_output_directory/$_.sgml >>$tmp_name`;
1795     `rm -f $opt_output_directory/$_.sgml`;
1796   }
1797
1798   # close the chapter, and overwite the dll source
1799   open(OUTPUT,">>$tmp_name") || die "Couldn't create $tmp_name\n";
1800   print OUTPUT "</chapter>\n";
1801   close OUTPUT;
1802   `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.sgml`;
1803 }
1804
1805 # Output the stylesheet for HTML output
1806 sub output_html_stylesheet
1807 {
1808   if ($opt_output_format ne "h")
1809   {
1810     return;
1811   }
1812
1813   my $css;
1814   ($css = <<HERE_TARGET) =~ s/^\s+//gm;
1815 /*
1816  * Default styles for Wine HTML Documentation.
1817  *
1818  * This style sheet should be altered to suit your needs/taste.
1819  */
1820 BODY { /* Page body */
1821 background-color: white;
1822 color: black;
1823 font-family: Tahomsans-serif;
1824 font-style: normal;
1825 font-size: 10pt;
1826 }
1827 a:link { color: #4444ff; } /* Links */
1828 a:visited { color: #333377 }
1829 a:active { color: #0000dd }
1830 H2.section { /* Section Headers */
1831 font-family: sans-serif;
1832 color: #777777;
1833 background-color: #F0F0FE;
1834 margin-left: 0.2in;
1835 margin-right: 1.0in;
1836 }
1837 b.func_name { /* Function Name */
1838 font-size: 10pt;
1839 font-style: bold;
1840 }
1841 i.dll_ord { /* Italicised DLL+ordinal */
1842 color: #888888;
1843 font-family: sans-serif;
1844 font-size: 8pt;
1845 }
1846 p { /* Paragraphs */
1847 margin-left: 0.5in;
1848 margin-right: 0.5in;
1849 }
1850 table { /* tables */
1851 margin-left: 0.5in;
1852 margin-right: 0.5in;
1853 }
1854 pre.proto /* API Function prototype */
1855 {
1856 border-style: solid;
1857 border-width: 1px;
1858 border-color: #777777;
1859 background-color: #F0F0BB;
1860 color: black;
1861 font-size: 10pt;
1862 vertical-align: top;
1863 margin-left: 0.5in;
1864 margin-right: 1.0in;
1865 }
1866 tt.param { /* Parameter name */
1867 font-style: italic;
1868 color: blue;
1869 }
1870 tt.const { /* Constant */
1871 color: red;
1872 }
1873 i.in_out { /* In/Out */
1874 font-size: 8pt;
1875 color: grey;
1876 }
1877 tt.coderef { /* Code in description text */
1878 color: darkgreen;
1879 }
1880 b.emp /* Emphasis */ {
1881 font-style: bold;
1882 color: darkblue;
1883 }
1884 i.footer { /* Footer */
1885 font-family: sans-serif;
1886 font-size: 6pt;
1887 color: darkgrey;
1888 }
1889 HERE_TARGET
1890
1891   my $output_file = "$opt_output_directory/apidoc.css";
1892   open(CSS,">$output_file") || die "Couldn't create the file $output_file\n";
1893   print CSS $css;
1894   close(CSS);
1895 }