diff --git a/tools/c2man.pl b/tools/c2man.pl index 9f5315cdb11..6d23bed7669 100755 --- a/tools/c2man.pl +++ b/tools/c2man.pl @@ -1,14 +1,9 @@ -#!/usr/bin/perl - -##################################################################################### +#! /usr/bin/perl -w # -# c2man.pl v0.1 Copyright (C) 2000 Mike McCormack +# Generate API documentation. See documentation/documentation.sgml for details. # -# Generates Documents from C source code. -# -# Input is source code with specially formatted comments, output -# is man pages. The functionality is meant to be similar to c2man. -# The following is an example provided in the Wine documentation. +# 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 @@ -24,314 +19,1875 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -# TODO: -# Write code to generate HTML output with the -Th option. -# Need somebody who knows about TROFF to help touch up the man page generation. -# Parse spec files passed with -w option and generate pages for the functions -# in the spec files only. -# Modify Makefiles to pass multiple C files to speed up man page generation. -# Use nm on the shared libraries specified in the spec files to determine which -# source files should be parsed, and only parse them.(requires wine to be compiled) -# -##################################################################################### -# Input from C source file: -# -# /****************************************************************** -# * CopyMetaFile32A (GDI32.23) -# * -# * Copies the metafile corresponding to hSrcMetaFile to either -# * a disk file, if a filename is given, or to a new memory based -# * metafile, if lpFileName is NULL. -# * -# * RETURNS -# * -# * Handle to metafile copy on success, NULL on failure. -# * -# * BUGS -# * -# * Copying to disk returns NULL even if successful. -# */ -# HMETAFILE32 WINAPI CopyMetaFile32A( -# HMETAFILE32 hSrcMetaFile, /* handle of metafile to copy */ -# LPCSTR lpFilename /* filename if copying to a file */ -# ) { ... } -# -##################################################################################### -# Output after processing with nroff -man -# -# CopyMetaFileA(3w) CopyMetaFileA(3w) -# -# -# NAME -# CopyMetaFileA - CopyMetaFile32A (GDI32.23) -# -# SYNOPSIS -# HMETAFILE32 CopyMetaFileA -# ( -# HMETAFILE32 hSrcMetaFile, -# LPCSTR lpFilename -# ); -# -# PARAMETERS -# HMETAFILE32 hSrcMetaFile -# Handle of metafile to copy. -# -# LPCSTR lpFilename -# Filename if copying to a file. -# -# DESCRIPTION -# Copies the metafile corresponding to hSrcMetaFile to -# either a disk file, if a filename is given, or to a new -# memory based metafile, if lpFileName is NULL. -# -# RETURNS -# Handle to metafile copy on success, NULL on failure. -# -# BUGS -# Copying to disk returns NULL even if successful. -# -# SEE ALSO -# GetMetaFileA(3w), GetMetaFileW(3w), CopyMetaFileW(3w), -# PlayMetaFile(3w), SetMetaFileBitsEx(3w), GetMetaFileBit- -# sEx(3w) -# -##################################################################################### +# TODO +# SGML gurus - feel free to smarten up the SGML. +# Add any other relevant information for the dll - imports etc +# Read .spec flags such as -i386,-noname and add to docs appropriately +# Generate an alpabetical listing of every API call, with links +# Should we have a special output mode for WineHQ? -sub output_manpage +use strict; + +# Options +my $opt_output_directory = "man3w"; # All default options are for nroff (man pages) +my $opt_manual_section = "3w"; +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; + +# 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 usage { - my ($buffer,$apiref) = @_; - my $parameters; - my $desc; - - # join all the lines of the description together and highlight the headings - for (@$buffer) { - s/\n//g; - s/^\s*//g; - s/\s*$//g; - if ( /^([A-Z]+)$/ ) { - $desc = $desc.".SH $1\n.PP\n"; - } - elsif ( /^$/ ) { - $desc = "$desc\n"; - } - else { - $desc = "$desc $_"; - } - } - - #seperate out all the parameters - - $plist = join ( ' ', @$apiref ); - - $name_type = $plist; - $name_type =~ s/\n//g; # remove newlines - $name_type =~ s/\(.*$//; - $name_type =~ s/WINAPI//; - - #check that this is a function that we want - if ( $funcdb{$apiname."ORD"} eq "" ) { return; } - print "Generating $apiname.$section\n"; - - $plist =~ s/\n//g; # remove newlines - $plist =~ s/^.*\(\s*//; # remove leading bracket and before - $plist =~ s/\s*\).*$//; # remove trailing bracket and leftovers - $plist =~ s/\s*,?\s*\/\*([^*]*)\*\// - $1,/g; # move the comma to the back - @params = split ( /,/ , $plist); # split parameters - for(@params) { - s/^\s*//; - s/\s*$//; - } - - # figure the month and the year - @datetime = localtime; - @months = ( "January", "Febuary", "March", "April", "May", "June", - "July", "August", "September", "October", "November", "December" ); - $date = "$months[$datetime[4]] $datetime[5]"; - - # create the manual page - $manfile = "$mandir/$apiname.$section"; - open(MAN,">$manfile") || die "Couldn't create the man page file $manfile\n"; - print MAN ".\\\" DO NOT MODIFY THIS FILE! It was generated by gendoc 1.0.\n"; - print MAN ".TH $apiname \"$section\" \"$date\" \"Wine API\" \"The Wine Project\"\n"; - print MAN ".SH NAME\n"; - print MAN "$apiname ($apientry)\n"; - print MAN ".SH SYNOPSIS\n"; - print MAN ".PP\n"; - print MAN "$name_type\n"; - print MAN " (\n"; - for($i=0; $i<@params; $i++) { - $x = ($i == (@params-1)) ? "" : ","; - $c = $params[$i]; - $c =~ s/-.*//; - print MAN " $c$x\n"; - } - print MAN " );\n"; - print MAN ".SH PARAMETERS\n"; - print MAN ".PP\n"; - for($i=0; $i<@params; $i++) { - print MAN " $params[$i]\n"; - } - print MAN ".SH DESCRIPTION\n"; - print MAN ".PP\n"; - print MAN $desc; - close(MAN); + print "\nCreate API Documentation from Wine source code.\n\n", + "Usage: c2man.pl [options] {-w } {-I } {}\n", + "Where: is a .spec file giving a DLL's exports.\n", + " is an include directory used by the DLL.\n", + " 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", + " -o : Create output in , default is \"",$opt_output_directory,"\"\n", + " -s : Set manual section to , 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"; } -# -# extract the comments from source file -# -sub parse_source +# Print usage if we're called with no args +if( @ARGV == 0) { - my $file = $_[0]; - print "Processing $file\n"; + usage(); +} - open(SOURCE,"<$file") || die "Couldn't open the source file $file\n"; - $state = 0; - while() { - if($state == 0 ) { - if ( /^\/\**$/ ) { - # find the start of the comment /************** - $state = 3; - @buffer = (); - } - } - elsif ($state == 3) { - #extract the wine API name and DLLNAME.XXX string - if ( / *([A-Za-z_0-9]+) *\(([A-Za-z0-9_]+\.(([0-9]+)|@))\) *$/ ) { - $apiname = $1; - $apientry = $2; - $state = 1; - } - else { - $state = 0; - } - } - elsif ($state == 1) { - #save the comment text into buffer, removing leading astericks - if ( /^ \*\// ) { - $state = 2; - } - else { - # find the end of the comment - if ( s/^ \*// ) { - @buffer = ( @buffer , $_ ); - } - else { - $state = 0; - } - } - } - elsif ($state == 2) { - # check that the comment is followed by the declaration of - # a WINAPI function. - if ( /WINAPI/ ) { - @apidef = ( $_ ); - #check if the function's parameters end on this line - if( /\)/ ) { - output_manpage(\@buffer, \@apidef); - $state = 0; - } - else { - $state = 4; - } - } - else { - $state = 0; - } - } - elsif ($state == 4) { - @apidef = ( @apidef , $_ ); - #find the end of the parameters list - if( /\)/ ) { - output_manpage(\@buffer, \@apidef); - $state = 0; - } +# 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 { 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/^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"; } } - close(SOURCE); + else + { + # A source file. + push (@opt_source_file_list, $_); + } } -# generate a database of functions to have man pages created from the source -# creates funclist and funcdb -sub parse_spec -{ - my $spec = $_[0]; - my $name,$type,$ord,$func; +# Remove duplicate include directories +my %htmp; +@opt_header_file_list = grep(!$htmp{$_}++, @opt_header_file_list); - open(SPEC,"<$spec") || die "Couldn't open the spec file $spec\n"; - while() +if ($opt_verbose > 3) +{ + print "Output dir:'".$opt_output_directory."'\n"; + print "Section :'".$opt_manual_section."'\n"; + print "Format :'".$opt_output_format."'\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 +} + +# 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 ) { - if( /^#/ ) { next; } - if( /^name/ ) { next; } - if( /^type/ ) { next; } - if( /^init/ ) { next; } - if( /^rsrc/ ) { next; } - if( /^import/ ) { next; } - if( /^\s*$/ ) { next; } - if( /^\s*(([0-9]+)|@)/ ) { - s/\(.*\)//; #remove all the args - ($ord,$type,$name,$func) = split( /\s+/ ); - if(( $type eq "stub" ) || ($type eq "forward")) {next;} - if( $func eq "" ) { next; } - @funclist = ( @funclist , $func ); - $funcdb{$func."ORD"} = $ord; - $funcdb{$func."TYPE"} = $type; - $funcdb{$func."NAME"} = $name; - $funcdb{$func."SPEC"} = $spec; + 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"; } } - close(SPEC); } -###################################################################### - -#main starts here - -$mandir = "man3w"; -$section = "3"; - -#process args -while(@ARGV) { - if($ARGV[0] eq "-o") { # extract output directory - shift @ARGV; - $mandir = $ARGV[0]; - shift @ARGV; - next; - } - if($ARGV[0] =~ s/^-S// ) { # extract man section - $section = $ARGV[0]; - shift @ARGV; - next; - } - if($ARGV[0] =~ s/^-w// ) { # extract man section - shift @ARGV; - @specfiles = ( @specfiles , $ARGV[0] ); - shift @ARGV; - next; - } - if($ARGV[0] =~ s/^-T// ) { - die "FIXME: Only NROFF supported\n"; - } - if($ARGV[0] =~ s/^-[LDiI]// ) { #compatible with C2MAN flags - shift @ARGV; - next; - } - last; # stop after there's no more flags +# Extract and output the comments from each source file +while(defined($_ = shift @opt_source_file_list)) +{ + process_source_file($_); } -#print "manual section: $section\n"; -#print "man directory : $mandir\n"; -#print "input files : @ARGV\n"; -#print "spec files : @specfiles\n"; +# Write the index files for each spec +process_index_files(); -while(@specfiles) { - parse_spec($specfiles[0]); - shift @specfiles; +# Write the master index file +output_master_index_files(); + +exit 0; + + +# 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") || return; + + while() + { + 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/ \-[a-z0-9]+//g; # Strip modifiers + s/\s+/ /g; # Strip multiple tabs & spaces to a single space (again) + s/\n$//; # Strip newline + + 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|pascal16)$/ + && do { $spec_details->{NUM_FUNCS}++; last; }; + /^(variable|equate)$/ + && do { $spec_details->{NUM_VARS}++; last; }; + /^(forward|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; + } + # 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 $documented = 0; + my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented); + 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]; } -#print "Functions: @funclist\n"; +# 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 -while(@ARGV) { - parse_source($ARGV[0]); - shift @ARGV; + if ($opt_verbose > 0) + { + print "Processing ".$source_file."\n"; + } + open(SOURCE_FILE,"<$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() + { + 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 ( /(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]+)|@)(\)|\])$/ ) + { + # Found a correctly formed "ApiName (DLLNAME.Ordinal)" line. + $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(@_); + + 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/^PARAMETERS$/PARAMS/; # Name of parameter section should be 'PARAMS' + # Trademarks + s/( |\.)(MS|Microsoft|microsoft)( |\.)/$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|unix)( |\.)/$1Unix\(tm\)$3/g; + # Abbreviations + s/( chars)/characters/g; + s/( info )/ information /g; + s/( app )/ application /g; + s/( apps )/ applications /g; + } + } +} + +# Standardise our comment and output it if it is suitable. +sub process_comment +{ + my $comment = shift(@_); + + # Don't process this comment if the function isnt 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 "@") + { + # 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}}; + + if (!defined($export_index)) + { + # Perhaps the comment uses the implementation name? + my $implementation_names = $spec_details->{IMPLEMENTATION_NAMES}; + $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] |= 1; + $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] |= 1; + + # 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]+$/ ) + { + if ( /^See ([A-Za-z0-9_]+)\.$/ || /^Unicode version of ([A-Za-z0-9_]+)\.$/) + { + # Dont reject comments that refer to their A/W couterpart + $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] &= ~1; + 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; + $prototype =~ s/^(.*?) (WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16) (.*?)\( *(.*)/$4/; + $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; # Seperate 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 + # FIXME: This sometimes gives the error "sh: .h: Permission denied" - why? + my $h_file = ""; + 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.*//; + if ($tmp ne "") + { + $h_file = `basename $tmp`; + } + } + else + { + $tmp = "grep -s -l $comment->{ALT_NAME} @opt_header_file_list"." 2>/dev/null"; + $tmp = `$tmp`; + $exit_value = $? >> 8; + if ($exit_value == 0) + { + $tmp =~ s/\n.*//; + 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"; + } + 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); + + 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, documented; + 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 + } + elsif (@$_[1] eq "equate" || @$_[1] eq "variable") + { + $line = @$_[2]." (data)"; + } + else + { + # A function + if (@$_[4] & 1) + { + # Documented + $line = @$_[2]." (implemented as ".@$_[3]."())"; + if (@$_[2] ne @$_[3]) + { + $line = @$_[2]." (implemented as ".@$_[3]."())"; + } + else + { + $line = @$_[2]."()"; + } + } + 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}}, $_."()", ""); + } + } + } + # 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 "\n"; + print OUTPUT "\n"; + print OUTPUT "\n\n"; + print OUTPUT "\n"; + print OUTPUT "\n"; + print OUTPUT "{COMMENT_NAME}\">\n"; + print OUTPUT "Wine API: $comment->{COMMENT_NAME}\n\n\n"; + } + elsif ($opt_output_format eq "s") + { + print OUTPUT "\n", + "\n", + "$comment->{COMMENT_NAME}\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 "

Copyright © ".$year." The Wine Project.". + "Visit WineHQ for license details.". + "Generated $date

\n\n\n"; + } + elsif ($opt_output_format eq "s") + { + print OUTPUT "
\n"; + return; + } + else + { + } +} + +sub output_api_section_start +{ + my $comment = shift(@_); + my $section_name = shift(@_); + + if ($opt_output_format eq "h") + { + print OUTPUT "\n

",$section_name,"

\n"; + } + elsif ($opt_output_format eq "s") + { + print OUTPUT "",$section_name,"\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(@_); + + 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 "

",$comment->{COMMENT_NAME}, + "  ", + ,$dll_ordinal,"

\n"; + } + elsif ($opt_output_format eq "s") + { + print OUTPUT "\n ",$comment->{COMMENT_NAME}," ", + $dll_ordinal,"\n\n"; + } + else + { + print OUTPUT "\\fB",$comment->{COMMENT_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 "

\n ", $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
+    @fmt = ("", "\n", "", "");
+  }
+  elsif ($opt_output_format eq "s")
+  {
+    print OUTPUT "\n ",$comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
+    @fmt = ("", "\n", "", "");
+  }
+  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\n

\n"; + } + elsif ($opt_output_format eq "s") + { + print OUTPUT " )\n\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 = ("

", "

\n", "", "", "", "", + "", "", "", "", + "", "", "
\n", "
\n", + "\n", + "
\n","","\n","",""); + } + elsif ($opt_output_format eq "s") + { + @fmt = ("\n","\n\n","","","","", + "","","","","","", + "\n","\n", + "\n\n\n", + "\n\n\n","","\n", + "",""); + } + 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/\([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 + { + # 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 + s/([A-Za-z_]+[A-Za-z_0-9]+)(\(\))/$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)/ $1<\/a> $2/g; + # Convert any web addresses to real links + s/(http\:\/\/)(.+?)($| )/$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$/ ) + { + 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; + } + + 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 + } + + # 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. For docbook the index creates this for us + push (@{$comment->{TEXT}}, + "DLLS", + "The following dlls are provided by Wine:", + "" + ); + # 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 "\nIntroduction to The Wine Api Guide\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 "\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_stylesheet(); + # FIXME: Create an alphabetical index + 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 "\n"; + print OUTPUT "\n"; + + # List the entities + for (@$dlls) + { + $_ =~ s/(\..*)?\n//; + print OUTPUT "\n" + } + + print OUTPUT "]>\n\n\nThe Wine Api Guide\n\n"; + print OUTPUT " &blurb;\n"; + + for (@$dlls) + { + print OUTPUT " &",$_,";\n" + } + print OUTPUT "\n\n\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 "\n$spec_details->{DLL_NAME}\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 "\n"; + close OUTPUT; + `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.sgml`; +} + +# Output the stylesheet for HTML output +sub output_html_stylesheet +{ + if ($opt_output_format ne "h") + { + return; + } + + my $css; + ($css = <$output_file") || die "Couldn't create the file $output_file\n"; + print CSS $css; + close(CSS); }