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