diff options
Diffstat (limited to 'doc/doxydown')
m--------- | doc/doxydown | 0 | ||||
-rw-r--r-- | doc/doxydown/.gitignore | 19 | ||||
-rw-r--r-- | doc/doxydown/LICENSE | 21 | ||||
-rw-r--r-- | doc/doxydown/README.md | 139 | ||||
-rwxr-xr-x | doc/doxydown/doxydown.pl | 388 |
5 files changed, 567 insertions, 0 deletions
diff --git a/doc/doxydown b/doc/doxydown deleted file mode 160000 -Subproject 6c1f79c4294ef66a55bfc75845f3fdf3e2e1c32 diff --git a/doc/doxydown/.gitignore b/doc/doxydown/.gitignore new file mode 100644 index 000000000..eaca02ed3 --- /dev/null +++ b/doc/doxydown/.gitignore @@ -0,0 +1,19 @@ +/blib/ +/.build/ +_build/ +cover_db/ +inc/ +Build +!Build/ +Build.bat +.last_cover_stats +/Makefile +/Makefile.old +/MANIFEST.bak +/META.yml +/META.json +/MYMETA.* +nytprof.out +/pm_to_blib +*.o +*.bs diff --git a/doc/doxydown/LICENSE b/doc/doxydown/LICENSE new file mode 100644 index 000000000..d39277a3e --- /dev/null +++ b/doc/doxydown/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Vsevolod Stakhov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doc/doxydown/README.md b/doc/doxydown/README.md new file mode 100644 index 000000000..fb3f9f14b --- /dev/null +++ b/doc/doxydown/README.md @@ -0,0 +1,139 @@ +# Doxydown - documentation utility + +## Introduction + +Doxydown is an utility to convert `doxygen`-like comments from the source code to markdown. +Unlike other documentation systems, `doxydown` is specifically designed to generate markdown output only. +At the moment, doxydown can work with C and lua comments and produce kramdown/pandoc or github +flavoured markdown. Doxydown produces output with anchors, links and table of content. +It also can highlight syntax for examples in the documentation. + +### Why markdown + +Markdown is used by many contemporary engines and can be rendered to HTML using +advanced templates, styles and scripts. Markdown provides excellent formatting +capabilities while it doesn't require authors to be web designers to create +documentation. Markdown is rendered by [`github`](https://github.com) and +doxydown can generate documentation easily viewed directly inside github. Moreover, +doxydown supports pandoc style of markdown and that means that markdown output +can be converted to all formats supported by pandoc (html, pdf, latex, +man pages and many others). + +### Why not `other documentation generator` + +Doxydown is extremely simple as it can output markdown only but it is very +convenient tool to generate nice markdown with all features required from the +documentation system. Doxydown uses input format that is very close to `doxygen` +that allows to re-use the existing documentation comments. Currenly, doxydown +does not support many features but they could be easily added on demand. + +## Input format + +Doxydown extracts documentation from the comments blocks. The start of block is indicated by + + /*** + +in `C` or by + + --[[[ + +in `lua`. The end of documentation block is the normal multiline comment ending +specific for the input language. Doxydown also strips an initial comment character, +therefore the following inputs are equal: + +~~~c +/*** + * some text + * other text + * + */ +~~~ +and + +~~~c +/*** +some text +other text + +*/ +~~~ + +Note that doxydown preserves empty lines and all markdown elements. + +### Documentation blocks + +Each documentation block describes either module or function/method. Modules are +logical compounds of functions and methods. The difference between method and +function is significant for languages with methods support (e.g. by `lua` via +metatables). To define method or function you can use the following: + + /*** + @function my_awesome_function(param1[, param2]) + This function is awesome. + */ + +All text met in the current documentation block is used as function or method description. +You can also define parameters and return values for functions and methods: + + @param {type} param1 mandatory param + +Here, `{type}` is optional type description for a parameter, `param1` is parameter's name +and the rest of the string is parameter description. Currently, you cannot split +parameter description by newline character. In future versions of doxydown this might +be fixed. + +You can specify return type of your function by using of `@return` tag: + + @return {type} some cool result + +This tag is similar to `@param` and has the same limitation regarding newlines. +You can also add some example code by using of `@example` tag: + + @example + my_awesome_function('hello'); // returns 42 + +All text after `@example` tag and until documentation block end is used as an example +and is highlighted in markdown. Also you can switch the language of example by using +the extended `@example` tag: + + @example lua + +In this example, the code will be highlighted as `lua` code. + +Modules descriptions uses the same conventions, but `@param` and `@return` are +meaningless for the modules. Function and methods blocks that follows some `@module` +block are automatically attached to that module. + +Both modules and function can use links to other functions and methods by using of +`@see` tag: + + @see my_awesome_function + +This inserts a hyperlink to the specified function definition to the markdown. + +## Output format + +Doxydown can generate github flavoured markdown and pandoc/kramdown compatible +markdown. The main difference is in how anchors are organized. In kramdown and +pandoc it is possible to specify an explicit id for each header, whilst in +GH flavoured markdown we can use only implicit anchors. + +### Examples + +You can see an example of github flavoured markdown render at +[libucl github page](https://github.com/vstakhov/libucl/blob/master/doc/lua_api.md). +The same page bu rendered by kramdown engine in `jekyll` platform can be +accessed by [this address](https://rspamd.com/doc/lua/ucl.html). + +## Program invocation + + doxydown [-hg] [-l language] < input_source > markdown.md + +* `-h`: help message +* `-e`: sets default example language (default: lua) +* `-l`: sets input language (default: c) +* `-g`: use github flavoured markdown (default: kramdown/pandoc) + +## License + +Doxydown is published by terms of `MIT` license.
\ No newline at end of file diff --git a/doc/doxydown/doxydown.pl b/doc/doxydown/doxydown.pl new file mode 100755 index 000000000..3aa1b6b1d --- /dev/null +++ b/doc/doxydown/doxydown.pl @@ -0,0 +1,388 @@ +#!/usr/bin/env perl + +$VERSION = "0.1"; + +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*$/, + }, + lua => { + 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 $module_re = qr/^\s*\@(?:module|file)\s*(\S.+)$/oi; + +my $language; + +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\n"; + } + else { + print "> [`$name`](#$id)\n\n"; + } + } + + print "\n### Brief content:\n\n"; + if (scalar(@{ $m->{'functions'} }) > 0) { + print "**Functions**:\n\n"; + foreach ( @{ $m->{'functions'} } ) { + print_func($_); + } + } + if (scalar(@{ $m->{'methods'} }) > 0) { + print "\n\n**Methods**:\n\n"; + foreach (@{ $m->{'methods'} }) { + print_func($_); + } + } +} + +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 "\tnothing\n"; + } + print "\n**Returns:**\n\n"; + if ( $f->{'return'} && $f->{'return'}->{'description'} ) { + $_ = $f->{'return'}; + if ( $_->{'type'} ) { + print "- `\{$_->{'type'}\}`: $_->{'description'}\n"; + } + else { + print "- $_->{'description'}\n"; + } + } + else { + print "\tnothing\n"; + } + if ( $f->{'example'} ) { + print <<EOD; + +Example: + +~~~$f->{'example_language'} +$f->{'example'} +~~~ +EOD + } +} + +sub print_markdown { + for my $m (@modules) { + my $mname = $m->{name}; + print_module_markdown( $mname, $m ); + + 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 (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"; + + } + } + + print "\nBack to [top](#).\n\n"; + } +} + +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; + } +} + +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; +} + +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, description => $3 }; + push @{ $f->{'params'} }, $p; + } + elsif (/^\s*\@return\s*(?:\{([^}]+)\})?\s*(.+)?\s*$/) { + my $r = { type => $1, description => $2 }; + $f->{'return'} = $r; + } + 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 "method" ) { + push @{ $cur_module->{'methods'} }, $f; + } + else { + push @{ $cur_module->{'functions'} }, $f; + } +} + +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; +} + +sub parse_content { + my @func = grep /$function_re/, @_; + if ( scalar @func > 0 ) { + parse_function( $func[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}; +} + +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; |