Added check for st_blocks in struct stat.
[wine/multimedia.git] / tools / c2man.pl
blob0dab3bf5ca6d76af3f72efa55600465c9cf1c400
1 #! /usr/bin/perl -w
3 # Generate API documentation. See documentation/documentation.sgml for details.
5 # Copyright (C) 2000 Mike McCormack
6 # Copyright (C) 2003 Jon Griffiths
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2.1 of the License, or (at your option) any later version.
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # Lesser General Public License for more details.
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this library; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 # TODO
23 # SGML gurus - feel free to smarten up the SGML.
24 # Add any other relevant information for the dll - imports etc
25 # Should we have a special output mode for WineHQ?
27 use strict;
28 use bytes;
30 # Function flags. most of these come from the spec flags
31 my $FLAG_DOCUMENTED = 1;
32 my $FLAG_NONAME = 2;
33 my $FLAG_I386 = 4;
34 my $FLAG_REGISTER = 8;
35 my $FLAG_APAIR = 16; # The A version of a matching W function
36 my $FLAG_WPAIR = 32; # The W version of a matching A function
39 # Options
40 my $opt_output_directory = "man3w"; # All default options are for nroff (man pages)
41 my $opt_manual_section = "3w";
42 my $opt_wine_root_dir = "";
43 my $opt_output_format = ""; # '' = nroff, 'h' = html, 's' = sgml
44 my $opt_output_empty = 0; # Non-zero = Create 'empty' comments (for every implemented function)
45 my $opt_fussy = 1; # Non-zero = Create only if we have a RETURNS section
46 my $opt_verbose = 0; # >0 = verbosity. Can be given multiple times (for debugging)
47 my @opt_header_file_list = ();
48 my @opt_spec_file_list = ();
49 my @opt_source_file_list = ();
51 # All the collected details about all the .spec files being processed
52 my %spec_files;
53 # All the collected details about all the source files being processed
54 my %source_files;
55 # All documented functions that are to be placed in the index
56 my @index_entries_list = ();
58 # useful globals
59 my $pwd = `pwd`."/";
60 $pwd =~ s/\n//;
61 my @datetime = localtime;
62 my @months = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
63 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
64 my $year = $datetime[5] + 1900;
65 my $date = "$months[$datetime[4]] $year";
67 sub usage
69 print "\nCreate API Documentation from Wine source code.\n\n",
70 "Usage: c2man.pl [options] {-w <spec>} {-I <include>} {<source>}\n",
71 "Where: <spec> is a .spec file giving a DLL's exports.\n",
72 " <include> is an include directory used by the DLL.\n",
73 " <source> is a source file of the DLL.\n",
74 " The above can be given multiple times on the command line, as appropriate.\n",
75 "Options:\n",
76 " -Th : Output HTML instead of a man page\n",
77 " -Ts : Output SGML (Docbook source) instead of a man page\n",
78 " -o <dir> : Create output in <dir>, default is \"",$opt_output_directory,"\"\n",
79 " -s <sect>: Set manual section to <sect>, default is ",$opt_manual_section,"\n",
80 " -e : Output \"FIXME\" documentation from empty comments.\n",
81 " -v : Verbosity. Can be given more than once for more detail.\n";
84 # Print usage if we're called with no args
85 if( @ARGV == 0)
87 usage();
90 # Process command line options
91 while(defined($_ = shift @ARGV))
93 if( s/^-// )
95 # An option.
96 for ($_)
98 /^o$/ && do { $opt_output_directory = shift @ARGV; last; };
99 s/^S// && do { $opt_manual_section = $_; last; };
100 /^Th$/ && do { $opt_output_format = "h"; last; };
101 /^Ts$/ && do { $opt_output_format = "s"; last; };
102 /^v$/ && do { $opt_verbose++; last; };
103 /^e$/ && do { $opt_output_empty = 1; last; };
104 /^L$/ && do { last; };
105 /^w$/ && do { @opt_spec_file_list = (@opt_spec_file_list, shift @ARGV); last; };
106 s/^I// && do { if ($_ ne ".") {
107 my $include = $_."/*.h";
108 $include =~ s/\/\//\//g;
109 my $have_headers = `ls $include >/dev/null 2>&1`;
110 if ($? >> 8 == 0) { @opt_header_file_list = (@opt_header_file_list, $include); }
112 last;
114 s/^R// && do { if ($_ =~ /^\//) { $opt_wine_root_dir = $_; }
115 else { $opt_wine_root_dir = `cd $pwd/$_ && pwd`; }
116 $opt_wine_root_dir =~ s/\n//;
117 $opt_wine_root_dir =~ s/\/\//\//g;
118 if (! $opt_wine_root_dir =~ /\/$/ ) { $opt_wine_root_dir = $opt_wine_root_dir."/"; };
119 last;
121 die "Unrecognised option $_\n";
124 else
126 # A source file.
127 push (@opt_source_file_list, $_);
131 # Remove duplicate include directories
132 my %htmp;
133 @opt_header_file_list = grep(!$htmp{$_}++, @opt_header_file_list);
135 if ($opt_verbose > 3)
137 print "Output dir:'".$opt_output_directory."'\n";
138 print "Section :'".$opt_manual_section."'\n";
139 print "Format :'".$opt_output_format."'\n";
140 print "Root :'".$opt_wine_root_dir."'\n";
141 print "Spec files:'@opt_spec_file_list'\n";
142 print "Includes :'@opt_header_file_list'\n";
143 print "Sources :'@opt_source_file_list'\n";
146 if (@opt_spec_file_list == 0)
148 exit 0; # Don't bother processing non-dll files
151 # Read in each .spec files exports and other details
152 while(my $spec_file = shift @opt_spec_file_list)
154 process_spec_file($spec_file);
157 if ($opt_verbose > 3)
159 foreach my $spec_file ( keys %spec_files )
161 print "in '$spec_file':\n";
162 my $spec_details = $spec_files{$spec_file}[0];
163 my $exports = $spec_details->{EXPORTS};
164 for (@$exports)
166 print @$_[0].",".@$_[1].",".@$_[2].",".@$_[3]."\n";
171 # Extract and output the comments from each source file
172 while(defined($_ = shift @opt_source_file_list))
174 process_source_file($_);
177 # Write the index files for each spec
178 process_index_files();
180 # Write the master index file
181 output_master_index_files();
183 exit 0;
186 # Generate the list of exported entries for the dll
187 sub process_spec_file
189 my $spec_name = shift(@_);
190 my $dll_name = $spec_name;
191 $dll_name =~ s/\..*//; # Strip the file extension
192 my $uc_dll_name = uc $dll_name;
194 my $spec_details =
196 NAME => $spec_name,
197 DLL_NAME => $dll_name,
198 NUM_EXPORTS => 0,
199 NUM_STUBS => 0,
200 NUM_FUNCS => 0,
201 NUM_FORWARDS => 0,
202 NUM_VARS => 0,
203 NUM_DOCS => 0,
204 CONTRIBUTORS => [ ],
205 SOURCES => [ ],
206 DESCRIPTION => [ ],
207 EXPORTS => [ ],
208 EXPORTED_NAMES => { },
209 IMPLEMENTATION_NAMES => { },
210 EXTRA_COMMENTS => [ ],
211 CURRENT_EXTRA => [ ] ,
214 if ($opt_verbose > 0)
216 print "Processing ".$spec_name."\n";
219 # We allow opening to fail just to cater for the peculiarities of
220 # the Wine build system. This doesn't hurt, in any case
221 open(SPEC_FILE, "<$spec_name") || return;
223 while(<SPEC_FILE>)
225 s/^\s+//; # Strip leading space
226 s/\s+\n$/\n/; # Strip trailing space
227 s/\s+/ /g; # Strip multiple tabs & spaces to a single space
228 s/\s*#.*//; # Strip comments
229 s/\(.*\)/ /; # Strip arguments
230 s/\s+/ /g; # Strip multiple tabs & spaces to a single space (again)
231 s/\n$//; # Strip newline
233 my $flags = 0;
234 if( /\-noname/ )
236 $flags |= $FLAG_NONAME;
238 if( /\-i386/ )
240 $flags |= $FLAG_I386;
242 if( /\-register/ )
244 $flags |= $FLAG_REGISTER;
246 s/ \-[a-z0-9]+//g; # Strip flags
248 if( /^(([0-9]+)|@) / )
250 # This line contains an exported symbol
251 my ($ordinal, $call_convention, $exported_name, $implementation_name) = split(' ');
253 for ($call_convention)
255 /^(cdecl|stdcall|varargs|pascal)$/
256 && do { $spec_details->{NUM_FUNCS}++; last; };
257 /^(variable|equate)$/
258 && do { $spec_details->{NUM_VARS}++; last; };
259 /^(extern)$/
260 && do { $spec_details->{NUM_FORWARDS}++; last; };
261 /^stub$/ && do { $spec_details->{NUM_STUBS}++; last; };
262 if ($opt_verbose > 0)
264 print "Warning: didn't recognise convention \'",$call_convention,"'\n";
266 last;
269 # Convert ordinal only names so we can find them later
270 if ($exported_name eq "@")
272 $exported_name = $uc_dll_name.'_'.$ordinal;
274 if (!defined($implementation_name))
276 $implementation_name = $exported_name;
278 if ($implementation_name eq "")
280 $implementation_name = $exported_name;
283 if ($implementation_name =~ /(.*?)\./)
285 $call_convention = "forward"; # Referencing a function from another dll
286 $spec_details->{NUM_FUNCS}--;
287 $spec_details->{NUM_FORWARDS}++;
290 # Add indices for the exported and implementation names
291 $spec_details->{EXPORTED_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
292 if ($implementation_name ne $exported_name)
294 $spec_details->{IMPLEMENTATION_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
297 # Add the exported entry
298 $spec_details->{NUM_EXPORTS}++;
299 my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $flags);
300 push (@{$spec_details->{EXPORTS}},[@export]);
303 close(SPEC_FILE);
305 # Add this .spec files details to the list of .spec files
306 $spec_files{$uc_dll_name} = [$spec_details];
309 # Read each source file, extract comments, and generate API documentation if appropriate
310 sub process_source_file
312 my $source_file = shift(@_);
313 my $source_details =
315 CONTRIBUTORS => [ ],
316 DEBUG_CHANNEL => "",
318 my $comment =
320 FILE => $source_file,
321 COMMENT_NAME => "",
322 ALT_NAME => "",
323 DLL_NAME => "",
324 ORDINAL => "",
325 RETURNS => "",
326 PROTOTYPE => [],
327 TEXT => [],
329 my $parse_state = 0;
330 my $ignore_blank_lines = 1;
331 my $extra_comment = 0; # 1 if this is an extra comment, i.e its not a .spec export
333 if ($opt_verbose > 0)
335 print "Processing ".$source_file."\n";
337 open(SOURCE_FILE,"<$source_file") || die "couldn't open ".$source_file."\n";
339 # Add this source file to the list of source files
340 $source_files{$source_file} = [$source_details];
342 while(<SOURCE_FILE>)
344 s/\n$//; # Strip newline
345 s/^\s+//; # Strip leading space
346 s/\s+$//; # Strip trailing space
347 if (! /^\*\|/ )
349 # Strip multiple tabs & spaces to a single space
350 s/\s+/ /g;
353 if ( / +Copyright *(\([Cc]\))*[0-9 \-\,\/]*([[:alpha:][:^ascii:] \.\-]+)/ )
355 # Extract a contributor to this file
356 my $contributor = $2;
357 $contributor =~ s/ *$//;
358 $contributor =~ s/^by //;
359 $contributor =~ s/\.$//;
360 $contributor =~ s/ (for .*)/ \($1\)/;
361 if ($contributor ne "")
363 if ($opt_verbose > 3)
365 print "Info: Found contributor:'".$contributor."'\n";
367 push (@{$source_details->{CONTRIBUTORS}},$contributor);
370 elsif ( /WINE_DEFAULT_DEBUG_CHANNEL\(([A-Za-z]*)\)/ )
372 # Extract the debug channel to use
373 if ($opt_verbose > 3)
375 print "Info: Found debug channel:'".$1."'\n";
377 $source_details->{DEBUG_CHANNEL} = $1;
380 if ($parse_state == 0) # Searching for a comment
382 if ( /^\/\**$/ )
384 # Found a comment start
385 $comment->{COMMENT_NAME} = "";
386 $comment->{ALT_NAME} = "";
387 $comment->{DLL_NAME} = "";
388 $comment->{ORDINAL} = "";
389 $comment->{RETURNS} = "";
390 $comment->{PROTOTYPE} = [];
391 $comment->{TEXT} = [];
392 $ignore_blank_lines = 1;
393 $extra_comment = 0;
394 $parse_state = 3;
397 elsif ($parse_state == 1) # Reading in a comment
399 if ( /^\**\// )
401 # Found the end of the comment
402 $parse_state = 2;
404 elsif ( s/^\*\|/\|/ )
406 # A line of comment not meant to be pre-processed
407 push (@{$comment->{TEXT}},$_); # Add the comment text
409 elsif ( s/^ *\** *// )
411 # A line of comment, starting with an asterisk
412 if ( /^[A-Z]+$/ || $_ eq "")
414 # This is a section start, so skip blank lines before and after it.
415 my $last_line = pop(@{$comment->{TEXT}});
416 if (defined($last_line) && $last_line ne "")
418 # Put it back
419 push (@{$comment->{TEXT}},$last_line);
421 if ( /^[A-Z]+$/ )
423 $ignore_blank_lines = 1;
425 else
427 $ignore_blank_lines = 0;
431 if ($ignore_blank_lines == 0 || $_ ne "")
433 push (@{$comment->{TEXT}},$_); # Add the comment text
436 else
438 # This isn't a well formatted comment: look for the next one
439 $parse_state = 0;
442 elsif ($parse_state == 2) # Finished reading in a comment
444 if ( /(WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ )
446 # Comment is followed by a function definition
447 $parse_state = 4; # Fall through to read prototype
449 else
451 # Allow cpp directives and blank lines between the comment and prototype
452 if ($extra_comment == 1)
454 # An extra comment not followed by a function definition
455 $parse_state = 5; # Fall through to process comment
457 elsif (!/^\#/ && !/^ *$/ && !/^__ASM_GLOBAL_FUNC/)
459 # This isn't a well formatted comment: look for the next one
460 if ($opt_verbose > 1)
462 print "Info: Function '",$comment->{COMMENT_NAME},"' not followed by prototype.\n";
464 $parse_state = 0;
468 elsif ($parse_state == 3) # Reading in the first line of a comment
470 s/^ *\** *//;
471 if ( /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ )
473 # Found a correctly formed "ApiName (DLLNAME.Ordinal)" line.
474 $comment->{COMMENT_NAME} = $1;
475 $comment->{DLL_NAME} = uc $3;
476 $comment->{ORDINAL} = $4;
477 $comment->{DLL_NAME} =~ s/^KERNEL$/KRNL386/; # Too many of these to ignore, _old_ code
478 $parse_state = 1;
480 elsif ( /^([A-Za-z0-9_]+) +\{([A-Za-z0-9_]+)\}$/ )
482 # Found a correctly formed "CommentTitle {DLLNAME}" line (extra documentation)
483 $comment->{COMMENT_NAME} = $1;
484 $comment->{DLL_NAME} = uc $2;
485 $comment->{ORDINAL} = "";
486 $extra_comment = 1;
487 $parse_state = 1;
489 else
491 # This isn't a well formatted comment: look for the next one
492 $parse_state = 0;
496 if ($parse_state == 4) # Reading in the function definition
498 push (@{$comment->{PROTOTYPE}},$_);
499 # Strip comments from the line before checking for ')'
500 my $stripped_line = $_;
501 $stripped_line =~ s/ *(\/\* *)(.*?)( *\*\/ *)//;
502 if ( $stripped_line =~ /\)/ )
504 # Strip a blank last line
505 my $last_line = pop(@{$comment->{TEXT}});
506 if (defined($last_line) && $last_line ne "")
508 # Put it back
509 push (@{$comment->{TEXT}},$last_line);
512 if ($opt_output_empty != 0 && @{$comment->{TEXT}} == 0)
514 # Create a 'not implemented' comment
515 @{$comment->{TEXT}} = ("fixme: This function has not yet been documented.");
517 $parse_state = 5;
521 if ($parse_state == 5) # Processing the comment
523 # Process it, if it has any text
524 if (@{$comment->{TEXT}} > 0)
526 if ($extra_comment == 1)
528 process_extra_comment($comment);
530 else
532 @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
533 process_comment($comment);
536 elsif ($opt_verbose > 1 && $opt_output_empty == 0)
538 print "Info: Function '",$comment->{COMMENT_NAME},"' has no documentation.\n";
540 $parse_state = 0;
543 close(SOURCE_FILE);
546 # Standardise a comments text for consistency
547 sub process_comment_text
549 my $comment = shift(@_);
550 my $i = 0;
552 for (@{$comment->{TEXT}})
554 if (! /^\|/ )
556 # Map I/O values. These come in too many formats to standardise now....
557 s/\[I\]|\[i\]|\[in\]|\[IN\]/\[In\] /g;
558 s/\[O\]|\[o\]|\[out\]|\[OUT\]/\[Out\]/g;
559 s/\[I\/O\]|\[I\,O\]|\[i\/o\]|\[in\/out\]|\[IN\/OUT\]/\[In\/Out\]/g;
560 # TRUE/FALSE/NULL are defines, capitilise them
561 s/True|true/TRUE/g;
562 s/False|false/FALSE/g;
563 s/Null|null/NULL/g;
564 # Preferred capitalisations
565 s/ wine| WINE/ Wine/g;
566 s/ API | api / Api /g;
567 s/DLL|Dll/dll /g;
568 s/ URL | url / Url /g;
569 s/WIN16|win16/Win16/g;
570 s/WIN32|win32/Win32/g;
571 s/WIN64|win64/Win64/g;
572 s/ ID | id / Id /g;
573 # Grammar
574 s/([a-z])\.([A-Z])/$1\. $2/g; # Space after full stop
575 s/ \:/\:/g; # Colons to the left
576 s/ \;/\;/g; # Semi-colons too
577 # Common idioms
578 s/^See ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Referring to A version from W
579 s/^Unicode version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Ditto
580 s/^PARAMETERS$/PARAMS/; # Name of parameter section should be 'PARAMS'
581 # Trademarks
582 s/( |\.)(MS|Microsoft|microsoft)( |\.)/$1Microsoft\(tm\)$3/g;
583 s/( |\.)(Windows|windows|windoze|winblows)( |\.)/$1Windows\(tm\)$3/g;
584 s/( |\.)(DOS|dos|msdos)( |\.)/$1MS-DOS\(tm\)$3/g;
585 s/( |\.)(UNIX|Unix|unix)( |\.)/$1Unix\(tm\)$3/g;
586 # Abbreviations
587 s/( char )/ character /g;
588 s/( chars )/ characters /g;
589 s/( info )/ information /g;
590 s/( app )/ application /g;
591 s/( apps )/ applications /g;
592 s/( exe )/ executable /g;
593 s/( ptr )/ pointer /g;
594 s/( obj )/ object /g;
595 s/( err )/ error /g;
596 s/( bool )/ boolean /g;
597 # Punctuation
598 if ( /\[I|\[O/ && ! /\.$/ )
600 $_ = $_."."; # Always have a full stop at the end of parameter desc.
602 elsif ($i > 0 && /^[A-Z]*$/ &&
603 !(@{$comment->{TEXT}}[$i-1] =~ /\.$/) &&
604 !(@{$comment->{TEXT}}[$i-1] =~ /\:$/))
607 if (!(@{$comment->{TEXT}}[$i-1] =~ /^[A-Z]*$/))
609 # Paragraphs always end with a full stop
610 @{$comment->{TEXT}}[$i-1] = @{$comment->{TEXT}}[$i-1].".";
614 $i++;
618 # Standardise our comment and output it if it is suitable.
619 sub process_comment
621 my $comment = shift(@_);
623 # Don't process this comment if the function isnt exported
624 my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
626 if (!defined($spec_details))
628 if ($opt_verbose > 2)
630 print "Warning: Function '".$comment->{COMMENT_NAME}."' belongs to '".
631 $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
633 return;
636 if ($comment->{COMMENT_NAME} eq "@")
638 my $found = 0;
640 # Find the name from the .spec file
641 for (@{$spec_details->{EXPORTS}})
643 if (@$_[0] eq $comment->{ORDINAL})
645 $comment->{COMMENT_NAME} = @$_[2];
646 $found = 1;
650 if ($found == 0)
652 # Create an implementation name
653 $comment->{COMMENT_NAME} = $comment->{DLL_NAME}."_".$comment->{ORDINAL};
657 my $exported_names = $spec_details->{EXPORTED_NAMES};
658 my $export_index = $exported_names->{$comment->{COMMENT_NAME}};
659 my $implementation_names = $spec_details->{IMPLEMENTATION_NAMES};
661 if (!defined($export_index))
663 # Perhaps the comment uses the implementation name?
664 $export_index = $implementation_names->{$comment->{COMMENT_NAME}};
666 if (!defined($export_index))
668 # This function doesn't appear to be exported. hmm.
669 if ($opt_verbose > 2)
671 print "Warning: Function '".$comment->{COMMENT_NAME}."' claims to belong to '".
672 $comment->{DLL_NAME}."' but is not exported by it: not processing it.\n";
674 return;
677 # When the function is exported twice we have the second name below the first
678 # (you see this a lot in ntdll, but also in some other places).
679 my $first_line = ${@{$comment->{TEXT}}}[1];
681 if ( $first_line =~ /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ )
683 # Found a second name - mark it as documented
684 my $alt_index = $exported_names->{$1};
685 if (defined($alt_index))
687 if ($opt_verbose > 2)
689 print "Info: Found alternate name '",$1,"\n";
691 my $alt_export = @{$spec_details->{EXPORTS}}[$alt_index];
692 @$alt_export[4] |= $FLAG_DOCUMENTED;
693 $spec_details->{NUM_DOCS}++;
694 ${@{$comment->{TEXT}}}[1] = "";
698 if (@{$spec_details->{CURRENT_EXTRA}})
700 # We have an extra comment that might be related to this one
701 my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
702 my $current_name = $current_comment->{COMMENT_NAME};
703 if ($comment->{COMMENT_NAME} =~ /^$current_name/ && $comment->{COMMENT_NAME} ne $current_name)
705 if ($opt_verbose > 2)
707 print "Linking ",$comment->{COMMENT_NAME}," to $current_name\n";
709 # Add a reference to this comment to our extra comment
710 push (@{$current_comment->{TEXT}}, $comment->{COMMENT_NAME}."()","");
714 # We want our docs generated using the implementation name, so they are unique
715 my $export = @{$spec_details->{EXPORTS}}[$export_index];
716 $comment->{COMMENT_NAME} = @$export[3];
717 $comment->{ALT_NAME} = @$export[2];
719 # Mark the function as documented
720 $spec_details->{NUM_DOCS}++;
721 @$export[4] |= $FLAG_DOCUMENTED;
723 # This file is used by the DLL - Make sure we get our contributors right
724 push (@{$spec_details->{SOURCES}},$comment->{FILE});
726 # If we have parameter comments in the prototype, extract them
727 my @parameter_comments;
728 for (@{$comment->{PROTOTYPE}})
730 s/ *\, */\,/g; # Strip spaces from around commas
732 if ( s/ *(\/\* *)(.*?)( *\*\/ *)// ) # Strip out comment
734 my $parameter_comment = $2;
735 if (!$parameter_comment =~ /^\[/ )
737 # Add [IO] markers so we format the comment correctly
738 $parameter_comment = "[fixme] ".$parameter_comment;
740 if ( /( |\*)([A-Za-z_]{1}[A-Za-z_0-9]*)(\,|\))/ )
742 # Add the parameter name
743 $parameter_comment = $2." ".$parameter_comment;
745 push (@parameter_comments, $parameter_comment);
749 # If we extracted any prototype comments, add them to the comment text.
750 if (@parameter_comments)
752 @parameter_comments = ("PARAMS", @parameter_comments);
753 my @new_comment = ();
754 my $inserted_params = 0;
756 for (@{$comment->{TEXT}})
758 if ( $inserted_params == 0 && /^[A-Z]+$/ )
760 # Found a section header, so this is where we insert
761 push (@new_comment, @parameter_comments);
762 $inserted_params = 1;
764 push (@new_comment, $_);
766 if ($inserted_params == 0)
768 # Add them to the end
769 push (@new_comment, @parameter_comments);
771 $comment->{TEXT} = [@new_comment];
774 if ($opt_fussy == 1 && $opt_output_empty == 0)
776 # Reject any comment that doesn't have a description or a RETURNS section.
777 # This is the default for now, 'coz many comments aren't suitable.
778 my $found_returns = 0;
779 my $found_description_text = 0;
780 my $in_description = 0;
781 for (@{$comment->{TEXT}})
783 if ( /^RETURNS$/ )
785 $found_returns = 1;
786 $in_description = 0;
788 elsif ( /^DESCRIPTION$/ )
790 $in_description = 1;
792 elsif ($in_description == 1)
794 if ( !/^[A-Z]+$/ )
796 # Dont reject comments that refer to another doc (e.g. A/W)
797 if ( /^See ([A-Za-z0-9_]+)\.$/ )
799 if ($comment->{COMMENT_NAME} =~ /W$/ )
801 # This is probably a Unicode version of an Ascii function.
802 # Create the Ascii name and see if its been documented
803 my $ascii_name = $comment->{COMMENT_NAME};
804 $ascii_name =~ s/W$/A/;
806 my $ascii_export_index = $exported_names->{$ascii_name};
808 if (!defined($ascii_export_index))
810 $ascii_export_index = $implementation_names->{$ascii_name};
812 if (!defined($ascii_export_index))
814 if ($opt_verbose > 2)
816 print "Warning: Function '".$comment->{COMMENT_NAME}."' is not an A/W pair.\n";
819 else
821 my $ascii_export = @{$spec_details->{EXPORTS}}[$ascii_export_index];
822 if (@$ascii_export[4] & $FLAG_DOCUMENTED)
824 # Flag these functions as an A/W pair
825 @$ascii_export[4] |= $FLAG_APAIR;
826 @$export[4] |= $FLAG_WPAIR;
830 $found_returns = 1;
832 elsif ( /^Unicode version of ([A-Za-z0-9_]+)\.$/ )
834 @$export[4] |= $FLAG_WPAIR; # Explicitly marked as W version
835 $found_returns = 1;
837 $found_description_text = 1;
839 else
841 $in_description = 0;
845 if ($found_returns == 0 || $found_description_text == 0)
847 if ($opt_verbose > 2)
849 print "Info: Function '",$comment->{COMMENT_NAME},"' has no ",
850 "description and/or RETURNS section, skipping\n";
852 $spec_details->{NUM_DOCS}--;
853 @$export[4] &= ~$FLAG_DOCUMENTED;
854 return;
858 process_comment_text($comment);
860 # Strip the prototypes return value, call convention, name and brackets
861 # (This leaves it as a list of types and names, or empty for void functions)
862 my $prototype = join(" ", @{$comment->{PROTOTYPE}});
863 $prototype =~ s/ / /g;
864 $prototype =~ s/^(.*?) (WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16) (.*?)\( *(.*)/$4/;
865 $comment->{RETURNS} = $1;
866 $prototype =~ s/ *\).*//; # Strip end bracket
867 $prototype =~ s/ *\* */\*/g; # Strip space around pointers
868 $prototype =~ s/ *\, */\,/g; # Strip space around commas
869 $prototype =~ s/^(void|VOID)$//; # If void, leave blank
870 $prototype =~ s/\*([A-Za-z_])/\* $1/g; # Separate pointers from parameter name
871 @{$comment->{PROTOTYPE}} = split ( /,/ ,$prototype);
873 # FIXME: If we have no parameters, make sure we have a PARAMS: None. section
875 # Find header file
876 my $h_file = "";
877 if (@$export[4] & $FLAG_NONAME)
879 $h_file = "Exported by ordinal only. Use GetProcAddress() to obtain a pointer to the function.";
881 else
883 if ($comment->{COMMENT_NAME} ne "")
885 my $tmp = "grep -s -l $comment->{COMMENT_NAME} @opt_header_file_list 2>/dev/null";
886 $tmp = `$tmp`;
887 my $exit_value = $? >> 8;
888 if ($exit_value == 0)
890 $tmp =~ s/\n.*//g;
891 if ($tmp ne "")
893 $h_file = `basename $tmp`;
897 elsif ($comment->{ALT_NAME} ne "")
899 my $tmp = "grep -s -l $comment->{ALT_NAME} @opt_header_file_list"." 2>/dev/null";
900 $tmp = `$tmp`;
901 my $exit_value = $? >> 8;
902 if ($exit_value == 0)
904 $tmp =~ s/\n.*//g;
905 if ($tmp ne "")
907 $h_file = `basename $tmp`;
911 $h_file =~ s/^ *//;
912 $h_file =~ s/\n//;
913 if ($h_file eq "")
915 $h_file = "Not defined in a Wine header. The function is either undocumented, or missing from Wine."
917 else
919 $h_file = "Defined in \"".$h_file."\".";
923 # Find source file
924 my $c_file = $comment->{FILE};
925 if ($opt_wine_root_dir ne "")
927 my $cfile = $pwd."/".$c_file; # Current dir + file
928 $cfile =~ s/(.+)(\/.*$)/$1/; # Strip the filename
929 $cfile = `cd $cfile && pwd`; # Strip any relative parts (e.g. "../../")
930 $cfile =~ s/\n//; # Strip newline
931 my $newfile = $c_file;
932 $newfile =~ s/(.+)(\/.*$)/$2/; # Strip all but the filename
933 $cfile = $cfile."/".$newfile; # Append filename to base path
934 $cfile =~ s/$opt_wine_root_dir//; # Get rid of the root directory
935 $cfile =~ s/\/\//\//g; # Remove any double slashes
936 $cfile =~ s/^\/+//; # Strip initial directory slash
937 $c_file = $cfile;
939 $c_file = "Implemented in \"".$c_file."\".";
941 # Add the implementation details
942 push (@{$comment->{TEXT}}, "IMPLEMENTATION","",$h_file,"",$c_file);
944 if (@$export[4] & $FLAG_I386)
946 push (@{$comment->{TEXT}}, "", "Available on x86 platforms only.");
948 if (@$export[4] & $FLAG_REGISTER)
950 push (@{$comment->{TEXT}}, "", "This function passes one or more arguments in registers. ",
951 "For more details, please read the source code.");
953 my $source_details = $source_files{$comment->{FILE}}[0];
954 if ($source_details->{DEBUG_CHANNEL} ne "")
956 push (@{$comment->{TEXT}}, "", "Debug channel \"".$source_details->{DEBUG_CHANNEL}."\".");
959 # Write out the documentation for the API
960 output_comment($comment)
963 # process our extra comment and output it if it is suitable.
964 sub process_extra_comment
966 my $comment = shift(@_);
968 my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
970 if (!defined($spec_details))
972 if ($opt_verbose > 2)
974 print "Warning: Extra comment '".$comment->{COMMENT_NAME}."' belongs to '".
975 $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
977 return;
980 # Check first to see if this is documentation for the DLL.
981 if ($comment->{COMMENT_NAME} eq $comment->{DLL_NAME})
983 if ($opt_verbose > 2)
985 print "Info: Found DLL documentation\n";
987 for (@{$comment->{TEXT}})
989 push (@{$spec_details->{DESCRIPTION}}, $_);
991 return;
994 # Add the comment to the DLL page as a link
995 push (@{$spec_details->{EXTRA_COMMENTS}},$comment->{COMMENT_NAME});
997 # If we have a prototype, process as a regular comment
998 if (@{$comment->{PROTOTYPE}})
1000 $comment->{ORDINAL} = "@";
1002 # Add an index for the comment name
1003 $spec_details->{EXPORTED_NAMES}{$comment->{COMMENT_NAME}} = $spec_details->{NUM_EXPORTS};
1005 # Add a fake exported entry
1006 $spec_details->{NUM_EXPORTS}++;
1007 my ($ordinal, $call_convention, $exported_name, $implementation_name, $documented) =
1008 ("@", "fake", $comment->{COMMENT_NAME}, $comment->{COMMENT_NAME}, 0);
1009 my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented);
1010 push (@{$spec_details->{EXPORTS}},[@export]);
1011 @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
1012 process_comment($comment);
1013 return;
1016 if ($opt_verbose > 0)
1018 print "Processing ",$comment->{COMMENT_NAME},"\n";
1021 if (@{$spec_details->{CURRENT_EXTRA}})
1023 my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
1025 if ($opt_verbose > 0)
1027 print "Processing old current: ",$current_comment->{COMMENT_NAME},"\n";
1029 # Output the current comment
1030 process_comment_text($current_comment);
1031 output_open_api_file($current_comment->{COMMENT_NAME});
1032 output_api_header($current_comment);
1033 output_api_name($current_comment);
1034 output_api_comment($current_comment);
1035 output_api_footer($current_comment);
1036 output_close_api_file();
1039 if ($opt_verbose > 2)
1041 print "Setting current to ",$comment->{COMMENT_NAME},"\n";
1044 my $comment_copy =
1046 FILE => $comment->{FILE},
1047 COMMENT_NAME => $comment->{COMMENT_NAME},
1048 ALT_NAME => $comment->{ALT_NAME},
1049 DLL_NAME => $comment->{DLL_NAME},
1050 ORDINAL => $comment->{ORDINAL},
1051 RETURNS => $comment->{RETURNS},
1052 PROTOTYPE => [],
1053 TEXT => [],
1056 for (@{$comment->{TEXT}})
1058 push (@{$comment_copy->{TEXT}}, $_);
1060 # Set this comment to be the current extra comment
1061 @{$spec_details->{CURRENT_EXTRA}} = ($comment_copy);
1064 # Write a standardised comment out in the appropriate format
1065 sub output_comment
1067 my $comment = shift(@_);
1069 if ($opt_verbose > 0)
1071 print "Processing ",$comment->{COMMENT_NAME},"\n";
1074 if ($opt_verbose > 4)
1076 print "--PROTO--\n";
1077 for (@{$comment->{PROTOTYPE}})
1079 print "'".$_."'\n";
1082 print "--COMMENT--\n";
1083 for (@{$comment->{TEXT} })
1085 print $_."\n";
1089 output_open_api_file($comment->{COMMENT_NAME});
1090 output_api_header($comment);
1091 output_api_name($comment);
1092 output_api_synopsis($comment);
1093 output_api_comment($comment);
1094 output_api_footer($comment);
1095 output_close_api_file();
1098 # Write out an index file for each .spec processed
1099 sub process_index_files
1101 foreach my $spec_file (keys %spec_files)
1103 my $spec_details = $spec_files{$spec_file}[0];
1104 if (defined ($spec_details->{DLL_NAME}))
1106 if (@{$spec_details->{CURRENT_EXTRA}})
1108 # We have an unwritten extra comment, write it
1109 my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
1110 process_extra_comment($current_comment);
1111 @{$spec_details->{CURRENT_EXTRA}} = ();
1113 output_spec($spec_details);
1118 # Write a spec files documentation out in the appropriate format
1119 sub output_spec
1121 my $spec_details = shift(@_);
1123 if ($opt_verbose > 2)
1125 print "Writing:",$spec_details->{DLL_NAME},"\n";
1128 # Use the comment output functions for consistency
1129 my $comment =
1131 FILE => $spec_details->{DLL_NAME},
1132 COMMENT_NAME => $spec_details->{DLL_NAME}.".dll",
1133 ALT_NAME => $spec_details->{DLL_NAME},
1134 DLL_NAME => "",
1135 ORDINAL => "",
1136 RETURNS => "",
1137 PROTOTYPE => [],
1138 TEXT => [],
1140 my $total_implemented = $spec_details->{NUM_FORWARDS} + $spec_details->{NUM_VARS} +
1141 $spec_details->{NUM_FUNCS};
1142 my $percent_implemented = 0;
1143 if ($total_implemented)
1145 $percent_implemented = $total_implemented /
1146 ($total_implemented + $spec_details->{NUM_STUBS}) * 100;
1148 $percent_implemented = int($percent_implemented);
1149 my $percent_documented = 0;
1150 if ($spec_details->{NUM_DOCS})
1152 # Treat forwards and data as documented funcs for statistics
1153 $percent_documented = $spec_details->{NUM_DOCS} / $spec_details->{NUM_FUNCS} * 100;
1154 $percent_documented = int($percent_documented);
1157 # Make a list of the contributors to this DLL. Do this only for the source
1158 # files that make up the DLL, because some directories specify multiple dlls.
1159 my @contributors;
1161 for (@{$spec_details->{SOURCES}})
1163 my $source_details = $source_files{$_}[0];
1164 for (@{$source_details->{CONTRIBUTORS}})
1166 push (@contributors, $_);
1170 my %saw;
1171 @contributors = grep(!$saw{$_}++, @contributors); # remove dups, from perlfaq4 manpage
1172 @contributors = sort @contributors;
1174 # Remove duplicates and blanks
1175 for(my $i=0; $i<@contributors; $i++)
1177 if ($i > 0 && ($contributors[$i] =~ /$contributors[$i-1]/ || $contributors[$i-1] eq ""))
1179 $contributors[$i-1] = $contributors[$i];
1182 undef %saw;
1183 @contributors = grep(!$saw{$_}++, @contributors);
1185 if ($opt_verbose > 3)
1187 print "Contributors:\n";
1188 for (@contributors)
1190 print "'".$_."'\n";
1193 my $contribstring = join (", ", @contributors);
1195 # Create the initial comment text
1196 @{$comment->{TEXT}} = (
1197 "NAME",
1198 $comment->{COMMENT_NAME}
1201 # Add the description, if we have one
1202 if (@{$spec_details->{DESCRIPTION}})
1204 push (@{$comment->{TEXT}}, "DESCRIPTION");
1205 for (@{$spec_details->{DESCRIPTION}})
1207 push (@{$comment->{TEXT}}, $_);
1211 # Add the statistics and contributors
1212 push (@{$comment->{TEXT}},
1213 "STATISTICS",
1214 "Forwards: ".$spec_details->{NUM_FORWARDS},
1215 "Variables: ".$spec_details->{NUM_VARS},
1216 "Stubs: ".$spec_details->{NUM_STUBS},
1217 "Functions: ".$spec_details->{NUM_FUNCS},
1218 "Exports-Total: ".$spec_details->{NUM_EXPORTS},
1219 "Implemented-Total: ".$total_implemented." (".$percent_implemented."%)",
1220 "Documented-Total: ".$spec_details->{NUM_DOCS}." (".$percent_documented."%)",
1221 "CONTRIBUTORS",
1222 "The following people hold copyrights on the source files comprising this dll:",
1224 $contribstring,
1225 "Note: This list may not be complete.",
1226 "For a complete listing, see the Files \"AUTHORS\" and \"Changelog\" in the Wine source tree.",
1230 if ($opt_output_format eq "h")
1232 # Add the exports to the comment text
1233 push (@{$comment->{TEXT}},"EXPORTS");
1234 my $exports = $spec_details->{EXPORTS};
1235 for (@$exports)
1237 my $line = "";
1239 # @$_ => ordinal, call convention, exported name, implementation name, flags;
1240 if (@$_[1] eq "forward")
1242 my $forward_dll = @$_[3];
1243 $forward_dll =~ s/\.(.*)//;
1244 $line = @$_[2]." (forward to ".$1."() in ".$forward_dll."())";
1246 elsif (@$_[1] eq "extern")
1248 $line = @$_[2]." (extern)";
1250 elsif (@$_[1] eq "stub")
1252 $line = @$_[2]." (stub)";
1254 elsif (@$_[1] eq "fake")
1256 # Don't add this function here, it gets listed with the extra documentation
1257 if (!(@$_[4] & $FLAG_WPAIR))
1259 # This function should be indexed
1260 push (@index_entries_list, @$_[3].",".@$_[3]);
1263 elsif (@$_[1] eq "equate" || @$_[1] eq "variable")
1265 $line = @$_[2]." (data)";
1267 else
1269 # A function
1270 if (@$_[4] & $FLAG_DOCUMENTED)
1272 # Documented
1273 $line = @$_[2]." (implemented as ".@$_[3]."())";
1274 if (@$_[2] ne @$_[3])
1276 $line = @$_[2]." (implemented as ".@$_[3]."())";
1278 else
1280 $line = @$_[2]."()";
1282 if (!(@$_[4] & $FLAG_WPAIR))
1284 # This function should be indexed
1285 push (@index_entries_list, @$_[2].",".@$_[3]);
1288 else
1290 $line = @$_[2]." (not documented)";
1293 if ($line ne "")
1295 push (@{$comment->{TEXT}}, $line, "");
1299 # Add links to the extra documentation
1300 if (@{$spec_details->{EXTRA_COMMENTS}})
1302 push (@{$comment->{TEXT}}, "SEE ALSO");
1303 my %htmp;
1304 @{$spec_details->{EXTRA_COMMENTS}} = grep(!$htmp{$_}++, @{$spec_details->{EXTRA_COMMENTS}});
1305 for (@{$spec_details->{EXTRA_COMMENTS}})
1307 push (@{$comment->{TEXT}}, $_."()", "");
1311 # The dll entry should also be indexed
1312 push (@index_entries_list, $spec_details->{DLL_NAME}.",".$spec_details->{DLL_NAME});
1314 # Write out the document
1315 output_open_api_file($spec_details->{DLL_NAME});
1316 output_api_header($comment);
1317 output_api_comment($comment);
1318 output_api_footer($comment);
1319 output_close_api_file();
1321 # Add this dll to the database of dll names
1322 my $output_file = $opt_output_directory."/dlls.db";
1324 # Append the dllname to the output db of names
1325 open(DLLDB,">>$output_file") || die "Couldn't create $output_file\n";
1326 print DLLDB $spec_details->{DLL_NAME},"\n";
1327 close(DLLDB);
1329 if ($opt_output_format eq "s")
1331 output_sgml_dll_file($spec_details);
1332 return;
1337 # OUTPUT FUNCTIONS
1338 # ----------------
1339 # Only these functions know anything about formatting for a specific
1340 # output type. The functions above work only with plain text.
1341 # This is to allow new types of output to be added easily.
1343 # Open the api file
1344 sub output_open_api_file
1346 my $output_name = shift(@_);
1347 $output_name = $opt_output_directory."/".$output_name;
1349 if ($opt_output_format eq "h")
1351 $output_name = $output_name.".html";
1353 elsif ($opt_output_format eq "s")
1355 $output_name = $output_name.".sgml";
1357 else
1359 $output_name = $output_name.".".$opt_manual_section;
1361 open(OUTPUT,">$output_name") || die "Couldn't create file '$output_name'\n";
1364 # Close the api file
1365 sub output_close_api_file
1367 close (OUTPUT);
1370 # Output the api file header
1371 sub output_api_header
1373 my $comment = shift(@_);
1375 if ($opt_output_format eq "h")
1377 print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1378 print OUTPUT "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
1379 print OUTPUT "<HTML>\n<HEAD>\n";
1380 print OUTPUT "<LINK REL=\"StyleSheet\" href=\"apidoc.css\" type=\"text/css\">\n";
1381 print OUTPUT "<META NAME=\"GENERATOR\" CONTENT=\"tools/c2man.pl\">\n";
1382 print OUTPUT "<META NAME=\"keywords\" CONTENT=\"Win32,Wine,API,$comment->{COMMENT_NAME}\">\n";
1383 print OUTPUT "<TITLE>Wine API: $comment->{COMMENT_NAME}</TITLE>\n</HEAD>\n<BODY>\n";
1385 elsif ($opt_output_format eq "s")
1387 print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n",
1388 "<sect1>\n",
1389 "<title>$comment->{COMMENT_NAME}</title>\n";
1391 else
1393 print OUTPUT ".\\\" -*- nroff -*-\n.\\\" Generated file - DO NOT EDIT!\n".
1394 ".TH ",$comment->{COMMENT_NAME}," ",$opt_manual_section," \"",$date,"\" \"".
1395 "Wine API\" \"Wine API\"\n";
1399 sub output_api_footer
1401 if ($opt_output_format eq "h")
1403 print OUTPUT "<hr><p><i class=\"copy\">Copyright &copy ".$year." The Wine Project.".
1404 " All trademarks are the property of their respective owners.".
1405 " Visit <a HREF=http://www.winehq.org>WineHQ</a> for license details.".
1406 " Generated $date.</i></p>\n</body>\n</html>\n";
1408 elsif ($opt_output_format eq "s")
1410 print OUTPUT "</sect1>\n";
1411 return;
1413 else
1418 sub output_api_section_start
1420 my $comment = shift(@_);
1421 my $section_name = shift(@_);
1423 if ($opt_output_format eq "h")
1425 print OUTPUT "\n<p><h2 class=\"section\">",$section_name,"</h2></p>\n";
1427 elsif ($opt_output_format eq "s")
1429 print OUTPUT "<bridgehead>",$section_name,"</bridgehead>\n";
1431 else
1433 print OUTPUT "\n\.SH ",$section_name,"\n";
1437 sub output_api_section_end
1439 # Not currently required by any output formats
1442 sub output_api_name
1444 my $comment = shift(@_);
1446 output_api_section_start($comment,"NAME");
1448 my $dll_ordinal = "";
1449 if ($comment->{ORDINAL} ne "")
1451 $dll_ordinal = "(".$comment->{DLL_NAME}.".".$comment->{ORDINAL}.")";
1453 if ($opt_output_format eq "h")
1455 print OUTPUT "<p><b class=\"func_name\">",$comment->{COMMENT_NAME},
1456 "</b>&nbsp;&nbsp;<i class=\"dll_ord\">",
1457 ,$dll_ordinal,"</i></p>\n";
1459 elsif ($opt_output_format eq "s")
1461 print OUTPUT "<para>\n <command>",$comment->{COMMENT_NAME},"</command> <emphasis>",
1462 $dll_ordinal,"</emphasis>\n</para>\n";
1464 else
1466 print OUTPUT "\\fB",$comment->{COMMENT_NAME},"\\fR ",$dll_ordinal;
1469 output_api_section_end();
1472 sub output_api_synopsis
1474 my $comment = shift(@_);
1475 my @fmt;
1477 output_api_section_start($comment,"SYNOPSIS");
1479 if ($opt_output_format eq "h")
1481 print OUTPUT "<p><pre class=\"proto\">\n ", $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1482 @fmt = ("", "\n", "<tt class=\"param\">", "</tt>");
1484 elsif ($opt_output_format eq "s")
1486 print OUTPUT "<screen>\n ",$comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1487 @fmt = ("", "\n", "<emphasis>", "</emphasis>");
1489 else
1491 print OUTPUT $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1492 @fmt = ("", "\n", "\\fI", "\\fR");
1495 # Since our prototype is output in a pre-formatted block, line up the
1496 # parameters and parameter comments in the same column.
1498 # First caluculate where the columns should start
1499 my $biggest_length = 0;
1500 for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1502 my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1503 if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1505 my $length = length $1;
1506 if ($length > $biggest_length)
1508 $biggest_length = $length;
1513 # Now pad the string with blanks
1514 for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1516 my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1517 if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1519 my $pad_len = $biggest_length - length $1;
1520 my $padding = " " x ($pad_len);
1521 ${@{$comment->{PROTOTYPE}}}[$i] = $1.$padding.$2;
1525 for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1527 # Format the parameter name
1528 my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1529 my $comma = ($i == @{$comment->{PROTOTYPE}}-1) ? "" : ",";
1530 $line =~ s/(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ $fmt[0]$1$fmt[2]$2$fmt[3]$comma$fmt[1]/;
1531 print OUTPUT $line;
1534 if ($opt_output_format eq "h")
1536 print OUTPUT " )\n\n</pre></p>\n";
1538 elsif ($opt_output_format eq "s")
1540 print OUTPUT " )\n</screen>\n";
1542 else
1544 print OUTPUT " )\n";
1547 output_api_section_end();
1550 sub output_api_comment
1552 my $comment = shift(@_);
1553 my $open_paragraph = 0;
1554 my $open_raw = 0;
1555 my $param_docs = 0;
1556 my @fmt;
1558 if ($opt_output_format eq "h")
1560 @fmt = ("<p>", "</p>\n", "<tt class=\"const\">", "</tt>", "<b class=\"emp\">", "</b>",
1561 "<tt class=\"coderef\">", "</tt>", "<tt class=\"param\">", "</tt>",
1562 "<i class=\"in_out\">", "</i>", "<pre class=\"raw\">\n", "</pre>\n",
1563 "<table class=\"tab\"><colgroup><col><col><col></colgroup><tbody>\n",
1564 "</tbody></table>\n","<tr><td>","</td></tr>\n","</td>","</td><td>");
1566 elsif ($opt_output_format eq "s")
1568 @fmt = ("<para>\n","\n</para>\n","<constant>","</constant>","<emphasis>","</emphasis>",
1569 "<command>","</command>","<constant>","</constant>","<emphasis>","</emphasis>",
1570 "<screen>\n","</screen>\n",
1571 "<informaltable frame=\"none\">\n<tgroup cols=\"3\">\n<tbody>\n",
1572 "</tbody>\n</tgroup>\n</informaltable>\n","<row><entry>","</entry></row>\n",
1573 "</entry>","</entry><entry>");
1575 else
1577 @fmt = ("\.PP\n", "\n", "\\fB", "\\fR", "\\fB", "\\fR", "\\fB", "\\fR", "\\fI", "\\fR",
1578 "\\fB", "\\fR ", "", "", "", "","","\n.PP\n","","");
1581 # Extract the parameter names
1582 my @parameter_names;
1583 for (@{$comment->{PROTOTYPE}})
1585 if ( /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ )
1587 push (@parameter_names, $2);
1591 for (@{$comment->{TEXT}})
1593 if ($opt_output_format eq "h" || $opt_output_format eq "s")
1595 # Map special characters
1596 s/\&/\&amp;/g;
1597 s/\</\&lt;/g;
1598 s/\>/\&gt;/g;
1599 s/\([Cc]\)/\&copy;/g;
1600 s/\(tm\)/&#174;/;
1603 if ( s/^\|// )
1605 # Raw output
1606 if ($open_raw == 0)
1608 if ($open_paragraph == 1)
1610 # Close the open paragraph
1611 print OUTPUT $fmt[1];
1612 $open_paragraph = 0;
1614 # Start raw output
1615 print OUTPUT $fmt[12];
1616 $open_raw = 1;
1618 if ($opt_output_format eq "")
1620 print OUTPUT ".br\n"; # Prevent 'man' running these lines together
1622 print OUTPUT $_,"\n";
1624 else
1626 # Highlight strings
1627 s/(\".+?\")/$fmt[2]$1$fmt[3]/g;
1628 # Highlight literal chars
1629 s/(\'.\')/$fmt[2]$1$fmt[3]/g;
1630 s/(\'.{2}\')/$fmt[2]$1$fmt[3]/g;
1631 # Highlight numeric constants
1632 s/( |\-|\+|\.|\()([0-9\-\.]+)( |\-|$|\.|\,|\*|\?|\))/$1$fmt[2]$2$fmt[3]$3/g;
1634 # Leading cases ("xxxx:","-") start new paragraphs & are emphasised
1635 # FIXME: Using bullet points for leading '-' would look nicer.
1636 if ($open_paragraph == 1)
1638 s/^(\-)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1639 s/^([[A-Za-z\-]+\:)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1641 else
1643 s/^(\-)/$fmt[4]$1$fmt[5]/;
1644 s/^([[A-Za-z\-]+\:)/$fmt[4]$1$fmt[5]/;
1647 if ($opt_output_format eq "h")
1649 # Html uses links for API calls
1650 s/([A-Za-z_]+[A-Za-z_0-9]+)(\(\))/<a href\=\"$1\.html\">$1<\/a>/g;
1651 # Index references
1652 s/\{\{(.*?)\}\}\{\{(.*?)\}\}/<a href\=\"$2\.html\">$1<\/a>/g;
1653 s/ ([A-Z_])(\(\))/<a href\=\"$1\.html\">$1<\/a>/g;
1654 # And references to COM objects (hey, they'll get documented one day)
1655 s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ <a href\=\"$1\.html\">$1<\/a> $2/g;
1656 # Convert any web addresses to real links
1657 s/(http\:\/\/)(.+?)($| )/<a href\=\"$1$2\">$2<\/a>$3/g;
1659 else
1661 if ($opt_output_format eq "")
1663 # Give the man section for API calls
1664 s/ ([A-Za-z_]+[A-Za-z_0-9]+)\(\)/ $fmt[6]$1\($opt_manual_section\)$fmt[7]/g;
1666 else
1668 # Highlight API calls
1669 s/ ([A-Za-z_]+[A-Za-z_0-9]+\(\))/ $fmt[6]$1$fmt[7]/g;
1672 # And references to COM objects
1673 s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ $fmt[6]$1$fmt[7] $2/g;
1676 if ($open_raw == 1)
1678 # Finish the raw output
1679 print OUTPUT $fmt[13];
1680 $open_raw = 0;
1683 if ( /^[A-Z]+$/ || /^SEE ALSO$/ )
1685 # Start of a new section
1686 if ($open_paragraph == 1)
1688 if ($param_docs == 1)
1690 print OUTPUT $fmt[17],$fmt[15];
1692 else
1694 print OUTPUT $fmt[1];
1696 $open_paragraph = 0;
1698 output_api_section_start($comment,$_);
1699 if ( /^PARAMS$/ )
1701 print OUTPUT $fmt[14];
1702 $param_docs = 1;
1704 else
1706 #print OUTPUT $fmt[15];
1707 $param_docs = 0;
1710 elsif ( /^$/ )
1712 # Empty line, indicating a new paragraph
1713 if ($open_paragraph == 1)
1715 if ($param_docs == 0)
1717 print OUTPUT $fmt[1];
1718 $open_paragraph = 0;
1722 else
1724 if ($param_docs == 1)
1726 if ($open_paragraph == 1)
1728 # For parameter docs, put each parameter into a new paragraph/table row
1729 print OUTPUT $fmt[17];
1730 $open_paragraph = 0;
1732 s/(\[.+\])( *)/$fmt[19]$fmt[10]$1$fmt[11]$fmt[19] /; # Format In/Out
1734 else
1736 # Within paragraph lines, prevent lines running together
1737 $_ = $_." ";
1740 # Format parameter names where they appear in the comment
1741 for my $parameter_name (@parameter_names)
1743 s/(^|[ \.\,\(\-\*])($parameter_name)($|[ \.\)\,\-\=\/])/$1$fmt[8]$2$fmt[9]$3/g;
1745 # Structure dereferences include the dereferenced member
1746 s/(\-\>[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g;
1747 s/(\-\&gt\;[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g;
1749 if ($open_paragraph == 0)
1751 if ($param_docs == 1)
1753 print OUTPUT $fmt[16];
1755 else
1757 print OUTPUT $fmt[0];
1759 $open_paragraph = 1;
1761 # Anything in all uppercase on its own gets emphasised
1762 s/(^|[ \.\,\(\[\|\=])([A-Z]+?[A-Z0-9_]+)($|[ \.\,\*\?\|\)\=\'])/$1$fmt[6]$2$fmt[7]$3/g;
1764 print OUTPUT $_;
1768 if ($open_raw == 1)
1770 print OUTPUT $fmt[13];
1772 if ($open_paragraph == 1)
1774 print OUTPUT $fmt[1];
1778 # Create the master index file
1779 sub output_master_index_files
1781 if ($opt_output_format eq "")
1783 return; # No master index for man pages
1786 if ($opt_output_format eq "h")
1788 # Append the index entries to the output db of index entries
1789 my $output_file = $opt_output_directory."/index.db";
1790 open(INDEXDB,">>$output_file") || die "Couldn't create $output_file\n";
1791 for (@index_entries_list)
1793 $_ =~ s/A\,/\,/;
1794 print INDEXDB $_."\n";
1796 close(INDEXDB);
1799 # Use the comment output functions for consistency
1800 my $comment =
1802 FILE => "",
1803 COMMENT_NAME => "The Wine Api Guide",
1804 ALT_NAME => "The Wine Api Guide",
1805 DLL_NAME => "",
1806 ORDINAL => "",
1807 RETURNS => "",
1808 PROTOTYPE => [],
1809 TEXT => [],
1812 if ($opt_output_format eq "s")
1814 $comment->{COMMENT_NAME} = "Introduction";
1815 $comment->{ALT_NAME} = "Introduction",
1817 elsif ($opt_output_format eq "h")
1819 @{$comment->{TEXT}} = (
1820 "NAME",
1821 $comment->{COMMENT_NAME},
1822 "INTRODUCTION",
1826 # Create the initial comment text
1827 push (@{$comment->{TEXT}},
1828 "This document describes the Api calls made available",
1829 "by Wine. They are grouped by the dll that exports them.",
1831 "Please do not edit this document, since it is generated automatically",
1832 "from the Wine source code tree. Details on updating this documentation",
1833 "are given in the \"Wine Developers Guide\".",
1834 "CONTRIBUTORS",
1835 "Api documentation is generally written by the person who ",
1836 "implements a given Api call. Authors of each dll are listed in the overview ",
1837 "section for that dll. Additional contributors who have updated source files ",
1838 "but have not entered their names in a copyright statement are noted by an ",
1839 "entry in the file \"Changelog\" from the Wine source code distribution.",
1843 # Read in all dlls from the database of dll names
1844 my $input_file = $opt_output_directory."/dlls.db";
1845 my @dlls = `cat $input_file|sort|uniq`;
1847 if ($opt_output_format eq "h")
1849 # HTML gets a list of all the dlls and an index. For docbook the index creates this for us
1850 push (@{$comment->{TEXT}},
1851 "INDEX",
1852 "For an alphabetical listing of the functions available, please click the ",
1853 "first letter of the functions name below:","",
1854 "[ _(), A(), B(), C(), D(), E(), F(), G(), H(), ".
1855 "I(), J(), K(), L(), M(), N(), O(), P(), Q() ".
1856 "R(), S(), T(), U(), V(), W(), X(), Y(), Z() ]", "",
1857 "DLLS",
1858 "Each dll provided by Wine is documented individually. The following dlls are provided :",
1861 # Add the dlls to the comment
1862 for (@dlls)
1864 $_ =~ s/(\..*)?\n/\(\)/;
1865 push (@{$comment->{TEXT}}, $_, "");
1867 output_open_api_file("index");
1869 elsif ($opt_output_format eq "s")
1871 # Just write this as the initial blurb, with a chapter heading
1872 output_open_api_file("blurb");
1873 print OUTPUT "<chapter id =\"blurb\">\n<title>Introduction to The Wine Api Guide</title>\n"
1876 # Write out the document
1877 output_api_header($comment);
1878 output_api_comment($comment);
1879 output_api_footer($comment);
1880 if ($opt_output_format eq "s")
1882 print OUTPUT "</chapter>\n" # finish the chapter
1884 output_close_api_file();
1886 if ($opt_output_format eq "s")
1888 output_sgml_master_file(\@dlls);
1889 return;
1891 if ($opt_output_format eq "h")
1893 output_html_index_files();
1894 output_html_stylesheet();
1895 return;
1899 # Write the master wine-api.sgml, linking it to each dll.
1900 sub output_sgml_master_file
1902 my $dlls = shift(@_);
1904 output_open_api_file("wine-api");
1905 print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1906 print OUTPUT "<!doctype book PUBLIC \"-//OASIS//DTD DocBook V3.1//EN\" [\n\n";
1907 print OUTPUT "<!entity blurb SYSTEM \"blurb.sgml\">\n";
1909 # List the entities
1910 for (@$dlls)
1912 $_ =~ s/(\..*)?\n//;
1913 print OUTPUT "<!entity ",$_," SYSTEM \"",$_,".sgml\">\n"
1916 print OUTPUT "]>\n\n<book id=\"index\">\n<bookinfo><title>The Wine Api Guide</title></bookinfo>\n\n";
1917 print OUTPUT " &blurb;\n";
1919 for (@$dlls)
1921 print OUTPUT " &",$_,";\n"
1923 print OUTPUT "\n\n</book>\n";
1925 output_close_api_file();
1928 # Produce the sgml for the dll chapter from the generated files
1929 sub output_sgml_dll_file
1931 my $spec_details = shift(@_);
1933 # Make a list of all the documentation files to include
1934 my $exports = $spec_details->{EXPORTS};
1935 my @source_files = ();
1936 for (@$exports)
1938 # @$_ => ordinal, call convention, exported name, implementation name, documented;
1939 if (@$_[1] ne "forward" && @$_[1] ne "extern" && @$_[1] ne "stub" && @$_[1] ne "equate" &&
1940 @$_[1] ne "variable" && @$_[1] ne "fake" && @$_[4] & 1)
1942 # A documented function
1943 push (@source_files,@$_[3]);
1947 push (@source_files,@{$spec_details->{EXTRA_COMMENTS}});
1949 @source_files = sort @source_files;
1951 # create a new chapter for this dll
1952 my $tmp_name = $opt_output_directory."/".$spec_details->{DLL_NAME}.".tmp";
1953 open(OUTPUT,">$tmp_name") || die "Couldn't create $tmp_name\n";
1954 print OUTPUT "<chapter>\n<title>$spec_details->{DLL_NAME}</title>\n";
1955 output_close_api_file();
1957 # Add the sorted documentation, cleaning up as we go
1958 `cat $opt_output_directory/$spec_details->{DLL_NAME}.sgml >>$tmp_name`;
1959 for (@source_files)
1961 `cat $opt_output_directory/$_.sgml >>$tmp_name`;
1962 `rm -f $opt_output_directory/$_.sgml`;
1965 # close the chapter, and overwite the dll source
1966 open(OUTPUT,">>$tmp_name") || die "Couldn't create $tmp_name\n";
1967 print OUTPUT "</chapter>\n";
1968 close OUTPUT;
1969 `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.sgml`;
1972 # Write the html index files containing the function names
1973 sub output_html_index_files
1975 if ($opt_output_format ne "h")
1977 return;
1980 my @letters = ('_', 'A' .. 'Z');
1982 # Read in all functions
1983 my $input_file = $opt_output_directory."/index.db";
1984 my @funcs = `cat $input_file|sort|uniq`;
1986 for (@letters)
1988 my $letter = $_;
1989 my $comment =
1991 FILE => "",
1992 COMMENT_NAME => "",
1993 ALT_NAME => "",
1994 DLL_NAME => "",
1995 ORDINAL => "",
1996 RETURNS => "",
1997 PROTOTYPE => [],
1998 TEXT => [],
2001 $comment->{COMMENT_NAME} = $letter." Functions";
2002 $comment->{ALT_NAME} = $letter." Functions";
2004 push (@{$comment->{TEXT}},
2005 "NAME",
2006 $comment->{COMMENT_NAME},
2007 "FUNCTIONS"
2010 # Add the functions to the comment
2011 for (@funcs)
2013 my $first_char = substr ($_, 0, 1);
2014 $first_char = uc $first_char;
2016 if ($first_char eq $letter)
2018 my $name = $_;
2019 my $file;
2020 $name =~ s/(^.*?)\,(.*?)\n/$1/;
2021 $file = $2;
2022 push (@{$comment->{TEXT}}, "{{".$name."}}{{".$file."}}","");
2026 # Write out the document
2027 output_open_api_file($letter);
2028 output_api_header($comment);
2029 output_api_comment($comment);
2030 output_api_footer($comment);
2031 output_close_api_file();
2035 # Output the stylesheet for HTML output
2036 sub output_html_stylesheet
2038 if ($opt_output_format ne "h")
2040 return;
2043 my $css;
2044 ($css = <<HERE_TARGET) =~ s/^\s+//gm;
2046 * Default styles for Wine HTML Documentation.
2048 * This style sheet should be altered to suit your needs/taste.
2050 BODY { /* Page body */
2051 background-color: white;
2052 color: black;
2053 font-family: Tahoma,sans-serif;
2054 font-style: normal;
2055 font-size: 10pt;
2057 a:link { color: #4444ff; } /* Links */
2058 a:visited { color: #333377 }
2059 a:active { color: #0000dd }
2060 H2.section { /* Section Headers */
2061 font-family: sans-serif;
2062 color: #777777;
2063 background-color: #F0F0FE;
2064 margin-left: 0.2in;
2065 margin-right: 1.0in;
2067 b.func_name { /* Function Name */
2068 font-size: 10pt;
2069 font-style: bold;
2071 i.dll_ord { /* Italicised DLL+ordinal */
2072 color: #888888;
2073 font-family: sans-serif;
2074 font-size: 8pt;
2076 p { /* Paragraphs */
2077 margin-left: 0.5in;
2078 margin-right: 0.5in;
2080 table { /* tables */
2081 margin-left: 0.5in;
2082 margin-right: 0.5in;
2084 pre.proto /* API Function prototype */
2086 border-style: solid;
2087 border-width: 1px;
2088 border-color: #777777;
2089 background-color: #F0F0BB;
2090 color: black;
2091 font-size: 10pt;
2092 vertical-align: top;
2093 margin-left: 0.5in;
2094 margin-right: 1.0in;
2096 pre.raw { /* Raw text output */
2097 margin-left: 0.6in;
2098 margin-right: 1.1in;
2099 background-color: #8080DC;
2101 tt.param { /* Parameter name */
2102 font-style: italic;
2103 color: blue;
2105 tt.const { /* Constant */
2106 color: red;
2108 i.in_out { /* In/Out */
2109 font-size: 8pt;
2110 color: grey;
2112 tt.coderef { /* Code in description text */
2113 color: darkgreen;
2115 b.emp /* Emphasis */ {
2116 font-style: bold;
2117 color: darkblue;
2119 i.footer { /* Footer */
2120 font-family: sans-serif;
2121 font-size: 6pt;
2122 color: darkgrey;
2124 HERE_TARGET
2126 my $output_file = "$opt_output_directory/apidoc.css";
2127 open(CSS,">$output_file") || die "Couldn't create the file $output_file\n";
2128 print CSS $css;
2129 close(CSS);