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