diff options
Diffstat (limited to '')
-rwxr-xr-x | doc/doxydown/doxydown.pl | 624 |
1 files changed, 624 insertions, 0 deletions
diff --git a/doc/doxydown/doxydown.pl b/doc/doxydown/doxydown.pl new file mode 100755 index 0000000..5002f58 --- /dev/null +++ b/doc/doxydown/doxydown.pl @@ -0,0 +1,624 @@ +#!/usr/bin/env perl + +$VERSION = "0.1.4"; + +use strict; +use warnings; +use Data::Dumper; +use Digest::MD5 qw(md5_hex); + +my @modules; +my %options = (); +my $cur_module; +my $example_language = "lua"; + +my %languages = ( + c => { + start => qr/^\s*\/\*\*\*(?:\s*|(\s+\S.+\s*))$/, + end => qr/^\s*\*+\/\s*$/, + filter => qr/^(?:\s*\*+\s?)?(\s*\S.+)\s*$/, + }, + lua => { + start => qr/^\s*\--(?:\[\[\[+|-+)\s*$/, + end => qr/^\s*--(:?\]\]+|-+)\s*$/, + filter => qr/^(?:\s*--!?\s?)?(\s*\S?.+)\s*$/, + }, + sql => { + start => qr/^\s*\--(?:\[\[+|-+)\s*$/, + end => qr/^\s*--(:?\]\]+|-+)\s*$/, + filter => qr/^(?:\s*--\s?)?(\s*\S.+)\s*$/, + }, + pl => { + start => qr/^\s*\##+\s*$/, + end => qr/^\s*##+\s*/, + filter => qr/^(?:\s*#+\s?)(\s*\S.+)\s*$/, + }, +); + +my $function_re = qr/^\s*\@(function|fn|method)\s*(\S.+)$/oi; +my $struct_re = qr/^\s*\@(table|struct)\s*(\S.+)$/oi; +my $module_re = qr/^\s*\@(?:module|file)\s*(\S.+)$/oi; +my $language; + +# /function print_module_markdown +sub print_module_markdown { + my ( $mname, $m ) = @_; + my $idline = $options{g} ? "" : " {#$m->{'id'}}"; + + print <<EOD; +## Module `$mname`$idline + +$m->{'data'} +EOD + if ( $m->{'example'} ) { + print <<EOD; + +### Example: + +~~~$m->{'example_language'} +$m->{'example'} +~~~ +EOD + } + + sub print_func { + my ($f) = @_; + + my $name = $f->{'name'}; + my $id = $f->{'id'}; + + if ($f->{'brief'}) { + print "[`$name`](#$id) | ". $f->{'brief'} . "\n"; + } else { + print "[`$name`](#$id) | No description\n"; + } + } + + sub print_table { + my ($f) = @_; + + my $name = $f->{'name'}; + my $id = $f->{'id'}; + + if ($f->{'brief'}) { + print "> [`$name`](#$id): ". $f->{'brief'} . "\n\n"; + } else { + print "> [`$name`](#$id)\n\n"; + } + } + + print "\n### Brief content:\n\n"; + + if ($m->{'functions'}) { + if (scalar(@{ $m->{'functions'} }) > 0) { + print "**Functions**:\n\n"; + + print " Function | Description\n"; + print "----------|------------\n"; + foreach ( @{ $m->{'functions'} } ) { + print_func($_); + } + } + } + + if ($m->{'methods'}) { + if (scalar(@{ $m->{'methods'} }) > 0) { + print "\n\n**Methods**:\n\n"; + print " Method | Description\n"; + print "----------|------------\n"; + foreach ( @{ $m->{'methods'} } ) { + print_func($_); + } + } + } + + if ($m->{'tables'}) { + if (scalar(@{ $m->{'tables'} }) > 0) { + print "\n\n**Tables**:\n\n"; + + foreach ( @{ $m->{'tables'} } ) { + print_table($_); + } + } + } + + if ($m->{'structs'}) { + if (scalar(@{ $m->{'structs'} }) > 0) { + print "\n\n**Structs**:\n\n"; + + foreach ( @{ $m->{'structs'} } ) { + print_table($_); + } + } + } +} + +# /function print_function_markdown +sub print_function_markdown { + my ( $type, $fname, $f ) = @_; + + my $idline = $options{g} ? "" : " {#$f->{'id'}}"; + print <<EOD; +### $type `$fname`$idline + +$f->{'data'} +EOD + print "\n**Parameters:**\n\n"; + + if ( $f->{'params'} && scalar @{ $f->{'params'} } > 0 ) { + foreach ( @{ $f->{'params'} } ) { + if ( $_->{'type'} ) { + print + "- `$_->{'name'} \{$_->{'type'}\}`: $_->{'description'}\n"; + } else { + print "- `$_->{'name'}`: $_->{'description'}\n"; + } + } + } else { + print "No parameters\n"; + } + + print "\n**Returns:**\n\n"; + + if ( $f->{'returns'} && scalar @{ $f->{'returns'} } > 0 ) { + foreach ( @{ $f->{'returns'} } ) { + if ( $_->{'type'} ) { + print "- `\{$_->{'type'}\}`: $_->{'description'}\n"; + } else { + print "- $_->{'description'}\n"; + } + } + } else { + print "No return\n"; + } + + if ( $f->{'available'} ) { + printf "\n**Available in:** %s\n", $f->{'available'}; + } + + if ( $f->{'example'} ) { + print <<EOD; + +### Example: + +~~~$f->{'example_language'} +$f->{'example'} +~~~ +EOD + } +} + +# /function print_struct_markdown +sub print_struct_markdown { + my ( $type, $fname, $f ) = @_; + + my $idline = $options{g} ? "" : " {#$f->{'id'}}"; + print <<EOD; +### $type `$fname`$idline + +$f->{'data'} +EOD + print "\n**Elements:**\n\n"; + + if ( $f->{'params'} && scalar @{ $f->{'params'} } > 0 ) { + foreach ( @{ $f->{'params'} } ) { + if ( $_->{'type'} ) { + print + "- `$_->{'name'} \{$_->{'type'}\}`: $_->{'description'}\n"; + } else { + print "- `$_->{'name'}`: $_->{'description'}\n"; + } + } + } else { + print "No elements\n"; + } + + if ( $f->{'example'} ) { + print <<EOD; + +### Example: + +~~~$f->{'example_language'} +$f->{'example'} +~~~ +EOD + } +} + +# /function print_markdown +sub print_markdown { + for my $m (@modules) { + my $mname = $m->{name}; + + print_module_markdown( $mname, $m ); + + if ($m->{'functions'}) { + if ( scalar(@{ $m->{'functions'} }) > 0 ) { + print "\n## Functions\n\nThe module `$mname` defines the following functions.\n\n"; + + foreach ( @{ $m->{'functions'} } ) { + print_function_markdown( "Function", $_->{'name'}, $_ ); + + print "\nBack to [module description](#$m->{'id'}).\n\n"; + + } + } + } + + if ($m->{'methods'}) { + if ( scalar(@{ $m->{'methods'} }) > 0 ) { + print "\n## Methods\n\nThe module `$mname` defines the following methods.\n\n"; + + foreach ( @{ $m->{'methods'} } ) { + print_function_markdown( "Method", $_->{'name'}, $_ ); + + print "\nBack to [module description](#$m->{'id'}).\n\n"; + + } + } + } + + if ($m->{'tables'}) { + if ( scalar(@{ $m->{'tables'} }) > 0 ) { + print "\n## Tables\n\nThe module `$mname` defines the following tables.\n\n"; + + foreach ( @{ $m->{'tables'} } ) { + print_struct_markdown( "Table", $_->{'name'}, $_ ); + + print "\nBack to [module description](#$m->{'id'}).\n\n"; + + } + } + } + + if ($m->{'structs'}) { + if ( scalar(@{ $m->{'structs'} }) > 0 ) { + print "\n## Structs\n\nThe module `$mname` defines the following structs.\n\n"; + + foreach ( @{ $m->{'structs'} } ) { + print_struct_markdown( "Struct", $_->{'name'}, $_ ); + + print "\nBack to [module description](#$m->{'id'}).\n\n"; + + } + } + } + + print "\nBack to [top](#).\n\n"; + } +} + +# /function make_id +sub make_id { + my ( $name, $prefix ) = @_; + + if ( !$prefix ) { + $prefix = "f"; + } + + if ( !$options{g} ) { + + # Kramdown/pandoc version of ID's + $name =~ /^(\S+).*$/; + + return substr( substr( $prefix, 0, 1 ) . md5_hex($1), 0, 6 ); + } else { + my $input = lc $prefix . "-" . $name; + my $id = join '-', split /\s+/, $input; + + $id =~ s/[^\w_-]+//g; + + return $id; + } +} + +# /function substitute_data_keywords +sub substitute_data_keywords { + my ($line) = @_; + + if ( $line =~ /^.*\@see\s+(\S+)\s*.*$/ ) { + my $name = $1; + my $id = make_id($name); + + return $line =~ s/\@see\s+\S+/[`$name`](#$id)/r; + } + + return $line; +} + +# /function parse_function +sub parse_function { + my ( $func, @data ) = @_; + + my ( $type, $name ) = ( $func =~ $function_re ); + chomp $name; + + my $f = { + name => $name, + data => '', + example => undef, + example_language => $example_language, + id => make_id( $name, $type ), + }; + my $example = 0; + + foreach ( @data ) { + if ( /^\s*\@param\s*(?:\{([^}]+)\})?\s*(\S+)\s*(.+)?\s*$/ ) { + my $p = { + name => $2, + type => $1 || "no type", + description => $3 || "no description" + }; + + push @{ $f->{'params'} }, $p; + } elsif ( /^\s*\@return\s*(?:\{([^}]+)\})?\s*(.+)?\s*$/ ) { + my $r = { + type => $1, + description => $2 || "no description" + }; + + push @{ $f->{'returns'} }, $r; + } elsif ( /^\s*\@brief\s*(\S.+)$/ ) { + $f->{'brief'} = $1; + } elsif ( /^\s*\@available\s*(\S.+)$/ ) { + $f->{'available'} = $1; + } + elsif ( /^\s*\@example\s*(\S)?\s*$/ ) { + $example = 1; + if ( $1 ) { + $f->{'example_language'} = $1; + } + } elsif ( $_ ne $func ) { + if ( $example ) { + $f->{'example'} .= $_; + } else { + $f->{'data'} .= substitute_data_keywords($_); + } + } + } + + if ( $f->{'data'} ) { + chomp $f->{'data'}; + } elsif ($f->{'brief'}) { + chomp $f->{'brief'}; + $f->{'data'} = $f->{'brief'}; + } + + if ( $f->{'example'} ) { + chomp $f->{'example'}; + } + + if ( $f->{'available'} ) { + chomp $f->{'available'} + } + + if ( !$f->{'brief'} && $f->{'data'} ) { + + + if ( $f->{'data'} =~ /^(.*?)(?:(?:[.:]\s|$)|\n).*/ ) { + $f->{'brief'} = "$1"; + chomp $f->{'brief'}; + + if ( $f->{'brief'} !~ /\.$/) { + $f->{'brief'} .= "."; + } + } + } + + if ( $type eq "method" ) { + push @{ $cur_module->{'methods'} }, $f; + } elsif ( $type eq "function" || $type eq "fn") { + push @{ $cur_module->{'functions'} }, $f; + } +} + +# /function parse_struct +sub parse_struct { + my ( $func, @data ) = @_; + + my ( $type, $name ) = ( $func =~ $struct_re ); + chomp $name; + + my $f = { + name => $name, + data => '', + example => undef, + example_language => $example_language, + id => make_id( $name, $type ), + }; + my $example = 0; + + foreach ( @data ) { + if ( /^\s*\@param\s*(?:\{([^}]+)\})?\s*(\S+)\s*(.+)?\s*$/ ) { + my $p = { + name => $2, + type => $1, + description => $3 + }; + + push @{ $f->{'params'} }, $p; + } elsif ( /^\s*\@brief\s*(\S.+)$/ ) { + $f->{'brief'} = $1; + } elsif ( /^\s*\@example\s*(\S)?\s*$/ ) { + $example = 1; + if ( $1 ) { + $f->{'example_language'} = $1; + } + } elsif ( $_ ne $func ) { + if ( $example ) { + $f->{'example'} .= $_; + } else { + $f->{'data'} .= substitute_data_keywords($_); + } + } + } + + if ( $f->{'data'} ) { + chomp $f->{'data'}; + } elsif ($f->{'brief'}) { + chomp $f->{'brief'}; + $f->{'data'} = $f->{'brief'}; + } + + if ( $f->{'example'} ) { + chomp $f->{'example'}; + } + + if ( $type eq "table" ) { + push @{ $cur_module->{'tables'} }, $f; + } elsif ( $type eq "struct" ) { + push @{ $cur_module->{'structs'} }, $f; + } +} + +# /function parse_module +sub parse_module { + my ( $module, @data ) = @_; + my ( $name ) = ( $module =~ $module_re ); + + chomp $name; + + my $f = { + name => $name, + functions => [], + methods => [], + data => '', + example => undef, + example_language => $example_language, + id => make_id( $name, "module" ), + }; + + my $example = 0; + + foreach ( @data ) { + if ( /^\s*\@example\s*(\S)?\s*$/ ) { + $example = 1; + if ($1) { + $f->{'example_language'} = $1; + } + } elsif ( /^\s*\@brief\s*(\S.+)$/ ) { + $f->{'brief'} = $1; + } elsif ( $_ ne $module ) { + if ( $example ) { + $f->{'example'} .= $_; + } else { + $f->{'data'} .= substitute_data_keywords($_); + } + } + } + + if ( $f->{'data'} ) { + chomp $f->{'data'}; + } elsif ( $f->{'brief'} ) { + chomp $f->{'brief'}; + + $f->{'data'} = $f->{'brief'}; + } + + if ( $f->{'example'} ) { + chomp $f->{'example'}; + } + + $cur_module = $f; + push @modules, $f; +} + +# /function parse_content +sub parse_content { + # + my @func = grep /$function_re/, @_; + if ( scalar @func > 0 ) { + parse_function( $func[0], @_ ); + } + + # + my @struct = grep /$struct_re/, @_; + if ( scalar @struct > 0 ) { + parse_struct( $struct[0], @_ ); + } + + # + my @module = grep /$module_re/, @_; + if ( scalar @module > 0 ) { + parse_module( $module[0], @_ ); + } +} + +sub HELP_MESSAGE { + print STDERR <<EOF; +Utility to convert doxygen comments to markdown. + +usage: $0 [-hg] [-l language] < input_source > markdown.md + + -h : this (help) message + -e : sets default example language (default: lua) + -l : sets input language (default: c) + -g : use github flavoured markdown (default: kramdown/pandoc) +EOF + + exit; +} + +$Getopt::Std::STANDARD_HELP_VERSION = 1; +use Getopt::Std; + +getopts( 'he:gl:', \%options ); + +HELP_MESSAGE() if $options{h}; + +$example_language = $options{e} if $options{e}; +$language = $languages{ lc $options{l} } if $options{l}; + +if ( !$language ) { + $language = $languages{c}; +} + +## TODO: select language based on file extension +## TODO: change calling structure to allow looping through directory + +use constant { + STATE_READ_SKIP => 0, + STATE_READ_CONTENT => 1, + STATE_READ_ENUM => 2, + STATE_READ_STRUCT => 3, +}; + +my $state = STATE_READ_SKIP; +my $content; + +while ( <> ) { + if ( $state == STATE_READ_SKIP ) { + if ( $_ =~ $language->{start} ) { + $state = STATE_READ_CONTENT; + + if (defined($1)) { + chomp($content = $1); + $content =~ tr/\r//d; + $content .= "\n"; + } else { + $content = ""; + } + } + } elsif ( $state == STATE_READ_CONTENT ) { + if ( $_ =~ $language->{end} ) { + $state = STATE_READ_SKIP; + + parse_content( split /^/, $content ); + + $content = ""; + } else { + my ($line) = ( $_ =~ $language->{filter} ); + + if ( $line ) { + $line =~ tr/\r//d; + $content .= $line . "\n"; + } else { + # Preserve empty lines + $content .= "\n"; + } + } + } +} + +#print Dumper( \@modules ); +print_markdown; |