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