diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
commit | 133a45c109da5310add55824db21af5239951f93 (patch) | |
tree | ba6ac4c0a950a0dda56451944315d66409923918 /doc/doxydown | |
parent | Initial commit. (diff) | |
download | rspamd-133a45c109da5310add55824db21af5239951f93.tar.xz rspamd-133a45c109da5310add55824db21af5239951f93.zip |
Adding upstream version 3.8.1.upstream/3.8.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'doc/doxydown')
-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 | 624 |
4 files changed, 803 insertions, 0 deletions
diff --git a/doc/doxydown/.gitignore b/doc/doxydown/.gitignore new file mode 100644 index 0000000..eaca02e --- /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 0000000..d39277a --- /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 0000000..86d4c30 --- /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. Currently, 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 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; |