17 "html|h=s" => \
$SOURCE_DIR,
18 "target|t=s" => \
$TARGET_DIR,
19 "warnings|W" => \
$WARNINGS,
22 pod2usage
(0) if $HELP;
32 my $stylesheet = load_stylesheet
($SOURCE_DIR);
33 load_templates
($SOURCE_DIR, \
%templates);
34 process_source_files
(\
%docs);
35 merge
(\
%docs, \
%templates, \
$stylesheet);
39 # Load CSS stylesheet.
43 my $file_path = "$dir_path/api-style.css";
44 open (my $file, '<', $file_path) or die "Could not open $file_path";
46 my $contents = <$file>;
52 # Load HTML templates.
55 my ($dir_path, $templates) = @_;
56 opendir (my $dir, "$dir_path/sources/") or die "Could not open $dir_path";
57 while (my $file_name = readdir ($dir)) {
58 next if $file_name !~ /mono-api-.*\.html$/;
59 open (my $file, "$dir_path/sources/$file_name") or die "Could not open $file_name";
64 if (/name="api:(.*?)"/) {
65 s/.*name="api:(\w+?)".*/$1/;
70 $templates->{$file_name}->{contents
} = $contents;
71 $templates->{$file_name}->{api
} = \
@api;
77 # Extract documentation from all source files.
79 sub process_source_files
{
81 for my $file_path (@ARGV) {
82 process_source_file
($file_path, $docs);
87 # Extract documentation from a single source file.
89 sub process_source_file
{
90 my ($file_path, $docs) = @_;
91 open (my $file, '<', $file_path) or die "Could not open $file_path";
93 next if (!/\/\
*\
* *\n/);
94 process_function
($file, $file_path, $docs);
100 # Extract documentation from a single function.
102 sub process_function
{
104 my ($file, $file_path, $docs) = @_;
106 my $PARAMETER_SECTION = 0;
107 my $BODY_SECTION = 1;
108 my $RETURN_SECTION = 2;
109 my $section = $PARAMETER_SECTION;
119 # Ignore irrelevant functions, and those with the wrong doc format.
120 return if $name !~ /^mono_\w+$/;
130 # We've reached the last line in the documentation block.
133 # Grab function prototype.
139 # Clean up prototype.
142 # Strip braces and trailing whitespace.
145 # Turn "Type * xxx" into "Type* xxx"
150 # Process formatting within sections.
151 for my $parameter (@parameters) {
152 process_formatting
(\
$parameter->{description
}, $file_path, $.);
154 process_formatting
(\
$returns, $file_path, $.);
155 process_formatting
(\
$body, $file_path, $.);
156 if (defined($deprecated)) {
157 process_formatting
(\
$deprecated, $file_path, $.);
161 if (exists($docs->{body
}->{$name})) {
162 my $origin = $docs->{origin
}->{$name};
165 "$file_path:$.: Redundant documentation for $name\n",
166 "$origin->{file}:$origin->{line}: Previously defined here\n";
169 $docs->{origin
}->{$name} = { file
=> $file_path, line
=> $. };
170 $docs->{body
}->{$name} = $body;
171 $docs->{parameters
}->{$name} = \
@parameters;
172 $docs->{deprecated
}->{$name} = $deprecated if defined $deprecated;
173 $docs->{return}->{$name} = $returns;
174 $docs->{prototype}->{$name} = $prototype;
179 # Strip newlines and asterisk prefix.
183 # Replace blank lines with paragraph breaks.
184 $_ = '<p>' if /^\s*$/;
186 if ($section == $PARAMETER_SECTION) {
187 if (/\s*\\param +(\w+)(.*)/) {
188 # print "$file_path:$.: warning: Got parameter $1\n";
189 push @parameters, { name
=> $1, description
=> $2 };
190 } elsif (/\s*\\deprecated(.*)/) {
191 # print "$file_path:$.: warning: Got deprecated annotation\n";
193 } elsif (/\s*(\w+):(.*)/) {
194 if ($1 eq 'deprecated') {
195 warn "$file_path:$.: Old-style monodoc notation 'deprecated:' used\n"
199 warn "$file_path:$.: Old-style monodoc notation 'param:' used\n"
201 push @parameters, { name
=> $1, description
=> $2 };
205 $section = $BODY_SECTION;
208 } elsif ($section == $BODY_SECTION) {
209 if (s/(Returns?:\s*|\\returns?\s*)//) {
211 $section = $RETURN_SECTION;
215 } elsif ($section == $RETURN_SECTION) {
216 $returns .= "\n\t$_";
218 die "Invalid section $section\n";
224 # Substitute formatting within documentation text.
226 sub process_formatting
{
227 my ($content, $file_path, $current_line) = @_;
231 s{NULL}{<code>NULL</code>}g;
232 s{TRUE}{<code>TRUE</code>}g;
233 s{FALSE}{<code>FALSE</code>}g;
236 warn "$file_path:$current_line: Old-style monodoc notation '\@param' used\n"
237 if s{@(\w+)}{<i>$1</i>}g && $WARNINGS;
238 s{\\p +(\w+)}{<i>$1</i>}g;
241 warn "$file_path:$current_line: Old-style monodoc notation '#code' used\n"
242 if s{#(\w+)}{<code>$1</code>}g && $WARNINGS;
243 warn "$file_path:$current_line: Old-style monodoc notation '`code`' used\n"
244 if s{\`((?!api:)[:.\w\*]+)\`}{<code>$1</code>}g && $WARNINGS;
245 s{\\c +(\S+(?<![.,:;]))}{<code>$1</code>}g;
251 # Merge templates with stylesheet and documentation extracted from sources.
254 my ($docs, $templates, $stylesheet) = @_;
256 for my $name (keys %$templates) {
257 open (my $output_file, '>', "$TARGET_DIR/html/$name")
258 or die "Could not create $TARGET_DIR/html/$name";
259 print "Merging: $name\n";
260 print $output_file <<EOF;
261 <?xml version="1.0" encoding="utf-8"?>
262 <html xmlns="http://www.w3.org/1999/xhtml">
265 <style type="text/css">
270 <div class="mapi-docs">
272 my @a = split (/\n/, $templates->{$name}->{contents
});
274 my $strikeextra = '';
276 for (my $ai = 0; $ai < $#a; $ai++) {
278 if (my ($api, $caption) = ($line =~ /<h4><a name=\"api:(\w+)\">(\w+)<\/a><\
/h4>/)) {
279 if ($api_shown == 1) {
280 print $output_file "</div> <!-- class=mapi -->\n\n";
281 if ($docs->{deprecated
}->{$api}) {
282 $strike = "mapi-strike";
283 $strikeextra = "</div><br><div class='mapi-deprecated'><b>Deprecated:</b> " . $docs->{deprecated
}->{$api};
290 my $proto = $docs->{prototype}->{$api} // $api;
292 print $output_file <<EOF;
293 <a name="api:$api"></a>
295 <div class="mapi-entry $strike"><code>$api$strikeextra</code></div>
296 <div class="mapi-height-container">
297 <div class="mapi-ptr-container"></div>
298 <div class="mapi-description">
299 <div class="mapi-ptr"></div>
301 <div class="mapi-declaration mapi-section">Syntax</div>
302 <div class="mapi-prototype">$proto</div>
305 if (exists ($docs->{parameters
}->{$api})) {
306 my $ppars = $docs->{parameters
}->{$api};
309 " <div class=\"mapi-section\">Parameters</div>\n",
310 " <table class=\"mapi-parameters\"><tbody>",
311 render_parameters
($ppars),
316 opt_print
($output_file, "Return value", $docs->{return}->{$api});
317 opt_print
($output_file, "Description", $docs->{body
}->{$api});
318 print $output_file " </div><!--mapi-description-->\n </div><!--height container-->\n";
320 if ($line =~ /\@API_IDX\@/) {
321 my $apis_toc = create_toc
($docs, $templates->{$name}->{api
});
322 $line =~ s/\@API_IDX\@/$apis_toc/;
324 if ($line =~ /^<h4/) {
325 print $output_file "</div>\n";
330 print $output_file "$line\n";
338 system ("$ENV{runtimedir}/mono-wrapper convert.exe $TARGET_DIR/html/$name $TARGET_DIR/html/x-$name");
340 # Clean up the mess that AgilityPack makes (it CDATAs our CSS).
341 open (my $hack_input, '<', "$TARGET_DIR/html/x-$name")
342 or die "Could not open $TARGET_DIR/html/x-$name";
343 open (my $hack_output, '>', "$TARGET_DIR/deploy/$name")
344 or die "Could not open output";
348 while (<$hack_input>) {
349 print $hack_output $last if ($doprint);
351 s/^\/\/<!\
[CDATA\
[//;
354 # Remove the junk <span> wrapper generated by AgilityPack.
359 # Replace the CSS in the XHTML output with the original CSS.
360 print $hack_output $_;
361 print $hack_output $$stylesheet;
362 while (<$hack_input>) {
363 last if (/<\/style
>/);
369 if (!($last =~ /span/)) {
370 print $hack_output $last;
372 # system ("cp.exe $TARGET_DIR/html/$name $TARGET_DIR/deploy/$name");
377 my ($docs, $apis_listed) = @_;
380 my ($ret, $xname, $args);
383 # Try to align things; compute type size, method size, and arguments.
384 foreach my $line (split /\n/, $apis_listed) {
385 if (exists ($docs->{prototype}->{$line})) {
386 my $p = $docs->{prototype}->{$line};
387 if (my ($ret, $xname, $args) = ($p =~ /(.*)\n(\w+)[ \t](.*)/)) {
388 my $tl = length ($ret);
389 my $pl = length ($xname);
390 $type_size = $tl if ($tl > $type_size);
391 $name_size = $pl if ($pl > $name_size);
399 foreach my $line (split /\n/, $apis_listed) {
401 if (exists($docs->{prototype}->{$line})) {
402 my $p = $docs->{prototype}->{$line};
403 if (my ($ret, $xname, $args) = ($p =~ /(.*)\n(\w+)[ \t](.*)/)) {
404 $xname = $line if $xname eq "";
405 my $rspace = " " x
($type_size - length ($ret));
406 my $nspace = " " x
($name_size - length ($xname));
407 $args = wrap
($args, length ($ret . $rspace . $xname . $nspace), 60);
408 $apis_toc .= "$ret$rspace<a href=\"\#api:$line\">$xname</a>$nspace$args\n";
416 my ($args, $size, $limit) = @_;
419 # return $args if ((length (args) + size) < $limit);
421 my $remain = $limit - $size;
422 my @sa = split /,/, $args;
424 foreach my $arg (@sa) {
427 $linelen += length ($sret);
429 if ($linelen + length ($arg) < $limit) {
430 $sret .= "FITS" . $arg . ", ";
432 my $newline = " " x
($size) . $arg . ", ";
433 my $linelen = length ($newline);
434 $sret .= "\n" . $newline;
443 # Print a section if non-empty.
446 my ($output, $caption, $opttext) = @_;
447 if (defined($opttext) && $opttext ne '' && $opttext !~ /^[ \t]+$/) {
449 " <div class=\"mapi-section\">$caption</div>\n",
450 " <div>$opttext</div>\n";
455 # Render parameter information as table.
457 sub render_parameters
{
458 my ($parameters) = @_;
460 for my $parameter (@
$parameters) {
461 $result .= "<tr><td><i>$parameter->{name}</i></td><td>$parameter->{description}</td></tr>";
470 exdoc - Compiles API docs from Mono sources and HTML templates.
474 exdoc [OPTIONS] [FILE...]
482 Print this help message.
484 =item B<--html> I<DIR>, B<-h> I<DIR>
486 Use I<DIR> as the input path for HTML sources.
488 =item B<--target> I<DIR>, B<-t> I<DIR>
490 Use I<DIR> as the target path for output.
492 =item B<--warnings>, B<-W>
494 Enable warnings about documentation errors.
500 Reads HTML templates and C sources, extracting documentation from the sources and splicing it into the templates.