Fwd: API Documentation 1

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This one didn't seem to make it through last time:

----------  Forwarded Message  ----------

Hi,

Start of a few patches improving the generated API docs:
Adds docbook and (rather sexy IMO) html output, and makes the manpages nicer.
Also provides a mechanism for documenting exported com interfaces and other
things Winelib users should know (next patch is to
documentation/documentation.sgml, you can read the details there).

Apply, run ./configure, make htmlpages, point your browser at
documentation/html/index.html, then immediately start sending patches for
documentation glitches :-)

License: X11

Cheers,
Jon

ChangeLog:

  Jon Griffiths <jon_p_griffiths@yahoo.com>

  +tools/c2man.pl
   Improve the look/content of the man page output
   Generate for all exported functions (that have docs)
   Add dll summary page, HTML and SGML output

  +makefile.in
   Fix htmlpages target to use 'doc-html' in subdirs
   Add sgmlpages target for making a docbook 'Wine API Guide'
   Only make html/sgml api docs under dlls dir (_much_ faster)

  +make.rules.in
   Get rid of olsolete $MANSPECS
   Add $api_manext for api-doc manual section
   Update rules for doc generation

  +documentation/Makefile.in
   Get rid of references to 3w - use $api_manext
   Install the api man pages if they were generated
   Remove the HTML and SGML output on 'make clean'

-------------------------------------------------------

diff -ur wine/Make.rules.in wine-develop/Make.rules.in
--- wine/Make.rules.in	Wed Feb 19 22:07:58 2003
+++ wine-develop/Make.rules.in	Thu Mar  6 20:07:22 2003
@@ -55,11 +55,6 @@
 RM        = rm -f
 MV        = mv
 C2MAN     = @C2MAN@
-MANSPECS  = -w $(TOPSRCDIR)/dlls/gdi/gdi32.spec \
-	    -w $(TOPSRCDIR)/dlls/user/user32.spec \
-	    -w $(TOPSRCDIR)/dlls/comctl32/comctl32.spec \
-	    -w $(TOPSRCDIR)/dlls/commdlg/comdlg32.spec \
-	    -w $(TOPSRCDIR)/dlls/kernel/kernel32.spec
 LINT      = @LINT@
 LINTFLAGS = @LINTFLAGS@
 ALLLINTFLAGS = $(LINTFLAGS) $(DEFS) $(OPTIONS) $(DIVINCL)
@@ -98,6 +93,7 @@
 includedir      = @includedir@/wine
 dlldir          = @libdir@/wine
 prog_manext     = 1
+api_manext      = 3w
 conf_manext     = 5
 CLEAN_FILES     = *.o *.a *.so *.ln *.$(LIBEXT) \\\#*\\\# *~ *% .\\\#* *.bak *.orig *.rej \
                   *.flc *.spec.c *.spec.def *.glue.c *.dbg.c y.tab.c y.tab.h @LEX_OUTPUT_ROOT@.c core
@@ -172,15 +168,21 @@
 	cd `dirname $@` && $(MAKE) man
 
 man: $(C_SRCS) $(SUBDIRS:%=%/__man__)
-	if [ -n "$(C_SRCS)" ]; then $(MKINSTALLDIRS) $(TOPOBJDIR)/documentation/man3w; for i in $(C_SRCS); do $(C2MAN) -L -o $(TOPOBJDIR)/documentation/man3w -S3w $(DIVINCL) $(MANSPECS) $$i; done; fi
+	$(MKINSTALLDIRS) $(TOPOBJDIR)/documentation/man$(api_manext) && $(C2MAN) -o $(TOPOBJDIR)/documentation/man$(api_manext) -R$(TOPOBJDIR) -S$(api_manext) $(DIVINCL) $(MAINSPEC:%=-w %) $(SPEC_SRCS:%=-w %) $(C_SRCS) $(C_SRCS16)
 
 $(SUBDIRS:%=%/__doc_html__): dummy
 	cd `dirname $@` && $(MAKE) doc-html
 
 doc-html: $(C_SRCS) $(SUBDIRS:%=%/__doc_html__)
-	if [ -n "$(C_SRCS)" ]; then $(MKINSTALLDIRS) $(TOPOBJDIR)/documentation/html; for i in $(C_SRCS); do $(C2MAN) -L -o $(TOPOBJDIR)/documentation/html -Th -iwindows.h  $(DIVINCL) $(MANSPECS) $$i; done; fi
+	$(MKINSTALLDIRS) $(TOPOBJDIR)/documentation/html && $(C2MAN) -o $(TOPOBJDIR)/documentation/html -R$(TOPOBJDIR) $(DIVINCL) -Th $(MAINSPEC:%=-w %) $(SPEC_SRCS:%=-w %) $(C_SRCS) $(C_SRCS16)
 
-.PHONY: man doc-html $(SUBDIRS:%=%/__man__) $(SUBDIRS:%=%/__doc_html__)
+$(SUBDIRS:%=%/__doc_sgml__): dummy
+	cd `dirname $@` && $(MAKE) doc-sgml
+
+doc-sgml: $(C_SRCS) $(SUBDIRS:%=%/__doc_sgml__)
+	$(MKINSTALLDIRS) $(TOPOBJDIR)/documentation/api-guide && $(C2MAN) -o $(TOPOBJDIR)/documentation/api-guide -R$(TOPOBJDIR) $(DIVINCL) -Ts $(MAINSPEC:%=-w %) $(SPEC_SRCS:%=-w %) $(C_SRCS) $(C_SRCS16)
+
+.PHONY: man doc-html doc-sgml $(SUBDIRS:%=%/__man__) $(SUBDIRS:%=%/__doc_html__) $(SUBDIRS:%=%/__doc_smml__)
 
 # Rule for linting
 
diff -ur wine/Makefile.in wine-develop/Makefile.in
--- wine/Makefile.in	Wed Feb 19 22:07:58 2003
+++ wine-develop/Makefile.in	Thu Mar  6 21:07:48 2003
@@ -12,7 +12,8 @@
 # depend:          create the dependencies
 # etags:           create a TAGS file for Emacs.
 # manpages:        compile manpages for Wine API
-#
+# htmlpages:       compile html pages for Wine API
+# sgmlpages:       compile sqml source for the Wine API Guide
 
 # Directories
 
@@ -155,7 +156,11 @@
 
 htmlpages:
 	$(MKINSTALLDIRS) $(TOPOBJDIR)/documentation/html
-	for i in $(SUBDIRS); do (cd $$i && $(MAKE) html); done
+	cd dlls && $(MAKE) doc-html
+
+sgmlpages:
+	$(MKINSTALLDIRS) $(TOPOBJDIR)/documentation/api-guide
+	cd dlls && $(MAKE) doc-sgml
 
 clean::
 	$(RM) wine
diff -ur wine/documentation/Makefile.in wine-develop/documentation/Makefile.in
--- wine/documentation/Makefile.in	Tue Dec  3 23:35:32 2002
+++ wine-develop/documentation/Makefile.in	Thu Mar  6 20:58:58 2003
@@ -130,16 +130,17 @@
 	tar cf - $(ALLBOOKS:%=%/*.txt) | gzip -9 > $@ || $(RM) $@
 
 install:: $(MAN_TARGETS)
-	$(MKINSTALLDIRS) $(mandir)/man$(prog_manext) $(mandir)/man$(conf_manext)
+	$(MKINSTALLDIRS) $(mandir)/man$(prog_manext) $(mandir)/man$(conf_manext) $(mandir)/man$(api_manext)
 	$(INSTALL_DATA) wine.man $(mandir)/man$(prog_manext)/wine.$(prog_manext)
 	$(INSTALL_DATA) $(SRCDIR)/wine.conf.man $(mandir)/man$(conf_manext)/wine.conf.$(conf_manext)
-
+	if [ -e $(SRCDIR)/man$(api_manext)/* ]; then for i in $(SRCDIR)/man$(api_manext)/*; do $(INSTALL_DATA) $$i $(mandir)/man$(api_manext); done; fi
+	
 uninstall::
 	$(RM) $(mandir)/man$(prog_manext)/wine.$(prog_manext)
 	$(RM) $(mandir)/man$(conf_manext)/wine.conf.$(conf_manext)
 
 clean::
 	$(RM) *.aux *.dvi *.out *.pdf *.ps *.tex *.log wine-doc-*.tar.gz $(MAN_TARGETS)
-	$(RM) -r wine-doc $(ALLBOOKS) html man3w *.junk DBTOHTML_OUTPUT_DIR*
+	$(RM) -r wine-doc $(ALLBOOKS) html api-guide man$(api_manext) *.junk DBTOHTML_OUTPUT_DIR*
 
 ### Dependencies:
diff -ur wine/tools/c2man.pl wine-develop/tools/c2man.pl
--- wine/tools/c2man.pl	Sat Jun  1 02:55:52 2002
+++ wine-develop/tools/c2man.pl	Thu Mar  6 20:34:20 2003
@@ -1,14 +1,9 @@
-#!/usr/bin/perl
-
-#####################################################################################
-#
-# c2man.pl v0.1  Copyright (C) 2000 Mike McCormack
+#! /usr/bin/perl -w
 #
-# Generates Documents from C source code.
+# Generate API documentation. See documentation/documentation.sgml for details.
 #
-# 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?
+
+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 output_manpage
+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 <spec>} {-I <include>} {<source>}\n",
+        "Where: <spec> is a .spec file giving a DLL's exports.\n",
+        "       <include> is an include directory used by the DLL.\n",
+        "       <source> is a source file of the DLL.\n",
+        "       The above can be given multiple times on the command line, as appropriate.\n",
+        "Options:\n",
+        " -Th      : Output HTML instead of a man page\n",
+        " -Ts      : Output SGML (Docbook source) instead of a man page\n",
+        " -o <dir> : Create output in <dir>, default is \"",$opt_output_directory,"\"\n",
+        " -s <sect>: Set manual section to <sect>, default is ",$opt_manual_section,"\n",
+        " -e       : Output \"FIXME\" documentation from empty comments.\n",
+        " -v       : Verbosity. Can be given more than once for more detail.\n";
 }
 
-#
-# extract the comments from source file
-#
-sub parse_source
+# Print usage if we're called with no args
+if( @ARGV == 0)
+{
+  usage();
+}
+
+# Process command line options
+while(defined($_ = shift @ARGV))
+{
+  if( s/^-// )
+  {
+    # An option.
+    for ($_)
+    {
+      /^o$/  && do { $opt_output_directory = shift @ARGV; last; };
+      s/^S// && do { $opt_manual_section   = $_;          last; };
+      /^Th$/ && do { $opt_output_format  = "h";           last; };
+      /^Ts$/ && do { $opt_output_format  = "s";           last; };
+      /^v$/  && do { $opt_verbose++;                      last; };
+      /^e$/  && do { $opt_output_empty = 1;               last; };
+      /^L$/  && do { last; };
+      /^w$/  && do { @opt_spec_file_list = (@opt_spec_file_list, shift @ARGV); last; };
+      s/^I// && do { 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";
+    }
+  }
+  else
+  {
+    # A source file.
+    push (@opt_source_file_list, $_);
+  }
+}
+
+# Remove duplicate include directories
+my %htmp;
+@opt_header_file_list = grep(!$htmp{$_}++, @opt_header_file_list);
+
+if ($opt_verbose > 3)
+{
+  print "Output dir:'".$opt_output_directory."'\n";
+  print "Section   :'".$opt_manual_section."'\n";
+  print "Format  :'".$opt_output_format."'\n";
+  print "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 )
+    {
+        print "in '$spec_file':\n";
+        my $spec_details = $spec_files{$spec_file}[0];
+        my $exports = $spec_details->{EXPORTS};
+        for (@$exports)
+        {
+           print @$_[0].",".@$_[1].",".@$_[2].",".@$_[3]."\n";
+        }
+    }
+}
+
+# Extract and output the comments from each source file
+while(defined($_ = shift @opt_source_file_list))
+{
+  process_source_file($_);
+}
+
+# Write the index files for each spec
+process_index_files();
+
+# Write the master index file
+output_master_index_files();
+
+exit 0;
+
+
+# Generate the list of exported entries for the dll
+sub process_spec_file
 {
-  my $file = $_[0];
-  print "Processing $file\n";
+  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(<SPEC_FILE>)
+  {
+    s/^\s+//;            # Strip leading space
+    s/\s+\n$/\n/;        # Strip trailing space
+    s/\s+/ /g;           # Strip multiple tabs & spaces to a single space
+    s/\s*#.*//;          # Strip comments
+    s/\(.*\)/ /;         # Strip arguments
+    s/ \-[a-z0-9]+//g;   # Strip modifiers
+    s/\s+/ /g;           # Strip multiple tabs & spaces to a single space (again)
+    s/\n$//;             # Strip newline
 
-  open(SOURCE,"<$file") || die "Couldn't open the source file $file\n";
-  $state = 0;
-  while(<SOURCE>) {
-    if($state == 0 ) {
-        if ( /^\/\**$/ ) {
-            # find the start of the comment /**************
-            $state = 3;
-            @buffer = ();
+    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];
+}
+
+# Read each source file, extract comments, and generate API documentation if appropriate
+sub process_source_file
+{
+  my $source_file = shift(@_);
+  my $source_details =
+  {
+    CONTRIBUTORS => [ ],
+    DEBUG_CHANNEL => "",
+  };
+  my $comment =
+  {
+    FILE => $source_file,
+    COMMENT_NAME => "",
+    ALT_NAME => "",
+    DLL_NAME => "",
+    ORDINAL => "",
+    RETURNS => "",
+    PROTOTYPE => [],
+    TEXT => [],
+  };
+  my $parse_state = 0;
+  my $ignore_blank_lines = 1;
+  my $extra_comment = 0; # 1 if this is an extra comment, i.e its not a .spec export
+
+  if ($opt_verbose > 0)
+  {
+    print "Processing ".$source_file."\n";
+  }
+  open(SOURCE_FILE,"<$source_file") || die "couldn't open ".$source_file."\n";
+
+  # Add this source file to the list of source files
+  $source_files{$source_file} = [$source_details];
+
+  while(<SOURCE_FILE>)
+  {
+    s/\n$//;   # Strip newline
+    s/^\s+//;  # Strip leading space
+    s/\s+$//;  # Strip trailing space
+    if (! /^\*\|/ )
+    {
+      # Strip multiple tabs & spaces to a single space
+      s/\s+/ /g;
     }
-    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;
+
+    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";
         }
-        else {
-            $state = 0;
+        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 ($state == 1) {
-        #save the comment text into buffer, removing leading astericks
-        if ( /^ \*\// ) {
-            $state = 2;
+    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
         }
-        else {
-            # find the end of the comment
-            if ( s/^ \*// ) {
-                @buffer = ( @buffer , $_ );
-            }
-            else {
-                $state = 0;
-            }
+        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;
+      }
     }
-    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;
-            }
+
+    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);
         }
-        else {
-            $state = 0;
+
+        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;
+      }
     }
-    elsif ($state == 4) {
-        @apidef = ( @apidef , $_ );
-        #find the end of the parameters list
-        if( /\)/ ) {
-            output_manpage(\@buffer, \@apidef);
-            $state = 0;
+
+    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);
+  close(SOURCE_FILE);
 }
 
-# generate a database of functions to have man pages created from the source
-# creates funclist and funcdb
-sub parse_spec
+# Standardise a comments text for consistency
+sub process_comment_text
 {
-    my $spec = $_[0];
-    my $name,$type,$ord,$func;
+  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];
 
-    open(SPEC,"<$spec") || die "Couldn't open the spec file $spec\n";
-    while(<SPEC>)
+  if (!defined($spec_details))
+  {
+    if ($opt_verbose > 2)
     {
-        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 "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;
         }
+      }
     }
-    close(SPEC);
+    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: <file>.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(@_);
 
-#main starts here
+  my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
 
-$mandir = "man3w";
-$section = "3";
+  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;
+  }
 
-#process args
-while(@ARGV) {
-    if($ARGV[0] eq "-o") {      # extract output directory
-        shift @ARGV;
-        $mandir = $ARGV[0];
-        shift @ARGV;
-        next;
+  # 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";
     }
-    if($ARGV[0] =~ s/^-S// ) {  # extract man section
-        $section = $ARGV[0];
-        shift @ARGV;
-        next;
+    for (@{$comment->{TEXT}})
+    {
+      push (@{$spec_details->{DESCRIPTION}}, $_);
     }
-    if($ARGV[0] =~ s/^-w// ) {  # extract man section
-        shift @ARGV;
-        @specfiles = ( @specfiles , $ARGV[0] );
-        shift @ARGV;
-        next;
+    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";
     }
-    if($ARGV[0] =~ s/^-T// ) {
-        die "FIXME: Only NROFF supported\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";
     }
-    if($ARGV[0] =~ s/^-[LDiI]// ) {  #compatible with C2MAN flags
-        shift @ARGV;
-        next;
+
+    print "--COMMENT--\n";
+    for (@{$comment->{TEXT} })
+    {
+      print $_."\n";
     }
-    last; # stop after there's no more flags
+  }
+
+  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();
 }
 
-#print "manual section: $section\n";
-#print "man directory : $mandir\n";
-#print "input files   : @ARGV\n";
-#print "spec files    : @specfiles\n";
+# 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);
+    }
+  }
+}
 
-while(@specfiles) {
-    parse_spec($specfiles[0]);
-    shift @specfiles;
+# 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 "<!-- Generated file - DO NOT EDIT! -->\n";
+      print OUTPUT "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
+      print OUTPUT "<HTML>\n<HEAD>\n";
+      print OUTPUT "<LINK REL=\"StyleSheet\" href=\"apidoc.css\" type=\"text/css\">\n";
+      print OUTPUT "<META NAME=\"GENERATOR\" CONTENT=\"tools/c2man.pl\">\n";
+      print OUTPUT "<META NAME=\"keywords\" CONTENT=\"Win32,Wine,API,$comment->{COMMENT_NAME}\">\n";
+      print OUTPUT "<TITLE>Wine API: $comment->{COMMENT_NAME}</TITLE>\n</HEAD>\n<BODY>\n";
+  }
+  elsif ($opt_output_format eq "s")
+  {
+      print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n",
+                   "<sect1>\n",
+                   "<title>$comment->{COMMENT_NAME}</title>\n";
+  }
+  else
+  {
+      print OUTPUT ".\\\" -*- nroff -*-\n.\\\" Generated file - DO NOT EDIT!\n".
+                   ".TH ",$comment->{COMMENT_NAME}," ",$opt_manual_section," \"",$date,"\" \"".
+                   "Wine API\" \"Wine API\"\n";
+  }
+}
+
+sub output_api_footer
+{
+  if ($opt_output_format eq "h")
+  {
+      print OUTPUT "<hr><p><i class=\"copy\">Copyright &copy ".$year." The Wine Project.".
+                   "Visit <a HREF=http://www.winehq.org>WineHQ</a> for license details.".
+                   "Generated $date</i></p>\n</body>\n</html>\n";
+  }
+  elsif ($opt_output_format eq "s")
+  {
+      print OUTPUT "</sect1>\n";
+      return;
+  }
+  else
+  {
+  }
+}
+
+sub output_api_section_start
+{
+  my $comment = shift(@_);
+  my $section_name = shift(@_);
+
+  if ($opt_output_format eq "h")
+  {
+    print OUTPUT "\n<p><h2 class=\"section\">",$section_name,"</h2></p>\n";
+  }
+  elsif ($opt_output_format eq "s")
+  {
+    print OUTPUT "<bridgehead>",$section_name,"</bridgehead>\n";
+  }
+  else
+  {
+    print OUTPUT "\n\.SH ",$section_name,"\n";
+  }
 }
 
-#print "Functions: @funclist\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 "<p><b class=\"func_name\">",$comment->{COMMENT_NAME},
+                 "</b>&nbsp;&nbsp;<i class=\"dll_ord\">",
+                 ,$dll_ordinal,"</i></p>\n";
+  }
+  elsif ($opt_output_format eq "s")
+  {
+    print OUTPUT "<para>\n  <command>",$comment->{COMMENT_NAME},"</command>  <emphasis>",
+                 $dll_ordinal,"</emphasis>\n</para>\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 "<p><pre class=\"proto\">\n ", $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
+    @fmt = ("", "\n", "<tt class=\"param\">", "</tt>");
+  }
+  elsif ($opt_output_format eq "s")
+  {
+    print OUTPUT "<screen>\n ",$comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
+    @fmt = ("", "\n", "<emphasis>", "</emphasis>");
+  }
+  else
+  {
+    print OUTPUT $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
+    @fmt = ("", "\n", "\\fI", "\\fR");
+  }
+
+  # Since our prototype is output in a pre-formatted block, line up the
+  # parameters and parameter comments in the same column.
+
+  # First caluculate where the columns should start
+  my $biggest_length = 0;
+  for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
+  {
+    my $line = ${@{$comment->{PROTOTYPE}}}[$i];
+    if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
+    {
+      my $length = length $1;
+      if ($length > $biggest_length)
+      {
+        $biggest_length = $length;
+      }
+    }
+  }
+
+  # Now pad the string with blanks
+  for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
+  {
+    my $line = ${@{$comment->{PROTOTYPE}}}[$i];
+    if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
+    {
+      my $pad_len = $biggest_length - length $1;
+      my $padding = " " x ($pad_len);
+      ${@{$comment->{PROTOTYPE}}}[$i] = $1.$padding.$2;
+    }
+  }
+
+  for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
+  {
+    # Format the parameter name
+    my $line = ${@{$comment->{PROTOTYPE}}}[$i];
+    my $comma = ($i == @{$comment->{PROTOTYPE}}-1) ? "" : ",";
+    $line =~ s/(.+?)([A-Za-z_][A-Za-z_0-9]*)$/  $fmt[0]$1$fmt[2]$2$fmt[3]$comma$fmt[1]/;
+    print OUTPUT $line;
+  }
+
+  if ($opt_output_format eq "h")
+  {
+    print OUTPUT " )\n\n</pre></p>\n";
+  }
+  elsif ($opt_output_format eq "s")
+  {
+    print OUTPUT " )\n</screen>\n";
+  }
+  else
+  {
+    print OUTPUT " )\n";
+  }
+
+  output_api_section_end();
+}
+
+sub output_api_comment
+{
+  my $comment = shift(@_);
+  my $open_paragraph = 0;
+  my $open_raw = 0;
+  my $param_docs = 0;
+  my @fmt;
+
+  if ($opt_output_format eq "h")
+  {
+    @fmt = ("<p>", "</p>\n", "<tt class=\"const\">", "</tt>", "<b class=\"emp\">", "</b>",
+            "<tt class=\"coderef\">", "</tt>", "<tt class=\"param\">", "</tt>",
+            "<i class=\"in_out\">", "</i>", "<pre class=\"raw\">\n", "</pre>\n",
+            "<table class=\"tab\"><colgroup><col><col><col></colgroup><tbody>\n",
+            "</tbody></table>\n","<tr><td>","</td></tr>\n","</td>","</td><td>");
+  }
+  elsif ($opt_output_format eq "s")
+  {
+    @fmt = ("<para>\n","\n</para>\n","<constant>","</constant>","<emphasis>","</emphasis>",
+            "<command>","</command>","<constant>","</constant>","<emphasis>","</emphasis>",
+            "<screen>\n","</screen>\n",
+            "<informaltable frame=\"none\">\n<tgroup cols=\"3\">\n<tbody>\n",
+            "</tbody>\n</tgroup>\n</informaltable>\n","<row><entry>","</entry></row>\n",
+            "</entry>","</entry><entry>");
+  }
+  else
+  {
+    @fmt = ("\.PP\n", "\n", "\\fB", "\\fR", "\\fB", "\\fR", "\\fB", "\\fR", "\\fI", "\\fR",
+            "\\fB", "\\fR ", "", "", "", "","","\n.PP\n","","");
+  }
+
+  # Extract the parameter names
+  my @parameter_names;
+  for (@{$comment->{PROTOTYPE}})
+  {
+    if ( /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ )
+    {
+      push (@parameter_names, $2);
+    }
+  }
+
+  for (@{$comment->{TEXT}})
+  {
+    if ($opt_output_format eq "h" || $opt_output_format eq "s")
+    {
+      # Map special characters
+      s/\&/\&amp;/g;
+      s/\</\&lt;/g;
+      s/\>/\&gt;/g;
+      s/\([Cc]\)/\&copy;/g;
+      s/\(tm\)/&#174;/;
+    }
+
+    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]+)(\(\))/<a href\=\"$1\.html\">$1<\/a>/g;
+        # And references to COM objects (hey, they'll get documented one day)
+        s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ <a href\=\"$1\.html\">$1<\/a> $2/g;
+        # Convert any web addresses to real links
+        s/(http\:\/\/)(.+?)($| )/<a href\=\"$1$2\">$2<\/a>$3/g;
+      }
+      else
+      {
+        if ($opt_output_format eq "")
+        {
+          # Give the man section for API calls
+          s/ ([A-Za-z_]+[A-Za-z_0-9]+)\(\)/ $fmt[6]$1\($opt_manual_section\)$fmt[7]/g;
+        }
+        else
+        {
+          # Highlight API calls
+          s/ ([A-Za-z_]+[A-Za-z_0-9]+\(\))/ $fmt[6]$1$fmt[7]/g;
+        }
+
+        # And references to COM objects
+        s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ $fmt[6]$1$fmt[7] $2/g;
+      }
+
+      if ($open_raw == 1)
+      {
+        # Finish the raw output
+        print OUTPUT $fmt[13];
+        $open_raw = 0;
+      }
+
+      if ( /^[A-Z]+$/ || /^SEE ALSO$/ )
+      {
+        # Start of a new section
+        if ($open_paragraph == 1)
+        {
+          if ($param_docs == 1)
+          {
+            print OUTPUT $fmt[17],$fmt[15];
+          }
+          else
+          {
+            print OUTPUT $fmt[1];
+          }
+          $open_paragraph = 0;
+        }
+        output_api_section_start($comment,$_);
+        if ( /^PARAMS$/ )
+        {
+          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 "<chapter id =\"blurb\">\n<title>Introduction to The Wine Api Guide</title>\n"
+  }
+
+  # Write out the document
+  output_api_header($comment);
+  output_api_comment($comment);
+  output_api_footer($comment);
+  if ($opt_output_format eq "s")
+  {
+    print OUTPUT "</chapter>\n" # finish the chapter
+  }
+  output_close_api_file();
+
+  if ($opt_output_format eq "s")
+  {
+    output_sgml_master_file(\@dlls);
+    return;
+  }
+  if ($opt_output_format eq "h")
+  {
+    output_html_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 "<!-- Generated file - DO NOT EDIT! -->\n";
+  print OUTPUT "<!doctype book PUBLIC \"-//OASIS//DTD DocBook V3.1//EN\" [\n\n";
+  print OUTPUT "<!entity blurb SYSTEM \"blurb.sgml\">\n";
+
+  # List the entities
+  for (@$dlls)
+  {
+    $_ =~ s/(\..*)?\n//;
+    print OUTPUT "<!entity ",$_," SYSTEM \"",$_,".sgml\">\n"
+  }
+
+  print OUTPUT "]>\n\n<book id=\"index\">\n<bookinfo><title>The Wine Api Guide</title></bookinfo>\n\n";
+  print OUTPUT "  &blurb;\n";
+
+  for (@$dlls)
+  {
+    print OUTPUT "  &",$_,";\n"
+  }
+  print OUTPUT "\n\n</book>\n";
+
+  output_close_api_file();
+}
+
+# Produce the sgml for the dll chapter from the generated files
+sub output_sgml_dll_file
+{
+  my $spec_details = shift(@_);
+
+  # Make a list of all the documentation files to include
+  my $exports = $spec_details->{EXPORTS};
+  my @source_files = ();
+  for (@$exports)
+  {
+    # @$_ => ordinal, call convention, exported name, implementation name, documented;
+    if (@$_[1] ne "forward" && @$_[1] ne "extern" && @$_[1] ne "stub" && @$_[1] ne "equate" &&
+        @$_[1] ne "variable" && @$_[1] ne "fake" && @$_[4] & 1)
+    {
+      # A documented function
+      push (@source_files,@$_[3]);
+    }
+  }
+
+  push (@source_files,@{$spec_details->{EXTRA_COMMENTS}});
+
+  @source_files = sort @source_files;
+
+  # create a new chapter for this dll
+  my $tmp_name = $opt_output_directory."/".$spec_details->{DLL_NAME}.".tmp";
+  open(OUTPUT,">$tmp_name") || die "Couldn't create $tmp_name\n";
+  print OUTPUT "<chapter>\n<title>$spec_details->{DLL_NAME}</title>\n";
+  output_close_api_file();
+
+  # Add the sorted documentation, cleaning up as we go
+  `cat $opt_output_directory/$spec_details->{DLL_NAME}.sgml >>$tmp_name`;
+  for (@source_files)
+  {
+    `cat $opt_output_directory/$_.sgml >>$tmp_name`;
+    `rm -f $opt_output_directory/$_.sgml`;
+  }
+
+  # close the chapter, and overwite the dll source
+  open(OUTPUT,">>$tmp_name") || die "Couldn't create $tmp_name\n";
+  print OUTPUT "</chapter>\n";
+  close OUTPUT;
+  `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.sgml`;
+}
+
+# Output the stylesheet for HTML output
+sub output_html_stylesheet
+{
+  if ($opt_output_format ne "h")
+  {
+    return;
+  }
+
+  my $css;
+  ($css = <<HERE_TARGET) =~ s/^\s+//gm;
+/*
+ * Default styles for Wine HTML Documentation.
+ *
+ * This style sheet should be altered to suit your needs/taste.
+ */
+BODY { /* Page body */
+background-color: white;
+color: black;
+font-family: Tahomsans-serif;
+font-style: normal;
+font-size: 10pt;
+}
+a:link { color: #4444ff; } /* Links */
+a:visited { color: #333377 }
+a:active { color: #0000dd }
+H2.section { /* Section Headers */
+font-family: sans-serif;
+color: #777777;
+background-color: #F0F0FE;
+margin-left: 0.2in;
+margin-right: 1.0in;
+}
+b.func_name { /* Function Name */
+font-size: 10pt;
+font-style: bold;
+}
+i.dll_ord { /* Italicised DLL+ordinal */
+color: #888888;
+font-family: sans-serif;
+font-size: 8pt;
+}
+p { /* Paragraphs */
+margin-left: 0.5in;
+margin-right: 0.5in;
+}
+table { /* tables */
+margin-left: 0.5in;
+margin-right: 0.5in;
+}
+pre.proto /* API Function prototype */
+{
+border-style: solid;
+border-width: 1px;
+border-color: #777777;
+background-color: #F0F0BB;
+color: black;
+font-size: 10pt;
+vertical-align: top;
+margin-left: 0.5in;
+margin-right: 1.0in;
+}
+tt.param { /* Parameter name */
+font-style: italic;
+color: blue;
+}
+tt.const { /* Constant */
+color: red;
+}
+i.in_out { /* In/Out */
+font-size: 8pt;
+color: grey;
+}
+tt.coderef { /* Code in description text */
+color: darkgreen;
+}
+b.emp /* Emphasis */ {
+font-style: bold;
+color: darkblue;
+}
+i.footer { /* Footer */
+font-family: sans-serif;
+font-size: 6pt;
+color: darkgrey;
+}
+HERE_TARGET
 
-while(@ARGV) {
-    parse_source($ARGV[0]);
-    shift @ARGV;
+  my $output_file = "$opt_output_directory/apidoc.css";
+  open(CSS,">$output_file") || die "Couldn't create the file $output_file\n";
+  print CSS $css;
+  close(CSS);
 }

[Index of Archives]     [Gimp for Windows]     [Red Hat]     [Samba]     [Yosemite Camping]     [Graphics Cards]     [Wine Home]

  Powered by Linux