summaryrefslogtreecommitdiffstats
path: root/doc/doxydown
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 21:30:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 21:30:40 +0000
commit133a45c109da5310add55824db21af5239951f93 (patch)
treeba6ac4c0a950a0dda56451944315d66409923918 /doc/doxydown
parentInitial commit. (diff)
downloadrspamd-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/.gitignore19
-rw-r--r--doc/doxydown/LICENSE21
-rw-r--r--doc/doxydown/README.md139
-rwxr-xr-xdoc/doxydown/doxydown.pl624
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;