4 # mp_doccer - Documentation generator
6 # Copyright (C) 2001/2008 Angel Ortega <angel@triptico.com>
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
13 # This program 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
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 # http://www.triptico.com/software/mp_doccer.html
28 $main::VERSION
= '1.2.2';
35 # output file or directory
41 # documentation abstract
45 my $man_section = '3';
47 # function (and variable) documentation database
56 # prefix for generated files
59 # author's name and email
72 if (!GetOptions
('f|format=s' => \
$format,
73 'o|output=s' => \
$output,
75 't|title=s' => \
$title,
76 'v|version' => \
$version,
77 'p|prefix=s' => \
$file_prefix,
78 'm|man-section=s' => \
$man_section,
79 'a|author=s' => \
$author,
80 'b|abstract=s' => \
$abstract,
88 print "$main::VERSION\n";
92 # list of source code files
93 my @sources = sort(@ARGV) or usage
();
95 extract_doc
(@sources);
98 if ($format eq 'html') {
101 elsif ($format eq 'man') {
104 elsif ($format eq 'localhelp') {
107 elsif ($format eq 'html1') {
110 elsif ($format eq 'grutatxt') {
114 print "Invalid output format '$format'\n";
115 print "Valid ones are: html man localhelp html1 grutatxt\n";
119 # ###################################################################
123 # extract the documentation from the source code files
128 foreach my $f (@sources) {
129 unless (open F
, $f) {
130 warn "Can't open $_";
134 # $f=$1 if $f =~ /\/([^\/]*)$/;
136 print("Processing $f...\n");
139 my ($fname, $bdesc, @arg, @argdesc, $desc,
140 $syn, $altsyn, $uniq, @category);
144 unless (/^\s*\/\
*\
*$/) {
148 chop($_ = <F
>) or last;
150 # extract function name and brief description
151 ($fname, $bdesc) = /([\w_\.]*) - (.*)/;
155 chop($_ = <F
>) or goto eof;
157 unless (/^\s+\*\s+\@([^:]*):\s+(.*)/) {
169 # rest of lines until */ are the description
171 chop($_ = <F
>) or goto eof;
174 # a line with only [text] is a category
175 if (/^\s+\*\s+\[(.*)\]$/) {
178 my $s = $categories{$sec};
180 unless (grep /^$fname$/, @
$s) {
182 $categories{$sec} = $s;
185 push(@category, $sec);
196 # rest of info until a { or ; is the synopsis
198 chop($_ = <F
>) or goto eof;
200 if (/^\s*\/\
*\
*(.*)\
*\
//) {
201 $altsyn .= $1 . "\n";
203 elsif (/^([^{;]*)[{;]/) {
207 elsif (/^\s\/\
*\
*$/) {
215 # fix synopsis to have a trailing ;
220 # delete (posible) leading 'sub'
221 $syn =~ s/^\s*sub\s+//;
223 # calculate a unique name
224 # (to avoid collisions in file names)
225 if ($func_idx{$fname}) {
226 $uniq = $fname . $func_idx{$fname}++;
230 $func_idx{$fname} = 1;
236 $func->{'file'} = $f;
237 $func->{'func'} = $fname;
238 $func->{'bdesc'} = $bdesc;
239 $func->{'desc'} = $desc;
240 $func->{'syn'} = $syn;
241 $func->{'uniq'} = $uniq;
244 $func->{'arg'} = \
@arg;
248 $func->{'argdesc'} = \
@argdesc;
252 $func->{'altsyn'} = $altsyn;
256 $func->{'category'} = \
@category;
259 push(@functions, $func);
267 # iterate now the functions, creating the 'prev' and 'next' fields
269 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
271 $prev->{'next'} = $f->{'func'};
272 $f->{'prev'} = $prev->{'func'};
283 mp_doccer
$main::VERSION
- C Source Code Documentation Generator
284 Copyright
(C
) 2001/2008 Angel Ortega
<angel\
@triptico.com
>
285 This software is covered by the GPL license
. NO WARRANTY
.
287 Usage
: mp_doccer
[options
] c_code_files
...
291 -o
|--output
=dest Directory
or file where the
292 documentation is generated
.
293 -t
|--title
="title" Title
for the documentation
.
294 -c
|--css
="css URL" URL to a Cascade Style Sheet
295 to include
in all HTML files
.
296 -f
|--format
="format" Format
for the generated
299 html man localhelp html1 grutatxt
300 -p
|--prefix
="prefix" Prefix
for the name of the
301 generated files
. Main
index
302 file will also have this name
.
303 -a
|--author
="author" Sets author info
(as name
and email
)
304 to be included
in the documentation
.
305 -b
|--abstract
="text" Abstract
for the documentation
.
306 -m
|--man
-section
="sect" Section number
for the generated
308 -v
|--version Shows version
.
309 -q
|--quiet Suppress
'built with...' info
.
312 The mp_doccer Home Page
:
313 http
://triptico
.com
/software
/mp_doccer
.html
320 #######################################################
323 # create a help shell script
328 $output = 'localhelp.sh';
331 open F
, ">$output" or die "Error: $!";
335 print F
"#!/bin/sh\n\n";
336 printf F
"# Help program generated by mp_doccer $main::VERSION on %s\n",scalar(localtime());
337 print F
"# mp_doccer is part of the Minimum Profit Text Editor\n";
338 print F
"# http://www.triptico.com/software/mp.html\n\n";
340 print F
"case \"\$1\" in\n";
342 for (my $n = 0; $n < scalar(@functions); $n++) {
347 print F
"$f->{'func'})\n";
349 print F
"cat << EOF\n";
351 print F
"$title\n\n";
354 print F
"$f->{'func'} - $f->{'bdesc'}\n\n";
356 print F
"SYNOPSIS\n\n";
358 $syn = defined($f->{'altsyn'}) ?
$f->{'altsyn'} : $f->{'syn'};
359 $syn =~ s/\@([\w]+)/$1/g;
360 $syn =~ s/\%([\w]+)/$1/g;
369 $d = $f->{'argdesc'};
371 print F
"ARGUMENTS\n\n";
373 for (my $n = 0; $n < scalar(@
$a); $n++) {
374 print F
"$$a[$n] - $$d[$n]\n";
381 print F
"DESCRIPTION\n\n";
383 my ($desc) = $f->{'desc'};
384 $desc =~ s/\@([\w]+)/$1/g;
385 $desc =~ s/\%([\w]+)/$1/g;
389 if ($f->{'category'}) {
390 my $s = $f->{'category'};
392 print F
"CATEGORIES\n\n";
394 for (my $n = 0; $n < scalar(@
$s); $n++) {
404 print F
"AUTHOR\n\n";
413 print F
"\techo \"Usage: \$0 {keyword}\"\n";
417 print F
"\techo \"No help for \$1\"\n";
442 unless (-d
$output) {
443 print "$output must be a directory; aborting\n";
448 $pf = $file_prefix . '_';
451 for(my $n = 0; $n < scalar(@functions); $n++) {
457 open F
, ">$output/${pf}$f->{'func'}.$man_section" or die "Error: $!";
459 print F
".TH $f->{'func'} $man_section \"\" \"$title\"\n";
460 print F
".SH NAME\n";
461 print F
"$f->{'func'} \\- $f->{'bdesc'}\n";
462 print F
".SH SYNOPSIS\n";
465 $syn = defined($f->{'altsyn'}) ?
$f->{'altsyn'} : $f->{'syn'};
473 $d = $f->{'argdesc'};
475 print F
".SH ARGUMENTS\n";
477 for (my $n = 0; $n < scalar(@
$a); $n++) {
478 print F
".B $$a[$n] \\-\n";
485 print F
".SH DESCRIPTION\n";
487 # take the description
488 my ($desc) = $f->{'desc'};
495 if ($f->{'category'}) {
496 my ($s) = $f->{'category'};
498 print F
".SH CATEGORIES\n";
500 for (my $n = 0; $n < scalar(@
$s); $n++) {
510 print F
".SH AUTHOR\n";
526 $ret .= "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\n";
527 $ret .= "\"http://www.w3.org/TR/REC-html40/loose.dtd\">\n";
528 $ret .= "<head><title>$title</title>\n";
529 $ret .= "<link rel = 'StyleSheet' href = '$css' type = 'text/css'>\n" if $css;
530 $ret .= "<meta name = 'generator' content = 'mp_doccer $main::VERSION'>\n";
531 $ret .= "<meta name = 'date' content = '" . scalar(localtime()) . "'>\n";
532 $ret .= "<meta name = 'author' content = '$author'>\n" if $author;
533 $ret .= "</head>\n<body>\n";
541 my $ret = "<div class = 'footer'>\n";
544 $ret .= "<span class = 'author'>$author</span>";
548 $ret .= " - <em class = 'built_with'>Built with <a href = 'http://www.triptico.com/software/mp_doccer.html'>mp_doccer $main::VERSION</a></em>";
551 $ret .= "\n</div>\n</body>\n</html>\n";
559 my $func_link = shift;
562 $ret .= "<a name = '_TOP_'></a><h1>$title</h1>\n";
564 $ret .= "<p>$abstract</p>\n" if $abstract;
566 $ret .= "<div class = 'toc'>\n";
568 if (scalar(keys(%categories))) {
569 $ret .= "<h2>By Category</h2>\n";
571 foreach my $sn (sort keys %categories) {
572 $ret .= "<a name = '$sn'></a>\n";
573 $ret .= "<h3 class = 'category'>$sn</h3>\n";
575 $ret .= "<ul class = 'by_category'>\n";
578 map { " <li><a href = '" . $func_link->($_) . "'>$_</a></li>\n" }
579 sort(@
{$categories{$sn}})
586 $ret .= "<h2>By Source</h2>\n";
588 foreach my $s (@sources) {
589 my @f = grep { $_->{'file'} eq $s } @functions;
595 $ret .= "<h3 class = 'source_file'>$s</h3>\n";
597 $ret .= "<ul class = 'by_source'>\n";
600 map { " <li><a href = '" . $func_link->($_) . "'>$_</a></li>\n" }
601 sort(map { $_->{'func'} } @f)
607 $ret .= "<h2>Alphabetical</h2>\n";
608 $ret .= "<ul class = 'alphabetical'>\n";
610 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
611 $ret .= " <li><a href = '" . $func_link->($f->{'func'}) .
612 "'>$f->{'func'}</a> - $f->{'bdesc'}</li>\n";
615 $ret .= "</ul></div>\n";
627 $ret .= "\n<div class = 'func' style = 'margin-left: 1em;'>\n";
629 $ret .= "<h3>Name</h3>\n";
630 $ret .= "<strong class = 'funcname'>$f->{'func'}</strong> - $f->{'bdesc'}\n";
632 $ret .= "<h3>Synopsis</h3>\n";
634 $syn = defined($f->{'altsyn'}) ?
$f->{'altsyn'} : $f->{'syn'};
636 # synopsis decoration
637 $syn =~ s/\b$f->{'func'}\b/\<strong class = 'funcname'>$f->{'func'}\<\/strong
>/g
;
639 $syn =~ s/@([\w]+)/<em class = 'funcarg'>$1<\/em>/g
;
640 $syn =~ s/\%([\w]+)/<em class = 'funcret'>$1<\/em>/g
;
643 foreach my $a (@
{$f->{'arg'}}) {
644 $syn =~ s/\b$a\b/\<em class = 'funcarg'>$a\<\/em>/g
;
648 $ret .= "<pre class = 'funcsyn'>\n$syn</pre>\n";
651 my @a = @
{$f->{'arg'}};
652 my @d = @
{$f->{'argdesc'}};
654 $ret .= "<h3>Arguments</h3>\n";
655 $ret .= "<dl class = 'arguments'>\n";
658 $ret .= " <dt><em class = 'funcarg'>" . shift(@a) . "</em></dt>";
659 $ret .= "<dd>" . shift(@d) . "</dd>\n";
666 $ret .= "<h3>Description</h3>\n";
668 # take the description
669 my ($desc) = $f->{'desc'};
671 # decorate function names
672 $desc =~ s/([\w_]+\(\))/<code class = 'funcname'>$1<\/code>/g
;
674 # decorate function arguments
675 $desc =~ s/@([\w_]+)/<em class = 'funcarg'>$1<\/em>/g
;
677 # decorate return values
678 $desc =~ s/\%([\w_]+)/<em class = 'funcret'>$1<\/em>/g
;
680 # replace blank lines
681 $desc =~ s/\n\n/\n<p>\n/gs;
683 $ret .= "<p class = 'description'>$desc</p>\n";
685 if ($f->{category
}) {
686 $ret .= "<h3>Categories</h3>\n";
688 $ret .= "<ul class = 'categories'>\n" .
689 join('', map { " <li><a href = '#$_'>$_</a></li>\n" } @
{$f->{'category'}}) .
704 $file_prefix = '_' . $file_prefix;
708 my $fn = $output . $file_prefix . '.html';
710 open F
, ">$fn" or die "Error create $fn: $!";
712 print F html_header
($title);
714 print F html_toc
( sub { "#" . shift } );
716 # the functions themselves
717 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
718 # avoid duplicate function names
719 if ($f{$f->{'func'}}) {
725 print F
"\n<div class = 'func_container'>\n";
726 print F
"<a name = '$f->{'func'}'></a>\n";
727 print F
"<h2 style = 'border-bottom: solid 2px;'>$f->{'func'}</h2>\n";
729 print F html_func
($f);
734 print F html_footer
();
741 # create multipage html documents
743 $output = "." unless $output;
746 unless (-d
$output) {
747 print "$output must be a directory; aborting\n";
751 my $pf = $file_prefix ?
$file_prefix . '_' : '';
753 # create the table of contents
754 my $top = $file_prefix || 'index';
756 open TOC
, ">$output/${top}.html"
759 print TOC html_header
($title);
761 print TOC html_toc
( sub { $pf . shift() . ".html" } );
763 print TOC html_footer
();
767 # the functions themselves
768 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
770 open F
, ">$output/" . $pf . "$f->{'func'}.html"
773 print F html_header
($f->{'func'});
775 print F
"<div class = 'topnav'>\n";
777 print F
' ', $f->{'prev'} ?
"<a href = '${pf}$f->{'prev'}.html'>Prev</a>" : "Prev",
779 " <a href = '${top}.html'><b>$title</b></a>",
781 ' ', $f->{'next'} ?
"<a href = '${pf}$f->{'next'}.html'>Next</a>" : "Next",
786 print F
"<h2 style = 'border-bottom: solid 2px;'>$f->{'func'}</h2>\n";
788 print F html_func
($f);
790 print F html_footer
();
805 return $t . "\n" . $s . "\n\n";
821 # create a grutatxt document
826 $file_prefix = '_' . $file_prefix;
830 my $fn = $output . $file_prefix . '.txt';
832 open F
, ">$fn" or die "Error create $fn: $!";
834 print F _grutatxt_header
($title, "=");
836 print F
"$abstract\n\n" if $abstract;
838 if (scalar(keys(%categories))) {
840 print F _grutatxt_header
('By Category', '-');
842 foreach my $sn (sort keys %categories) {
844 print F _grutatxt_header
($sn, '~');
847 map { ' * ./#' . _gl
($_) . ' (' . $_ . ')' }
848 sort(@
{$categories{$sn}})
855 print F _grutatxt_header
('By Source', '-');
857 foreach my $s (@sources) {
858 my @f = grep { $_->{'file'} eq $s } @functions;
864 print F _grutatxt_header
($s, '~');
867 map { ' * ./#' . _gl
($_) . ' (' . $_ . ')' }
868 sort(map { $_->{'func'} } @f)
874 print F _grutatxt_header
('Alphabetical', '-');
876 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
888 # the functions themselves
889 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
890 # avoid duplicate function names
891 if ($f{$f->{'func'}}) {
897 print F _grutatxt_header
($f->{func
}, '-');
899 print F _grutatxt_header
('Name', '~');
901 print F
'*' . $f->{func
} . '* - ' . $f->{bdesc
} . "\n";
905 print F _grutatxt_header
('Synopsis', '~');
907 my $syn = $f->{'altsyn'} || (' ' . $f->{'syn'});
909 # strip arg and return value marks
910 $syn =~ s/[@%]([\w]+)/$1/g;
912 print F
$syn . "\n\n";
915 my @a = @
{$f->{'arg'}};
916 my @d = @
{$f->{'argdesc'}};
918 print F _grutatxt_header
('Arguments', '~');
921 print F
' * ' . shift(@a) . ': ' . shift(@d) . "\n";
928 print F _grutatxt_header
('Description', '~');
930 # take the description
931 my $desc = $f->{'desc'};
933 # decorate function arguments
934 $desc =~ s/@([\w_]+)/_$1_/g;
936 # decorate return values
937 $desc =~ s/\%([\w_]+)/_$1_/g;
941 if ($f->{category
}) {
942 print F _grutatxt_header
('Categories', '~');
945 map { ' * ./#' . _gl
($_) . ' (' . $_ . ')' }
946 @
{$f->{'category'}});
956 print F
"----\n$author ";
960 print F
"- Built with http://triptico.com/software/mp_doccer.html (mp_doccer $main::VERSION)";