| #! /usr/bin/perl -w | 
 | # | 
 | # Generate API documentation. See documentation/documentation.sgml for details. | 
 | # | 
 | # Copyright (C) 2000 Mike McCormack | 
 | # Copyright (C) 2003 Jon Griffiths | 
 | # | 
 | # This library is free software; you can redistribute it and/or | 
 | # modify it under the terms of the GNU Lesser General Public | 
 | # License as published by the Free Software Foundation; either | 
 | # version 2.1 of the License, or (at your option) any later version. | 
 | # | 
 | # This library is distributed in the hope that it will be useful, | 
 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
 | # Lesser General Public License for more details. | 
 | # | 
 | # You should have received a copy of the GNU Lesser General Public | 
 | # License along with this library; if not, write to the Free Software | 
 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
 | # | 
 | # TODO | 
 | #  Consolidate A+W pairs together, and only write one doc, without the suffix | 
 | #  Implement automatic docs fo structs/defines in headers | 
 | #  SGML gurus - feel free to smarten up the SGML. | 
 | #  Add any other relevant information for the dll - imports etc | 
 | #  Should we have a special output mode for WineHQ? | 
 |  | 
 | use strict; | 
 | use bytes; | 
 |  | 
 | # Function flags. most of these come from the spec flags | 
 | my $FLAG_DOCUMENTED = 1; | 
 | my $FLAG_NONAME     = 2; | 
 | my $FLAG_I386       = 4; | 
 | my $FLAG_REGISTER   = 8; | 
 | my $FLAG_APAIR      = 16; # The A version of a matching W function | 
 | my $FLAG_WPAIR      = 32; # The W version of a matching A function | 
 | my $FLAG_64PAIR     = 64; # The 64 bit version of a matching 32 bit function | 
 |  | 
 |  | 
 | # Options | 
 | my $opt_output_directory = "man3w"; # All default options are for nroff (man pages) | 
 | my $opt_manual_section = "3w"; | 
 | my $opt_source_dir = ""; | 
 | my $opt_wine_root_dir = ""; | 
 | my $opt_output_format = "";         # '' = nroff, 'h' = html, 's' = sgml | 
 | my $opt_output_empty = 0;           # Non-zero = Create 'empty' comments (for every implemented function) | 
 | my $opt_fussy = 1;                  # Non-zero = Create only if we have a RETURNS section | 
 | my $opt_verbose = 0;                # >0 = verbosity. Can be given multiple times (for debugging) | 
 | my @opt_header_file_list = (); | 
 | my @opt_spec_file_list = (); | 
 | my @opt_source_file_list = (); | 
 |  | 
 | # All the collected details about all the .spec files being processed | 
 | my %spec_files; | 
 | # All the collected details about all the source files being processed | 
 | my %source_files; | 
 | # All documented functions that are to be placed in the index | 
 | my @index_entries_list = (); | 
 |  | 
 | # useful globals | 
 | my $pwd = `pwd`."/"; | 
 | $pwd =~ s/\n//; | 
 | my @datetime = localtime; | 
 | my @months = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun", | 
 |                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ); | 
 | my $year = $datetime[5] + 1900; | 
 | my $date = "$months[$datetime[4]] $year"; | 
 |  | 
 |  | 
 | sub output_api_comment($); | 
 | sub output_api_footer($); | 
 | sub output_api_header($); | 
 | sub output_api_name($); | 
 | sub output_api_synopsis($); | 
 | sub output_close_api_file(); | 
 | sub output_comment($); | 
 | sub output_html_index_files(); | 
 | sub output_html_stylesheet(); | 
 | sub output_open_api_file($); | 
 | sub output_sgml_dll_file($); | 
 | sub output_sgml_master_file($); | 
 | sub output_spec($); | 
 | sub process_comment($); | 
 | sub process_extra_comment($); | 
 |  | 
 |  | 
 | # Generate the list of exported entries for the dll | 
 | sub process_spec_file($) | 
 | { | 
 |   my $spec_name = shift; | 
 |   my $dll_name  = $spec_name; | 
 |   $dll_name =~ s/\..*//;       # Strip the file extension | 
 |   my $uc_dll_name  = uc $dll_name; | 
 |  | 
 |   my $spec_details = | 
 |   { | 
 |     NAME => $spec_name, | 
 |     DLL_NAME => $dll_name, | 
 |     NUM_EXPORTS => 0, | 
 |     NUM_STUBS => 0, | 
 |     NUM_FUNCS => 0, | 
 |     NUM_FORWARDS => 0, | 
 |     NUM_VARS => 0, | 
 |     NUM_DOCS => 0, | 
 |     CONTRIBUTORS => [ ], | 
 |     SOURCES => [ ], | 
 |     DESCRIPTION => [ ], | 
 |     EXPORTS => [ ], | 
 |     EXPORTED_NAMES => { }, | 
 |     IMPLEMENTATION_NAMES => { }, | 
 |     EXTRA_COMMENTS => [ ], | 
 |     CURRENT_EXTRA => [ ] , | 
 |   }; | 
 |  | 
 |   if ($opt_verbose > 0) | 
 |   { | 
 |     print "Processing ".$spec_name."\n"; | 
 |   } | 
 |  | 
 |   # We allow opening to fail just to cater for the peculiarities of | 
 |   # the Wine build system. This doesn't hurt, in any case | 
 |   open(SPEC_FILE, "<$spec_name") | 
 |   || (($opt_source_dir ne "") | 
 |       && open(SPEC_FILE, "<$opt_source_dir/$spec_name")) | 
 |   || return; | 
 |  | 
 |   while(<SPEC_FILE>) | 
 |   { | 
 |     s/^\s+//;            # Strip leading space | 
 |     s/\s+\n$/\n/;        # Strip trailing space | 
 |     s/\s+/ /g;           # Strip multiple tabs & spaces to a single space | 
 |     s/\s*#.*//;          # Strip comments | 
 |     s/\(.*\)/ /;         # Strip arguments | 
 |     s/\s+/ /g;           # Strip multiple tabs & spaces to a single space (again) | 
 |     s/\n$//;             # Strip newline | 
 |  | 
 |     my $flags = 0; | 
 |     if( /\-noname/ ) | 
 |     { | 
 |       $flags |= $FLAG_NONAME; | 
 |     } | 
 |     if( /\-i386/ ) | 
 |     { | 
 |       $flags |= $FLAG_I386; | 
 |     } | 
 |     if( /\-register/ ) | 
 |     { | 
 |       $flags |= $FLAG_REGISTER; | 
 |     } | 
 |     s/ \-[a-z0-9]+//g;   # Strip flags | 
 |  | 
 |     if( /^(([0-9]+)|@) / ) | 
 |     { | 
 |       # This line contains an exported symbol | 
 |       my ($ordinal, $call_convention, $exported_name, $implementation_name) = split(' '); | 
 |  | 
 |       for ($call_convention) | 
 |       { | 
 |         /^(cdecl|stdcall|varargs|pascal)$/ | 
 |                  && do { $spec_details->{NUM_FUNCS}++;    last; }; | 
 |         /^(variable|equate)$/ | 
 |                  && do { $spec_details->{NUM_VARS}++;     last; }; | 
 |         /^(extern)$/ | 
 |                  && do { $spec_details->{NUM_FORWARDS}++; last; }; | 
 |         /^stub$/ && do { $spec_details->{NUM_STUBS}++;    last; }; | 
 |         if ($opt_verbose > 0) | 
 |         { | 
 |           print "Warning: didn't recognise convention \'",$call_convention,"'\n"; | 
 |         } | 
 |         last; | 
 |       } | 
 |  | 
 |       # Convert ordinal only names so we can find them later | 
 |       if ($exported_name eq "@") | 
 |       { | 
 |         $exported_name = $uc_dll_name.'_'.$ordinal; | 
 |       } | 
 |       if (!defined($implementation_name)) | 
 |       { | 
 |         $implementation_name = $exported_name; | 
 |       } | 
 |       if ($implementation_name eq "") | 
 |       { | 
 |         $implementation_name = $exported_name; | 
 |       } | 
 |  | 
 |       if ($implementation_name =~ /(.*?)\./) | 
 |       { | 
 |         $call_convention = "forward"; # Referencing a function from another dll | 
 |         $spec_details->{NUM_FUNCS}--; | 
 |         $spec_details->{NUM_FORWARDS}++; | 
 |       } | 
 |  | 
 |       # Add indices for the exported and implementation names | 
 |       $spec_details->{EXPORTED_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS}; | 
 |       if ($implementation_name ne $exported_name) | 
 |       { | 
 |         $spec_details->{IMPLEMENTATION_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS}; | 
 |       } | 
 |  | 
 |       # Add the exported entry | 
 |       $spec_details->{NUM_EXPORTS}++; | 
 |       my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $flags); | 
 |       push (@{$spec_details->{EXPORTS}},[@export]); | 
 |     } | 
 |   } | 
 |   close(SPEC_FILE); | 
 |  | 
 |   # Add this .spec files details to the list of .spec files | 
 |   $spec_files{$uc_dll_name} = [$spec_details]; | 
 | } | 
 |  | 
 | # Read each source file, extract comments, and generate API documentation if appropriate | 
 | sub process_source_file($) | 
 | { | 
 |   my $source_file = shift; | 
 |   my $source_details = | 
 |   { | 
 |     CONTRIBUTORS => [ ], | 
 |     DEBUG_CHANNEL => "", | 
 |   }; | 
 |   my $comment = | 
 |   { | 
 |     FILE => $source_file, | 
 |     COMMENT_NAME => "", | 
 |     ALT_NAME => "", | 
 |     DLL_NAME => "", | 
 |     ORDINAL => "", | 
 |     RETURNS => "", | 
 |     PROTOTYPE => [], | 
 |     TEXT => [], | 
 |   }; | 
 |   my $parse_state = 0; | 
 |   my $ignore_blank_lines = 1; | 
 |   my $extra_comment = 0; # 1 if this is an extra comment, i.e its not a .spec export | 
 |  | 
 |   if ($opt_verbose > 0) | 
 |   { | 
 |     print "Processing ".$source_file."\n"; | 
 |   } | 
 |   open(SOURCE_FILE,"<$source_file") | 
 |   || (($opt_source_dir ne "") | 
 |       && open(SOURCE_FILE,"<$opt_source_dir/$source_file")) | 
 |   || die "couldn't open ".$source_file."\n"; | 
 |  | 
 |   # Add this source file to the list of source files | 
 |   $source_files{$source_file} = [$source_details]; | 
 |  | 
 |   while(<SOURCE_FILE>) | 
 |   { | 
 |     s/\n$//;   # Strip newline | 
 |     s/^\s+//;  # Strip leading space | 
 |     s/\s+$//;  # Strip trailing space | 
 |     if (! /^\*\|/ ) | 
 |     { | 
 |       # Strip multiple tabs & spaces to a single space | 
 |       s/\s+/ /g; | 
 |     } | 
 |  | 
 |     if ( / +Copyright *(\([Cc]\))*[0-9 \-\,\/]*([[:alpha:][:^ascii:] \.\-]+)/ ) | 
 |     { | 
 |       # Extract a contributor to this file | 
 |       my $contributor = $2; | 
 |       $contributor =~ s/ *$//; | 
 |       $contributor =~ s/^by //; | 
 |       $contributor =~ s/\.$//; | 
 |       $contributor =~ s/ (for .*)/ \($1\)/; | 
 |       if ($contributor ne "") | 
 |       { | 
 |         if ($opt_verbose > 3) | 
 |         { | 
 |           print "Info: Found contributor:'".$contributor."'\n"; | 
 |         } | 
 |         push (@{$source_details->{CONTRIBUTORS}},$contributor); | 
 |       } | 
 |     } | 
 |     elsif ( /WINE_DEFAULT_DEBUG_CHANNEL\(([A-Za-z]*)\)/ ) | 
 |     { | 
 |       # Extract the debug channel to use | 
 |       if ($opt_verbose > 3) | 
 |       { | 
 |         print "Info: Found debug channel:'".$1."'\n"; | 
 |       } | 
 |       $source_details->{DEBUG_CHANNEL} = $1; | 
 |     } | 
 |  | 
 |     if ($parse_state == 0) # Searching for a comment | 
 |     { | 
 |       if ( /^\/\**$/ ) | 
 |       { | 
 |         # Found a comment start | 
 |         $comment->{COMMENT_NAME} = ""; | 
 |         $comment->{ALT_NAME} = ""; | 
 |         $comment->{DLL_NAME} = ""; | 
 |         $comment->{ORDINAL} = ""; | 
 |         $comment->{RETURNS} = ""; | 
 |         $comment->{PROTOTYPE} = []; | 
 |         $comment->{TEXT} = []; | 
 |         $ignore_blank_lines = 1; | 
 |         $extra_comment = 0; | 
 |         $parse_state = 3; | 
 |       } | 
 |     } | 
 |     elsif ($parse_state == 1) # Reading in a comment | 
 |     { | 
 |       if ( /^\**\// ) | 
 |       { | 
 |         # Found the end of the comment | 
 |         $parse_state = 2; | 
 |       } | 
 |       elsif ( s/^\*\|/\|/ ) | 
 |       { | 
 |         # A line of comment not meant to be pre-processed | 
 |         push (@{$comment->{TEXT}},$_); # Add the comment text | 
 |       } | 
 |       elsif ( s/^ *\** *// ) | 
 |       { | 
 |         # A line of comment, starting with an asterisk | 
 |         if ( /^[A-Z]+$/ || $_ eq "") | 
 |         { | 
 |           # This is a section start, so skip blank lines before and after it. | 
 |           my $last_line = pop(@{$comment->{TEXT}}); | 
 |           if (defined($last_line) && $last_line ne "") | 
 |           { | 
 |             # Put it back | 
 |             push (@{$comment->{TEXT}},$last_line); | 
 |           } | 
 |           if ( /^[A-Z]+$/ ) | 
 |           { | 
 |             $ignore_blank_lines = 1; | 
 |           } | 
 |           else | 
 |           { | 
 |             $ignore_blank_lines = 0; | 
 |           } | 
 |         } | 
 |  | 
 |         if ($ignore_blank_lines == 0 || $_ ne "") | 
 |         { | 
 |           push (@{$comment->{TEXT}},$_); # Add the comment text | 
 |         } | 
 |       } | 
 |       else | 
 |       { | 
 |         # This isn't a well formatted comment: look for the next one | 
 |         $parse_state = 0; | 
 |       } | 
 |     } | 
 |     elsif ($parse_state == 2) # Finished reading in a comment | 
 |     { | 
 |       if ( /(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ || | 
 |            /.*?\(/ ) | 
 |       { | 
 |         # Comment is followed by a function definition | 
 |         $parse_state = 4; # Fall through to read prototype | 
 |       } | 
 |       else | 
 |       { | 
 |         # Allow cpp directives and blank lines between the comment and prototype | 
 |         if ($extra_comment == 1) | 
 |         { | 
 |           # An extra comment not followed by a function definition | 
 |           $parse_state = 5; # Fall through to process comment | 
 |         } | 
 |         elsif (!/^\#/ && !/^ *$/ && !/^__ASM_GLOBAL_FUNC/) | 
 |         { | 
 |           # This isn't a well formatted comment: look for the next one | 
 |           if ($opt_verbose > 1) | 
 |           { | 
 |             print "Info: Function '",$comment->{COMMENT_NAME},"' not followed by prototype.\n"; | 
 |           } | 
 |           $parse_state = 0; | 
 |         } | 
 |       } | 
 |     } | 
 |     elsif ($parse_state == 3) # Reading in the first line of a comment | 
 |     { | 
 |       s/^ *\** *//; | 
 |       if ( /^([\@A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])\s*(.*)$/ ) | 
 |       { | 
 |         # Found a correctly formed "ApiName (DLLNAME.Ordinal)" line. | 
 |         if (defined ($7) && $7 ne "") | 
 |         { | 
 |           push (@{$comment->{TEXT}},$_); # Add the trailing comment text | 
 |         } | 
 |         $comment->{COMMENT_NAME} = $1; | 
 |         $comment->{DLL_NAME} = uc $3; | 
 |         $comment->{ORDINAL} = $4; | 
 |         $comment->{DLL_NAME} =~ s/^KERNEL$/KRNL386/; # Too many of these to ignore, _old_ code | 
 |         $parse_state = 1; | 
 |       } | 
 |       elsif ( /^([A-Za-z0-9_-]+) +\{([A-Za-z0-9_]+)\}$/ ) | 
 |       { | 
 |         # Found a correctly formed "CommentTitle {DLLNAME}" line (extra documentation) | 
 |         $comment->{COMMENT_NAME} = $1; | 
 |         $comment->{DLL_NAME} = uc $2; | 
 |         $comment->{ORDINAL} = ""; | 
 |         $extra_comment = 1; | 
 |         $parse_state = 1; | 
 |       } | 
 |       else | 
 |       { | 
 |         # This isn't a well formatted comment: look for the next one | 
 |         $parse_state = 0; | 
 |       } | 
 |     } | 
 |  | 
 |     if ($parse_state == 4) # Reading in the function definition | 
 |     { | 
 |       push (@{$comment->{PROTOTYPE}},$_); | 
 |       # Strip comments from the line before checking for ')' | 
 |       my $stripped_line = $_; | 
 |       $stripped_line =~ s/ *(\/\* *)(.*?)( *\*\/ *)//; | 
 |       if ( $stripped_line =~ /\)/ ) | 
 |       { | 
 |         # Strip a blank last line | 
 |         my $last_line = pop(@{$comment->{TEXT}}); | 
 |         if (defined($last_line) && $last_line ne "") | 
 |         { | 
 |           # Put it back | 
 |           push (@{$comment->{TEXT}},$last_line); | 
 |         } | 
 |  | 
 |         if ($opt_output_empty != 0 && @{$comment->{TEXT}} == 0) | 
 |         { | 
 |           # Create a 'not implemented' comment | 
 |           @{$comment->{TEXT}} = ("fixme: This function has not yet been documented."); | 
 |         } | 
 |         $parse_state = 5; | 
 |       } | 
 |     } | 
 |  | 
 |     if ($parse_state == 5) # Processing the comment | 
 |     { | 
 |       # Process it, if it has any text | 
 |       if (@{$comment->{TEXT}} > 0) | 
 |       { | 
 |         if ($extra_comment == 1) | 
 |         { | 
 |           process_extra_comment($comment); | 
 |         } | 
 |         else | 
 |         { | 
 |           @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}}); | 
 |           process_comment($comment); | 
 |         } | 
 |       } | 
 |       elsif ($opt_verbose > 1 && $opt_output_empty == 0) | 
 |       { | 
 |         print "Info: Function '",$comment->{COMMENT_NAME},"' has no documentation.\n"; | 
 |       } | 
 |       $parse_state = 0; | 
 |     } | 
 |   } | 
 |   close(SOURCE_FILE); | 
 | } | 
 |  | 
 | # Standardise a comments text for consistency | 
 | sub process_comment_text($) | 
 | { | 
 |   my $comment = shift; | 
 |   my $in_params = 0; | 
 |   my @tmp_list = (); | 
 |   my $i = 0; | 
 |  | 
 |   for (@{$comment->{TEXT}}) | 
 |   { | 
 |     my $line = $_; | 
 |  | 
 |     if ( /^\s*$/ || /^[A-Z]+$/ || /^-/ ) | 
 |     { | 
 |       $in_params = 0; | 
 |     } | 
 |     if ( $in_params > 0 && !/\[/ && !/\]/ ) | 
 |     { | 
 |       # Possibly a continuation of the parameter description | 
 |       my $last_line = pop(@tmp_list); | 
 |       if ( $last_line =~ /\[/ && $last_line =~ /\]/ ) | 
 |       { | 
 |         $line = $last_line." ".$_; | 
 |       } | 
 |       else | 
 |       { | 
 |         $in_params = 0; | 
 |         push (@tmp_list, $last_line); | 
 |       } | 
 |     } | 
 |     if ( /^(PARAMS|MEMBERS)$/ ) | 
 |     { | 
 |       $in_params = 1; | 
 |     } | 
 |     push (@tmp_list, $line); | 
 |   } | 
 |  | 
 |   @{$comment->{TEXT}} = @tmp_list; | 
 |  | 
 |   for (@{$comment->{TEXT}}) | 
 |   { | 
 |     if (! /^\|/ ) | 
 |     { | 
 |       # Map I/O values. These come in too many formats to standardise now.... | 
 |       s/\[I\]|\[i\]|\[in\]|\[IN\]/\[In\] /g; | 
 |       s/\[O\]|\[o\]|\[out\]|\[OUT\]/\[Out\]/g; | 
 |       s/\[I\/O\]|\[I\,O\]|\[i\/o\]|\[in\/out\]|\[IN\/OUT\]/\[In\/Out\]/g; | 
 |       # TRUE/FALSE/NULL are defines, capitilise them | 
 |       s/True|true/TRUE/g; | 
 |       s/False|false/FALSE/g; | 
 |       s/Null|null/NULL/g; | 
 |       # Preferred capitalisations | 
 |       s/ wine| WINE/ Wine/g; | 
 |       s/ API | api / Api /g; | 
 |       s/DLL|Dll/dll /g; | 
 |       s/ URL | url / Url /g; | 
 |       s/WIN16|win16/Win16/g; | 
 |       s/WIN32|win32/Win32/g; | 
 |       s/WIN64|win64/Win64/g; | 
 |       s/ ID | id / Id /g; | 
 |       # Grammar | 
 |       s/([a-z])\.([A-Z])/$1\. $2/g; # Space after full stop | 
 |       s/ \:/\:/g;                   # Colons to the left | 
 |       s/ \;/\;/g;                   # Semi-colons too | 
 |       # Common idioms | 
 |       s/^See ([A-Za-z0-9_]+)\.$/See $1\(\)\./;                # Referring to A version from W | 
 |       s/^Unicode version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Ditto | 
 |       s/^64\-bit version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Referring to 32 bit version from 64 | 
 |       s/^PARAMETERS$/PARAMS/;  # Name of parameter section should be 'PARAMS' | 
 |       # Trademarks | 
 |       s/( |\.)(M\$|MS|Microsoft|microsoft|micro\$oft|Micro\$oft)( |\.)/$1Microsoft\(tm\)$3/g; | 
 |       s/( |\.)(Windows|windows|windoze|winblows)( |\.)/$1Windows\(tm\)$3/g; | 
 |       s/( |\.)(DOS|dos|msdos)( |\.)/$1MS-DOS\(tm\)$3/g; | 
 |       s/( |\.)(UNIX|unix)( |\.)/$1Unix\(tm\)$3/g; | 
 |       s/( |\.)(LINIX|linux)( |\.)/$1Linux\(tm\)$3/g; | 
 |       # Abbreviations | 
 |       s/( char )/ character /g; | 
 |       s/( chars )/ characters /g; | 
 |       s/( info )/ information /g; | 
 |       s/( app )/ application /g; | 
 |       s/( apps )/ applications /g; | 
 |       s/( exe )/ executable /g; | 
 |       s/( ptr )/ pointer /g; | 
 |       s/( obj )/ object /g; | 
 |       s/( err )/ error /g; | 
 |       s/( bool )/ boolean /g; | 
 |       s/( no\. )/ number /g; | 
 |       s/( No\. )/ Number /g; | 
 |       # Punctuation | 
 |       if ( /\[I|\[O/ && ! /\.$/ ) | 
 |       { | 
 |         $_ = $_."."; # Always have a full stop at the end of parameter desc. | 
 |       } | 
 |       elsif ($i > 0 && /^[A-Z]*$/  && | 
 |                !(@{$comment->{TEXT}}[$i-1] =~ /\.$/) && | 
 |                !(@{$comment->{TEXT}}[$i-1] =~ /\:$/)) | 
 |       { | 
 |  | 
 |         if (!(@{$comment->{TEXT}}[$i-1] =~ /^[A-Z]*$/)) | 
 |         { | 
 |           # Paragraphs always end with a full stop | 
 |           @{$comment->{TEXT}}[$i-1] = @{$comment->{TEXT}}[$i-1]."."; | 
 |         } | 
 |       } | 
 |     } | 
 |     $i++; | 
 |   } | 
 | } | 
 |  | 
 | # Standardise our comment and output it if it is suitable. | 
 | sub process_comment($) | 
 | { | 
 |   my $comment = shift; | 
 |  | 
 |   # Don't process this comment if the function isn't exported | 
 |   my $spec_details = $spec_files{$comment->{DLL_NAME}}[0]; | 
 |  | 
 |   if (!defined($spec_details)) | 
 |   { | 
 |     if ($opt_verbose > 2) | 
 |     { | 
 |       print "Warning: Function '".$comment->{COMMENT_NAME}."' belongs to '". | 
 |             $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n"; | 
 |     } | 
 |     return; | 
 |   } | 
 |  | 
 |   if ($comment->{COMMENT_NAME} eq "@") | 
 |   { | 
 |     my $found = 0; | 
 |  | 
 |     # Find the name from the .spec file | 
 |     for (@{$spec_details->{EXPORTS}}) | 
 |     { | 
 |       if (@$_[0] eq $comment->{ORDINAL}) | 
 |       { | 
 |         $comment->{COMMENT_NAME} = @$_[2]; | 
 |         $found = 1; | 
 |       } | 
 |     } | 
 |  | 
 |     if ($found == 0) | 
 |     { | 
 |       # Create an implementation name | 
 |       $comment->{COMMENT_NAME} = $comment->{DLL_NAME}."_".$comment->{ORDINAL}; | 
 |     } | 
 |   } | 
 |  | 
 |   my $exported_names = $spec_details->{EXPORTED_NAMES}; | 
 |   my $export_index = $exported_names->{$comment->{COMMENT_NAME}}; | 
 |   my $implementation_names = $spec_details->{IMPLEMENTATION_NAMES}; | 
 |  | 
 |   if (!defined($export_index)) | 
 |   { | 
 |     # Perhaps the comment uses the implementation name? | 
 |     $export_index = $implementation_names->{$comment->{COMMENT_NAME}}; | 
 |   } | 
 |   if (!defined($export_index)) | 
 |   { | 
 |     # This function doesn't appear to be exported. hmm. | 
 |     if ($opt_verbose > 2) | 
 |     { | 
 |       print "Warning: Function '".$comment->{COMMENT_NAME}."' claims to belong to '". | 
 |             $comment->{DLL_NAME}."' but is not exported by it: not processing it.\n"; | 
 |     } | 
 |     return; | 
 |   } | 
 |  | 
 |   # When the function is exported twice we have the second name below the first | 
 |   # (you see this a lot in ntdll, but also in some other places). | 
 |   my $first_line = ${@{$comment->{TEXT}}}[1]; | 
 |  | 
 |   if ( $first_line =~ /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ ) | 
 |   { | 
 |     # Found a second name - mark it as documented | 
 |     my $alt_index = $exported_names->{$1}; | 
 |     if (defined($alt_index)) | 
 |     { | 
 |       if ($opt_verbose > 2) | 
 |       { | 
 |         print "Info: Found alternate name '",$1,"\n"; | 
 |       } | 
 |       my $alt_export = @{$spec_details->{EXPORTS}}[$alt_index]; | 
 |       @$alt_export[4] |= $FLAG_DOCUMENTED; | 
 |       $spec_details->{NUM_DOCS}++; | 
 |       ${@{$comment->{TEXT}}}[1] = ""; | 
 |     } | 
 |   } | 
 |  | 
 |   if (@{$spec_details->{CURRENT_EXTRA}}) | 
 |   { | 
 |     # We have an extra comment that might be related to this one | 
 |     my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0]; | 
 |     my $current_name = $current_comment->{COMMENT_NAME}; | 
 |     if ($comment->{COMMENT_NAME} =~ /^$current_name/ && $comment->{COMMENT_NAME} ne $current_name) | 
 |     { | 
 |       if ($opt_verbose > 2) | 
 |       { | 
 |         print "Linking ",$comment->{COMMENT_NAME}," to $current_name\n"; | 
 |       } | 
 |       # Add a reference to this comment to our extra comment | 
 |       push (@{$current_comment->{TEXT}}, $comment->{COMMENT_NAME}."()",""); | 
 |     } | 
 |   } | 
 |  | 
 |   # We want our docs generated using the implementation name, so they are unique | 
 |   my $export = @{$spec_details->{EXPORTS}}[$export_index]; | 
 |   $comment->{COMMENT_NAME} = @$export[3]; | 
 |   $comment->{ALT_NAME} = @$export[2]; | 
 |  | 
 |   # Mark the function as documented | 
 |   $spec_details->{NUM_DOCS}++; | 
 |   @$export[4] |= $FLAG_DOCUMENTED; | 
 |  | 
 |   # This file is used by the DLL - Make sure we get our contributors right | 
 |   push (@{$spec_details->{SOURCES}},$comment->{FILE}); | 
 |  | 
 |   # If we have parameter comments in the prototype, extract them | 
 |   my @parameter_comments; | 
 |   for (@{$comment->{PROTOTYPE}}) | 
 |   { | 
 |     s/ *\, */\,/g; # Strip spaces from around commas | 
 |  | 
 |     if ( s/ *(\/\* *)(.*?)( *\*\/ *)// ) # Strip out comment | 
 |     { | 
 |       my $parameter_comment = $2; | 
 |       if (!$parameter_comment =~ /^\[/ ) | 
 |       { | 
 |         # Add [IO] markers so we format the comment correctly | 
 |         $parameter_comment = "[fixme] ".$parameter_comment; | 
 |       } | 
 |       if ( /( |\*)([A-Za-z_]{1}[A-Za-z_0-9]*)(\,|\))/ ) | 
 |       { | 
 |         # Add the parameter name | 
 |         $parameter_comment = $2." ".$parameter_comment; | 
 |       } | 
 |       push (@parameter_comments, $parameter_comment); | 
 |     } | 
 |   } | 
 |  | 
 |   # If we extracted any prototype comments, add them to the comment text. | 
 |   if (@parameter_comments) | 
 |   { | 
 |     @parameter_comments = ("PARAMS", @parameter_comments); | 
 |     my @new_comment = (); | 
 |     my $inserted_params = 0; | 
 |  | 
 |     for (@{$comment->{TEXT}}) | 
 |     { | 
 |       if ( $inserted_params == 0 && /^[A-Z]+$/ ) | 
 |       { | 
 |         # Found a section header, so this is where we insert | 
 |         push (@new_comment, @parameter_comments); | 
 |         $inserted_params = 1; | 
 |       } | 
 |       push (@new_comment, $_); | 
 |     } | 
 |     if ($inserted_params == 0) | 
 |     { | 
 |       # Add them to the end | 
 |       push (@new_comment, @parameter_comments); | 
 |     } | 
 |     $comment->{TEXT} = [@new_comment]; | 
 |   } | 
 |  | 
 |   if ($opt_fussy == 1 && $opt_output_empty == 0) | 
 |   { | 
 |     # Reject any comment that doesn't have a description or a RETURNS section. | 
 |     # This is the default for now, 'coz many comments aren't suitable. | 
 |     my $found_returns = 0; | 
 |     my $found_description_text = 0; | 
 |     my $in_description = 0; | 
 |     for (@{$comment->{TEXT}}) | 
 |     { | 
 |       if ( /^RETURNS$/ ) | 
 |       { | 
 |         $found_returns = 1; | 
 |         $in_description = 0; | 
 |       } | 
 |       elsif ( /^DESCRIPTION$/ ) | 
 |       { | 
 |         $in_description = 1; | 
 |       } | 
 |       elsif ($in_description == 1) | 
 |       { | 
 |         if ( !/^[A-Z]+$/ ) | 
 |         { | 
 |           # Don't reject comments that refer to another doc (e.g. A/W) | 
 |           if ( /^See ([A-Za-z0-9_]+)\.$/ ) | 
 |           { | 
 |             if ($comment->{COMMENT_NAME} =~ /W$/ ) | 
 |             { | 
 |               # This is probably a Unicode version of an Ascii function. | 
 |               # Create the Ascii name and see if its been documented | 
 |               my $ascii_name = $comment->{COMMENT_NAME}; | 
 |               $ascii_name =~ s/W$/A/; | 
 |  | 
 |               my $ascii_export_index = $exported_names->{$ascii_name}; | 
 |  | 
 |               if (!defined($ascii_export_index)) | 
 |               { | 
 |                 $ascii_export_index = $implementation_names->{$ascii_name}; | 
 |               } | 
 |               if (!defined($ascii_export_index)) | 
 |               { | 
 |                 if ($opt_verbose > 2) | 
 |                 { | 
 |                   print "Warning: Function '".$comment->{COMMENT_NAME}."' is not an A/W pair.\n"; | 
 |                 } | 
 |               } | 
 |               else | 
 |               { | 
 |                 my $ascii_export = @{$spec_details->{EXPORTS}}[$ascii_export_index]; | 
 |                 if (@$ascii_export[4] & $FLAG_DOCUMENTED) | 
 |                 { | 
 |                   # Flag these functions as an A/W pair | 
 |                   @$ascii_export[4] |= $FLAG_APAIR; | 
 |                   @$export[4] |= $FLAG_WPAIR; | 
 |                 } | 
 |               } | 
 |             } | 
 |             $found_returns = 1; | 
 |           } | 
 |           elsif ( /^Unicode version of ([A-Za-z0-9_]+)\.$/ ) | 
 |           { | 
 |             @$export[4] |= $FLAG_WPAIR; # Explicitly marked as W version | 
 |             $found_returns = 1; | 
 |           } | 
 |           elsif ( /^64\-bit version of ([A-Za-z0-9_]+)\.$/ ) | 
 |           { | 
 |             @$export[4] |= $FLAG_64PAIR; # Explicitly marked as 64 bit version | 
 |             $found_returns = 1; | 
 |           } | 
 |           $found_description_text = 1; | 
 |         } | 
 |         else | 
 |         { | 
 |           $in_description = 0; | 
 |         } | 
 |       } | 
 |     } | 
 |     if ($found_returns == 0 || $found_description_text == 0) | 
 |     { | 
 |       if ($opt_verbose > 2) | 
 |       { | 
 |         print "Info: Function '",$comment->{COMMENT_NAME},"' has no ", | 
 |               "description and/or RETURNS section, skipping\n"; | 
 |       } | 
 |       $spec_details->{NUM_DOCS}--; | 
 |       @$export[4] &= ~$FLAG_DOCUMENTED; | 
 |       return; | 
 |     } | 
 |   } | 
 |  | 
 |   process_comment_text($comment); | 
 |  | 
 |   # Strip the prototypes return value, call convention, name and brackets | 
 |   # (This leaves it as a list of types and names, or empty for void functions) | 
 |   my $prototype = join(" ", @{$comment->{PROTOTYPE}}); | 
 |   $prototype =~ s/  / /g; | 
 |  | 
 |   if ( $prototype =~ /(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ ) | 
 |   { | 
 |     $prototype =~ s/^(.*?) (WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16) (.*?)\( *(.*)/$4/; | 
 |     $comment->{RETURNS} = $1; | 
 |   } | 
 |   else | 
 |   { | 
 |     $prototype =~ s/^(.*?)([A-Za-z0-9_]+)\( *(.*)/$3/; | 
 |     $comment->{RETURNS} = $1; | 
 |   } | 
 |  | 
 |   $prototype =~ s/ *\).*//;        # Strip end bracket | 
 |   $prototype =~ s/ *\* */\*/g;     # Strip space around pointers | 
 |   $prototype =~ s/ *\, */\,/g;     # Strip space around commas | 
 |   $prototype =~ s/^(void|VOID)$//; # If void, leave blank | 
 |   $prototype =~ s/\*([A-Za-z_])/\* $1/g; # Separate pointers from parameter name | 
 |   @{$comment->{PROTOTYPE}} = split ( /,/ ,$prototype); | 
 |  | 
 |   # FIXME: If we have no parameters, make sure we have a PARAMS: None. section | 
 |  | 
 |   # Find header file | 
 |   my $h_file = ""; | 
 |   if (@$export[4] & $FLAG_NONAME) | 
 |   { | 
 |     $h_file = "Exported by ordinal only. Use GetProcAddress() to obtain a pointer to the function."; | 
 |   } | 
 |   else | 
 |   { | 
 |     if ($comment->{COMMENT_NAME} ne "") | 
 |     { | 
 |       my $tmp = "grep -s -l $comment->{COMMENT_NAME} @opt_header_file_list 2>/dev/null"; | 
 |       $tmp = `$tmp`; | 
 |       my $exit_value  = $? >> 8; | 
 |       if ($exit_value == 0) | 
 |       { | 
 |         $tmp =~ s/\n.*//g; | 
 |         if ($tmp ne "") | 
 |         { | 
 |           $h_file = `basename $tmp`; | 
 |         } | 
 |       } | 
 |     } | 
 |     elsif ($comment->{ALT_NAME} ne "") | 
 |     { | 
 |       my $tmp = "grep -s -l $comment->{ALT_NAME} @opt_header_file_list"." 2>/dev/null"; | 
 |       $tmp = `$tmp`; | 
 |       my $exit_value  = $? >> 8; | 
 |       if ($exit_value == 0) | 
 |       { | 
 |         $tmp =~ s/\n.*//g; | 
 |         if ($tmp ne "") | 
 |         { | 
 |           $h_file = `basename $tmp`; | 
 |         } | 
 |       } | 
 |     } | 
 |     $h_file =~ s/^ *//; | 
 |     $h_file =~ s/\n//; | 
 |     if ($h_file eq "") | 
 |     { | 
 |       $h_file = "Not defined in a Wine header. The function is either undocumented, or missing from Wine." | 
 |     } | 
 |     else | 
 |     { | 
 |       $h_file = "Defined in \"".$h_file."\"."; | 
 |     } | 
 |   } | 
 |  | 
 |   # Find source file | 
 |   my $c_file = $comment->{FILE}; | 
 |   if ($opt_wine_root_dir ne "") | 
 |   { | 
 |     my $cfile = $pwd."/".$c_file;     # Current dir + file | 
 |     $cfile =~ s/(.+)(\/.*$)/$1/;      # Strip the filename | 
 |     $cfile = `cd $cfile && pwd`;      # Strip any relative parts (e.g. "../../") | 
 |     $cfile =~ s/\n//;                 # Strip newline | 
 |     my $newfile = $c_file; | 
 |     $newfile =~ s/(.+)(\/.*$)/$2/;    # Strip all but the filename | 
 |     $cfile = $cfile."/".$newfile;     # Append filename to base path | 
 |     $cfile =~ s/$opt_wine_root_dir//; # Get rid of the root directory | 
 |     $cfile =~ s/\/\//\//g;            # Remove any double slashes | 
 |     $cfile =~ s/^\/+//;               # Strip initial directory slash | 
 |     $c_file = $cfile; | 
 |   } | 
 |   $c_file = "Implemented in \"".$c_file."\"."; | 
 |  | 
 |   # Add the implementation details | 
 |   push (@{$comment->{TEXT}}, "IMPLEMENTATION","",$h_file,"",$c_file); | 
 |  | 
 |   if (@$export[4] & $FLAG_I386) | 
 |   { | 
 |     push (@{$comment->{TEXT}}, "", "Available on x86 platforms only."); | 
 |   } | 
 |   if (@$export[4] & $FLAG_REGISTER) | 
 |   { | 
 |     push (@{$comment->{TEXT}}, "", "This function passes one or more arguments in registers. ", | 
 |           "For more details, please read the source code."); | 
 |   } | 
 |   my $source_details = $source_files{$comment->{FILE}}[0]; | 
 |   if ($source_details->{DEBUG_CHANNEL} ne "") | 
 |   { | 
 |     push (@{$comment->{TEXT}}, "", "Debug channel \"".$source_details->{DEBUG_CHANNEL}."\"."); | 
 |   } | 
 |  | 
 |   # Write out the documentation for the API | 
 |   output_comment($comment) | 
 | } | 
 |  | 
 | # process our extra comment and output it if it is suitable. | 
 | sub process_extra_comment($) | 
 | { | 
 |   my $comment = shift; | 
 |  | 
 |   my $spec_details = $spec_files{$comment->{DLL_NAME}}[0]; | 
 |  | 
 |   if (!defined($spec_details)) | 
 |   { | 
 |     if ($opt_verbose > 2) | 
 |     { | 
 |       print "Warning: Extra comment '".$comment->{COMMENT_NAME}."' belongs to '". | 
 |             $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n"; | 
 |     } | 
 |     return; | 
 |   } | 
 |  | 
 |   # Check first to see if this is documentation for the DLL. | 
 |   if ($comment->{COMMENT_NAME} eq $comment->{DLL_NAME}) | 
 |   { | 
 |     if ($opt_verbose > 2) | 
 |     { | 
 |       print "Info: Found DLL documentation\n"; | 
 |     } | 
 |     for (@{$comment->{TEXT}}) | 
 |     { | 
 |       push (@{$spec_details->{DESCRIPTION}}, $_); | 
 |     } | 
 |     return; | 
 |   } | 
 |  | 
 |   # Add the comment to the DLL page as a link | 
 |   push (@{$spec_details->{EXTRA_COMMENTS}},$comment->{COMMENT_NAME}); | 
 |  | 
 |   # If we have a prototype, process as a regular comment | 
 |   if (@{$comment->{PROTOTYPE}}) | 
 |   { | 
 |     $comment->{ORDINAL} = "@"; | 
 |  | 
 |     # Add an index for the comment name | 
 |     $spec_details->{EXPORTED_NAMES}{$comment->{COMMENT_NAME}} = $spec_details->{NUM_EXPORTS}; | 
 |  | 
 |     # Add a fake exported entry | 
 |     $spec_details->{NUM_EXPORTS}++; | 
 |     my ($ordinal, $call_convention, $exported_name, $implementation_name, $documented) = | 
 |      ("@", "fake", $comment->{COMMENT_NAME}, $comment->{COMMENT_NAME}, 0); | 
 |     my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented); | 
 |     push (@{$spec_details->{EXPORTS}},[@export]); | 
 |     @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}}); | 
 |     process_comment($comment); | 
 |     return; | 
 |   } | 
 |  | 
 |   if ($opt_verbose > 0) | 
 |   { | 
 |     print "Processing ",$comment->{COMMENT_NAME},"\n"; | 
 |   } | 
 |  | 
 |   if (@{$spec_details->{CURRENT_EXTRA}}) | 
 |   { | 
 |     my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0]; | 
 |  | 
 |     if ($opt_verbose > 0) | 
 |     { | 
 |       print "Processing old current: ",$current_comment->{COMMENT_NAME},"\n"; | 
 |     } | 
 |     # Output the current comment | 
 |     process_comment_text($current_comment); | 
 |     output_open_api_file($current_comment->{COMMENT_NAME}); | 
 |     output_api_header($current_comment); | 
 |     output_api_name($current_comment); | 
 |     output_api_comment($current_comment); | 
 |     output_api_footer($current_comment); | 
 |     output_close_api_file(); | 
 |   } | 
 |  | 
 |   if ($opt_verbose > 2) | 
 |   { | 
 |     print "Setting current to ",$comment->{COMMENT_NAME},"\n"; | 
 |   } | 
 |  | 
 |   my $comment_copy = | 
 |   { | 
 |     FILE => $comment->{FILE}, | 
 |     COMMENT_NAME => $comment->{COMMENT_NAME}, | 
 |     ALT_NAME => $comment->{ALT_NAME}, | 
 |     DLL_NAME => $comment->{DLL_NAME}, | 
 |     ORDINAL => $comment->{ORDINAL}, | 
 |     RETURNS => $comment->{RETURNS}, | 
 |     PROTOTYPE => [], | 
 |     TEXT => [], | 
 |   }; | 
 |  | 
 |   for (@{$comment->{TEXT}}) | 
 |   { | 
 |     push (@{$comment_copy->{TEXT}}, $_); | 
 |   } | 
 |   # Set this comment to be the current extra comment | 
 |   @{$spec_details->{CURRENT_EXTRA}} = ($comment_copy); | 
 | } | 
 |  | 
 | # Write a standardised comment out in the appropriate format | 
 | sub output_comment($) | 
 | { | 
 |   my $comment = shift; | 
 |  | 
 |   if ($opt_verbose > 0) | 
 |   { | 
 |     print "Processing ",$comment->{COMMENT_NAME},"\n"; | 
 |   } | 
 |  | 
 |   if ($opt_verbose > 4) | 
 |   { | 
 |     print "--PROTO--\n"; | 
 |     for (@{$comment->{PROTOTYPE}}) | 
 |     { | 
 |       print "'".$_."'\n"; | 
 |     } | 
 |  | 
 |     print "--COMMENT--\n"; | 
 |     for (@{$comment->{TEXT} }) | 
 |     { | 
 |       print $_."\n"; | 
 |     } | 
 |   } | 
 |  | 
 |   output_open_api_file($comment->{COMMENT_NAME}); | 
 |   output_api_header($comment); | 
 |   output_api_name($comment); | 
 |   output_api_synopsis($comment); | 
 |   output_api_comment($comment); | 
 |   output_api_footer($comment); | 
 |   output_close_api_file(); | 
 | } | 
 |  | 
 | # Write out an index file for each .spec processed | 
 | sub process_index_files() | 
 | { | 
 |   foreach my $spec_file (keys %spec_files) | 
 |   { | 
 |     my $spec_details = $spec_files{$spec_file}[0]; | 
 |     if (defined ($spec_details->{DLL_NAME})) | 
 |     { | 
 |       if (@{$spec_details->{CURRENT_EXTRA}}) | 
 |       { | 
 |         # We have an unwritten extra comment, write it | 
 |         my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0]; | 
 |         process_extra_comment($current_comment); | 
 |         @{$spec_details->{CURRENT_EXTRA}} = (); | 
 |        } | 
 |        output_spec($spec_details); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | # Write a spec files documentation out in the appropriate format | 
 | sub output_spec($) | 
 | { | 
 |   my $spec_details = shift; | 
 |  | 
 |   if ($opt_verbose > 2) | 
 |   { | 
 |     print "Writing:",$spec_details->{DLL_NAME},"\n"; | 
 |   } | 
 |  | 
 |   # Use the comment output functions for consistency | 
 |   my $comment = | 
 |   { | 
 |     FILE => $spec_details->{DLL_NAME}, | 
 |     COMMENT_NAME => $spec_details->{DLL_NAME}.".dll", | 
 |     ALT_NAME => $spec_details->{DLL_NAME}, | 
 |     DLL_NAME => "", | 
 |     ORDINAL => "", | 
 |     RETURNS => "", | 
 |     PROTOTYPE => [], | 
 |     TEXT => [], | 
 |   }; | 
 |   my $total_implemented = $spec_details->{NUM_FORWARDS} + $spec_details->{NUM_VARS} + | 
 |      $spec_details->{NUM_FUNCS}; | 
 |   my $percent_implemented = 0; | 
 |   if ($total_implemented) | 
 |   { | 
 |     $percent_implemented = $total_implemented / | 
 |      ($total_implemented + $spec_details->{NUM_STUBS}) * 100; | 
 |   } | 
 |   $percent_implemented = int($percent_implemented); | 
 |   my $percent_documented = 0; | 
 |   if ($spec_details->{NUM_DOCS}) | 
 |   { | 
 |     # Treat forwards and data as documented funcs for statistics | 
 |     $percent_documented = $spec_details->{NUM_DOCS} / $spec_details->{NUM_FUNCS} * 100; | 
 |     $percent_documented = int($percent_documented); | 
 |   } | 
 |  | 
 |   # Make a list of the contributors to this DLL. Do this only for the source | 
 |   # files that make up the DLL, because some directories specify multiple dlls. | 
 |   my @contributors; | 
 |  | 
 |   for (@{$spec_details->{SOURCES}}) | 
 |   { | 
 |     my $source_details = $source_files{$_}[0]; | 
 |     for (@{$source_details->{CONTRIBUTORS}}) | 
 |     { | 
 |       push (@contributors, $_); | 
 |     } | 
 |   } | 
 |  | 
 |   my %saw; | 
 |   @contributors = grep(!$saw{$_}++, @contributors); # remove dups, from perlfaq4 manpage | 
 |   @contributors = sort @contributors; | 
 |  | 
 |   # Remove duplicates and blanks | 
 |   for(my $i=0; $i<@contributors; $i++) | 
 |   { | 
 |     if ($i > 0 && ($contributors[$i] =~ /$contributors[$i-1]/ || $contributors[$i-1] eq "")) | 
 |     { | 
 |       $contributors[$i-1] = $contributors[$i]; | 
 |     } | 
 |   } | 
 |   undef %saw; | 
 |   @contributors = grep(!$saw{$_}++, @contributors); | 
 |  | 
 |   if ($opt_verbose > 3) | 
 |   { | 
 |     print "Contributors:\n"; | 
 |     for (@contributors) | 
 |     { | 
 |       print "'".$_."'\n"; | 
 |     } | 
 |   } | 
 |   my $contribstring = join (", ", @contributors); | 
 |  | 
 |   # Create the initial comment text | 
 |   @{$comment->{TEXT}} = ( | 
 |     "NAME", | 
 |     $comment->{COMMENT_NAME} | 
 |   ); | 
 |  | 
 |   # Add the description, if we have one | 
 |   if (@{$spec_details->{DESCRIPTION}}) | 
 |   { | 
 |     push (@{$comment->{TEXT}}, "DESCRIPTION"); | 
 |     for (@{$spec_details->{DESCRIPTION}}) | 
 |     { | 
 |       push (@{$comment->{TEXT}}, $_); | 
 |     } | 
 |   } | 
 |  | 
 |   # Add the statistics and contributors | 
 |   push (@{$comment->{TEXT}}, | 
 |     "STATISTICS", | 
 |     "Forwards: ".$spec_details->{NUM_FORWARDS}, | 
 |     "Variables: ".$spec_details->{NUM_VARS}, | 
 |     "Stubs: ".$spec_details->{NUM_STUBS}, | 
 |     "Functions: ".$spec_details->{NUM_FUNCS}, | 
 |     "Exports-Total: ".$spec_details->{NUM_EXPORTS}, | 
 |     "Implemented-Total: ".$total_implemented." (".$percent_implemented."%)", | 
 |     "Documented-Total: ".$spec_details->{NUM_DOCS}." (".$percent_documented."%)", | 
 |     "CONTRIBUTORS", | 
 |     "The following people hold copyrights on the source files comprising this dll:", | 
 |     "", | 
 |     $contribstring, | 
 |     "Note: This list may not be complete.", | 
 |     "For a complete listing, see the Files \"AUTHORS\" and \"Changelog\" in the Wine source tree.", | 
 |     "", | 
 |   ); | 
 |  | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |     # Add the exports to the comment text | 
 |     push (@{$comment->{TEXT}},"EXPORTS"); | 
 |     my $exports = $spec_details->{EXPORTS}; | 
 |     for (@$exports) | 
 |     { | 
 |       my $line = ""; | 
 |  | 
 |       # @$_ => ordinal, call convention, exported name, implementation name, flags; | 
 |       if (@$_[1] eq "forward") | 
 |       { | 
 |         my $forward_dll = @$_[3]; | 
 |         $forward_dll =~ s/\.(.*)//; | 
 |         $line = @$_[2]." (forward to ".$1."() in ".$forward_dll."())"; | 
 |       } | 
 |       elsif (@$_[1] eq "extern") | 
 |       { | 
 |         $line = @$_[2]." (extern)"; | 
 |       } | 
 |       elsif (@$_[1] eq "stub") | 
 |       { | 
 |         $line = @$_[2]." (stub)"; | 
 |       } | 
 |       elsif (@$_[1] eq "fake") | 
 |       { | 
 |         # Don't add this function here, it gets listed with the extra documentation | 
 |         if (!(@$_[4] & $FLAG_WPAIR)) | 
 |         { | 
 |           # This function should be indexed | 
 |           push (@index_entries_list, @$_[3].",".@$_[3]); | 
 |         } | 
 |       } | 
 |       elsif (@$_[1] eq "equate" || @$_[1] eq "variable") | 
 |       { | 
 |         $line = @$_[2]." (data)"; | 
 |       } | 
 |       else | 
 |       { | 
 |         # A function | 
 |         if (@$_[4] & $FLAG_DOCUMENTED) | 
 |         { | 
 |           # Documented | 
 |           $line = @$_[2]." (implemented as ".@$_[3]."())"; | 
 |           if (@$_[2] ne @$_[3]) | 
 |           { | 
 |             $line = @$_[2]." (implemented as ".@$_[3]."())"; | 
 |           } | 
 |           else | 
 |           { | 
 |             $line = @$_[2]."()"; | 
 |           } | 
 |           if (!(@$_[4] & $FLAG_WPAIR)) | 
 |           { | 
 |             # This function should be indexed | 
 |             push (@index_entries_list, @$_[2].",".@$_[3]); | 
 |           } | 
 |         } | 
 |         else | 
 |         { | 
 |           $line = @$_[2]." (not documented)"; | 
 |         } | 
 |       } | 
 |       if ($line ne "") | 
 |       { | 
 |         push (@{$comment->{TEXT}}, $line, ""); | 
 |       } | 
 |     } | 
 |  | 
 |     # Add links to the extra documentation | 
 |     if (@{$spec_details->{EXTRA_COMMENTS}}) | 
 |     { | 
 |       push (@{$comment->{TEXT}}, "SEE ALSO"); | 
 |       my %htmp; | 
 |       @{$spec_details->{EXTRA_COMMENTS}} = grep(!$htmp{$_}++, @{$spec_details->{EXTRA_COMMENTS}}); | 
 |       for (@{$spec_details->{EXTRA_COMMENTS}}) | 
 |       { | 
 |         push (@{$comment->{TEXT}}, $_."()", ""); | 
 |       } | 
 |     } | 
 |   } | 
 |   # The dll entry should also be indexed | 
 |   push (@index_entries_list, $spec_details->{DLL_NAME}.",".$spec_details->{DLL_NAME}); | 
 |  | 
 |   # Write out the document | 
 |   output_open_api_file($spec_details->{DLL_NAME}); | 
 |   output_api_header($comment); | 
 |   output_api_comment($comment); | 
 |   output_api_footer($comment); | 
 |   output_close_api_file(); | 
 |  | 
 |   # Add this dll to the database of dll names | 
 |   my $output_file = $opt_output_directory."/dlls.db"; | 
 |  | 
 |   # Append the dllname to the output db of names | 
 |   open(DLLDB,">>$output_file") || die "Couldn't create $output_file\n"; | 
 |   print DLLDB $spec_details->{DLL_NAME},"\n"; | 
 |   close(DLLDB); | 
 |  | 
 |   if ($opt_output_format eq "s") | 
 |   { | 
 |     output_sgml_dll_file($spec_details); | 
 |     return; | 
 |   } | 
 | } | 
 |  | 
 | # | 
 | # OUTPUT FUNCTIONS | 
 | # ---------------- | 
 | # Only these functions know anything about formatting for a specific | 
 | # output type. The functions above work only with plain text. | 
 | # This is to allow new types of output to be added easily. | 
 |  | 
 | # Open the api file | 
 | sub output_open_api_file($) | 
 | { | 
 |   my $output_name = shift; | 
 |   $output_name = $opt_output_directory."/".$output_name; | 
 |  | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |     $output_name = $output_name.".html"; | 
 |   } | 
 |   elsif ($opt_output_format eq "s") | 
 |   { | 
 |     $output_name = $output_name.".sgml"; | 
 |   } | 
 |   else | 
 |   { | 
 |     $output_name = $output_name.".".$opt_manual_section; | 
 |   } | 
 |   open(OUTPUT,">$output_name") || die "Couldn't create file '$output_name'\n"; | 
 | } | 
 |  | 
 | # Close the api file | 
 | sub output_close_api_file() | 
 | { | 
 |   close (OUTPUT); | 
 | } | 
 |  | 
 | # Output the api file header | 
 | sub output_api_header($) | 
 | { | 
 |   my $comment = shift; | 
 |  | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |       print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n"; | 
 |       print OUTPUT "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"; | 
 |       print OUTPUT "<HTML>\n<HEAD>\n"; | 
 |       print OUTPUT "<LINK REL=\"StyleSheet\" href=\"apidoc.css\" type=\"text/css\">\n"; | 
 |       print OUTPUT "<META NAME=\"GENERATOR\" CONTENT=\"tools/c2man.pl\">\n"; | 
 |       print OUTPUT "<META NAME=\"keywords\" CONTENT=\"Win32,Wine,API,$comment->{COMMENT_NAME}\">\n"; | 
 |       print OUTPUT "<TITLE>Wine API: $comment->{COMMENT_NAME}</TITLE>\n</HEAD>\n<BODY>\n"; | 
 |   } | 
 |   elsif ($opt_output_format eq "s") | 
 |   { | 
 |       print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n", | 
 |                    "<sect1>\n", | 
 |                    "<title>$comment->{COMMENT_NAME}</title>\n"; | 
 |   } | 
 |   else | 
 |   { | 
 |       print OUTPUT ".\\\" -*- nroff -*-\n.\\\" Generated file - DO NOT EDIT!\n". | 
 |                    ".TH ",$comment->{COMMENT_NAME}," ",$opt_manual_section," \"",$date,"\" \"". | 
 |                    "Wine API\" \"Wine API\"\n"; | 
 |   } | 
 | } | 
 |  | 
 | sub output_api_footer($) | 
 | { | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |       print OUTPUT "<hr><p><i class=\"copy\">Copyright © ".$year." The Wine Project.". | 
 |                    " All trademarks are the property of their respective owners.". | 
 |                    " Visit <a href=\"http://www.winehq.org\">WineHQ</a> for license details.". | 
 |                    " Generated $date.</i></p>\n</body>\n</html>\n"; | 
 |   } | 
 |   elsif ($opt_output_format eq "s") | 
 |   { | 
 |       print OUTPUT "</sect1>\n"; | 
 |       return; | 
 |   } | 
 |   else | 
 |   { | 
 |   } | 
 | } | 
 |  | 
 | sub output_api_section_start($$) | 
 | { | 
 |   my $comment = shift; | 
 |   my $section_name = shift; | 
 |  | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |     print OUTPUT "\n<h2 class=\"section\">",$section_name,"</h2>\n"; | 
 |   } | 
 |   elsif ($opt_output_format eq "s") | 
 |   { | 
 |     print OUTPUT "<bridgehead>",$section_name,"</bridgehead>\n"; | 
 |   } | 
 |   else | 
 |   { | 
 |     print OUTPUT "\n\.SH ",$section_name,"\n"; | 
 |   } | 
 | } | 
 |  | 
 | sub output_api_section_end() | 
 | { | 
 |   # Not currently required by any output formats | 
 | } | 
 |  | 
 | sub output_api_name($) | 
 | { | 
 |   my $comment = shift; | 
 |   my $readable_name = $comment->{COMMENT_NAME}; | 
 |   $readable_name =~ s/-/ /g; # make section names more readable | 
 |  | 
 |   output_api_section_start($comment,"NAME"); | 
 |  | 
 |  | 
 |   my $dll_ordinal = ""; | 
 |   if ($comment->{ORDINAL} ne "") | 
 |   { | 
 |     $dll_ordinal = "(".$comment->{DLL_NAME}.".".$comment->{ORDINAL}.")"; | 
 |   } | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |     print OUTPUT "<p><b class=\"func_name\">",$readable_name, | 
 |                  "</b>  <i class=\"dll_ord\">", | 
 |                  ,$dll_ordinal,"</i></p>\n"; | 
 |   } | 
 |   elsif ($opt_output_format eq "s") | 
 |   { | 
 |     print OUTPUT "<para>\n  <command>",$readable_name,"</command>  <emphasis>", | 
 |                  $dll_ordinal,"</emphasis>\n</para>\n"; | 
 |   } | 
 |   else | 
 |   { | 
 |     print OUTPUT "\\fB",$readable_name,"\\fR ",$dll_ordinal; | 
 |   } | 
 |  | 
 |   output_api_section_end(); | 
 | } | 
 |  | 
 | sub output_api_synopsis($) | 
 | { | 
 |   my $comment = shift; | 
 |   my @fmt; | 
 |  | 
 |   output_api_section_start($comment,"SYNOPSIS"); | 
 |  | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |     print OUTPUT "<pre class=\"proto\">\n ", $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n"; | 
 |     @fmt = ("", "\n", "<tt class=\"param\">", "</tt>"); | 
 |   } | 
 |   elsif ($opt_output_format eq "s") | 
 |   { | 
 |     print OUTPUT "<screen>\n ",$comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n"; | 
 |     @fmt = ("", "\n", "<emphasis>", "</emphasis>"); | 
 |   } | 
 |   else | 
 |   { | 
 |     print OUTPUT $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n"; | 
 |     @fmt = ("", "\n", "\\fI", "\\fR"); | 
 |   } | 
 |  | 
 |   # Since our prototype is output in a pre-formatted block, line up the | 
 |   # parameters and parameter comments in the same column. | 
 |  | 
 |   # First caluculate where the columns should start | 
 |   my $biggest_length = 0; | 
 |   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++) | 
 |   { | 
 |     my $line = ${@{$comment->{PROTOTYPE}}}[$i]; | 
 |     if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/) | 
 |     { | 
 |       my $length = length $1; | 
 |       if ($length > $biggest_length) | 
 |       { | 
 |         $biggest_length = $length; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   # Now pad the string with blanks | 
 |   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++) | 
 |   { | 
 |     my $line = ${@{$comment->{PROTOTYPE}}}[$i]; | 
 |     if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/) | 
 |     { | 
 |       my $pad_len = $biggest_length - length $1; | 
 |       my $padding = " " x ($pad_len); | 
 |       ${@{$comment->{PROTOTYPE}}}[$i] = $1.$padding.$2; | 
 |     } | 
 |   } | 
 |  | 
 |   for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++) | 
 |   { | 
 |     # Format the parameter name | 
 |     my $line = ${@{$comment->{PROTOTYPE}}}[$i]; | 
 |     my $comma = ($i == @{$comment->{PROTOTYPE}}-1) ? "" : ","; | 
 |     $line =~ s/(.+?)([A-Za-z_][A-Za-z_0-9]*)$/  $fmt[0]$1$fmt[2]$2$fmt[3]$comma$fmt[1]/; | 
 |     print OUTPUT $line; | 
 |   } | 
 |  | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |     print OUTPUT " )\n</pre>\n"; | 
 |   } | 
 |   elsif ($opt_output_format eq "s") | 
 |   { | 
 |     print OUTPUT " )\n</screen>\n"; | 
 |   } | 
 |   else | 
 |   { | 
 |     print OUTPUT " )\n"; | 
 |   } | 
 |  | 
 |   output_api_section_end(); | 
 | } | 
 |  | 
 | sub output_api_comment($) | 
 | { | 
 |   my $comment = shift; | 
 |   my $open_paragraph = 0; | 
 |   my $open_raw = 0; | 
 |   my $param_docs = 0; | 
 |   my @fmt; | 
 |  | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |     @fmt = ("<p>", "</p>\n", "<tt class=\"const\">", "</tt>", "<b class=\"emp\">", "</b>", | 
 |             "<tt class=\"coderef\">", "</tt>", "<tt class=\"param\">", "</tt>", | 
 |             "<i class=\"in_out\">", "</i>", "<pre class=\"raw\">\n", "</pre>\n", | 
 |             "<table class=\"tab\"><colgroup><col><col><col></colgroup><tbody>\n", | 
 |             "</tbody></table>\n","<tr><td>","</td></tr>\n","</td>","</td><td>"); | 
 |   } | 
 |   elsif ($opt_output_format eq "s") | 
 |   { | 
 |     @fmt = ("<para>\n","\n</para>\n","<constant>","</constant>","<emphasis>","</emphasis>", | 
 |             "<command>","</command>","<constant>","</constant>","<emphasis>","</emphasis>", | 
 |             "<screen>\n","</screen>\n", | 
 |             "<informaltable frame=\"none\">\n<tgroup cols=\"3\">\n<tbody>\n", | 
 |             "</tbody>\n</tgroup>\n</informaltable>\n","<row><entry>","</entry></row>\n", | 
 |             "</entry>","</entry><entry>"); | 
 |   } | 
 |   else | 
 |   { | 
 |     @fmt = ("\.PP\n", "\n", "\\fB", "\\fR", "\\fB", "\\fR", "\\fB", "\\fR", "\\fI", "\\fR", | 
 |             "\\fB", "\\fR ", "", "", "", "","","\n.PP\n","",""); | 
 |   } | 
 |  | 
 |   # Extract the parameter names | 
 |   my @parameter_names; | 
 |   for (@{$comment->{PROTOTYPE}}) | 
 |   { | 
 |     if ( /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ ) | 
 |     { | 
 |       push (@parameter_names, $2); | 
 |     } | 
 |   } | 
 |  | 
 |   for (@{$comment->{TEXT}}) | 
 |   { | 
 |     if ($opt_output_format eq "h" || $opt_output_format eq "s") | 
 |     { | 
 |       # Map special characters | 
 |       s/\&/\&/g; | 
 |       s/\</\</g; | 
 |       s/\>/\>/g; | 
 |       s/\([Cc]\)/\©/g; | 
 |       s/\(tm\)/®/; | 
 |     } | 
 |  | 
 |     if ( s/^\|// ) | 
 |     { | 
 |       # Raw output | 
 |       if ($open_raw == 0) | 
 |       { | 
 |         if ($open_paragraph == 1) | 
 |         { | 
 |           # Close the open paragraph | 
 |           print OUTPUT $fmt[1]; | 
 |           $open_paragraph = 0; | 
 |         } | 
 |         # Start raw output | 
 |         print OUTPUT $fmt[12]; | 
 |         $open_raw = 1; | 
 |       } | 
 |       if ($opt_output_format eq "") | 
 |       { | 
 |         print OUTPUT ".br\n"; # Prevent 'man' running these lines together | 
 |       } | 
 |       print OUTPUT $_,"\n"; | 
 |     } | 
 |     else | 
 |     { | 
 |       if ($opt_output_format eq "h") | 
 |       { | 
 |         # Link to the file in WineHQ cvs | 
 |         s/^(Implemented in \")(.+?)(\"\.)/$1$2$3 http:\/\/source.winehq.org\/source\/$2/g; | 
 |       } | 
 |       # Highlight strings | 
 |       s/(\".+?\")/$fmt[2]$1$fmt[3]/g; | 
 |       # Highlight literal chars | 
 |       s/(\'.\')/$fmt[2]$1$fmt[3]/g; | 
 |       s/(\'.{2}\')/$fmt[2]$1$fmt[3]/g; | 
 |       # Highlight numeric constants | 
 |       s/( |\-|\+|\.|\()([0-9\-\.]+)( |\-|$|\.|\,|\*|\?|\))/$1$fmt[2]$2$fmt[3]$3/g; | 
 |  | 
 |       # Leading cases ("xxxx:","-") start new paragraphs & are emphasised | 
 |       # FIXME: Using bullet points for leading '-' would look nicer. | 
 |       if ($open_paragraph == 1) | 
 |       { | 
 |         s/^(\-)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/; | 
 |         s/^([[A-Za-z\-]+\:)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/; | 
 |       } | 
 |       else | 
 |       { | 
 |         s/^(\-)/$fmt[4]$1$fmt[5]/; | 
 |         s/^([[A-Za-z\-]+\:)/$fmt[4]$1$fmt[5]/; | 
 |       } | 
 |  | 
 |       if ($opt_output_format eq "h") | 
 |       { | 
 |         # Html uses links for API calls | 
 |         while ( /([A-Za-z_]+[A-Za-z_0-9-]+)(\(\))/) | 
 |         { | 
 |           my $link = $1; | 
 |           my $readable_link = $1; | 
 |           $readable_link =~ s/-/ /g; | 
 |  | 
 |           s/([A-Za-z_]+[A-Za-z_0-9-]+)(\(\))/<a href\=\"$link\.html\">$readable_link<\/a>/; | 
 |         } | 
 |         # Index references | 
 |         s/\{\{(.*?)\}\}\{\{(.*?)\}\}/<a href\=\"$2\.html\">$1<\/a>/g; | 
 |         s/ ([A-Z_])(\(\))/<a href\=\"$1\.html\">$1<\/a>/g; | 
 |         # And references to COM objects (hey, they'll get documented one day) | 
 |         s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ <a href\=\"$1\.html\">$1<\/a> $2/g; | 
 |         # Convert any web addresses to real links | 
 |         s/(http\:\/\/)(.+?)($| )/<a href\=\"$1$2\">$2<\/a>$3/g; | 
 |       } | 
 |       else | 
 |       { | 
 |         if ($opt_output_format eq "") | 
 |         { | 
 |           # Give the man section for API calls | 
 |           s/ ([A-Za-z_]+[A-Za-z_0-9-]+)\(\)/ $fmt[6]$1\($opt_manual_section\)$fmt[7]/g; | 
 |         } | 
 |         else | 
 |         { | 
 |           # Highlight API calls | 
 |           s/ ([A-Za-z_]+[A-Za-z_0-9-]+\(\))/ $fmt[6]$1$fmt[7]/g; | 
 |         } | 
 |  | 
 |         # And references to COM objects | 
 |         s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ $fmt[6]$1$fmt[7] $2/g; | 
 |       } | 
 |  | 
 |       if ($open_raw == 1) | 
 |       { | 
 |         # Finish the raw output | 
 |         print OUTPUT $fmt[13]; | 
 |         $open_raw = 0; | 
 |       } | 
 |  | 
 |       if ( /^[A-Z]+$/ || /^SEE ALSO$/ ) | 
 |       { | 
 |         # Start of a new section | 
 |         if ($open_paragraph == 1) | 
 |         { | 
 |           if ($param_docs == 1) | 
 |           { | 
 |             print OUTPUT $fmt[17],$fmt[15]; | 
 |           } | 
 |           else | 
 |           { | 
 |             print OUTPUT $fmt[1]; | 
 |           } | 
 |           $open_paragraph = 0; | 
 |         } | 
 |         output_api_section_start($comment,$_); | 
 |         if ( /^PARAMS$/ || /^MEMBERS$/ ) | 
 |         { | 
 |           print OUTPUT $fmt[14]; | 
 |           $param_docs = 1; | 
 |         } | 
 |         else | 
 |         { | 
 |           #print OUTPUT $fmt[15]; | 
 |           $param_docs = 0; | 
 |         } | 
 |       } | 
 |       elsif ( /^$/ ) | 
 |       { | 
 |         # Empty line, indicating a new paragraph | 
 |         if ($open_paragraph == 1) | 
 |         { | 
 |           if ($param_docs == 0) | 
 |           { | 
 |             print OUTPUT $fmt[1]; | 
 |             $open_paragraph = 0; | 
 |           } | 
 |         } | 
 |       } | 
 |       else | 
 |       { | 
 |         if ($param_docs == 1) | 
 |         { | 
 |           if ($open_paragraph == 1) | 
 |           { | 
 |             # For parameter docs, put each parameter into a new paragraph/table row | 
 |             print OUTPUT $fmt[17]; | 
 |             $open_paragraph = 0; | 
 |           } | 
 |           s/(\[.+\])( *)/$fmt[19]$fmt[10]$1$fmt[11]$fmt[19] /; # Format In/Out | 
 |         } | 
 |         else | 
 |         { | 
 |           # Within paragraph lines, prevent lines running together | 
 |           $_ = $_." "; | 
 |         } | 
 |  | 
 |         # Format parameter names where they appear in the comment | 
 |         for my $parameter_name (@parameter_names) | 
 |         { | 
 |           s/(^|[ \.\,\(\-\*])($parameter_name)($|[ \.\)\,\-\/]|(\=[^"]))/$1$fmt[8]$2$fmt[9]$3/g; | 
 |         } | 
 |         # Structure dereferences include the dereferenced member | 
 |         s/(\-\>[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g; | 
 |         s/(\-\>\;[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g; | 
 |  | 
 |         if ($open_paragraph == 0) | 
 |         { | 
 |           if ($param_docs == 1) | 
 |           { | 
 |             print OUTPUT $fmt[16]; | 
 |           } | 
 |           else | 
 |           { | 
 |             print OUTPUT $fmt[0]; | 
 |           } | 
 |           $open_paragraph = 1; | 
 |         } | 
 |         # Anything in all uppercase on its own gets emphasised | 
 |         s/(^|[ \.\,\(\[\|\=])([A-Z]+?[A-Z0-9_]+)($|[ \.\,\*\?\|\)\=\'])/$1$fmt[6]$2$fmt[7]$3/g; | 
 |  | 
 |         print OUTPUT $_; | 
 |       } | 
 |     } | 
 |   } | 
 |   if ($open_raw == 1) | 
 |   { | 
 |     print OUTPUT $fmt[13]; | 
 |   } | 
 |   if ($open_paragraph == 1) | 
 |   { | 
 |     print OUTPUT $fmt[1]; | 
 |   } | 
 | } | 
 |  | 
 | # Create the master index file | 
 | sub output_master_index_files() | 
 | { | 
 |   if ($opt_output_format eq "") | 
 |   { | 
 |     return; # No master index for man pages | 
 |   } | 
 |  | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |     # Append the index entries to the output db of index entries | 
 |     my $output_file = $opt_output_directory."/index.db"; | 
 |     open(INDEXDB,">>$output_file") || die "Couldn't create $output_file\n"; | 
 |     for (@index_entries_list) | 
 |     { | 
 |       $_ =~ s/A\,/\,/; | 
 |       print INDEXDB $_."\n"; | 
 |     } | 
 |     close(INDEXDB); | 
 |   } | 
 |  | 
 |   # Use the comment output functions for consistency | 
 |   my $comment = | 
 |   { | 
 |     FILE => "", | 
 |     COMMENT_NAME => "The Wine Api Guide", | 
 |     ALT_NAME => "The Wine Api Guide", | 
 |     DLL_NAME => "", | 
 |     ORDINAL => "", | 
 |     RETURNS => "", | 
 |     PROTOTYPE => [], | 
 |     TEXT => [], | 
 |   }; | 
 |  | 
 |   if ($opt_output_format eq "s") | 
 |   { | 
 |     $comment->{COMMENT_NAME} = "Introduction"; | 
 |     $comment->{ALT_NAME} = "Introduction", | 
 |   } | 
 |   elsif ($opt_output_format eq "h") | 
 |   { | 
 |     @{$comment->{TEXT}} = ( | 
 |       "NAME", | 
 |        $comment->{COMMENT_NAME}, | 
 |        "INTRODUCTION", | 
 |     ); | 
 |   } | 
 |  | 
 |   # Create the initial comment text | 
 |   push (@{$comment->{TEXT}}, | 
 |     "This document describes the Api calls made available", | 
 |     "by Wine. They are grouped by the dll that exports them.", | 
 |     "", | 
 |     "Please do not edit this document, since it is generated automatically", | 
 |     "from the Wine source code tree. Details on updating this documentation", | 
 |     "are given in the \"Wine Developers Guide\".", | 
 |     "CONTRIBUTORS", | 
 |     "Api documentation is generally written by the person who ", | 
 |     "implements a given Api call. Authors of each dll are listed in the overview ", | 
 |     "section for that dll. Additional contributors who have updated source files ", | 
 |     "but have not entered their names in a copyright statement are noted by an ", | 
 |     "entry in the file \"Changelog\" from the Wine source code distribution.", | 
 |       "" | 
 |   ); | 
 |  | 
 |   # Read in all dlls from the database of dll names | 
 |   my $input_file = $opt_output_directory."/dlls.db"; | 
 |   my @dlls = `cat $input_file|sort|uniq`; | 
 |  | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |     # HTML gets a list of all the dlls and an index. For docbook the index creates this for us | 
 |     push (@{$comment->{TEXT}}, | 
 |       "INDEX", | 
 |       "For an alphabetical listing of the functions available, please click the ", | 
 |       "first letter of the functions name below:","", | 
 |       "[ _(), A(), B(), C(), D(), E(), F(), G(), H(), ". | 
 |       "I(), J(), K(), L(), M(), N(), O(), P(), Q(), ". | 
 |       "R(), S(), T(), U(), V(), W(), X(), Y(), Z() ]", "", | 
 |       "DLLS", | 
 |       "Each dll provided by Wine is documented individually. The following dlls are provided :", | 
 |       "" | 
 |     ); | 
 |     # Add the dlls to the comment | 
 |     for (@dlls) | 
 |     { | 
 |       $_ =~ s/(\..*)?\n/\(\)/; | 
 |       push (@{$comment->{TEXT}}, $_, ""); | 
 |     } | 
 |     output_open_api_file("index"); | 
 |   } | 
 |   elsif ($opt_output_format eq "s") | 
 |   { | 
 |     # Just write this as the initial blurb, with a chapter heading | 
 |     output_open_api_file("blurb"); | 
 |     print OUTPUT "<chapter id =\"blurb\">\n<title>Introduction to The Wine Api Guide</title>\n" | 
 |   } | 
 |  | 
 |   # Write out the document | 
 |   output_api_header($comment); | 
 |   output_api_comment($comment); | 
 |   output_api_footer($comment); | 
 |   if ($opt_output_format eq "s") | 
 |   { | 
 |     print OUTPUT "</chapter>\n" # finish the chapter | 
 |   } | 
 |   output_close_api_file(); | 
 |  | 
 |   if ($opt_output_format eq "s") | 
 |   { | 
 |     output_sgml_master_file(\@dlls); | 
 |     return; | 
 |   } | 
 |   if ($opt_output_format eq "h") | 
 |   { | 
 |     output_html_index_files(); | 
 |     output_html_stylesheet(); | 
 |     return; | 
 |   } | 
 | } | 
 |  | 
 | # Write the master wine-api.sgml, linking it to each dll. | 
 | sub output_sgml_master_file($) | 
 | { | 
 |   my $dlls = shift; | 
 |  | 
 |   output_open_api_file("wine-api"); | 
 |   print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n"; | 
 |   print OUTPUT "<!doctype book PUBLIC \"-//OASIS//DTD DocBook V3.1//EN\" [\n\n"; | 
 |   print OUTPUT "<!entity blurb SYSTEM \"blurb.sgml\">\n"; | 
 |  | 
 |   # List the entities | 
 |   for (@$dlls) | 
 |   { | 
 |     $_ =~ s/(\..*)?\n//; | 
 |     print OUTPUT "<!entity ",$_," SYSTEM \"",$_,".sgml\">\n" | 
 |   } | 
 |  | 
 |   print OUTPUT "]>\n\n<book id=\"index\">\n<bookinfo><title>The Wine Api Guide</title></bookinfo>\n\n"; | 
 |   print OUTPUT "  &blurb;\n"; | 
 |  | 
 |   for (@$dlls) | 
 |   { | 
 |     print OUTPUT "  &",$_,";\n" | 
 |   } | 
 |   print OUTPUT "\n\n</book>\n"; | 
 |  | 
 |   output_close_api_file(); | 
 | } | 
 |  | 
 | # Produce the sgml for the dll chapter from the generated files | 
 | sub output_sgml_dll_file($) | 
 | { | 
 |   my $spec_details = shift; | 
 |  | 
 |   # Make a list of all the documentation files to include | 
 |   my $exports = $spec_details->{EXPORTS}; | 
 |   my @source_files = (); | 
 |   for (@$exports) | 
 |   { | 
 |     # @$_ => ordinal, call convention, exported name, implementation name, documented; | 
 |     if (@$_[1] ne "forward" && @$_[1] ne "extern" && @$_[1] ne "stub" && @$_[1] ne "equate" && | 
 |         @$_[1] ne "variable" && @$_[1] ne "fake" && @$_[4] & 1) | 
 |     { | 
 |       # A documented function | 
 |       push (@source_files,@$_[3]); | 
 |     } | 
 |   } | 
 |  | 
 |   push (@source_files,@{$spec_details->{EXTRA_COMMENTS}}); | 
 |  | 
 |   @source_files = sort @source_files; | 
 |  | 
 |   # create a new chapter for this dll | 
 |   my $tmp_name = $opt_output_directory."/".$spec_details->{DLL_NAME}.".tmp"; | 
 |   open(OUTPUT,">$tmp_name") || die "Couldn't create $tmp_name\n"; | 
 |   print OUTPUT "<chapter>\n<title>$spec_details->{DLL_NAME}</title>\n"; | 
 |   output_close_api_file(); | 
 |  | 
 |   # Add the sorted documentation, cleaning up as we go | 
 |   `cat $opt_output_directory/$spec_details->{DLL_NAME}.sgml >>$tmp_name`; | 
 |   for (@source_files) | 
 |   { | 
 |     `cat $opt_output_directory/$_.sgml >>$tmp_name`; | 
 |     `rm -f $opt_output_directory/$_.sgml`; | 
 |   } | 
 |  | 
 |   # close the chapter, and overwite the dll source | 
 |   open(OUTPUT,">>$tmp_name") || die "Couldn't create $tmp_name\n"; | 
 |   print OUTPUT "</chapter>\n"; | 
 |   close OUTPUT; | 
 |   `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.sgml`; | 
 | } | 
 |  | 
 | # Write the html index files containing the function names | 
 | sub output_html_index_files() | 
 | { | 
 |   if ($opt_output_format ne "h") | 
 |   { | 
 |     return; | 
 |   } | 
 |  | 
 |   my @letters = ('_', 'A' .. 'Z'); | 
 |  | 
 |   # Read in all functions | 
 |   my $input_file = $opt_output_directory."/index.db"; | 
 |   my @funcs = `cat $input_file|sort|uniq`; | 
 |  | 
 |   for (@letters) | 
 |   { | 
 |     my $letter = $_; | 
 |     my $comment = | 
 |     { | 
 |       FILE => "", | 
 |       COMMENT_NAME => "", | 
 |       ALT_NAME => "", | 
 |       DLL_NAME => "", | 
 |       ORDINAL => "", | 
 |       RETURNS => "", | 
 |       PROTOTYPE => [], | 
 |       TEXT => [], | 
 |     }; | 
 |  | 
 |     $comment->{COMMENT_NAME} = $letter." Functions"; | 
 |     $comment->{ALT_NAME} = $letter." Functions"; | 
 |  | 
 |     push (@{$comment->{TEXT}}, | 
 |       "NAME", | 
 |       $comment->{COMMENT_NAME}, | 
 |       "FUNCTIONS" | 
 |     ); | 
 |  | 
 |     # Add the functions to the comment | 
 |     for (@funcs) | 
 |     { | 
 |       my $first_char = substr ($_, 0, 1); | 
 |       $first_char = uc $first_char; | 
 |  | 
 |       if ($first_char eq $letter) | 
 |       { | 
 |         my $name = $_; | 
 |         my $file; | 
 |         $name =~ s/(^.*?)\,(.*?)\n/$1/; | 
 |         $file = $2; | 
 |         push (@{$comment->{TEXT}}, "{{".$name."}}{{".$file."}}",""); | 
 |       } | 
 |     } | 
 |  | 
 |     # Write out the document | 
 |     output_open_api_file($letter); | 
 |     output_api_header($comment); | 
 |     output_api_comment($comment); | 
 |     output_api_footer($comment); | 
 |     output_close_api_file(); | 
 |   } | 
 | } | 
 |  | 
 | # Output the stylesheet for HTML output | 
 | sub output_html_stylesheet() | 
 | { | 
 |   if ($opt_output_format ne "h") | 
 |   { | 
 |     return; | 
 |   } | 
 |  | 
 |   my $css; | 
 |   ($css = <<HERE_TARGET) =~ s/^\s+//gm; | 
 | /* | 
 |  * Default styles for Wine HTML Documentation. | 
 |  * | 
 |  * This style sheet should be altered to suit your needs/taste. | 
 |  */ | 
 | BODY { /* Page body */ | 
 | background-color: white; | 
 | color: black; | 
 | font-family: Tahoma,sans-serif; | 
 | font-style: normal; | 
 | font-size: 10pt; | 
 | } | 
 | a:link { color: #4444ff; } /* Links */ | 
 | a:visited { color: #333377 } | 
 | a:active { color: #0000dd } | 
 | H2.section { /* Section Headers */ | 
 | font-family: sans-serif; | 
 | color: #777777; | 
 | background-color: #F0F0FE; | 
 | margin-left: 0.2in; | 
 | margin-right: 1.0in; | 
 | } | 
 | b.func_name { /* Function Name */ | 
 | font-size: 10pt; | 
 | font-style: bold; | 
 | } | 
 | i.dll_ord { /* Italicised DLL+ordinal */ | 
 | color: #888888; | 
 | font-family: sans-serif; | 
 | font-size: 8pt; | 
 | } | 
 | p { /* Paragraphs */ | 
 | margin-left: 0.5in; | 
 | margin-right: 0.5in; | 
 | } | 
 | table { /* tables */ | 
 | margin-left: 0.5in; | 
 | margin-right: 0.5in; | 
 | } | 
 | pre.proto /* API Function prototype */ | 
 | { | 
 | border-style: solid; | 
 | border-width: 1px; | 
 | border-color: #777777; | 
 | background-color: #F0F0BB; | 
 | color: black; | 
 | font-size: 10pt; | 
 | vertical-align: top; | 
 | margin-left: 0.5in; | 
 | margin-right: 1.0in; | 
 | } | 
 | pre.raw { /* Raw text output */ | 
 | margin-left: 0.6in; | 
 | margin-right: 1.1in; | 
 | background-color: #8080DC; | 
 | } | 
 | tt.param { /* Parameter name */ | 
 | font-style: italic; | 
 | color: blue; | 
 | } | 
 | tt.const { /* Constant */ | 
 | color: red; | 
 | } | 
 | i.in_out { /* In/Out */ | 
 | font-size: 8pt; | 
 | color: grey; | 
 | } | 
 | tt.coderef { /* Code in description text */ | 
 | color: darkgreen; | 
 | } | 
 | b.emp /* Emphasis */ { | 
 | font-style: bold; | 
 | color: darkblue; | 
 | } | 
 | i.footer { /* Footer */ | 
 | font-family: sans-serif; | 
 | font-size: 6pt; | 
 | color: darkgrey; | 
 | } | 
 | HERE_TARGET | 
 |  | 
 |   my $output_file = "$opt_output_directory/apidoc.css"; | 
 |   open(CSS,">$output_file") || die "Couldn't create the file $output_file\n"; | 
 |   print CSS $css; | 
 |   close(CSS); | 
 | } | 
 |  | 
 |  | 
 | sub usage() | 
 | { | 
 |   print "\nCreate API Documentation from Wine source code.\n\n", | 
 |         "Usage: c2man.pl [options] {-w <spec>} {-I <include>} {<source>}\n", | 
 |         "Where: <spec> is a .spec file giving a DLL's exports.\n", | 
 |         "       <include> is an include directory used by the DLL.\n", | 
 |         "       <source> is a source file of the DLL.\n", | 
 |         "       The above can be given multiple times on the command line, as appropriate.\n", | 
 |         "Options:\n", | 
 |         " -Th      : Output HTML instead of a man page\n", | 
 |         " -Ts      : Output SGML (Docbook source) instead of a man page\n", | 
 |         " -C <dir> : Source directory, to find source files if they are not found in the\n", | 
 |         "            current directory. Default is \"",$opt_source_dir,"\"\n", | 
 |         " -R <dir> : Root of build directory, default is \"",$opt_wine_root_dir,"\"\n", | 
 |         " -o <dir> : Create output in <dir>, default is \"",$opt_output_directory,"\"\n", | 
 |         " -s <sect>: Set manual section to <sect>, default is ",$opt_manual_section,"\n", | 
 |         " -e       : Output \"FIXME\" documentation from empty comments.\n", | 
 |         " -v       : Verbosity. Can be given more than once for more detail.\n"; | 
 | } | 
 |  | 
 |  | 
 | # | 
 | # Main | 
 | # | 
 |  | 
 | # Print usage if we're called with no args | 
 | if( @ARGV == 0) | 
 | { | 
 |   usage(); | 
 | } | 
 |  | 
 | # Process command line options | 
 | while(defined($_ = shift @ARGV)) | 
 | { | 
 |   if( s/^-// ) | 
 |   { | 
 |     # An option. | 
 |     for ($_) | 
 |     { | 
 |       /^o$/  && do { $opt_output_directory = shift @ARGV; last; }; | 
 |       s/^S// && do { $opt_manual_section   = $_;          last; }; | 
 |       /^Th$/ && do { $opt_output_format  = "h";           last; }; | 
 |       /^Ts$/ && do { $opt_output_format  = "s";           last; }; | 
 |       /^v$/  && do { $opt_verbose++;                      last; }; | 
 |       /^e$/  && do { $opt_output_empty = 1;               last; }; | 
 |       /^L$/  && do { last; }; | 
 |       /^w$/  && do { @opt_spec_file_list = (@opt_spec_file_list, shift @ARGV); last; }; | 
 |       s/^I// && do { if ($_ ne ".") { | 
 |                        my $include = $_."/*.h"; | 
 |                        $include =~ s/\/\//\//g; | 
 |                        my $have_headers = `ls $include >/dev/null 2>&1`; | 
 |                        if ($? >> 8 == 0) { @opt_header_file_list = (@opt_header_file_list, $include); } | 
 |                      } | 
 |                      last; | 
 |                    }; | 
 |       s/^C// && do { | 
 |                      if ($_ ne "") { $opt_source_dir = $_; } | 
 |                      last; | 
 |                    }; | 
 |       s/^R// && do { if ($_ =~ /^\//) { $opt_wine_root_dir = $_; } | 
 |                      else { $opt_wine_root_dir = `cd $pwd/$_ && pwd`; } | 
 |                      $opt_wine_root_dir =~ s/\n//; | 
 |                      $opt_wine_root_dir =~ s/\/\//\//g; | 
 |                      if (! $opt_wine_root_dir =~ /\/$/ ) { $opt_wine_root_dir = $opt_wine_root_dir."/"; }; | 
 |                      last; | 
 |              }; | 
 |       die "Unrecognised option $_\n"; | 
 |     } | 
 |   } | 
 |   else | 
 |   { | 
 |     # A source file. | 
 |     push (@opt_source_file_list, $_); | 
 |   } | 
 | } | 
 |  | 
 | # Remove duplicate include directories | 
 | my %htmp; | 
 | @opt_header_file_list = grep(!$htmp{$_}++, @opt_header_file_list); | 
 |  | 
 | if ($opt_verbose > 3) | 
 | { | 
 |   print "Output dir:'".$opt_output_directory."'\n"; | 
 |   print "Section   :'".$opt_manual_section."'\n"; | 
 |   print "Format    :'".$opt_output_format."'\n"; | 
 |   print "Source dir:'".$opt_source_dir."'\n"; | 
 |   print "Root      :'".$opt_wine_root_dir."'\n"; | 
 |   print "Spec files:'@opt_spec_file_list'\n"; | 
 |   print "Includes  :'@opt_header_file_list'\n"; | 
 |   print "Sources   :'@opt_source_file_list'\n"; | 
 | } | 
 |  | 
 | if (@opt_spec_file_list == 0) | 
 | { | 
 |   exit 0; # Don't bother processing non-dll files | 
 | } | 
 |  | 
 | # Make sure the output directory exists | 
 | unless (-d $opt_output_directory) | 
 | { | 
 |     mkdir $opt_output_directory or die "Cannot create directory $opt_output_directory\n"; | 
 | } | 
 |  | 
 | # Read in each .spec files exports and other details | 
 | while(my $spec_file = shift @opt_spec_file_list) | 
 | { | 
 |   process_spec_file($spec_file); | 
 | } | 
 |  | 
 | if ($opt_verbose > 3) | 
 | { | 
 |     foreach my $spec_file ( keys %spec_files ) | 
 |     { | 
 |         print "in '$spec_file':\n"; | 
 |         my $spec_details = $spec_files{$spec_file}[0]; | 
 |         my $exports = $spec_details->{EXPORTS}; | 
 |         for (@$exports) | 
 |         { | 
 |            print @$_[0].",".@$_[1].",".@$_[2].",".@$_[3]."\n"; | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | # Extract and output the comments from each source file | 
 | while(defined($_ = shift @opt_source_file_list)) | 
 | { | 
 |   process_source_file($_); | 
 | } | 
 |  | 
 | # Write the index files for each spec | 
 | process_index_files(); | 
 |  | 
 | # Write the master index file | 
 | output_master_index_files(); | 
 |  | 
 | exit 0; |