Fix alignment issues for MEMORYSTATUSEX, WIN32_STREAM_ID and
[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       if ($opt_output_format eq "h")
1645       {
1646         # Link to the file in WineHQ cvs
1647         s/^(Implemented in \")(.+?)(\"\.)/$1$2$3 http:\/\/source.winehq.org\/source\/$2/g;
1648       }
1649       # Highlight strings
1650       s/(\".+?\")/$fmt[2]$1$fmt[3]/g;
1651       # Highlight literal chars
1652       s/(\'.\')/$fmt[2]$1$fmt[3]/g;
1653       s/(\'.{2}\')/$fmt[2]$1$fmt[3]/g;
1654       # Highlight numeric constants
1655       s/( |\-|\+|\.|\()([0-9\-\.]+)( |\-|$|\.|\,|\*|\?|\))/$1$fmt[2]$2$fmt[3]$3/g;
1656
1657       # Leading cases ("xxxx:","-") start new paragraphs & are emphasised
1658       # FIXME: Using bullet points for leading '-' would look nicer.
1659       if ($open_paragraph == 1)
1660       {
1661         s/^(\-)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1662         s/^([[A-Za-z\-]+\:)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1663       }
1664       else
1665       {
1666         s/^(\-)/$fmt[4]$1$fmt[5]/;
1667         s/^([[A-Za-z\-]+\:)/$fmt[4]$1$fmt[5]/;
1668       }
1669
1670       if ($opt_output_format eq "h")
1671       {
1672         # Html uses links for API calls
1673         s/([A-Za-z_]+[A-Za-z_0-9]+)(\(\))/<a href\=\"$1\.html\">$1<\/a>/g;
1674         # Index references
1675         s/\{\{(.*?)\}\}\{\{(.*?)\}\}/<a href\=\"$2\.html\">$1<\/a>/g;
1676         s/ ([A-Z_])(\(\))/<a href\=\"$1\.html\">$1<\/a>/g;
1677         # And references to COM objects (hey, they'll get documented one day)
1678         s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ <a href\=\"$1\.html\">$1<\/a> $2/g;
1679         # Convert any web addresses to real links
1680         s/(http\:\/\/)(.+?)($| )/<a href\=\"$1$2\">$2<\/a>$3/g;
1681       }
1682       else
1683       {
1684         if ($opt_output_format eq "")
1685         {
1686           # Give the man section for API calls
1687           s/ ([A-Za-z_]+[A-Za-z_0-9]+)\(\)/ $fmt[6]$1\($opt_manual_section\)$fmt[7]/g;
1688         }
1689         else
1690         {
1691           # Highlight API calls
1692           s/ ([A-Za-z_]+[A-Za-z_0-9]+\(\))/ $fmt[6]$1$fmt[7]/g;
1693         }
1694
1695         # And references to COM objects
1696         s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ $fmt[6]$1$fmt[7] $2/g;
1697       }
1698
1699       if ($open_raw == 1)
1700       {
1701         # Finish the raw output
1702         print OUTPUT $fmt[13];
1703         $open_raw = 0;
1704       }
1705
1706       if ( /^[A-Z]+$/ || /^SEE ALSO$/ )
1707       {
1708         # Start of a new section
1709         if ($open_paragraph == 1)
1710         {
1711           if ($param_docs == 1)
1712           {
1713             print OUTPUT $fmt[17],$fmt[15];
1714           }
1715           else
1716           {
1717             print OUTPUT $fmt[1];
1718           }
1719           $open_paragraph = 0;
1720         }
1721         output_api_section_start($comment,$_);
1722         if ( /^PARAMS$/ )
1723         {
1724           print OUTPUT $fmt[14];
1725           $param_docs = 1;
1726         }
1727         else
1728         {
1729           #print OUTPUT $fmt[15];
1730           $param_docs = 0;
1731         }
1732       }
1733       elsif ( /^$/ )
1734       {
1735         # Empty line, indicating a new paragraph
1736         if ($open_paragraph == 1)
1737         {
1738           if ($param_docs == 0)
1739           {
1740             print OUTPUT $fmt[1];
1741             $open_paragraph = 0;
1742           }
1743         }
1744       }
1745       else
1746       {
1747         if ($param_docs == 1)
1748         {
1749           if ($open_paragraph == 1)
1750           {
1751             # For parameter docs, put each parameter into a new paragraph/table row
1752             print OUTPUT $fmt[17];
1753             $open_paragraph = 0;
1754           }
1755           s/(\[.+\])( *)/$fmt[19]$fmt[10]$1$fmt[11]$fmt[19] /; # Format In/Out
1756         }
1757         else
1758         {
1759           # Within paragraph lines, prevent lines running together
1760           $_ = $_." ";
1761         }
1762
1763         # Format parameter names where they appear in the comment
1764         for my $parameter_name (@parameter_names)
1765         {
1766           s/(^|[ \.\,\(\-\*])($parameter_name)($|[ \.\)\,\-\=\/])/$1$fmt[8]$2$fmt[9]$3/g;
1767         }
1768         # Structure dereferences include the dereferenced member
1769         s/(\-\>[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g;
1770         s/(\-\&gt\;[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g;
1771
1772         if ($open_paragraph == 0)
1773         {
1774           if ($param_docs == 1)
1775           {
1776             print OUTPUT $fmt[16];
1777           }
1778           else
1779           {
1780             print OUTPUT $fmt[0];
1781           }
1782           $open_paragraph = 1;
1783         }
1784         # Anything in all uppercase on its own gets emphasised
1785         s/(^|[ \.\,\(\[\|\=])([A-Z]+?[A-Z0-9_]+)($|[ \.\,\*\?\|\)\=\'])/$1$fmt[6]$2$fmt[7]$3/g;
1786
1787         print OUTPUT $_;
1788       }
1789     }
1790   }
1791   if ($open_raw == 1)
1792   {
1793     print OUTPUT $fmt[13];
1794   }
1795   if ($open_paragraph == 1)
1796   {
1797     print OUTPUT $fmt[1];
1798   }
1799 }
1800
1801 # Create the master index file
1802 sub output_master_index_files
1803 {
1804   if ($opt_output_format eq "")
1805   {
1806     return; # No master index for man pages
1807   }
1808
1809   if ($opt_output_format eq "h")
1810   {
1811     # Append the index entries to the output db of index entries
1812     my $output_file = $opt_output_directory."/index.db";
1813     open(INDEXDB,">>$output_file") || die "Couldn't create $output_file\n";
1814     for (@index_entries_list)
1815     {
1816       $_ =~ s/A\,/\,/;
1817       print INDEXDB $_."\n";
1818     }
1819     close(INDEXDB);
1820   }
1821
1822   # Use the comment output functions for consistency
1823   my $comment =
1824   {
1825     FILE => "",
1826     COMMENT_NAME => "The Wine Api Guide",
1827     ALT_NAME => "The Wine Api Guide",
1828     DLL_NAME => "",
1829     ORDINAL => "",
1830     RETURNS => "",
1831     PROTOTYPE => [],
1832     TEXT => [],
1833   };
1834
1835   if ($opt_output_format eq "s")
1836   {
1837     $comment->{COMMENT_NAME} = "Introduction";
1838     $comment->{ALT_NAME} = "Introduction",
1839   }
1840   elsif ($opt_output_format eq "h")
1841   {
1842     @{$comment->{TEXT}} = (
1843       "NAME",
1844        $comment->{COMMENT_NAME},
1845        "INTRODUCTION",
1846     );
1847   }
1848
1849   # Create the initial comment text
1850   push (@{$comment->{TEXT}},
1851     "This document describes the Api calls made available",
1852     "by Wine. They are grouped by the dll that exports them.",
1853     "",
1854     "Please do not edit this document, since it is generated automatically",
1855     "from the Wine source code tree. Details on updating this documentation",
1856     "are given in the \"Wine Developers Guide\".",
1857     "CONTRIBUTORS",
1858     "Api documentation is generally written by the person who ",
1859     "implements a given Api call. Authors of each dll are listed in the overview ",
1860     "section for that dll. Additional contributors who have updated source files ",
1861     "but have not entered their names in a copyright statement are noted by an ",
1862     "entry in the file \"Changelog\" from the Wine source code distribution.",
1863       ""
1864   );
1865
1866   # Read in all dlls from the database of dll names
1867   my $input_file = $opt_output_directory."/dlls.db";
1868   my @dlls = `cat $input_file|sort|uniq`;
1869
1870   if ($opt_output_format eq "h")
1871   {
1872     # HTML gets a list of all the dlls and an index. For docbook the index creates this for us
1873     push (@{$comment->{TEXT}},
1874       "INDEX",
1875       "For an alphabetical listing of the functions available, please click the ",
1876       "first letter of the functions name below:","",
1877       "[ _(), A(), B(), C(), D(), E(), F(), G(), H(), ".
1878       "I(), J(), K(), L(), M(), N(), O(), P(), Q(), ".
1879       "R(), S(), T(), U(), V(), W(), X(), Y(), Z() ]", "",
1880       "DLLS",
1881       "Each dll provided by Wine is documented individually. The following dlls are provided :",
1882       ""
1883     );
1884     # Add the dlls to the comment
1885     for (@dlls)
1886     {
1887       $_ =~ s/(\..*)?\n/\(\)/;
1888       push (@{$comment->{TEXT}}, $_, "");
1889     }
1890     output_open_api_file("index");
1891   }
1892   elsif ($opt_output_format eq "s")
1893   {
1894     # Just write this as the initial blurb, with a chapter heading
1895     output_open_api_file("blurb");
1896     print OUTPUT "<chapter id =\"blurb\">\n<title>Introduction to The Wine Api Guide</title>\n"
1897   }
1898
1899   # Write out the document
1900   output_api_header($comment);
1901   output_api_comment($comment);
1902   output_api_footer($comment);
1903   if ($opt_output_format eq "s")
1904   {
1905     print OUTPUT "</chapter>\n" # finish the chapter
1906   }
1907   output_close_api_file();
1908
1909   if ($opt_output_format eq "s")
1910   {
1911     output_sgml_master_file(\@dlls);
1912     return;
1913   }
1914   if ($opt_output_format eq "h")
1915   {
1916     output_html_index_files();
1917     output_html_stylesheet();
1918     return;
1919   }
1920 }
1921
1922 # Write the master wine-api.sgml, linking it to each dll.
1923 sub output_sgml_master_file
1924 {
1925   my $dlls = shift(@_);
1926
1927   output_open_api_file("wine-api");
1928   print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1929   print OUTPUT "<!doctype book PUBLIC \"-//OASIS//DTD DocBook V3.1//EN\" [\n\n";
1930   print OUTPUT "<!entity blurb SYSTEM \"blurb.sgml\">\n";
1931
1932   # List the entities
1933   for (@$dlls)
1934   {
1935     $_ =~ s/(\..*)?\n//;
1936     print OUTPUT "<!entity ",$_," SYSTEM \"",$_,".sgml\">\n"
1937   }
1938
1939   print OUTPUT "]>\n\n<book id=\"index\">\n<bookinfo><title>The Wine Api Guide</title></bookinfo>\n\n";
1940   print OUTPUT "  &blurb;\n";
1941
1942   for (@$dlls)
1943   {
1944     print OUTPUT "  &",$_,";\n"
1945   }
1946   print OUTPUT "\n\n</book>\n";
1947
1948   output_close_api_file();
1949 }
1950
1951 # Produce the sgml for the dll chapter from the generated files
1952 sub output_sgml_dll_file
1953 {
1954   my $spec_details = shift(@_);
1955
1956   # Make a list of all the documentation files to include
1957   my $exports = $spec_details->{EXPORTS};
1958   my @source_files = ();
1959   for (@$exports)
1960   {
1961     # @$_ => ordinal, call convention, exported name, implementation name, documented;
1962     if (@$_[1] ne "forward" && @$_[1] ne "extern" && @$_[1] ne "stub" && @$_[1] ne "equate" &&
1963         @$_[1] ne "variable" && @$_[1] ne "fake" && @$_[4] & 1)
1964     {
1965       # A documented function
1966       push (@source_files,@$_[3]);
1967     }
1968   }
1969
1970   push (@source_files,@{$spec_details->{EXTRA_COMMENTS}});
1971
1972   @source_files = sort @source_files;
1973
1974   # create a new chapter for this dll
1975   my $tmp_name = $opt_output_directory."/".$spec_details->{DLL_NAME}.".tmp";
1976   open(OUTPUT,">$tmp_name") || die "Couldn't create $tmp_name\n";
1977   print OUTPUT "<chapter>\n<title>$spec_details->{DLL_NAME}</title>\n";
1978   output_close_api_file();
1979
1980   # Add the sorted documentation, cleaning up as we go
1981   `cat $opt_output_directory/$spec_details->{DLL_NAME}.sgml >>$tmp_name`;
1982   for (@source_files)
1983   {
1984     `cat $opt_output_directory/$_.sgml >>$tmp_name`;
1985     `rm -f $opt_output_directory/$_.sgml`;
1986   }
1987
1988   # close the chapter, and overwite the dll source
1989   open(OUTPUT,">>$tmp_name") || die "Couldn't create $tmp_name\n";
1990   print OUTPUT "</chapter>\n";
1991   close OUTPUT;
1992   `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.sgml`;
1993 }
1994
1995 # Write the html index files containing the function names
1996 sub output_html_index_files
1997 {
1998   if ($opt_output_format ne "h")
1999   {
2000     return;
2001   }
2002
2003   my @letters = ('_', 'A' .. 'Z');
2004
2005   # Read in all functions
2006   my $input_file = $opt_output_directory."/index.db";
2007   my @funcs = `cat $input_file|sort|uniq`;
2008
2009   for (@letters)
2010   {
2011     my $letter = $_;
2012     my $comment =
2013     {
2014       FILE => "",
2015       COMMENT_NAME => "",
2016       ALT_NAME => "",
2017       DLL_NAME => "",
2018       ORDINAL => "",
2019       RETURNS => "",
2020       PROTOTYPE => [],
2021       TEXT => [],
2022     };
2023
2024     $comment->{COMMENT_NAME} = $letter." Functions";
2025     $comment->{ALT_NAME} = $letter." Functions";
2026
2027     push (@{$comment->{TEXT}},
2028       "NAME",
2029       $comment->{COMMENT_NAME},
2030       "FUNCTIONS"
2031     );
2032
2033     # Add the functions to the comment
2034     for (@funcs)
2035     {
2036       my $first_char = substr ($_, 0, 1);
2037       $first_char = uc $first_char;
2038
2039       if ($first_char eq $letter)
2040       {
2041         my $name = $_;
2042         my $file;
2043         $name =~ s/(^.*?)\,(.*?)\n/$1/;
2044         $file = $2;
2045         push (@{$comment->{TEXT}}, "{{".$name."}}{{".$file."}}","");
2046       }
2047     }
2048
2049     # Write out the document
2050     output_open_api_file($letter);
2051     output_api_header($comment);
2052     output_api_comment($comment);
2053     output_api_footer($comment);
2054     output_close_api_file();
2055   }
2056 }
2057
2058 # Output the stylesheet for HTML output
2059 sub output_html_stylesheet
2060 {
2061   if ($opt_output_format ne "h")
2062   {
2063     return;
2064   }
2065
2066   my $css;
2067   ($css = <<HERE_TARGET) =~ s/^\s+//gm;
2068 /*
2069  * Default styles for Wine HTML Documentation.
2070  *
2071  * This style sheet should be altered to suit your needs/taste.
2072  */
2073 BODY { /* Page body */
2074 background-color: white;
2075 color: black;
2076 font-family: Tahoma,sans-serif;
2077 font-style: normal;
2078 font-size: 10pt;
2079 }
2080 a:link { color: #4444ff; } /* Links */
2081 a:visited { color: #333377 }
2082 a:active { color: #0000dd }
2083 H2.section { /* Section Headers */
2084 font-family: sans-serif;
2085 color: #777777;
2086 background-color: #F0F0FE;
2087 margin-left: 0.2in;
2088 margin-right: 1.0in;
2089 }
2090 b.func_name { /* Function Name */
2091 font-size: 10pt;
2092 font-style: bold;
2093 }
2094 i.dll_ord { /* Italicised DLL+ordinal */
2095 color: #888888;
2096 font-family: sans-serif;
2097 font-size: 8pt;
2098 }
2099 p { /* Paragraphs */
2100 margin-left: 0.5in;
2101 margin-right: 0.5in;
2102 }
2103 table { /* tables */
2104 margin-left: 0.5in;
2105 margin-right: 0.5in;
2106 }
2107 pre.proto /* API Function prototype */
2108 {
2109 border-style: solid;
2110 border-width: 1px;
2111 border-color: #777777;
2112 background-color: #F0F0BB;
2113 color: black;
2114 font-size: 10pt;
2115 vertical-align: top;
2116 margin-left: 0.5in;
2117 margin-right: 1.0in;
2118 }
2119 pre.raw { /* Raw text output */
2120 margin-left: 0.6in;
2121 margin-right: 1.1in;
2122 background-color: #8080DC;
2123 }
2124 tt.param { /* Parameter name */
2125 font-style: italic;
2126 color: blue;
2127 }
2128 tt.const { /* Constant */
2129 color: red;
2130 }
2131 i.in_out { /* In/Out */
2132 font-size: 8pt;
2133 color: grey;
2134 }
2135 tt.coderef { /* Code in description text */
2136 color: darkgreen;
2137 }
2138 b.emp /* Emphasis */ {
2139 font-style: bold;
2140 color: darkblue;
2141 }
2142 i.footer { /* Footer */
2143 font-family: sans-serif;
2144 font-size: 6pt;
2145 color: darkgrey;
2146 }
2147 HERE_TARGET
2148
2149   my $output_file = "$opt_output_directory/apidoc.css";
2150   open(CSS,">$output_file") || die "Couldn't create the file $output_file\n";
2151   print CSS $css;
2152   close(CSS);
2153 }