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