Updated TODO (Closes: #1006, #1008).
[mp_doccer.git] / mp_doccer
blob5aad0e3427fc9b5a8c821d36325044984eed4874
1 #!/usr/bin/perl
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
25 use strict;
26 use warnings;
28 $main::VERSION = '1.2.2-dev';
30 use Getopt::Long;
32 # output format
33 my $format = 'html';
35 # output file or directory
36 my $output = '';
38 # documentation title
39 my $title = 'API';
41 # man section
42 my $man_section = '3';
44 # function (and variable) documentation database
45 my @functions = ();
47 # function categories
48 my %categories = ();
50 # the style sheet
51 my $css = '';
53 # prefix for generated files
54 my $file_prefix = '';
56 # author's name and email
57 my $author = '';
59 # quiet flag
60 my $quiet = 0;
62 # show version
63 my $version = 0;
65 # show usage
66 my $usage = 0;
68 # parse options
69 if (!GetOptions('f|format=s' => \$format,
70 'o|output=s' => \$output,
71 'c|css=s' => \$css,
72 't|title=s' => \$title,
73 'v|version' => \$version,
74 'p|prefix=s' => \$file_prefix,
75 'm|man-section=s' => \$man_section,
76 'a|author=s' => \$author,
77 'q|quiet' => \$quiet,
78 'h|help' => \$usage)
79 or $usage) {
80 usage();
83 if ($version) {
84 print "$main::VERSION\n";
85 exit(0);
88 # list of source code files
89 my @sources = sort(@ARGV) or usage();
91 extract_doc(@sources);
93 # create
94 if ($format eq 'html') {
95 format_html();
97 elsif ($format eq 'man') {
98 format_man();
100 elsif ($format eq 'localhelp') {
101 format_sh();
103 elsif ($format eq 'html1') {
104 format_html_1();
106 elsif ($format eq 'grutatxt') {
107 format_grutatxt();
109 else {
110 print "Invalid output format '$format'\n";
111 print "Valid ones are: html man localhelp html1 grutatxt\n";
115 # ###################################################################
118 sub extract_doc
119 # extract the documentation from the source code files
121 my (@sources) = @_;
122 my %func_idx;
124 foreach my $f (@sources) {
125 unless (open F, $f) {
126 warn "Can't open $_";
127 next;
130 # $f=$1 if $f =~ /\/([^\/]*)$/;
132 print("Processing $f...\n");
134 while (<F>) {
135 my ($fname, $bdesc, @arg, @argdesc, $desc,
136 $syn, $altsyn, $uniq, @category);
138 chop;
140 unless (/^\s*\/\*\*$/) {
141 next;
144 chop($_ = <F>) or last;
146 # extract function name and brief description
147 ($fname, $bdesc) = /([\w_\.]*) - (.*)/;
149 # possible arguments
150 for (;;) {
151 chop($_ = <F>) or goto eof;
153 unless (/^\s+\*\s+\@([^:]*):\s+(.*)/) {
154 last;
157 push(@arg, $1);
158 push(@argdesc, $2);
161 if (/^\s+\*\//) {
162 goto skipdesc;
165 # rest of lines until */ are the description
166 for (;;) {
167 chop($_ = <F>) or goto eof;
168 last if /^\s+\*\//;
170 # a line with only [text] is a category
171 if (/^\s+\*\s+\[(.*)\]$/) {
172 my $sec = $1;
174 my $s = $categories{$sec};
176 unless (grep /^$fname$/, @$s) {
177 push(@$s, $fname);
178 $categories{$sec} = $s;
181 push(@category, $sec);
183 next;
186 /^\s+\*\s*(.*)$/;
187 $desc .= $1 . "\n";
190 skipdesc:
192 # rest of info until a { or ; is the synopsis
193 for (;;) {
194 chop($_ = <F>) or goto eof;
196 if (/^\s*\/\*\*(.*)\*\//) {
197 $altsyn .= $1 . "\n";
199 elsif (/^([^{;]*)[{;]/) {
200 $syn .= $1 . "\n";
201 last;
203 elsif (/^\s\/\*\*$/) {
204 last;
206 else {
207 $syn .= $_ . "\n";
211 # fix synopsis to have a trailing ;
212 $syn =~ s/^(\s*)//;
213 $syn =~ s/(\s*)$//;
214 $syn .= ";";
216 # delete (posible) leading 'sub'
217 $syn =~ s/^\s*sub\s+//;
219 # calculate a unique name
220 # (to avoid collisions in file names)
221 if ($func_idx{$fname}) {
222 $uniq = $fname . $func_idx{$fname}++;
224 else {
225 $uniq = $fname;
226 $func_idx{$fname} = 1;
229 my $func = {};
231 # store
232 $func->{'file'} = $f;
233 $func->{'func'} = $fname;
234 $func->{'bdesc'} = $bdesc;
235 $func->{'desc'} = $desc;
236 $func->{'syn'} = $syn;
237 $func->{'uniq'} = $uniq;
239 if (@arg) {
240 $func->{'arg'} = \@arg;
243 if (@argdesc) {
244 $func->{'argdesc'} = \@argdesc;
247 if ($altsyn) {
248 $func->{'altsyn'} = $altsyn;
251 if (@category) {
252 $func->{'category'} = \@category;
255 push(@functions, $func);
258 eof:
260 close F;
263 # iterate now the functions, creating the 'prev' and 'next' fields
264 my $prev = undef;
265 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
266 if ($prev) {
267 $prev->{'next'} = $f->{'func'};
268 $f->{'prev'} = $prev->{'func'};
271 $prev = $f;
276 sub usage
278 print << "EOF";
279 mp_doccer $main::VERSION - C Source Code Documentation Generator
280 Copyright (C) 2001/2008 Angel Ortega <angel\@triptico.com>
281 This software is covered by the GPL license. NO WARRANTY.
283 Usage: mp_doccer [options] c_code_files...
285 Options:
287 -o|--output=dest Directory or file where the
288 documentation is generated.
289 -t|--title="title" Title for the documentation.
290 -c|--css="css URL" URL to a Cascade Style Sheet
291 to include in all HTML files.
292 -f|--format="format" Format for the generated
293 documentation.
294 Valid ones are:
295 html man localhelp html1
296 -p|--prefix="prefix" Prefix for the name of the
297 generated files. Main index
298 file will also have this name.
299 -a|--author="author" Sets author info (as name and email)
300 to be included in the documentation.
301 -m|--man-section="sect" Section number for the generated
302 man pages.
303 -v|--version Shows version.
304 -q|--quiet Suppress 'built with...' info.
305 -h|--help This help.
307 The mp_doccer Home Page:
308 http://www.triptico.com/software/mp_doccer.html
311 exit(0);
315 #######################################################
317 sub format_sh
318 # create a help shell script
320 my ($o, $h);
322 unless ($output) {
323 $output = 'localhelp.sh';
326 open F, ">$output" or die "Error: $!";
328 # build the header
330 print F "#!/bin/sh\n\n";
331 printf F "# Help program generated by mp_doccer $main::VERSION on %s\n",scalar(localtime());
332 print F "# mp_doccer is part of the Minimum Profit Text Editor\n";
333 print F "# http://www.triptico.com/software/mp.html\n\n";
335 print F "case \"\$1\" in\n";
337 for (my $n = 0; $n < scalar(@functions); $n++) {
338 my ($f,$syn);
340 $f = $functions[$n];
342 print F "$f->{'func'})\n";
344 print F "cat << EOF\n";
346 print F "$title\n\n";
348 print F "NAME\n\n";
349 print F "$f->{'func'} - $f->{'bdesc'}\n\n";
351 print F "SYNOPSIS\n\n";
353 $syn = defined($f->{'altsyn'}) ? $f->{'altsyn'} : $f->{'syn'};
354 $syn =~ s/\@([\w]+)/$1/g;
355 $syn =~ s/\%([\w]+)/$1/g;
357 chomp($syn);
358 print F "$syn\n\n";
360 if ($f->{'arg'}) {
361 my ($a, $d);
363 $a = $f->{'arg'};
364 $d = $f->{'argdesc'};
366 print F "ARGUMENTS\n\n";
368 for (my $n = 0; $n < scalar(@$a); $n++) {
369 print F "$$a[$n] - $$d[$n]\n";
372 print F "\n";
375 if ($f->{'desc'}) {
376 print F "DESCRIPTION\n\n";
378 my ($desc) = $f->{'desc'};
379 $desc =~ s/\@([\w]+)/$1/g;
380 $desc =~ s/\%([\w]+)/$1/g;
382 print F "$desc\n";
384 if ($f->{'category'}) {
385 my $s = $f->{'category'};
387 print F "CATEGORIES\n\n";
389 for (my $n = 0; $n < scalar(@$s); $n++) {
390 print F ", " if $n;
391 print F "$$s[$n]";
394 print F "\n";
398 if ($author) {
399 print F "AUTHOR\n\n";
400 print F "$author\n";
403 print F "EOF\n";
404 print F "\t;;\n";
407 print F "\"\")\n";
408 print F "\techo \"Usage: \$0 {keyword}\"\n";
409 print F "\t;;\n";
411 print F "*)\n";
412 print F "\techo \"No help for \$1\"\n";
413 print F "\texit 1";
414 print F "\t;;\n";
416 print F "esac\n";
417 print F "exit 0\n";
419 close F;
421 chmod 0755, $output;
425 sub format_man
426 # create man pages
428 my ($o, $h);
429 my ($pf);
431 unless ($output) {
432 $output = '.';
435 $output =~ s/\/$//;
437 unless (-d $output) {
438 print "$output must be a directory; aborting\n";
439 exit(1);
442 if ($file_prefix) {
443 $pf = $file_prefix . '_';
446 for(my $n = 0; $n < scalar(@functions); $n++) {
447 my ($f, $syn);
449 $f = $functions[$n];
451 # write the file
452 open F, ">$output/${pf}$f->{'func'}.$man_section" or die "Error: $!";
454 print F ".TH $f->{'func'} $man_section \"\" \"$title\"\n";
455 print F ".SH NAME\n";
456 print F "$f->{'func'} \\- $f->{'bdesc'}\n";
457 print F ".SH SYNOPSIS\n";
458 print F ".nf\n";
460 $syn = defined($f->{'altsyn'}) ? $f->{'altsyn'} : $f->{'syn'};
461 print F ".B $syn\n";
462 print F ".fi\n";
464 if ($f->{'arg'}) {
465 my ($a, $d);
467 $a = $f->{'arg'};
468 $d = $f->{'argdesc'};
470 print F ".SH ARGUMENTS\n";
472 for (my $n = 0; $n < scalar(@$a); $n++) {
473 print F ".B $$a[$n] \\-\n";
474 print F "$$d[$n]\n";
475 print F ".sp\n";
479 if ($f->{'desc'}) {
480 print F ".SH DESCRIPTION\n";
482 # take the description
483 my ($desc) = $f->{'desc'};
484 $desc =~ s/\@//g;
485 $desc =~ s/\%//g;
487 chomp($desc);
488 print F "$desc\n";
490 if ($f->{'category'}) {
491 my ($s) = $f->{'category'};
493 print F ".SH CATEGORIES\n";
495 for (my $n = 0; $n < scalar(@$s); $n++) {
496 print F ", " if $n;
497 print F "$$s[$n]";
500 print F "\n";
504 if ($author) {
505 print F ".SH AUTHOR\n";
506 print F "$author\n";
509 close F;
514 # HTML
516 sub html_header
518 my $title = shift;
519 my $ret = '';
521 $ret .= "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\"\n";
522 $ret .= "\"http://www.w3.org/TR/REC-html40/loose.dtd\">\n";
523 $ret .= "<head><title>$title</title>\n";
524 $ret .= "<link rel = 'StyleSheet' href = '$css' type = 'text/css'>\n" if $css;
525 $ret .= "<meta name = 'generator' content = 'mp_doccer $main::VERSION'>\n";
526 $ret .= "<meta name = 'date' content = '" . scalar(localtime()) . "'>\n";
527 $ret .= "<meta name = 'author' content = '$author'>\n" if $author;
528 $ret .= "</head>\n<body>\n";
530 return $ret;
534 sub html_footer
536 my $ret = "<div class = 'footer'>\n";
538 if ($author) {
539 $ret .= "<span class = 'author'>$author</span>";
542 if (!$quiet) {
543 $ret .= " - <em class = 'built_with'>Built with <a href = 'http://www.triptico.com/software/mp_doccer.html'>mp_doccer $main::VERSION</a></em>";
546 $ret .= "\n</div>\n</body>\n</html>\n";
548 return $ret;
552 sub html_toc
554 my $func_link = shift;
555 my $ret = '';
557 $ret .= "<a name = '_TOP_'></a><h1>$title</h1>\n<div class = 'toc'>\n";
559 if (scalar(keys(%categories))) {
560 $ret .= "<h2>By Category</h2>\n";
562 foreach my $sn (sort keys %categories) {
563 $ret .= "<a name = '$sn'></a>\n";
564 $ret .= "<h3 class = 'category'>$sn</h3>\n";
566 $ret .= "<ul class = 'by_category'>\n";
568 $ret .= join('',
569 map { " <li><a href = '" . $func_link->($_) . "'>$_</a></li>\n" }
570 sort(@{$categories{$sn}})
573 $ret .= "</ul>\n";
577 $ret .= "<h2>By Source</h2>\n";
579 foreach my $s (@sources) {
580 my @f = grep { $_->{'file'} eq $s } @functions;
582 unless (@f) {
583 next;
586 $ret .= "<h3 class = 'source_file'>$s</h3>\n";
588 $ret .= "<ul class = 'by_source'>\n";
590 $ret .= join('',
591 map { " <li><a href = '" . $func_link->($_) . "'>$_</a></li>\n" }
592 sort(map { $_->{'func'} } @f)
595 $ret .= "</ul>\n";
598 $ret .= "<h2>Alphabetical</h2>\n";
599 $ret .= "<ul class = 'alphabetical'>\n";
601 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
602 $ret .= " <li><a href = '" . $func_link->($f->{'func'}) .
603 "'>$f->{'func'}</a> - $f->{'bdesc'}</li>\n";
606 $ret .= "</ul></div>\n";
608 return $ret;
612 sub html_func
614 my $f = shift;
615 my $ret = '';
616 my $syn;
618 $ret .= "\n<div class = 'func' style = 'margin-left: 1em;'>\n";
620 $ret .= "<h3>Name</h3>\n";
621 $ret .= "<strong class = 'funcname'>$f->{'func'}</strong> - $f->{'bdesc'}\n";
623 $ret .= "<h3>Synopsis</h3>\n";
625 $syn = defined($f->{'altsyn'}) ? $f->{'altsyn'} : $f->{'syn'};
627 # synopsis decoration
628 $syn =~ s/\b$f->{'func'}\b/\<strong class = 'funcname'>$f->{'func'}\<\/strong>/g;
630 $syn =~ s/@([\w]+)/<em class = 'funcarg'>$1<\/em>/g;
631 $syn =~ s/\%([\w]+)/<em class = 'funcret'>$1<\/em>/g;
633 if ($f->{'arg'}) {
634 foreach my $a (@{$f->{'arg'}}) {
635 $syn =~ s/\b$a\b/\<em class = 'funcarg'>$a\<\/em>/g;
639 $ret .= "<pre class = 'funcsyn'>\n$syn</pre>\n";
641 if ($f->{'arg'}) {
642 my @a = @{$f->{'arg'}};
643 my @d = @{$f->{'argdesc'}};
645 $ret .= "<h3>Arguments</h3>\n";
646 $ret .= "<dl class = 'arguments'>\n";
648 while (@a) {
649 $ret .= " <dt><em class = 'funcarg'>" . shift(@a) . "</em></dt>";
650 $ret .= "<dd>" . shift(@d) . "</dd>\n";
653 $ret .= "</dl>\n";
656 if ($f->{'desc'}) {
657 $ret .= "<h3>Description</h3>\n";
659 # take the description
660 my ($desc) = $f->{'desc'};
662 # decorate function names
663 $desc =~ s/([\w_]+\(\))/<code class = 'funcname'>$1<\/code>/g;
665 # decorate function arguments
666 $desc =~ s/@([\w_]+)/<em class = 'funcarg'>$1<\/em>/g;
668 # decorate return values
669 $desc =~ s/\%([\w_]+)/<em class = 'funcret'>$1<\/em>/g;
671 # replace blank lines
672 $desc =~ s/\n\n/\n<p>\n/gs;
674 $ret .= "<p class = 'description'>$desc</p>\n";
676 if ($f->{category}) {
677 $ret .= "<h3>Categories</h3>\n";
679 $ret .= "<ul class = 'categories'>\n" .
680 join('', map { " <li><a href = '#$_'>$_</a></li>\n" } @{$f->{'category'}}) .
681 "</ul>\n";
685 $ret .= "</div>\n";
689 sub format_html_1
690 # create 1 html page
692 my (%f);
694 if ($file_prefix) {
695 $file_prefix = '_' . $file_prefix;
698 # create the file
699 my $fn = $output . $file_prefix . '.html';
701 open F, ">$fn" or die "Error create $fn: $!";
703 print F html_header($title);
705 print F html_toc( sub { "#" . shift } );
707 # the functions themselves
708 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
709 # avoid duplicate function names
710 if ($f{$f->{'func'}}) {
711 next;
714 $f{$f->{'func'}}++;
716 print F "\n<div class = 'func_container'>\n";
717 print F "<a name = '$f->{'func'}'></a>\n";
718 print F "<h2 style = 'border-bottom: solid 2px;'>$f->{'func'}</h2>\n";
720 print F html_func($f);
722 print F "</div>\n";
725 print F html_footer();
727 close F;
731 sub format_html
732 # create multipage html documents
734 $output = "." unless $output;
735 $output =~ s/\/$//;
737 unless (-d $output) {
738 print "$output must be a directory; aborting\n";
739 exit(1);
742 my $pf = $file_prefix ? $file_prefix . '_' : '';
744 # create the table of contents
745 my $top = $file_prefix || 'index';
747 open TOC, ">$output/${top}.html"
748 or die "Error: $!";
750 print TOC html_header($title);
752 print TOC html_toc( sub { $pf . shift() . ".html" } );
754 print TOC html_footer();
756 close TOC;
758 # the functions themselves
759 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
760 # write the file
761 open F, ">$output/" . $pf . "$f->{'func'}.html"
762 or die "Error: $!";
764 print F html_header($f->{'func'});
766 print F "<div class = 'topnav'>\n";
768 print F ' ', $f->{'prev'} ? "<a href = '${pf}$f->{'prev'}.html'>Prev</a>" : "Prev",
769 " |\n",
770 " <a href = '${top}.html'><b>$title</b></a>",
771 " |\n",
772 ' ', $f->{'next'} ? "<a href = '${pf}$f->{'next'}.html'>Next</a>" : "Next",
773 "\n";
775 print F "</div>\n";
777 print F "<h2 style = 'border-bottom: solid 2px;'>$f->{'func'}</h2>\n";
779 print F html_func($f);
781 print F html_footer();
783 close F;
788 sub _grutatxt_header
790 my $t = shift;
791 my $m = shift;
793 my $s = $t;
794 $s =~ s/./$m/g;
796 return $t . "\n" . $s . "\n\n";
800 sub _gl
802 my $s = shift;
804 $s = lc($s);
805 $s =~ s/\s/_/g;
807 return $s;
811 sub format_grutatxt
812 # create a grutatxt document
814 my (%f);
816 if ($file_prefix) {
817 $file_prefix = '_' . $file_prefix;
820 # create the file
821 my $fn = $output . $file_prefix . '.txt';
823 open F, ">$fn" or die "Error create $fn: $!";
825 print F _grutatxt_header($title, "=");
827 if (scalar(keys(%categories))) {
829 print F _grutatxt_header('By Category', '-');
831 foreach my $sn (sort keys %categories) {
833 print F _grutatxt_header($sn, '~');
835 print F join("\n",
836 map { ' * ./#' . _gl($_) . ' (' . $_ . ')' }
837 sort(@{$categories{$sn}})
840 print F "\n\n";
844 print F _grutatxt_header('By Source', '-');
846 foreach my $s (@sources) {
847 my @f = grep { $_->{'file'} eq $s } @functions;
849 unless (@f) {
850 next;
853 print F _grutatxt_header($s, '~');
855 print F join("\n",
856 map { ' * ./#' . _gl($_) . ' (' . $_ . ')' }
857 sort(map { $_->{'func'} } @f)
860 print F "\n\n";
863 print F _grutatxt_header('Alphabetical', '-');
865 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
866 print F ' * ./#',
867 _gl($f->{'func'}),
868 ' (',
869 $f->{func},
870 ') - ',
871 $f->{bdesc},
872 "\n";
875 print F "\n\n";
877 # the functions themselves
878 foreach my $f (sort { $a->{'func'} cmp $b->{'func'} } @functions) {
879 # avoid duplicate function names
880 if ($f{$f->{'func'}}) {
881 next;
884 $f{$f->{'func'}}++;
886 print F _grutatxt_header($f->{func}, '-');
888 print F _grutatxt_header('Name', '~');
890 print F '*' . $f->{func} . '* - ' . $f->{bdesc} . "\n";
892 print F "\n";
894 print F _grutatxt_header('Synopsis', '~');
896 my $syn = $f->{'altsyn'} || (' ' . $f->{'syn'});
898 # strip arg and return value marks
899 $syn =~ s/[@%]([\w]+)/$1/g;
901 print F $syn . "\n\n";
903 if ($f->{'arg'}) {
904 my @a = @{$f->{'arg'}};
905 my @d = @{$f->{'argdesc'}};
907 print F _grutatxt_header('Arguments', '~');
909 while (@a) {
910 print F ' * ' . shift(@a) . ': ' . shift(@d) . "\n";
913 print F "\n";
916 if ($f->{'desc'}) {
917 print F _grutatxt_header('Description', '~');
919 # take the description
920 my $desc = $f->{'desc'};
922 # decorate function arguments
923 $desc =~ s/@([\w_]+)/_$1_/g;
925 # decorate return values
926 $desc =~ s/\%([\w_]+)/_$1_/g;
928 print F $desc, "\n";
930 if ($f->{category}) {
931 print F _grutatxt_header('Categories', '~');
933 print F join("\n",
934 map { ' * ./#' . _gl($_) . ' (' . $_ . ')' }
935 @{$f->{'category'}});
937 print F "\n";
941 print F "\n";
944 if ($author) {
945 print F "----\n$author ";
948 if (!$quiet) {
949 print F "- Built with http://triptico.com/software/mp_doccer.html (mp_doccer $main::VERSION)";
952 print F "\n";
953 close F;