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