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","","");
+ }
+ 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/\>/\>/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);
}