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