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