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