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