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