Fixed potential crash in fd_dump function.
[wine/multimedia.git] / tools / c2man.pl
blob829f8cceabf966810efe0040fc5fafa4387502d0
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 # Consolidate A+W pairs together, and only write one doc, without the suffix
24 # Implement automatic docs fo structs/defines in headers
25 # SGML gurus - feel free to smarten up the SGML.
26 # Add any other relevant information for the dll - imports etc
27 # Should we have a special output mode for WineHQ?
29 use strict;
30 use bytes;
32 # Function flags. most of these come from the spec flags
33 my $FLAG_DOCUMENTED = 1;
34 my $FLAG_NONAME = 2;
35 my $FLAG_I386 = 4;
36 my $FLAG_REGISTER = 8;
37 my $FLAG_APAIR = 16; # The A version of a matching W function
38 my $FLAG_WPAIR = 32; # The W version of a matching A function
39 my $FLAG_64PAIR = 64; # The 64 bit version of a matching 32 bit function
42 # Options
43 my $opt_output_directory = "man3w"; # All default options are for nroff (man pages)
44 my $opt_manual_section = "3w";
45 my $opt_wine_root_dir = "";
46 my $opt_output_format = ""; # '' = nroff, 'h' = html, 's' = sgml
47 my $opt_output_empty = 0; # Non-zero = Create 'empty' comments (for every implemented function)
48 my $opt_fussy = 1; # Non-zero = Create only if we have a RETURNS section
49 my $opt_verbose = 0; # >0 = verbosity. Can be given multiple times (for debugging)
50 my @opt_header_file_list = ();
51 my @opt_spec_file_list = ();
52 my @opt_source_file_list = ();
54 # All the collected details about all the .spec files being processed
55 my %spec_files;
56 # All the collected details about all the source files being processed
57 my %source_files;
58 # All documented functions that are to be placed in the index
59 my @index_entries_list = ();
61 # useful globals
62 my $pwd = `pwd`."/";
63 $pwd =~ s/\n//;
64 my @datetime = localtime;
65 my @months = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun",
66 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
67 my $year = $datetime[5] + 1900;
68 my $date = "$months[$datetime[4]] $year";
71 sub output_api_comment($);
72 sub output_api_footer($);
73 sub output_api_header($);
74 sub output_api_name($);
75 sub output_api_synopsis($);
76 sub output_close_api_file();
77 sub output_comment($);
78 sub output_html_index_files();
79 sub output_html_stylesheet();
80 sub output_open_api_file($);
81 sub output_sgml_dll_file($);
82 sub output_sgml_master_file($);
83 sub output_spec($);
84 sub process_comment($);
85 sub process_extra_comment($);
88 # Generate the list of exported entries for the dll
89 sub process_spec_file($)
91 my $spec_name = shift;
92 my $dll_name = $spec_name;
93 $dll_name =~ s/\..*//; # Strip the file extension
94 my $uc_dll_name = uc $dll_name;
96 my $spec_details =
98 NAME => $spec_name,
99 DLL_NAME => $dll_name,
100 NUM_EXPORTS => 0,
101 NUM_STUBS => 0,
102 NUM_FUNCS => 0,
103 NUM_FORWARDS => 0,
104 NUM_VARS => 0,
105 NUM_DOCS => 0,
106 CONTRIBUTORS => [ ],
107 SOURCES => [ ],
108 DESCRIPTION => [ ],
109 EXPORTS => [ ],
110 EXPORTED_NAMES => { },
111 IMPLEMENTATION_NAMES => { },
112 EXTRA_COMMENTS => [ ],
113 CURRENT_EXTRA => [ ] ,
116 if ($opt_verbose > 0)
118 print "Processing ".$spec_name."\n";
121 # We allow opening to fail just to cater for the peculiarities of
122 # the Wine build system. This doesn't hurt, in any case
123 open(SPEC_FILE, "<$spec_name") || return;
125 while(<SPEC_FILE>)
127 s/^\s+//; # Strip leading space
128 s/\s+\n$/\n/; # Strip trailing space
129 s/\s+/ /g; # Strip multiple tabs & spaces to a single space
130 s/\s*#.*//; # Strip comments
131 s/\(.*\)/ /; # Strip arguments
132 s/\s+/ /g; # Strip multiple tabs & spaces to a single space (again)
133 s/\n$//; # Strip newline
135 my $flags = 0;
136 if( /\-noname/ )
138 $flags |= $FLAG_NONAME;
140 if( /\-i386/ )
142 $flags |= $FLAG_I386;
144 if( /\-register/ )
146 $flags |= $FLAG_REGISTER;
148 s/ \-[a-z0-9]+//g; # Strip flags
150 if( /^(([0-9]+)|@) / )
152 # This line contains an exported symbol
153 my ($ordinal, $call_convention, $exported_name, $implementation_name) = split(' ');
155 for ($call_convention)
157 /^(cdecl|stdcall|varargs|pascal)$/
158 && do { $spec_details->{NUM_FUNCS}++; last; };
159 /^(variable|equate)$/
160 && do { $spec_details->{NUM_VARS}++; last; };
161 /^(extern)$/
162 && do { $spec_details->{NUM_FORWARDS}++; last; };
163 /^stub$/ && do { $spec_details->{NUM_STUBS}++; last; };
164 if ($opt_verbose > 0)
166 print "Warning: didn't recognise convention \'",$call_convention,"'\n";
168 last;
171 # Convert ordinal only names so we can find them later
172 if ($exported_name eq "@")
174 $exported_name = $uc_dll_name.'_'.$ordinal;
176 if (!defined($implementation_name))
178 $implementation_name = $exported_name;
180 if ($implementation_name eq "")
182 $implementation_name = $exported_name;
185 if ($implementation_name =~ /(.*?)\./)
187 $call_convention = "forward"; # Referencing a function from another dll
188 $spec_details->{NUM_FUNCS}--;
189 $spec_details->{NUM_FORWARDS}++;
192 # Add indices for the exported and implementation names
193 $spec_details->{EXPORTED_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
194 if ($implementation_name ne $exported_name)
196 $spec_details->{IMPLEMENTATION_NAMES}{$exported_name} = $spec_details->{NUM_EXPORTS};
199 # Add the exported entry
200 $spec_details->{NUM_EXPORTS}++;
201 my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $flags);
202 push (@{$spec_details->{EXPORTS}},[@export]);
205 close(SPEC_FILE);
207 # Add this .spec files details to the list of .spec files
208 $spec_files{$uc_dll_name} = [$spec_details];
211 # Read each source file, extract comments, and generate API documentation if appropriate
212 sub process_source_file($)
214 my $source_file = shift;
215 my $source_details =
217 CONTRIBUTORS => [ ],
218 DEBUG_CHANNEL => "",
220 my $comment =
222 FILE => $source_file,
223 COMMENT_NAME => "",
224 ALT_NAME => "",
225 DLL_NAME => "",
226 ORDINAL => "",
227 RETURNS => "",
228 PROTOTYPE => [],
229 TEXT => [],
231 my $parse_state = 0;
232 my $ignore_blank_lines = 1;
233 my $extra_comment = 0; # 1 if this is an extra comment, i.e its not a .spec export
235 if ($opt_verbose > 0)
237 print "Processing ".$source_file."\n";
239 open(SOURCE_FILE,"<$source_file") || die "couldn't open ".$source_file."\n";
241 # Add this source file to the list of source files
242 $source_files{$source_file} = [$source_details];
244 while(<SOURCE_FILE>)
246 s/\n$//; # Strip newline
247 s/^\s+//; # Strip leading space
248 s/\s+$//; # Strip trailing space
249 if (! /^\*\|/ )
251 # Strip multiple tabs & spaces to a single space
252 s/\s+/ /g;
255 if ( / +Copyright *(\([Cc]\))*[0-9 \-\,\/]*([[:alpha:][:^ascii:] \.\-]+)/ )
257 # Extract a contributor to this file
258 my $contributor = $2;
259 $contributor =~ s/ *$//;
260 $contributor =~ s/^by //;
261 $contributor =~ s/\.$//;
262 $contributor =~ s/ (for .*)/ \($1\)/;
263 if ($contributor ne "")
265 if ($opt_verbose > 3)
267 print "Info: Found contributor:'".$contributor."'\n";
269 push (@{$source_details->{CONTRIBUTORS}},$contributor);
272 elsif ( /WINE_DEFAULT_DEBUG_CHANNEL\(([A-Za-z]*)\)/ )
274 # Extract the debug channel to use
275 if ($opt_verbose > 3)
277 print "Info: Found debug channel:'".$1."'\n";
279 $source_details->{DEBUG_CHANNEL} = $1;
282 if ($parse_state == 0) # Searching for a comment
284 if ( /^\/\**$/ )
286 # Found a comment start
287 $comment->{COMMENT_NAME} = "";
288 $comment->{ALT_NAME} = "";
289 $comment->{DLL_NAME} = "";
290 $comment->{ORDINAL} = "";
291 $comment->{RETURNS} = "";
292 $comment->{PROTOTYPE} = [];
293 $comment->{TEXT} = [];
294 $ignore_blank_lines = 1;
295 $extra_comment = 0;
296 $parse_state = 3;
299 elsif ($parse_state == 1) # Reading in a comment
301 if ( /^\**\// )
303 # Found the end of the comment
304 $parse_state = 2;
306 elsif ( s/^\*\|/\|/ )
308 # A line of comment not meant to be pre-processed
309 push (@{$comment->{TEXT}},$_); # Add the comment text
311 elsif ( s/^ *\** *// )
313 # A line of comment, starting with an asterisk
314 if ( /^[A-Z]+$/ || $_ eq "")
316 # This is a section start, so skip blank lines before and after it.
317 my $last_line = pop(@{$comment->{TEXT}});
318 if (defined($last_line) && $last_line ne "")
320 # Put it back
321 push (@{$comment->{TEXT}},$last_line);
323 if ( /^[A-Z]+$/ )
325 $ignore_blank_lines = 1;
327 else
329 $ignore_blank_lines = 0;
333 if ($ignore_blank_lines == 0 || $_ ne "")
335 push (@{$comment->{TEXT}},$_); # Add the comment text
338 else
340 # This isn't a well formatted comment: look for the next one
341 $parse_state = 0;
344 elsif ($parse_state == 2) # Finished reading in a comment
346 if ( /(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ ||
347 /.*?\(/ )
349 # Comment is followed by a function definition
350 $parse_state = 4; # Fall through to read prototype
352 else
354 # Allow cpp directives and blank lines between the comment and prototype
355 if ($extra_comment == 1)
357 # An extra comment not followed by a function definition
358 $parse_state = 5; # Fall through to process comment
360 elsif (!/^\#/ && !/^ *$/ && !/^__ASM_GLOBAL_FUNC/)
362 # This isn't a well formatted comment: look for the next one
363 if ($opt_verbose > 1)
365 print "Info: Function '",$comment->{COMMENT_NAME},"' not followed by prototype.\n";
367 $parse_state = 0;
371 elsif ($parse_state == 3) # Reading in the first line of a comment
373 s/^ *\** *//;
374 if ( /^([\@A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])\s*(.*)$/ )
376 # Found a correctly formed "ApiName (DLLNAME.Ordinal)" line.
377 if (defined ($7) && $7 ne "")
379 push (@{$comment->{TEXT}},$_); # Add the trailing comment text
381 $comment->{COMMENT_NAME} = $1;
382 $comment->{DLL_NAME} = uc $3;
383 $comment->{ORDINAL} = $4;
384 $comment->{DLL_NAME} =~ s/^KERNEL$/KRNL386/; # Too many of these to ignore, _old_ code
385 $parse_state = 1;
387 elsif ( /^([A-Za-z0-9_-]+) +\{([A-Za-z0-9_]+)\}$/ )
389 # Found a correctly formed "CommentTitle {DLLNAME}" line (extra documentation)
390 $comment->{COMMENT_NAME} = $1;
391 $comment->{DLL_NAME} = uc $2;
392 $comment->{ORDINAL} = "";
393 $extra_comment = 1;
394 $parse_state = 1;
396 else
398 # This isn't a well formatted comment: look for the next one
399 $parse_state = 0;
403 if ($parse_state == 4) # Reading in the function definition
405 push (@{$comment->{PROTOTYPE}},$_);
406 # Strip comments from the line before checking for ')'
407 my $stripped_line = $_;
408 $stripped_line =~ s/ *(\/\* *)(.*?)( *\*\/ *)//;
409 if ( $stripped_line =~ /\)/ )
411 # Strip a blank last line
412 my $last_line = pop(@{$comment->{TEXT}});
413 if (defined($last_line) && $last_line ne "")
415 # Put it back
416 push (@{$comment->{TEXT}},$last_line);
419 if ($opt_output_empty != 0 && @{$comment->{TEXT}} == 0)
421 # Create a 'not implemented' comment
422 @{$comment->{TEXT}} = ("fixme: This function has not yet been documented.");
424 $parse_state = 5;
428 if ($parse_state == 5) # Processing the comment
430 # Process it, if it has any text
431 if (@{$comment->{TEXT}} > 0)
433 if ($extra_comment == 1)
435 process_extra_comment($comment);
437 else
439 @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
440 process_comment($comment);
443 elsif ($opt_verbose > 1 && $opt_output_empty == 0)
445 print "Info: Function '",$comment->{COMMENT_NAME},"' has no documentation.\n";
447 $parse_state = 0;
450 close(SOURCE_FILE);
453 # Standardise a comments text for consistency
454 sub process_comment_text($)
456 my $comment = shift;
457 my $in_params = 0;
458 my @tmp_list = ();
459 my $i = 0;
461 for (@{$comment->{TEXT}})
463 my $line = $_;
465 if ( /^\s*$/ || /^[A-Z]+$/ || /^-/ )
467 $in_params = 0;
469 if ( $in_params > 0 && !/\[/ && !/\]/ )
471 # Possibly a continuation of the parameter description
472 my $last_line = pop(@tmp_list);
473 if ( $last_line =~ /\[/ && $last_line =~ /\]/ )
475 $line = $last_line." ".$_;
477 else
479 $in_params = 0;
480 push (@tmp_list, $last_line);
483 if ( /^(PARAMS|MEMBERS)$/ )
485 $in_params = 1;
487 push (@tmp_list, $line);
490 @{$comment->{TEXT}} = @tmp_list;
492 for (@{$comment->{TEXT}})
494 if (! /^\|/ )
496 # Map I/O values. These come in too many formats to standardise now....
497 s/\[I\]|\[i\]|\[in\]|\[IN\]/\[In\] /g;
498 s/\[O\]|\[o\]|\[out\]|\[OUT\]/\[Out\]/g;
499 s/\[I\/O\]|\[I\,O\]|\[i\/o\]|\[in\/out\]|\[IN\/OUT\]/\[In\/Out\]/g;
500 # TRUE/FALSE/NULL are defines, capitilise them
501 s/True|true/TRUE/g;
502 s/False|false/FALSE/g;
503 s/Null|null/NULL/g;
504 # Preferred capitalisations
505 s/ wine| WINE/ Wine/g;
506 s/ API | api / Api /g;
507 s/DLL|Dll/dll /g;
508 s/ URL | url / Url /g;
509 s/WIN16|win16/Win16/g;
510 s/WIN32|win32/Win32/g;
511 s/WIN64|win64/Win64/g;
512 s/ ID | id / Id /g;
513 # Grammar
514 s/([a-z])\.([A-Z])/$1\. $2/g; # Space after full stop
515 s/ \:/\:/g; # Colons to the left
516 s/ \;/\;/g; # Semi-colons too
517 # Common idioms
518 s/^See ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Referring to A version from W
519 s/^Unicode version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Ditto
520 s/^64\-bit version of ([A-Za-z0-9_]+)\.$/See $1\(\)\./; # Referring to 32 bit version from 64
521 s/^PARAMETERS$/PARAMS/; # Name of parameter section should be 'PARAMS'
522 # Trademarks
523 s/( |\.)(M\$|MS|Microsoft|microsoft|micro\$oft|Micro\$oft)( |\.)/$1Microsoft\(tm\)$3/g;
524 s/( |\.)(Windows|windows|windoze|winblows)( |\.)/$1Windows\(tm\)$3/g;
525 s/( |\.)(DOS|dos|msdos)( |\.)/$1MS-DOS\(tm\)$3/g;
526 s/( |\.)(UNIX|unix)( |\.)/$1Unix\(tm\)$3/g;
527 s/( |\.)(LINIX|linux)( |\.)/$1Linux\(tm\)$3/g;
528 # Abbreviations
529 s/( char )/ character /g;
530 s/( chars )/ characters /g;
531 s/( info )/ information /g;
532 s/( app )/ application /g;
533 s/( apps )/ applications /g;
534 s/( exe )/ executable /g;
535 s/( ptr )/ pointer /g;
536 s/( obj )/ object /g;
537 s/( err )/ error /g;
538 s/( bool )/ boolean /g;
539 s/( no\. )/ number /g;
540 s/( No\. )/ Number /g;
541 # Punctuation
542 if ( /\[I|\[O/ && ! /\.$/ )
544 $_ = $_."."; # Always have a full stop at the end of parameter desc.
546 elsif ($i > 0 && /^[A-Z]*$/ &&
547 !(@{$comment->{TEXT}}[$i-1] =~ /\.$/) &&
548 !(@{$comment->{TEXT}}[$i-1] =~ /\:$/))
551 if (!(@{$comment->{TEXT}}[$i-1] =~ /^[A-Z]*$/))
553 # Paragraphs always end with a full stop
554 @{$comment->{TEXT}}[$i-1] = @{$comment->{TEXT}}[$i-1].".";
558 $i++;
562 # Standardise our comment and output it if it is suitable.
563 sub process_comment($)
565 my $comment = shift;
567 # Don't process this comment if the function isn't exported
568 my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
570 if (!defined($spec_details))
572 if ($opt_verbose > 2)
574 print "Warning: Function '".$comment->{COMMENT_NAME}."' belongs to '".
575 $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
577 return;
580 if ($comment->{COMMENT_NAME} eq "@")
582 my $found = 0;
584 # Find the name from the .spec file
585 for (@{$spec_details->{EXPORTS}})
587 if (@$_[0] eq $comment->{ORDINAL})
589 $comment->{COMMENT_NAME} = @$_[2];
590 $found = 1;
594 if ($found == 0)
596 # Create an implementation name
597 $comment->{COMMENT_NAME} = $comment->{DLL_NAME}."_".$comment->{ORDINAL};
601 my $exported_names = $spec_details->{EXPORTED_NAMES};
602 my $export_index = $exported_names->{$comment->{COMMENT_NAME}};
603 my $implementation_names = $spec_details->{IMPLEMENTATION_NAMES};
605 if (!defined($export_index))
607 # Perhaps the comment uses the implementation name?
608 $export_index = $implementation_names->{$comment->{COMMENT_NAME}};
610 if (!defined($export_index))
612 # This function doesn't appear to be exported. hmm.
613 if ($opt_verbose > 2)
615 print "Warning: Function '".$comment->{COMMENT_NAME}."' claims to belong to '".
616 $comment->{DLL_NAME}."' but is not exported by it: not processing it.\n";
618 return;
621 # When the function is exported twice we have the second name below the first
622 # (you see this a lot in ntdll, but also in some other places).
623 my $first_line = ${@{$comment->{TEXT}}}[1];
625 if ( $first_line =~ /^(@|[A-Za-z0-9_]+) +(\(|\[)([A-Za-z0-9_]+)\.(([0-9]+)|@)(\)|\])$/ )
627 # Found a second name - mark it as documented
628 my $alt_index = $exported_names->{$1};
629 if (defined($alt_index))
631 if ($opt_verbose > 2)
633 print "Info: Found alternate name '",$1,"\n";
635 my $alt_export = @{$spec_details->{EXPORTS}}[$alt_index];
636 @$alt_export[4] |= $FLAG_DOCUMENTED;
637 $spec_details->{NUM_DOCS}++;
638 ${@{$comment->{TEXT}}}[1] = "";
642 if (@{$spec_details->{CURRENT_EXTRA}})
644 # We have an extra comment that might be related to this one
645 my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
646 my $current_name = $current_comment->{COMMENT_NAME};
647 if ($comment->{COMMENT_NAME} =~ /^$current_name/ && $comment->{COMMENT_NAME} ne $current_name)
649 if ($opt_verbose > 2)
651 print "Linking ",$comment->{COMMENT_NAME}," to $current_name\n";
653 # Add a reference to this comment to our extra comment
654 push (@{$current_comment->{TEXT}}, $comment->{COMMENT_NAME}."()","");
658 # We want our docs generated using the implementation name, so they are unique
659 my $export = @{$spec_details->{EXPORTS}}[$export_index];
660 $comment->{COMMENT_NAME} = @$export[3];
661 $comment->{ALT_NAME} = @$export[2];
663 # Mark the function as documented
664 $spec_details->{NUM_DOCS}++;
665 @$export[4] |= $FLAG_DOCUMENTED;
667 # This file is used by the DLL - Make sure we get our contributors right
668 push (@{$spec_details->{SOURCES}},$comment->{FILE});
670 # If we have parameter comments in the prototype, extract them
671 my @parameter_comments;
672 for (@{$comment->{PROTOTYPE}})
674 s/ *\, */\,/g; # Strip spaces from around commas
676 if ( s/ *(\/\* *)(.*?)( *\*\/ *)// ) # Strip out comment
678 my $parameter_comment = $2;
679 if (!$parameter_comment =~ /^\[/ )
681 # Add [IO] markers so we format the comment correctly
682 $parameter_comment = "[fixme] ".$parameter_comment;
684 if ( /( |\*)([A-Za-z_]{1}[A-Za-z_0-9]*)(\,|\))/ )
686 # Add the parameter name
687 $parameter_comment = $2." ".$parameter_comment;
689 push (@parameter_comments, $parameter_comment);
693 # If we extracted any prototype comments, add them to the comment text.
694 if (@parameter_comments)
696 @parameter_comments = ("PARAMS", @parameter_comments);
697 my @new_comment = ();
698 my $inserted_params = 0;
700 for (@{$comment->{TEXT}})
702 if ( $inserted_params == 0 && /^[A-Z]+$/ )
704 # Found a section header, so this is where we insert
705 push (@new_comment, @parameter_comments);
706 $inserted_params = 1;
708 push (@new_comment, $_);
710 if ($inserted_params == 0)
712 # Add them to the end
713 push (@new_comment, @parameter_comments);
715 $comment->{TEXT} = [@new_comment];
718 if ($opt_fussy == 1 && $opt_output_empty == 0)
720 # Reject any comment that doesn't have a description or a RETURNS section.
721 # This is the default for now, 'coz many comments aren't suitable.
722 my $found_returns = 0;
723 my $found_description_text = 0;
724 my $in_description = 0;
725 for (@{$comment->{TEXT}})
727 if ( /^RETURNS$/ )
729 $found_returns = 1;
730 $in_description = 0;
732 elsif ( /^DESCRIPTION$/ )
734 $in_description = 1;
736 elsif ($in_description == 1)
738 if ( !/^[A-Z]+$/ )
740 # Don't reject comments that refer to another doc (e.g. A/W)
741 if ( /^See ([A-Za-z0-9_]+)\.$/ )
743 if ($comment->{COMMENT_NAME} =~ /W$/ )
745 # This is probably a Unicode version of an Ascii function.
746 # Create the Ascii name and see if its been documented
747 my $ascii_name = $comment->{COMMENT_NAME};
748 $ascii_name =~ s/W$/A/;
750 my $ascii_export_index = $exported_names->{$ascii_name};
752 if (!defined($ascii_export_index))
754 $ascii_export_index = $implementation_names->{$ascii_name};
756 if (!defined($ascii_export_index))
758 if ($opt_verbose > 2)
760 print "Warning: Function '".$comment->{COMMENT_NAME}."' is not an A/W pair.\n";
763 else
765 my $ascii_export = @{$spec_details->{EXPORTS}}[$ascii_export_index];
766 if (@$ascii_export[4] & $FLAG_DOCUMENTED)
768 # Flag these functions as an A/W pair
769 @$ascii_export[4] |= $FLAG_APAIR;
770 @$export[4] |= $FLAG_WPAIR;
774 $found_returns = 1;
776 elsif ( /^Unicode version of ([A-Za-z0-9_]+)\.$/ )
778 @$export[4] |= $FLAG_WPAIR; # Explicitly marked as W version
779 $found_returns = 1;
781 elsif ( /^64\-bit version of ([A-Za-z0-9_]+)\.$/ )
783 @$export[4] |= $FLAG_64PAIR; # Explicitly marked as 64 bit version
784 $found_returns = 1;
786 $found_description_text = 1;
788 else
790 $in_description = 0;
794 if ($found_returns == 0 || $found_description_text == 0)
796 if ($opt_verbose > 2)
798 print "Info: Function '",$comment->{COMMENT_NAME},"' has no ",
799 "description and/or RETURNS section, skipping\n";
801 $spec_details->{NUM_DOCS}--;
802 @$export[4] &= ~$FLAG_DOCUMENTED;
803 return;
807 process_comment_text($comment);
809 # Strip the prototypes return value, call convention, name and brackets
810 # (This leaves it as a list of types and names, or empty for void functions)
811 my $prototype = join(" ", @{$comment->{PROTOTYPE}});
812 $prototype =~ s/ / /g;
814 if ( $prototype =~ /(WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16)/ )
816 $prototype =~ s/^(.*?) (WINAPIV|WINAPI|__cdecl|PASCAL|CALLBACK|FARPROC16) (.*?)\( *(.*)/$4/;
817 $comment->{RETURNS} = $1;
819 else
821 $prototype =~ s/^(.*?)([A-Za-z0-9_]+)\( *(.*)/$3/;
822 $comment->{RETURNS} = $1;
825 $prototype =~ s/ *\).*//; # Strip end bracket
826 $prototype =~ s/ *\* */\*/g; # Strip space around pointers
827 $prototype =~ s/ *\, */\,/g; # Strip space around commas
828 $prototype =~ s/^(void|VOID)$//; # If void, leave blank
829 $prototype =~ s/\*([A-Za-z_])/\* $1/g; # Separate pointers from parameter name
830 @{$comment->{PROTOTYPE}} = split ( /,/ ,$prototype);
832 # FIXME: If we have no parameters, make sure we have a PARAMS: None. section
834 # Find header file
835 my $h_file = "";
836 if (@$export[4] & $FLAG_NONAME)
838 $h_file = "Exported by ordinal only. Use GetProcAddress() to obtain a pointer to the function.";
840 else
842 if ($comment->{COMMENT_NAME} ne "")
844 my $tmp = "grep -s -l $comment->{COMMENT_NAME} @opt_header_file_list 2>/dev/null";
845 $tmp = `$tmp`;
846 my $exit_value = $? >> 8;
847 if ($exit_value == 0)
849 $tmp =~ s/\n.*//g;
850 if ($tmp ne "")
852 $h_file = `basename $tmp`;
856 elsif ($comment->{ALT_NAME} ne "")
858 my $tmp = "grep -s -l $comment->{ALT_NAME} @opt_header_file_list"." 2>/dev/null";
859 $tmp = `$tmp`;
860 my $exit_value = $? >> 8;
861 if ($exit_value == 0)
863 $tmp =~ s/\n.*//g;
864 if ($tmp ne "")
866 $h_file = `basename $tmp`;
870 $h_file =~ s/^ *//;
871 $h_file =~ s/\n//;
872 if ($h_file eq "")
874 $h_file = "Not defined in a Wine header. The function is either undocumented, or missing from Wine."
876 else
878 $h_file = "Defined in \"".$h_file."\".";
882 # Find source file
883 my $c_file = $comment->{FILE};
884 if ($opt_wine_root_dir ne "")
886 my $cfile = $pwd."/".$c_file; # Current dir + file
887 $cfile =~ s/(.+)(\/.*$)/$1/; # Strip the filename
888 $cfile = `cd $cfile && pwd`; # Strip any relative parts (e.g. "../../")
889 $cfile =~ s/\n//; # Strip newline
890 my $newfile = $c_file;
891 $newfile =~ s/(.+)(\/.*$)/$2/; # Strip all but the filename
892 $cfile = $cfile."/".$newfile; # Append filename to base path
893 $cfile =~ s/$opt_wine_root_dir//; # Get rid of the root directory
894 $cfile =~ s/\/\//\//g; # Remove any double slashes
895 $cfile =~ s/^\/+//; # Strip initial directory slash
896 $c_file = $cfile;
898 $c_file = "Implemented in \"".$c_file."\".";
900 # Add the implementation details
901 push (@{$comment->{TEXT}}, "IMPLEMENTATION","",$h_file,"",$c_file);
903 if (@$export[4] & $FLAG_I386)
905 push (@{$comment->{TEXT}}, "", "Available on x86 platforms only.");
907 if (@$export[4] & $FLAG_REGISTER)
909 push (@{$comment->{TEXT}}, "", "This function passes one or more arguments in registers. ",
910 "For more details, please read the source code.");
912 my $source_details = $source_files{$comment->{FILE}}[0];
913 if ($source_details->{DEBUG_CHANNEL} ne "")
915 push (@{$comment->{TEXT}}, "", "Debug channel \"".$source_details->{DEBUG_CHANNEL}."\".");
918 # Write out the documentation for the API
919 output_comment($comment)
922 # process our extra comment and output it if it is suitable.
923 sub process_extra_comment($)
925 my $comment = shift;
927 my $spec_details = $spec_files{$comment->{DLL_NAME}}[0];
929 if (!defined($spec_details))
931 if ($opt_verbose > 2)
933 print "Warning: Extra comment '".$comment->{COMMENT_NAME}."' belongs to '".
934 $comment->{DLL_NAME}."' (not passed with -w): not processing it.\n";
936 return;
939 # Check first to see if this is documentation for the DLL.
940 if ($comment->{COMMENT_NAME} eq $comment->{DLL_NAME})
942 if ($opt_verbose > 2)
944 print "Info: Found DLL documentation\n";
946 for (@{$comment->{TEXT}})
948 push (@{$spec_details->{DESCRIPTION}}, $_);
950 return;
953 # Add the comment to the DLL page as a link
954 push (@{$spec_details->{EXTRA_COMMENTS}},$comment->{COMMENT_NAME});
956 # If we have a prototype, process as a regular comment
957 if (@{$comment->{PROTOTYPE}})
959 $comment->{ORDINAL} = "@";
961 # Add an index for the comment name
962 $spec_details->{EXPORTED_NAMES}{$comment->{COMMENT_NAME}} = $spec_details->{NUM_EXPORTS};
964 # Add a fake exported entry
965 $spec_details->{NUM_EXPORTS}++;
966 my ($ordinal, $call_convention, $exported_name, $implementation_name, $documented) =
967 ("@", "fake", $comment->{COMMENT_NAME}, $comment->{COMMENT_NAME}, 0);
968 my @export = ($ordinal, $call_convention, $exported_name, $implementation_name, $documented);
969 push (@{$spec_details->{EXPORTS}},[@export]);
970 @{$comment->{TEXT}} = ("DESCRIPTION", @{$comment->{TEXT}});
971 process_comment($comment);
972 return;
975 if ($opt_verbose > 0)
977 print "Processing ",$comment->{COMMENT_NAME},"\n";
980 if (@{$spec_details->{CURRENT_EXTRA}})
982 my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
984 if ($opt_verbose > 0)
986 print "Processing old current: ",$current_comment->{COMMENT_NAME},"\n";
988 # Output the current comment
989 process_comment_text($current_comment);
990 output_open_api_file($current_comment->{COMMENT_NAME});
991 output_api_header($current_comment);
992 output_api_name($current_comment);
993 output_api_comment($current_comment);
994 output_api_footer($current_comment);
995 output_close_api_file();
998 if ($opt_verbose > 2)
1000 print "Setting current to ",$comment->{COMMENT_NAME},"\n";
1003 my $comment_copy =
1005 FILE => $comment->{FILE},
1006 COMMENT_NAME => $comment->{COMMENT_NAME},
1007 ALT_NAME => $comment->{ALT_NAME},
1008 DLL_NAME => $comment->{DLL_NAME},
1009 ORDINAL => $comment->{ORDINAL},
1010 RETURNS => $comment->{RETURNS},
1011 PROTOTYPE => [],
1012 TEXT => [],
1015 for (@{$comment->{TEXT}})
1017 push (@{$comment_copy->{TEXT}}, $_);
1019 # Set this comment to be the current extra comment
1020 @{$spec_details->{CURRENT_EXTRA}} = ($comment_copy);
1023 # Write a standardised comment out in the appropriate format
1024 sub output_comment($)
1026 my $comment = shift;
1028 if ($opt_verbose > 0)
1030 print "Processing ",$comment->{COMMENT_NAME},"\n";
1033 if ($opt_verbose > 4)
1035 print "--PROTO--\n";
1036 for (@{$comment->{PROTOTYPE}})
1038 print "'".$_."'\n";
1041 print "--COMMENT--\n";
1042 for (@{$comment->{TEXT} })
1044 print $_."\n";
1048 output_open_api_file($comment->{COMMENT_NAME});
1049 output_api_header($comment);
1050 output_api_name($comment);
1051 output_api_synopsis($comment);
1052 output_api_comment($comment);
1053 output_api_footer($comment);
1054 output_close_api_file();
1057 # Write out an index file for each .spec processed
1058 sub process_index_files()
1060 foreach my $spec_file (keys %spec_files)
1062 my $spec_details = $spec_files{$spec_file}[0];
1063 if (defined ($spec_details->{DLL_NAME}))
1065 if (@{$spec_details->{CURRENT_EXTRA}})
1067 # We have an unwritten extra comment, write it
1068 my $current_comment = ${@{$spec_details->{CURRENT_EXTRA}}}[0];
1069 process_extra_comment($current_comment);
1070 @{$spec_details->{CURRENT_EXTRA}} = ();
1072 output_spec($spec_details);
1077 # Write a spec files documentation out in the appropriate format
1078 sub output_spec($)
1080 my $spec_details = shift;
1082 if ($opt_verbose > 2)
1084 print "Writing:",$spec_details->{DLL_NAME},"\n";
1087 # Use the comment output functions for consistency
1088 my $comment =
1090 FILE => $spec_details->{DLL_NAME},
1091 COMMENT_NAME => $spec_details->{DLL_NAME}.".dll",
1092 ALT_NAME => $spec_details->{DLL_NAME},
1093 DLL_NAME => "",
1094 ORDINAL => "",
1095 RETURNS => "",
1096 PROTOTYPE => [],
1097 TEXT => [],
1099 my $total_implemented = $spec_details->{NUM_FORWARDS} + $spec_details->{NUM_VARS} +
1100 $spec_details->{NUM_FUNCS};
1101 my $percent_implemented = 0;
1102 if ($total_implemented)
1104 $percent_implemented = $total_implemented /
1105 ($total_implemented + $spec_details->{NUM_STUBS}) * 100;
1107 $percent_implemented = int($percent_implemented);
1108 my $percent_documented = 0;
1109 if ($spec_details->{NUM_DOCS})
1111 # Treat forwards and data as documented funcs for statistics
1112 $percent_documented = $spec_details->{NUM_DOCS} / $spec_details->{NUM_FUNCS} * 100;
1113 $percent_documented = int($percent_documented);
1116 # Make a list of the contributors to this DLL. Do this only for the source
1117 # files that make up the DLL, because some directories specify multiple dlls.
1118 my @contributors;
1120 for (@{$spec_details->{SOURCES}})
1122 my $source_details = $source_files{$_}[0];
1123 for (@{$source_details->{CONTRIBUTORS}})
1125 push (@contributors, $_);
1129 my %saw;
1130 @contributors = grep(!$saw{$_}++, @contributors); # remove dups, from perlfaq4 manpage
1131 @contributors = sort @contributors;
1133 # Remove duplicates and blanks
1134 for(my $i=0; $i<@contributors; $i++)
1136 if ($i > 0 && ($contributors[$i] =~ /$contributors[$i-1]/ || $contributors[$i-1] eq ""))
1138 $contributors[$i-1] = $contributors[$i];
1141 undef %saw;
1142 @contributors = grep(!$saw{$_}++, @contributors);
1144 if ($opt_verbose > 3)
1146 print "Contributors:\n";
1147 for (@contributors)
1149 print "'".$_."'\n";
1152 my $contribstring = join (", ", @contributors);
1154 # Create the initial comment text
1155 @{$comment->{TEXT}} = (
1156 "NAME",
1157 $comment->{COMMENT_NAME}
1160 # Add the description, if we have one
1161 if (@{$spec_details->{DESCRIPTION}})
1163 push (@{$comment->{TEXT}}, "DESCRIPTION");
1164 for (@{$spec_details->{DESCRIPTION}})
1166 push (@{$comment->{TEXT}}, $_);
1170 # Add the statistics and contributors
1171 push (@{$comment->{TEXT}},
1172 "STATISTICS",
1173 "Forwards: ".$spec_details->{NUM_FORWARDS},
1174 "Variables: ".$spec_details->{NUM_VARS},
1175 "Stubs: ".$spec_details->{NUM_STUBS},
1176 "Functions: ".$spec_details->{NUM_FUNCS},
1177 "Exports-Total: ".$spec_details->{NUM_EXPORTS},
1178 "Implemented-Total: ".$total_implemented." (".$percent_implemented."%)",
1179 "Documented-Total: ".$spec_details->{NUM_DOCS}." (".$percent_documented."%)",
1180 "CONTRIBUTORS",
1181 "The following people hold copyrights on the source files comprising this dll:",
1183 $contribstring,
1184 "Note: This list may not be complete.",
1185 "For a complete listing, see the Files \"AUTHORS\" and \"Changelog\" in the Wine source tree.",
1189 if ($opt_output_format eq "h")
1191 # Add the exports to the comment text
1192 push (@{$comment->{TEXT}},"EXPORTS");
1193 my $exports = $spec_details->{EXPORTS};
1194 for (@$exports)
1196 my $line = "";
1198 # @$_ => ordinal, call convention, exported name, implementation name, flags;
1199 if (@$_[1] eq "forward")
1201 my $forward_dll = @$_[3];
1202 $forward_dll =~ s/\.(.*)//;
1203 $line = @$_[2]." (forward to ".$1."() in ".$forward_dll."())";
1205 elsif (@$_[1] eq "extern")
1207 $line = @$_[2]." (extern)";
1209 elsif (@$_[1] eq "stub")
1211 $line = @$_[2]." (stub)";
1213 elsif (@$_[1] eq "fake")
1215 # Don't add this function here, it gets listed with the extra documentation
1216 if (!(@$_[4] & $FLAG_WPAIR))
1218 # This function should be indexed
1219 push (@index_entries_list, @$_[3].",".@$_[3]);
1222 elsif (@$_[1] eq "equate" || @$_[1] eq "variable")
1224 $line = @$_[2]." (data)";
1226 else
1228 # A function
1229 if (@$_[4] & $FLAG_DOCUMENTED)
1231 # Documented
1232 $line = @$_[2]." (implemented as ".@$_[3]."())";
1233 if (@$_[2] ne @$_[3])
1235 $line = @$_[2]." (implemented as ".@$_[3]."())";
1237 else
1239 $line = @$_[2]."()";
1241 if (!(@$_[4] & $FLAG_WPAIR))
1243 # This function should be indexed
1244 push (@index_entries_list, @$_[2].",".@$_[3]);
1247 else
1249 $line = @$_[2]." (not documented)";
1252 if ($line ne "")
1254 push (@{$comment->{TEXT}}, $line, "");
1258 # Add links to the extra documentation
1259 if (@{$spec_details->{EXTRA_COMMENTS}})
1261 push (@{$comment->{TEXT}}, "SEE ALSO");
1262 my %htmp;
1263 @{$spec_details->{EXTRA_COMMENTS}} = grep(!$htmp{$_}++, @{$spec_details->{EXTRA_COMMENTS}});
1264 for (@{$spec_details->{EXTRA_COMMENTS}})
1266 push (@{$comment->{TEXT}}, $_."()", "");
1270 # The dll entry should also be indexed
1271 push (@index_entries_list, $spec_details->{DLL_NAME}.",".$spec_details->{DLL_NAME});
1273 # Write out the document
1274 output_open_api_file($spec_details->{DLL_NAME});
1275 output_api_header($comment);
1276 output_api_comment($comment);
1277 output_api_footer($comment);
1278 output_close_api_file();
1280 # Add this dll to the database of dll names
1281 my $output_file = $opt_output_directory."/dlls.db";
1283 # Append the dllname to the output db of names
1284 open(DLLDB,">>$output_file") || die "Couldn't create $output_file\n";
1285 print DLLDB $spec_details->{DLL_NAME},"\n";
1286 close(DLLDB);
1288 if ($opt_output_format eq "s")
1290 output_sgml_dll_file($spec_details);
1291 return;
1296 # OUTPUT FUNCTIONS
1297 # ----------------
1298 # Only these functions know anything about formatting for a specific
1299 # output type. The functions above work only with plain text.
1300 # This is to allow new types of output to be added easily.
1302 # Open the api file
1303 sub output_open_api_file($)
1305 my $output_name = shift;
1306 $output_name = $opt_output_directory."/".$output_name;
1308 if ($opt_output_format eq "h")
1310 $output_name = $output_name.".html";
1312 elsif ($opt_output_format eq "s")
1314 $output_name = $output_name.".sgml";
1316 else
1318 $output_name = $output_name.".".$opt_manual_section;
1320 open(OUTPUT,">$output_name") || die "Couldn't create file '$output_name'\n";
1323 # Close the api file
1324 sub output_close_api_file()
1326 close (OUTPUT);
1329 # Output the api file header
1330 sub output_api_header($)
1332 my $comment = shift;
1334 if ($opt_output_format eq "h")
1336 print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1337 print OUTPUT "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n";
1338 print OUTPUT "<HTML>\n<HEAD>\n";
1339 print OUTPUT "<LINK REL=\"StyleSheet\" href=\"apidoc.css\" type=\"text/css\">\n";
1340 print OUTPUT "<META NAME=\"GENERATOR\" CONTENT=\"tools/c2man.pl\">\n";
1341 print OUTPUT "<META NAME=\"keywords\" CONTENT=\"Win32,Wine,API,$comment->{COMMENT_NAME}\">\n";
1342 print OUTPUT "<TITLE>Wine API: $comment->{COMMENT_NAME}</TITLE>\n</HEAD>\n<BODY>\n";
1344 elsif ($opt_output_format eq "s")
1346 print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n",
1347 "<sect1>\n",
1348 "<title>$comment->{COMMENT_NAME}</title>\n";
1350 else
1352 print OUTPUT ".\\\" -*- nroff -*-\n.\\\" Generated file - DO NOT EDIT!\n".
1353 ".TH ",$comment->{COMMENT_NAME}," ",$opt_manual_section," \"",$date,"\" \"".
1354 "Wine API\" \"Wine API\"\n";
1358 sub output_api_footer($)
1360 if ($opt_output_format eq "h")
1362 print OUTPUT "<hr><p><i class=\"copy\">Copyright &copy ".$year." The Wine Project.".
1363 " All trademarks are the property of their respective owners.".
1364 " Visit <a HREF=http://www.winehq.org>WineHQ</a> for license details.".
1365 " Generated $date.</i></p>\n</body>\n</html>\n";
1367 elsif ($opt_output_format eq "s")
1369 print OUTPUT "</sect1>\n";
1370 return;
1372 else
1377 sub output_api_section_start($$)
1379 my $comment = shift;
1380 my $section_name = shift;
1382 if ($opt_output_format eq "h")
1384 print OUTPUT "\n<p><h2 class=\"section\">",$section_name,"</h2></p>\n";
1386 elsif ($opt_output_format eq "s")
1388 print OUTPUT "<bridgehead>",$section_name,"</bridgehead>\n";
1390 else
1392 print OUTPUT "\n\.SH ",$section_name,"\n";
1396 sub output_api_section_end()
1398 # Not currently required by any output formats
1401 sub output_api_name($)
1403 my $comment = shift;
1404 my $readable_name = $comment->{COMMENT_NAME};
1405 $readable_name =~ s/-/ /g; # make section names more readable
1407 output_api_section_start($comment,"NAME");
1410 my $dll_ordinal = "";
1411 if ($comment->{ORDINAL} ne "")
1413 $dll_ordinal = "(".$comment->{DLL_NAME}.".".$comment->{ORDINAL}.")";
1415 if ($opt_output_format eq "h")
1417 print OUTPUT "<p><b class=\"func_name\">",$readable_name,
1418 "</b>&nbsp;&nbsp;<i class=\"dll_ord\">",
1419 ,$dll_ordinal,"</i></p>\n";
1421 elsif ($opt_output_format eq "s")
1423 print OUTPUT "<para>\n <command>",$readable_name,"</command> <emphasis>",
1424 $dll_ordinal,"</emphasis>\n</para>\n";
1426 else
1428 print OUTPUT "\\fB",$readable_name,"\\fR ",$dll_ordinal;
1431 output_api_section_end();
1434 sub output_api_synopsis($)
1436 my $comment = shift;
1437 my @fmt;
1439 output_api_section_start($comment,"SYNOPSIS");
1441 if ($opt_output_format eq "h")
1443 print OUTPUT "<p><pre class=\"proto\">\n ", $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1444 @fmt = ("", "\n", "<tt class=\"param\">", "</tt>");
1446 elsif ($opt_output_format eq "s")
1448 print OUTPUT "<screen>\n ",$comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1449 @fmt = ("", "\n", "<emphasis>", "</emphasis>");
1451 else
1453 print OUTPUT $comment->{RETURNS}," ",$comment->{COMMENT_NAME},"\n (\n";
1454 @fmt = ("", "\n", "\\fI", "\\fR");
1457 # Since our prototype is output in a pre-formatted block, line up the
1458 # parameters and parameter comments in the same column.
1460 # First caluculate where the columns should start
1461 my $biggest_length = 0;
1462 for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1464 my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1465 if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1467 my $length = length $1;
1468 if ($length > $biggest_length)
1470 $biggest_length = $length;
1475 # Now pad the string with blanks
1476 for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1478 my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1479 if ($line =~ /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/)
1481 my $pad_len = $biggest_length - length $1;
1482 my $padding = " " x ($pad_len);
1483 ${@{$comment->{PROTOTYPE}}}[$i] = $1.$padding.$2;
1487 for(my $i=0; $i < @{$comment->{PROTOTYPE}}; $i++)
1489 # Format the parameter name
1490 my $line = ${@{$comment->{PROTOTYPE}}}[$i];
1491 my $comma = ($i == @{$comment->{PROTOTYPE}}-1) ? "" : ",";
1492 $line =~ s/(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ $fmt[0]$1$fmt[2]$2$fmt[3]$comma$fmt[1]/;
1493 print OUTPUT $line;
1496 if ($opt_output_format eq "h")
1498 print OUTPUT " )\n\n</pre></p>\n";
1500 elsif ($opt_output_format eq "s")
1502 print OUTPUT " )\n</screen>\n";
1504 else
1506 print OUTPUT " )\n";
1509 output_api_section_end();
1512 sub output_api_comment($)
1514 my $comment = shift;
1515 my $open_paragraph = 0;
1516 my $open_raw = 0;
1517 my $param_docs = 0;
1518 my @fmt;
1520 if ($opt_output_format eq "h")
1522 @fmt = ("<p>", "</p>\n", "<tt class=\"const\">", "</tt>", "<b class=\"emp\">", "</b>",
1523 "<tt class=\"coderef\">", "</tt>", "<tt class=\"param\">", "</tt>",
1524 "<i class=\"in_out\">", "</i>", "<pre class=\"raw\">\n", "</pre>\n",
1525 "<table class=\"tab\"><colgroup><col><col><col></colgroup><tbody>\n",
1526 "</tbody></table>\n","<tr><td>","</td></tr>\n","</td>","</td><td>");
1528 elsif ($opt_output_format eq "s")
1530 @fmt = ("<para>\n","\n</para>\n","<constant>","</constant>","<emphasis>","</emphasis>",
1531 "<command>","</command>","<constant>","</constant>","<emphasis>","</emphasis>",
1532 "<screen>\n","</screen>\n",
1533 "<informaltable frame=\"none\">\n<tgroup cols=\"3\">\n<tbody>\n",
1534 "</tbody>\n</tgroup>\n</informaltable>\n","<row><entry>","</entry></row>\n",
1535 "</entry>","</entry><entry>");
1537 else
1539 @fmt = ("\.PP\n", "\n", "\\fB", "\\fR", "\\fB", "\\fR", "\\fB", "\\fR", "\\fI", "\\fR",
1540 "\\fB", "\\fR ", "", "", "", "","","\n.PP\n","","");
1543 # Extract the parameter names
1544 my @parameter_names;
1545 for (@{$comment->{PROTOTYPE}})
1547 if ( /(.+?)([A-Za-z_][A-Za-z_0-9]*)$/ )
1549 push (@parameter_names, $2);
1553 for (@{$comment->{TEXT}})
1555 if ($opt_output_format eq "h" || $opt_output_format eq "s")
1557 # Map special characters
1558 s/\&/\&amp;/g;
1559 s/\</\&lt;/g;
1560 s/\>/\&gt;/g;
1561 s/\([Cc]\)/\&copy;/g;
1562 s/\(tm\)/&#174;/;
1565 if ( s/^\|// )
1567 # Raw output
1568 if ($open_raw == 0)
1570 if ($open_paragraph == 1)
1572 # Close the open paragraph
1573 print OUTPUT $fmt[1];
1574 $open_paragraph = 0;
1576 # Start raw output
1577 print OUTPUT $fmt[12];
1578 $open_raw = 1;
1580 if ($opt_output_format eq "")
1582 print OUTPUT ".br\n"; # Prevent 'man' running these lines together
1584 print OUTPUT $_,"\n";
1586 else
1588 if ($opt_output_format eq "h")
1590 # Link to the file in WineHQ cvs
1591 s/^(Implemented in \")(.+?)(\"\.)/$1$2$3 http:\/\/source.winehq.org\/source\/$2/g;
1593 # Highlight strings
1594 s/(\".+?\")/$fmt[2]$1$fmt[3]/g;
1595 # Highlight literal chars
1596 s/(\'.\')/$fmt[2]$1$fmt[3]/g;
1597 s/(\'.{2}\')/$fmt[2]$1$fmt[3]/g;
1598 # Highlight numeric constants
1599 s/( |\-|\+|\.|\()([0-9\-\.]+)( |\-|$|\.|\,|\*|\?|\))/$1$fmt[2]$2$fmt[3]$3/g;
1601 # Leading cases ("xxxx:","-") start new paragraphs & are emphasised
1602 # FIXME: Using bullet points for leading '-' would look nicer.
1603 if ($open_paragraph == 1)
1605 s/^(\-)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1606 s/^([[A-Za-z\-]+\:)/$fmt[1]$fmt[0]$fmt[4]$1$fmt[5]/;
1608 else
1610 s/^(\-)/$fmt[4]$1$fmt[5]/;
1611 s/^([[A-Za-z\-]+\:)/$fmt[4]$1$fmt[5]/;
1614 if ($opt_output_format eq "h")
1616 # Html uses links for API calls
1617 while ( /([A-Za-z_]+[A-Za-z_0-9-]+)(\(\))/)
1619 my $link = $1;
1620 my $readable_link = $1;
1621 $readable_link =~ s/-/ /g;
1623 s/([A-Za-z_]+[A-Za-z_0-9-]+)(\(\))/<a href\=\"$link\.html\">$readable_link<\/a>/;
1625 # Index references
1626 s/\{\{(.*?)\}\}\{\{(.*?)\}\}/<a href\=\"$2\.html\">$1<\/a>/g;
1627 s/ ([A-Z_])(\(\))/<a href\=\"$1\.html\">$1<\/a>/g;
1628 # And references to COM objects (hey, they'll get documented one day)
1629 s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ <a href\=\"$1\.html\">$1<\/a> $2/g;
1630 # Convert any web addresses to real links
1631 s/(http\:\/\/)(.+?)($| )/<a href\=\"$1$2\">$2<\/a>$3/g;
1633 else
1635 if ($opt_output_format eq "")
1637 # Give the man section for API calls
1638 s/ ([A-Za-z_]+[A-Za-z_0-9-]+)\(\)/ $fmt[6]$1\($opt_manual_section\)$fmt[7]/g;
1640 else
1642 # Highlight API calls
1643 s/ ([A-Za-z_]+[A-Za-z_0-9-]+\(\))/ $fmt[6]$1$fmt[7]/g;
1646 # And references to COM objects
1647 s/ (I[A-Z]{1}[A-Za-z0-9_]+) (Object|object|Interface|interface)/ $fmt[6]$1$fmt[7] $2/g;
1650 if ($open_raw == 1)
1652 # Finish the raw output
1653 print OUTPUT $fmt[13];
1654 $open_raw = 0;
1657 if ( /^[A-Z]+$/ || /^SEE ALSO$/ )
1659 # Start of a new section
1660 if ($open_paragraph == 1)
1662 if ($param_docs == 1)
1664 print OUTPUT $fmt[17],$fmt[15];
1666 else
1668 print OUTPUT $fmt[1];
1670 $open_paragraph = 0;
1672 output_api_section_start($comment,$_);
1673 if ( /^PARAMS$/ || /^MEMBERS$/ )
1675 print OUTPUT $fmt[14];
1676 $param_docs = 1;
1678 else
1680 #print OUTPUT $fmt[15];
1681 $param_docs = 0;
1684 elsif ( /^$/ )
1686 # Empty line, indicating a new paragraph
1687 if ($open_paragraph == 1)
1689 if ($param_docs == 0)
1691 print OUTPUT $fmt[1];
1692 $open_paragraph = 0;
1696 else
1698 if ($param_docs == 1)
1700 if ($open_paragraph == 1)
1702 # For parameter docs, put each parameter into a new paragraph/table row
1703 print OUTPUT $fmt[17];
1704 $open_paragraph = 0;
1706 s/(\[.+\])( *)/$fmt[19]$fmt[10]$1$fmt[11]$fmt[19] /; # Format In/Out
1708 else
1710 # Within paragraph lines, prevent lines running together
1711 $_ = $_." ";
1714 # Format parameter names where they appear in the comment
1715 for my $parameter_name (@parameter_names)
1717 s/(^|[ \.\,\(\-\*])($parameter_name)($|[ \.\)\,\-\=\/])/$1$fmt[8]$2$fmt[9]$3/g;
1719 # Structure dereferences include the dereferenced member
1720 s/(\-\>[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g;
1721 s/(\-\&gt\;[A-Za-z_]+)/$fmt[8]$1$fmt[9]/g;
1723 if ($open_paragraph == 0)
1725 if ($param_docs == 1)
1727 print OUTPUT $fmt[16];
1729 else
1731 print OUTPUT $fmt[0];
1733 $open_paragraph = 1;
1735 # Anything in all uppercase on its own gets emphasised
1736 s/(^|[ \.\,\(\[\|\=])([A-Z]+?[A-Z0-9_]+)($|[ \.\,\*\?\|\)\=\'])/$1$fmt[6]$2$fmt[7]$3/g;
1738 print OUTPUT $_;
1742 if ($open_raw == 1)
1744 print OUTPUT $fmt[13];
1746 if ($open_paragraph == 1)
1748 print OUTPUT $fmt[1];
1752 # Create the master index file
1753 sub output_master_index_files()
1755 if ($opt_output_format eq "")
1757 return; # No master index for man pages
1760 if ($opt_output_format eq "h")
1762 # Append the index entries to the output db of index entries
1763 my $output_file = $opt_output_directory."/index.db";
1764 open(INDEXDB,">>$output_file") || die "Couldn't create $output_file\n";
1765 for (@index_entries_list)
1767 $_ =~ s/A\,/\,/;
1768 print INDEXDB $_."\n";
1770 close(INDEXDB);
1773 # Use the comment output functions for consistency
1774 my $comment =
1776 FILE => "",
1777 COMMENT_NAME => "The Wine Api Guide",
1778 ALT_NAME => "The Wine Api Guide",
1779 DLL_NAME => "",
1780 ORDINAL => "",
1781 RETURNS => "",
1782 PROTOTYPE => [],
1783 TEXT => [],
1786 if ($opt_output_format eq "s")
1788 $comment->{COMMENT_NAME} = "Introduction";
1789 $comment->{ALT_NAME} = "Introduction",
1791 elsif ($opt_output_format eq "h")
1793 @{$comment->{TEXT}} = (
1794 "NAME",
1795 $comment->{COMMENT_NAME},
1796 "INTRODUCTION",
1800 # Create the initial comment text
1801 push (@{$comment->{TEXT}},
1802 "This document describes the Api calls made available",
1803 "by Wine. They are grouped by the dll that exports them.",
1805 "Please do not edit this document, since it is generated automatically",
1806 "from the Wine source code tree. Details on updating this documentation",
1807 "are given in the \"Wine Developers Guide\".",
1808 "CONTRIBUTORS",
1809 "Api documentation is generally written by the person who ",
1810 "implements a given Api call. Authors of each dll are listed in the overview ",
1811 "section for that dll. Additional contributors who have updated source files ",
1812 "but have not entered their names in a copyright statement are noted by an ",
1813 "entry in the file \"Changelog\" from the Wine source code distribution.",
1817 # Read in all dlls from the database of dll names
1818 my $input_file = $opt_output_directory."/dlls.db";
1819 my @dlls = `cat $input_file|sort|uniq`;
1821 if ($opt_output_format eq "h")
1823 # HTML gets a list of all the dlls and an index. For docbook the index creates this for us
1824 push (@{$comment->{TEXT}},
1825 "INDEX",
1826 "For an alphabetical listing of the functions available, please click the ",
1827 "first letter of the functions name below:","",
1828 "[ _(), A(), B(), C(), D(), E(), F(), G(), H(), ".
1829 "I(), J(), K(), L(), M(), N(), O(), P(), Q(), ".
1830 "R(), S(), T(), U(), V(), W(), X(), Y(), Z() ]", "",
1831 "DLLS",
1832 "Each dll provided by Wine is documented individually. The following dlls are provided :",
1835 # Add the dlls to the comment
1836 for (@dlls)
1838 $_ =~ s/(\..*)?\n/\(\)/;
1839 push (@{$comment->{TEXT}}, $_, "");
1841 output_open_api_file("index");
1843 elsif ($opt_output_format eq "s")
1845 # Just write this as the initial blurb, with a chapter heading
1846 output_open_api_file("blurb");
1847 print OUTPUT "<chapter id =\"blurb\">\n<title>Introduction to The Wine Api Guide</title>\n"
1850 # Write out the document
1851 output_api_header($comment);
1852 output_api_comment($comment);
1853 output_api_footer($comment);
1854 if ($opt_output_format eq "s")
1856 print OUTPUT "</chapter>\n" # finish the chapter
1858 output_close_api_file();
1860 if ($opt_output_format eq "s")
1862 output_sgml_master_file(\@dlls);
1863 return;
1865 if ($opt_output_format eq "h")
1867 output_html_index_files();
1868 output_html_stylesheet();
1869 return;
1873 # Write the master wine-api.sgml, linking it to each dll.
1874 sub output_sgml_master_file($)
1876 my $dlls = shift;
1878 output_open_api_file("wine-api");
1879 print OUTPUT "<!-- Generated file - DO NOT EDIT! -->\n";
1880 print OUTPUT "<!doctype book PUBLIC \"-//OASIS//DTD DocBook V3.1//EN\" [\n\n";
1881 print OUTPUT "<!entity blurb SYSTEM \"blurb.sgml\">\n";
1883 # List the entities
1884 for (@$dlls)
1886 $_ =~ s/(\..*)?\n//;
1887 print OUTPUT "<!entity ",$_," SYSTEM \"",$_,".sgml\">\n"
1890 print OUTPUT "]>\n\n<book id=\"index\">\n<bookinfo><title>The Wine Api Guide</title></bookinfo>\n\n";
1891 print OUTPUT " &blurb;\n";
1893 for (@$dlls)
1895 print OUTPUT " &",$_,";\n"
1897 print OUTPUT "\n\n</book>\n";
1899 output_close_api_file();
1902 # Produce the sgml for the dll chapter from the generated files
1903 sub output_sgml_dll_file($)
1905 my $spec_details = shift;
1907 # Make a list of all the documentation files to include
1908 my $exports = $spec_details->{EXPORTS};
1909 my @source_files = ();
1910 for (@$exports)
1912 # @$_ => ordinal, call convention, exported name, implementation name, documented;
1913 if (@$_[1] ne "forward" && @$_[1] ne "extern" && @$_[1] ne "stub" && @$_[1] ne "equate" &&
1914 @$_[1] ne "variable" && @$_[1] ne "fake" && @$_[4] & 1)
1916 # A documented function
1917 push (@source_files,@$_[3]);
1921 push (@source_files,@{$spec_details->{EXTRA_COMMENTS}});
1923 @source_files = sort @source_files;
1925 # create a new chapter for this dll
1926 my $tmp_name = $opt_output_directory."/".$spec_details->{DLL_NAME}.".tmp";
1927 open(OUTPUT,">$tmp_name") || die "Couldn't create $tmp_name\n";
1928 print OUTPUT "<chapter>\n<title>$spec_details->{DLL_NAME}</title>\n";
1929 output_close_api_file();
1931 # Add the sorted documentation, cleaning up as we go
1932 `cat $opt_output_directory/$spec_details->{DLL_NAME}.sgml >>$tmp_name`;
1933 for (@source_files)
1935 `cat $opt_output_directory/$_.sgml >>$tmp_name`;
1936 `rm -f $opt_output_directory/$_.sgml`;
1939 # close the chapter, and overwite the dll source
1940 open(OUTPUT,">>$tmp_name") || die "Couldn't create $tmp_name\n";
1941 print OUTPUT "</chapter>\n";
1942 close OUTPUT;
1943 `mv $tmp_name $opt_output_directory/$spec_details->{DLL_NAME}.sgml`;
1946 # Write the html index files containing the function names
1947 sub output_html_index_files()
1949 if ($opt_output_format ne "h")
1951 return;
1954 my @letters = ('_', 'A' .. 'Z');
1956 # Read in all functions
1957 my $input_file = $opt_output_directory."/index.db";
1958 my @funcs = `cat $input_file|sort|uniq`;
1960 for (@letters)
1962 my $letter = $_;
1963 my $comment =
1965 FILE => "",
1966 COMMENT_NAME => "",
1967 ALT_NAME => "",
1968 DLL_NAME => "",
1969 ORDINAL => "",
1970 RETURNS => "",
1971 PROTOTYPE => [],
1972 TEXT => [],
1975 $comment->{COMMENT_NAME} = $letter." Functions";
1976 $comment->{ALT_NAME} = $letter." Functions";
1978 push (@{$comment->{TEXT}},
1979 "NAME",
1980 $comment->{COMMENT_NAME},
1981 "FUNCTIONS"
1984 # Add the functions to the comment
1985 for (@funcs)
1987 my $first_char = substr ($_, 0, 1);
1988 $first_char = uc $first_char;
1990 if ($first_char eq $letter)
1992 my $name = $_;
1993 my $file;
1994 $name =~ s/(^.*?)\,(.*?)\n/$1/;
1995 $file = $2;
1996 push (@{$comment->{TEXT}}, "{{".$name."}}{{".$file."}}","");
2000 # Write out the document
2001 output_open_api_file($letter);
2002 output_api_header($comment);
2003 output_api_comment($comment);
2004 output_api_footer($comment);
2005 output_close_api_file();
2009 # Output the stylesheet for HTML output
2010 sub output_html_stylesheet()
2012 if ($opt_output_format ne "h")
2014 return;
2017 my $css;
2018 ($css = <<HERE_TARGET) =~ s/^\s+//gm;
2020 * Default styles for Wine HTML Documentation.
2022 * This style sheet should be altered to suit your needs/taste.
2024 BODY { /* Page body */
2025 background-color: white;
2026 color: black;
2027 font-family: Tahoma,sans-serif;
2028 font-style: normal;
2029 font-size: 10pt;
2031 a:link { color: #4444ff; } /* Links */
2032 a:visited { color: #333377 }
2033 a:active { color: #0000dd }
2034 H2.section { /* Section Headers */
2035 font-family: sans-serif;
2036 color: #777777;
2037 background-color: #F0F0FE;
2038 margin-left: 0.2in;
2039 margin-right: 1.0in;
2041 b.func_name { /* Function Name */
2042 font-size: 10pt;
2043 font-style: bold;
2045 i.dll_ord { /* Italicised DLL+ordinal */
2046 color: #888888;
2047 font-family: sans-serif;
2048 font-size: 8pt;
2050 p { /* Paragraphs */
2051 margin-left: 0.5in;
2052 margin-right: 0.5in;
2054 table { /* tables */
2055 margin-left: 0.5in;
2056 margin-right: 0.5in;
2058 pre.proto /* API Function prototype */
2060 border-style: solid;
2061 border-width: 1px;
2062 border-color: #777777;
2063 background-color: #F0F0BB;
2064 color: black;
2065 font-size: 10pt;
2066 vertical-align: top;
2067 margin-left: 0.5in;
2068 margin-right: 1.0in;
2070 pre.raw { /* Raw text output */
2071 margin-left: 0.6in;
2072 margin-right: 1.1in;
2073 background-color: #8080DC;
2075 tt.param { /* Parameter name */
2076 font-style: italic;
2077 color: blue;
2079 tt.const { /* Constant */
2080 color: red;
2082 i.in_out { /* In/Out */
2083 font-size: 8pt;
2084 color: grey;
2086 tt.coderef { /* Code in description text */
2087 color: darkgreen;
2089 b.emp /* Emphasis */ {
2090 font-style: bold;
2091 color: darkblue;
2093 i.footer { /* Footer */
2094 font-family: sans-serif;
2095 font-size: 6pt;
2096 color: darkgrey;
2098 HERE_TARGET
2100 my $output_file = "$opt_output_directory/apidoc.css";
2101 open(CSS,">$output_file") || die "Couldn't create the file $output_file\n";
2102 print CSS $css;
2103 close(CSS);
2107 sub usage()
2109 print "\nCreate API Documentation from Wine source code.\n\n",
2110 "Usage: c2man.pl [options] {-w <spec>} {-I <include>} {<source>}\n",
2111 "Where: <spec> is a .spec file giving a DLL's exports.\n",
2112 " <include> is an include directory used by the DLL.\n",
2113 " <source> is a source file of the DLL.\n",
2114 " The above can be given multiple times on the command line, as appropriate.\n",
2115 "Options:\n",
2116 " -Th : Output HTML instead of a man page\n",
2117 " -Ts : Output SGML (Docbook source) instead of a man page\n",
2118 " -o <dir> : Create output in <dir>, default is \"",$opt_output_directory,"\"\n",
2119 " -s <sect>: Set manual section to <sect>, default is ",$opt_manual_section,"\n",
2120 " -e : Output \"FIXME\" documentation from empty comments.\n",
2121 " -v : Verbosity. Can be given more than once for more detail.\n";
2126 # Main
2129 # Print usage if we're called with no args
2130 if( @ARGV == 0)
2132 usage();
2135 # Process command line options
2136 while(defined($_ = shift @ARGV))
2138 if( s/^-// )
2140 # An option.
2141 for ($_)
2143 /^o$/ && do { $opt_output_directory = shift @ARGV; last; };
2144 s/^S// && do { $opt_manual_section = $_; last; };
2145 /^Th$/ && do { $opt_output_format = "h"; last; };
2146 /^Ts$/ && do { $opt_output_format = "s"; last; };
2147 /^v$/ && do { $opt_verbose++; last; };
2148 /^e$/ && do { $opt_output_empty = 1; last; };
2149 /^L$/ && do { last; };
2150 /^w$/ && do { @opt_spec_file_list = (@opt_spec_file_list, shift @ARGV); last; };
2151 s/^I// && do { if ($_ ne ".") {
2152 my $include = $_."/*.h";
2153 $include =~ s/\/\//\//g;
2154 my $have_headers = `ls $include >/dev/null 2>&1`;
2155 if ($? >> 8 == 0) { @opt_header_file_list = (@opt_header_file_list, $include); }
2157 last;
2159 s/^R// && do { if ($_ =~ /^\//) { $opt_wine_root_dir = $_; }
2160 else { $opt_wine_root_dir = `cd $pwd/$_ && pwd`; }
2161 $opt_wine_root_dir =~ s/\n//;
2162 $opt_wine_root_dir =~ s/\/\//\//g;
2163 if (! $opt_wine_root_dir =~ /\/$/ ) { $opt_wine_root_dir = $opt_wine_root_dir."/"; };
2164 last;
2166 die "Unrecognised option $_\n";
2169 else
2171 # A source file.
2172 push (@opt_source_file_list, $_);
2176 # Remove duplicate include directories
2177 my %htmp;
2178 @opt_header_file_list = grep(!$htmp{$_}++, @opt_header_file_list);
2180 if ($opt_verbose > 3)
2182 print "Output dir:'".$opt_output_directory."'\n";
2183 print "Section :'".$opt_manual_section."'\n";
2184 print "Format :'".$opt_output_format."'\n";
2185 print "Root :'".$opt_wine_root_dir."'\n";
2186 print "Spec files:'@opt_spec_file_list'\n";
2187 print "Includes :'@opt_header_file_list'\n";
2188 print "Sources :'@opt_source_file_list'\n";
2191 if (@opt_spec_file_list == 0)
2193 exit 0; # Don't bother processing non-dll files
2196 # Make sure the output directory exists
2197 unless (-d $opt_output_directory)
2199 mkdir $opt_output_directory or die "Cannot create directory $opt_output_directory\n";
2202 # Read in each .spec files exports and other details
2203 while(my $spec_file = shift @opt_spec_file_list)
2205 process_spec_file($spec_file);
2208 if ($opt_verbose > 3)
2210 foreach my $spec_file ( keys %spec_files )
2212 print "in '$spec_file':\n";
2213 my $spec_details = $spec_files{$spec_file}[0];
2214 my $exports = $spec_details->{EXPORTS};
2215 for (@$exports)
2217 print @$_[0].",".@$_[1].",".@$_[2].",".@$_[3]."\n";
2222 # Extract and output the comments from each source file
2223 while(defined($_ = shift @opt_source_file_list))
2225 process_source_file($_);
2228 # Write the index files for each spec
2229 process_index_files();
2231 # Write the master index file
2232 output_master_index_files();
2234 exit 0;