summaryrefslogtreecommitdiffstats
path: root/doc
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
parentInitial commit. (diff)
downloadrspamd-upstream.tar.xz
rspamd-upstream.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')
-rw-r--r--doc/Makefile94
-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
-rw-r--r--doc/rspamadm.1134
-rw-r--r--doc/rspamadm.1.md85
-rw-r--r--doc/rspamc.1307
-rw-r--r--doc/rspamc.1.md173
-rw-r--r--doc/rspamd.878
-rw-r--r--doc/rspamd.8.md57
11 files changed, 1731 insertions, 0 deletions
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..53ed9b6
--- /dev/null
+++ b/doc/Makefile
@@ -0,0 +1,94 @@
+# A simple makefile to generate documentation from .md using pandoc
+
+PANDOC ?= pandoc
+LUADOC ?= doxydown/doxydown.pl
+LLUADOC ?= ${LUADOC} -l lua -e lua
+
+all: man
+
+man: rspamd.8 rspamc.1 rspamadm.1
+
+rspamd.8: rspamd.8.md
+ $(PANDOC) -s -f markdown -t man -o rspamd.8 rspamd.8.md
+rspamc.1: rspamc.1.md
+ $(PANDOC) -s -f markdown -t man -o rspamc.1 rspamc.1.md
+rspamadm.1: rspamadm.1.md
+ $(PANDOC) -s -f markdown -t man -o rspamadm.1 rspamadm.1.md
+
+lua-dirs:
+ mkdir -p markdown/lua
+
+lua-doc: lua-dirs rspamd_regexp rspamd_ip rspamd_config rspamd_task ucl rspamd_http rspamd_trie \
+ rspamd_resolver rspamd_redis rspamd_upstream_list rspamd_expression rspamd_mimepart rspamd_logger rspamd_url \
+ rspamd_tcp rspamd_mempool rspamd_html rspamd_util rspamd_sqlite3 rspamd_cryptobox rspamd_map \
+ lua_redis lua_util lua_maps lua_clickhouse lua_selectors rspamd_udp rspamd_text lua_mime rspamd_parsers \
+ rspamd_cdb
+
+lua_redis:
+ $(LLUADOC) < ../lualib/lua_redis.lua > markdown/lua/lua_redis.md
+
+lua_util:
+ $(LLUADOC) < ../lualib/lua_util.lua > markdown/lua/lua_util.md
+
+lua_maps:
+ $(LLUADOC) < ../lualib/lua_maps.lua > markdown/lua/lua_maps.md
+
+lua_clickhouse:
+ $(LLUADOC) < ../lualib/lua_clickhouse.lua > markdown/lua/lua_clickhouse.md
+
+lua_selectors:
+ $(LLUADOC) < ../lualib/lua_selectors/init.lua > markdown/lua/lua_selectors.md
+
+lua_mime:
+ $(LLUADOC) < ../lualib/lua_mime.lua > markdown/lua/lua_mime.md
+
+rspamd_regexp: ../src/lua/lua_regexp.c
+ $(LUADOC) < ../src/lua/lua_regexp.c > markdown/lua/rspamd_regexp.md
+rspamd_ip: ../src/lua/lua_ip.c
+ $(LUADOC) < ../src/lua/lua_ip.c > markdown/lua/rspamd_ip.md
+rspamd_config: ../src/lua/lua_config.c
+ $(LUADOC) < ../src/lua/lua_config.c > markdown/lua/rspamd_config.md
+rspamd_task: ../src/lua/lua_task.c
+ $(LUADOC) < ../src/lua/lua_task.c > markdown/lua/rspamd_task.md
+ucl: ../contrib/libucl/lua_ucl.c
+ $(LUADOC) < ../contrib/libucl/lua_ucl.c > markdown/lua/ucl.md
+rspamd_http: ../src/lua/lua_http.c
+ $(LUADOC) < ../src/lua/lua_http.c > markdown/lua/rspamd_http.md
+rspamd_trie: ../src/lua/lua_trie.c
+ $(LUADOC) < ../src/lua/lua_trie.c > markdown/lua/rspamd_trie.md
+rspamd_resolver: ../src/lua/lua_dns_resolver.c
+ $(LUADOC) < ../src/lua/lua_dns_resolver.c > markdown/lua/rspamd_resolver.md
+rspamd_redis: ../src/lua/lua_redis.c
+ $(LUADOC) < ../src/lua/lua_redis.c > markdown/lua/rspamd_redis.md
+rspamd_upstream_list: ../src/lua/lua_upstream.c
+ $(LUADOC) < ../src/lua/lua_upstream.c > markdown/lua/rspamd_upstream.md
+rspamd_expression: ../src/lua/lua_expression.c
+ $(LUADOC) < ../src/lua/lua_expression.c > markdown/lua/rspamd_expression.md
+rspamd_mimepart: ../src/lua/lua_mimepart.c
+ $(LUADOC) < ../src/lua/lua_mimepart.c > markdown/lua/rspamd_mimepart.md
+rspamd_logger: ../src/lua/lua_logger.c
+ $(LUADOC) < ../src/lua/lua_logger.c > markdown/lua/rspamd_logger.md
+rspamd_url: ../src/lua/lua_url.c
+ $(LUADOC) < ../src/lua/lua_url.c > markdown/lua/rspamd_url.md
+rspamd_tcp: ../src/lua/lua_tcp.c
+ $(LUADOC) < ../src/lua/lua_tcp.c > markdown/lua/rspamd_tcp.md
+rspamd_mempool: ../src/lua/lua_mempool.c
+ $(LUADOC) < ../src/lua/lua_mempool.c > markdown/lua/rspamd_mempool.md
+rspamd_html: ../src/lua/lua_html.cxx
+ $(LUADOC) < ../src/lua/lua_html.cxx > markdown/lua/rspamd_html.md
+rspamd_util: ../src/lua/lua_util.c
+ $(LUADOC) < ../src/lua/lua_util.c > markdown/lua/rspamd_util.md
+rspamd_sqlite3: ../src/lua/lua_sqlite3.c
+ $(LUADOC) < ../src/lua/lua_sqlite3.c > markdown/lua/rspamd_sqlite3.md
+rspamd_cryptobox: ../src/lua/lua_cryptobox.c
+ $(LUADOC) < ../src/lua/lua_cryptobox.c > markdown/lua/rspamd_cryptobox.md
+rspamd_map: ../src/lua/lua_map.c
+ $(LUADOC) < ../src/lua/lua_map.c > markdown/lua/rspamd_map.md
+rspamd_udp: ../src/lua/lua_udp.c
+ $(LUADOC) < ../src/lua/lua_udp.c > markdown/lua/rspamd_udp.md
+rspamd_text: ../src/lua/lua_text.c
+ $(LUADOC) < ../src/lua/lua_text.c > markdown/lua/rspamd_text.md
+rspamd_parsers: ../src/lua/lua_parsers.c
+ $(LUADOC) < ../src/lua/lua_parsers.c > markdown/lua/rspamd_parsers.md
+rspamd_cdb: ../src/lua/lua_cdb.c
+ $(LUADOC) < ../src/lua/lua_cdb.c > markdown/lua/rspamd_cdb.md \ No newline at end of file
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;
diff --git a/doc/rspamadm.1 b/doc/rspamadm.1
new file mode 100644
index 0000000..bd3b02a
--- /dev/null
+++ b/doc/rspamadm.1
@@ -0,0 +1,134 @@
+.\" Automatically generated by Pandoc 1.17.2
+.\"
+.TH "RSPAMADM" "1" "" "Rspamd User Manual" ""
+.hy
+.SH NAME
+.PP
+rspamadm \- rspamd administration utility
+.SH SYNOPSIS
+.PP
+rspamadm [\f[I]global_options\f[]] [\f[I]command\f[]]
+[\f[I]command_options\f[]]...
+.SH DESCRIPTION
+.PP
+\f[C]rspamadm\f[] is a routine to manage rspamd spam filtering system.
+It is intended to perform such actions as merging databases, performing
+configuration tests, encrypting passwords, signing configurations and so
+on.
+You can get a list of available \f[B]commands\f[] by running
+.IP
+.nf
+\f[C]
+rspamadm\ \-l
+\f[]
+.fi
+.PP
+Also for each command you can check list of available
+\f[B]command_options\f[] by running
+.IP
+.nf
+\f[C]
+rspamadm\ help\ command
+rspamadm\ command\ \-\-help
+\f[]
+.fi
+.SH OPTIONS
+.TP
+.B \-h, \-\-help
+Show help message
+.RS
+.RE
+.TP
+.B \-v, \-\-verbose
+Enable verbose output
+.RS
+.RE
+.TP
+.B \-l, \-\-list\-commands
+List available commands
+.RS
+.RE
+.TP
+.B \-\-version
+Show version
+.RS
+.RE
+.TP
+.B \-\-var=\f[I]value\f[]
+Redefine ucl variable in format \f[C]VARIABLE=VALUE\f[]
+.RS
+.RE
+.SH RETURN VALUE
+.PP
+On exit \f[C]rspamadm\f[] returns \f[C]0\f[] if operation was successful
+and an error code otherwise.
+.SH EXAMPLES
+.PP
+Get help for pw command:
+.IP
+.nf
+\f[C]
+rspamadm\ help\ pw
+rspamadm\ pw\ \-\-help
+\f[]
+.fi
+.PP
+Encrypt controller\[aq]s password:
+.IP
+.nf
+\f[C]
+rspamadm\ pw\ encrypt
+\f[]
+.fi
+.PP
+Merge fuzzy databases:
+.IP
+.nf
+\f[C]
+rspamadm\ fuzzy_merge\ \-s\ data1.sqlite\ \-s\ data2.sqlite\ \-t\ dest.sqlite
+\f[]
+.fi
+.PP
+Perform configuration test:
+.IP
+.nf
+\f[C]
+rspamadm\ configtest\ \-c\ rspamd.conf
+\f[]
+.fi
+.PP
+Test configuration strictly and redefine some ucl vars:
+.IP
+.nf
+\f[C]
+rspamadm\ \-\-var=DBDIR=/tmp\ configtest\ \-c\ ./rspamd.conf\ \-s
+\f[]
+.fi
+.PP
+Dump the processed configuration:
+.IP
+.nf
+\f[C]
+rspamadm\ configdump
+\f[]
+.fi
+.PP
+Dump the processed configuration as JSON string:
+.IP
+.nf
+\f[C]
+rspamadm\ configdump\ \-j
+\f[]
+.fi
+.PP
+Generate a keypair to use for HTTPCrypt encryption:
+.IP
+.nf
+\f[C]
+rspamadm\ keypair
+\f[]
+.fi
+.SH SEE ALSO
+.PP
+Rspamd documentation and source codes may be downloaded from
+<https://rspamd.com/>.
diff --git a/doc/rspamadm.1.md b/doc/rspamadm.1.md
new file mode 100644
index 0000000..83c6c3e
--- /dev/null
+++ b/doc/rspamadm.1.md
@@ -0,0 +1,85 @@
+% RSPAMADM(1) Rspamd User Manual
+
+# NAME
+
+rspamadm - rspamd administration utility
+
+# SYNOPSIS
+
+rspamadm [*global_options*] [*command*] [*command_options*]...
+
+# DESCRIPTION
+
+`rspamadm` is a routine to manage rspamd spam filtering system. It is intended to perform
+such actions as merging databases, performing configuration tests, encrypting passwords,
+signing configurations and so on. You can get a list of available **commands** by running
+
+ rspamadm -l
+
+Also for each command you can check list of available **command_options** by running
+
+ rspamadm help command
+ rspamadm command --help
+
+
+# OPTIONS
+
+-h, \--help
+: Show help message
+
+-v, \--verbose
+: Enable verbose output
+
+-l, \--list-commands
+: List available commands
+
+\--version
+: Show version
+
+\--var=*value*
+: Redefine ucl variable in format `VARIABLE=VALUE`
+
+# RETURN VALUE
+
+On exit `rspamadm` returns `0` if operation was successful and an error code otherwise.
+
+# EXAMPLES
+
+Get help for pw command:
+
+ rspamadm help pw
+ rspamadm pw --help
+
+Encrypt controller's password:
+
+ rspamadm pw encrypt
+
+Merge fuzzy databases:
+
+ rspamadm fuzzy_merge -s data1.sqlite -s data2.sqlite -t dest.sqlite
+
+Perform configuration test:
+
+ rspamadm configtest -c rspamd.conf
+
+Test configuration strictly and redefine some ucl vars:
+
+ rspamadm --var=DBDIR=/tmp configtest -c ./rspamd.conf -s
+
+
+Dump the processed configuration:
+
+ rspamadm configdump
+
+Dump the processed configuration as JSON string:
+
+ rspamadm configdump -j
+
+Generate a keypair to use for HTTPCrypt encryption:
+
+ rspamadm keypair
+
+# SEE ALSO
+
+Rspamd documentation and source codes may be downloaded from
+<https://rspamd.com/>.
diff --git a/doc/rspamc.1 b/doc/rspamc.1
new file mode 100644
index 0000000..0f823e6
--- /dev/null
+++ b/doc/rspamc.1
@@ -0,0 +1,307 @@
+.\" Automatically generated by Pandoc 2.2.2.1
+.\"
+.TH "RSPAMC" "1" "" "Rspamd User Manual" ""
+.hy
+.SH NAME
+.PP
+\f[C]rspamc\f[] \- rspamd command line client
+.SH SYNOPSIS
+.PP
+rspamc [\f[I]options\f[]] [\f[I]command\f[]] [\f[I]input\-file\f[]]\&...
+.PP
+rspamc \[en]help
+.SH DESCRIPTION
+.PP
+\f[C]rspamc\f[] is a simple rspamd client, primarily for classifying or
+learning messages.
+\f[C]rspamc\f[] supports the following commands:
+.IP \[bu] 2
+Scan commands:
+.RS 2
+.IP \[bu] 2
+\f[C]symbols\f[]: scan message and show symbols (default command)
+.RE
+.IP \[bu] 2
+Control commands
+.RS 2
+.IP \[bu] 2
+\f[C]learn_spam\f[]: learn message as spam
+.IP \[bu] 2
+\f[C]learn_ham\f[]: learn message as ham
+.IP \[bu] 2
+\f[C]fuzzy_add\f[]: add message to fuzzy storage (check \f[C]\-f\f[] and
+\f[C]\-w\f[] options for this command)
+.IP \[bu] 2
+\f[C]fuzzy_del\f[]: delete message from fuzzy storage (check
+\f[C]\-f\f[] option for this command)
+.IP \[bu] 2
+\f[C]stat\f[]: show rspamd statistics
+.IP \[bu] 2
+\f[C]stat_reset\f[]: show and reset rspamd statistics (useful for
+graphs)
+.IP \[bu] 2
+\f[C]counters\f[]: display rspamd symbols statistics
+.IP \[bu] 2
+\f[C]uptime\f[]: show rspamd uptime
+.IP \[bu] 2
+\f[C]add_symbol\f[]: add or modify symbol settings in rspamd
+.IP \[bu] 2
+\f[C]add_action\f[]: add or modify action settings
+.RE
+.PP
+Control commands that modify rspamd state are considered privileged and
+require a password to be specified with the \f[C]\-P\f[] option (see
+\f[B]OPTIONS\f[], below, for details).
+.PD 0
+.P
+.PD
+This depends on a controller's settings and is discussed in the
+\f[C]rspamd\-workers\f[] page (see \f[B]SEE ALSO\f[], below, for
+details).
+.PP
+\f[C]Input\ files\f[] may be either regular file(s) or a directory to
+scan.
+If no files are specified \f[C]rspamc\f[] reads from the standard input.
+Controller commands usually do not accept any input, however learn* and
+fuzzy* commands requires input.
+.SH OPTIONS
+.TP
+.B \-h \f[I]host[:port]\f[], \-\-connect=\f[I]host[:port]\f[]
+Specify host and port
+.RS
+.RE
+.TP
+.B \-P \f[I]password\f[], \-\-password=\f[I]password\f[]
+Specify control password
+.RS
+.RE
+.TP
+.B \-c \f[I]name\f[], \-\-classifier=\f[I]name\f[]
+Classifier to learn spam or ham (bayes is used by default)
+.RS
+.RE
+.TP
+.B \-w \f[I]weight\f[], \-\-weight=\f[I]weight\f[]
+Weight for fuzzy operations
+.RS
+.RE
+.TP
+.B \-f \f[I]number\f[], \-\-flag=\f[I]number\f[]
+Flag for fuzzy operations
+.RS
+.RE
+.TP
+.B \-p, \-\-pass
+Pass all filters
+.RS
+.RE
+.TP
+.B \-v, \-\-verbose
+More verbose output
+.RS
+.RE
+.TP
+.B \-i \f[I]ip address\f[], \-\-ip=\f[I]ip address\f[]
+Emulate that message was received from specified ip address
+.RS
+.RE
+.TP
+.B \-u \f[I]username\f[], \-\-user=\f[I]username\f[]
+Emulate that message was received from specified authenticated user
+.RS
+.RE
+.TP
+.B \-d \f[I]user\@domain\f[], \-\-deliver=\f[I]user\@domain\f[]
+Emulate that message was delivered to specified user (for
+LDA/statistics)
+.RS
+.RE
+.TP
+.B \-F \f[I]user\@domain\f[], \-\-from=\f[I]user\@domain\f[]
+Emulate that message has specified SMTP FROM address
+.RS
+.RE
+.TP
+.B \-r \f[I]user\@domain\f[], \-\-rcpt=\f[I]user\@domain\f[]
+Emulate that message has specified SMTP RCPT address
+.RS
+.RE
+.TP
+.B \-\-helo=\f[I]helo_string\f[]
+Imitate SMTP HELO passing from MTA
+.RS
+.RE
+.TP
+.B \-\-hostname=\f[I]hostname\f[]
+Imitate hostname passing from MTA (rspamd assumes that it is verified by
+MTA)
+.RS
+.RE
+.TP
+.B \-t \f[I]seconds\f[], \-\-timeout=\f[I]seconds\f[]
+Timeout for waiting for a reply (can be floating point number,
+e.g.\ 0.1)
+.RS
+.RE
+.TP
+.B \-b \f[I]host:port\f[], \-\-bind=\f[I]host:port\f[]
+Bind to specified ip address
+.RS
+.RE
+.TP
+.B \-R, \-\-human
+Output human readable report.
+The first line of the output contains the message score and three
+threshold scores, in this format:
+.IP
+.nf
+\f[C]
+ score/greylist/addheader/reject,action=N:ACTION,spam=0|1,skipped=0|1
+\f[]
+.fi
+.RS
+.RE
+.TP
+.B \-j, \-\-json
+Output formatted JSON
+.RS
+.RE
+.TP
+.B \-\-ucl
+Output UCL
+.RS
+.RE
+.TP
+.B \-\-raw
+Output raw data received from rspamd (compacted JSON)
+.RS
+.RE
+.TP
+.B \-\-headers
+Output HTTP headers from a reply
+.RS
+.RE
+.TP
+.B \-\-extended\-urls
+Output URLs in an extended format, showing full URL, host and the part
+of host that was used by surbl module (if enabled).
+.RS
+.RE
+.TP
+.B \-n \f[I]parallel_count\f[], \-\-max\-requests=\f[I]parallel_count\f[]
+Maximum number of requests to rspamd executed in parallel (8 by default)
+.RS
+.RE
+.TP
+.B \-e \f[I]command\f[], \-\-execute=\f[I]command\f[]
+Execute the specified command with either mime output (if \f[C]mime\f[]
+option is also specified) or formatted rspamd output
+.RS
+.RE
+.TP
+.B \-\-mime
+Output the full mime message instead of scanning results only
+.RS
+.RE
+.TP
+.B \-\-header=\f[I]header\f[]
+Add custom HTTP header for a request.
+You may specify header in format \f[C]name=value\f[] or just
+\f[C]name\f[] for an empty header.
+This option can be repeated multiple times.
+.RS
+.RE
+.TP
+.B \-\-sort=\f[I]type\f[]
+Sort output according to a specific field.
+For \f[C]counters\f[] command the allowed values for this key are
+\f[C]name\f[], \f[C]weight\f[], \f[C]frequency\f[] and \f[C]hits\f[].
+Appending \f[C]:desc\f[] to any of these types inverts sorting order.
+.RS
+.RE
+.TP
+.B \-\-commands
+List available commands
+.RS
+.RE
+.SH RETURN VALUE
+.PP
+On exit \f[C]rspamc\f[] returns \f[C]0\f[] if operation was successful
+and an error code otherwise.
+.SH EXAMPLES
+.PP
+Check stdin:
+.IP
+.nf
+\f[C]
+rspamc\ <\ some_file
+\f[]
+.fi
+.PP
+Check files:
+.IP
+.nf
+\f[C]
+rspamc\ symbols\ file1\ file2\ file3
+\f[]
+.fi
+.PP
+Learn files:
+.IP
+.nf
+\f[C]
+rspamc\ \-P\ pass\ learn_spam\ file1\ file2\ file3
+\f[]
+.fi
+.PP
+Add fuzzy hash to set 2:
+.IP
+.nf
+\f[C]
+rspamc\ \-P\ pass\ \-f\ 2\ \-w\ 10\ fuzzy_add\ file1\ file2
+\f[]
+.fi
+.PP
+Delete fuzzy hash from other server:
+.IP
+.nf
+\f[C]
+rspamc\ \-P\ pass\ \-h\ hostname:11334\ \-f\ 2\ fuzzy_del\ file1\ file2
+\f[]
+.fi
+.PP
+Get statistics:
+.IP
+.nf
+\f[C]
+rspamc\ stat
+\f[]
+.fi
+.PP
+Get uptime:
+.IP
+.nf
+\f[C]
+rspamc\ uptime
+\f[]
+.fi
+.PP
+Add custom rule's weight:
+.IP
+.nf
+\f[C]
+rspamc\ add_symbol\ test\ 1.5
+\f[]
+.fi
+.PP
+Add custom action's weight:
+.IP
+.nf
+\f[C]
+rspamc\ add_action\ reject\ 7.1
+\f[]
+.fi
+.SH SEE ALSO
+.PP
+Rspamd documentation and source code may be downloaded from
+<https://rspamd.com/>.
diff --git a/doc/rspamc.1.md b/doc/rspamc.1.md
new file mode 100644
index 0000000..335c225
--- /dev/null
+++ b/doc/rspamc.1.md
@@ -0,0 +1,173 @@
+% RSPAMC(1) Rspamd User Manual
+
+# NAME
+
+`rspamc` - rspamd command line client
+
+# SYNOPSIS
+
+rspamc [*options*] [*command*] [*input-file*]...
+
+rspamc --help
+
+# DESCRIPTION
+
+`rspamc` is a simple rspamd client, primarily for classifying or learning messages.
+`rspamc` supports the following commands:
+
+* Scan commands:
+ * `symbols`: scan message and show symbols (default command)
+* Control commands
+ * `learn_spam`: learn message as spam
+ * `learn_ham`: learn message as ham
+ * `fuzzy_add`: add message to fuzzy storage (check `-f` and `-w` options for this command)
+ * `fuzzy_del`: delete message from fuzzy storage (check `-f` option for this command)
+ * `stat`: show rspamd statistics
+ * `stat_reset`: show and reset rspamd statistics (useful for graphs)
+ * `counters`: display rspamd symbols statistics
+ * `uptime`: show rspamd uptime
+ * `add_symbol`: add or modify symbol settings in rspamd
+ * `add_action`: add or modify action settings
+
+Control commands that modify rspamd state are considered privileged and require a password to be specified with the `-P` option (see **OPTIONS**, below, for details).
+This depends on a controller's settings and is discussed in the `rspamd-workers` page (see **SEE ALSO**, below, for details).
+
+`Input files` may be either regular file(s) or a directory to scan. If no files are specified `rspamc` reads
+from the standard input. Controller commands usually do not accept any input, however learn* and fuzzy* commands
+requires input.
+
+# OPTIONS
+
+-h *host[:port]*, \--connect=*host[:port]*
+: Specify host and port
+
+-P *password*, \--password=*password*
+: Specify control password
+
+-c *name*, \--classifier=*name*
+: Classifier to learn spam or ham (bayes is used by default)
+
+-w *weight*, \--weight=*weight*
+: Weight for fuzzy operations
+
+-f *number*, \--flag=*number*
+: Flag for fuzzy operations
+
+-p, \--pass
+: Pass all filters
+
+-v, \--verbose
+: More verbose output
+
+-i *ip address*, \--ip=*ip address*
+: Emulate that message was received from specified ip address
+
+-u *username*, \--user=*username*
+: Emulate that message was received from specified authenticated user
+
+-d *user@domain*, \--deliver=*user@domain*
+: Emulate that message was delivered to specified user (for LDA/statistics)
+
+-F *user@domain*, \--from=*user@domain*
+: Emulate that message has specified SMTP FROM address
+
+-r *user@domain*, \--rcpt=*user@domain*
+: Emulate that message has specified SMTP RCPT address
+
+\--helo=*helo_string*
+: Imitate SMTP HELO passing from MTA
+
+\--hostname=*hostname*
+: Imitate hostname passing from MTA (rspamd assumes that it is verified by MTA)
+
+-t *seconds*, \--timeout=*seconds*
+: Timeout for waiting for a reply (can be floating point number, e.g. 0.1)
+
+-b *host:port*, \--bind=*host:port*
+: Bind to specified ip address
+
+-R, \--human
+: Output human readable report. The first line of the output contains the message score and three threshold scores, in this format:
+: score/greylist/addheader/reject,action=N:ACTION,spam=0|1,skipped=0|1
+
+-j, \--json
+: Output formatted JSON
+
+\--ucl
+: Output UCL
+
+\--raw
+: Output raw data received from rspamd (compacted JSON)
+
+\--headers
+: Output HTTP headers from a reply
+
+\--extended-urls
+: Output URLs in an extended format, showing full URL, host and the part of host that was used by surbl module (if enabled).
+
+-n *parallel_count*, \--max-requests=*parallel_count*
+: Maximum number of requests to rspamd executed in parallel (8 by default)
+
+-e *command*, \--execute=*command*
+: Execute the specified command with either mime output (if `mime` option is also specified) or formatted rspamd output
+
+\--mime
+: Output the full mime message instead of scanning results only
+
+\--header=*header*
+: Add custom HTTP header for a request. You may specify header in format `name=value` or just `name` for an empty header. This option can be repeated multiple times.
+
+\--sort=*type*
+: Sort output according to a specific field. For `counters` command the allowed values for this key are `name`, `weight`, `frequency` and `hits`. Appending `:asc` to any of these types inverts sorting order.
+
+\--commands
+: List available commands
+
+# RETURN VALUE
+
+On exit `rspamc` returns `0` if operation was successful and an error code otherwise.
+
+# EXAMPLES
+
+Check stdin:
+
+ rspamc < some_file
+
+Check files:
+
+ rspamc symbols file1 file2 file3
+
+Learn files:
+
+ rspamc -P pass learn_spam file1 file2 file3
+
+Add fuzzy hash to set 2:
+
+ rspamc -P pass -f 2 -w 10 fuzzy_add file1 file2
+
+Delete fuzzy hash from other server:
+
+ rspamc -P pass -h hostname:11334 -f 2 fuzzy_del file1 file2
+
+Get statistics:
+
+ rspamc stat
+
+Get uptime:
+
+ rspamc uptime
+
+Add custom rule's weight:
+
+ rspamc add_symbol test 1.5
+
+Add custom action's weight:
+
+ rspamc add_action reject 7.1
+
+# SEE ALSO
+
+Rspamd documentation and source code may be downloaded from
+<https://rspamd.com/>.
+
+[rspamd-workers]: https://rspamd.com/doc/workers/
diff --git a/doc/rspamd.8 b/doc/rspamd.8
new file mode 100644
index 0000000..325a507
--- /dev/null
+++ b/doc/rspamd.8
@@ -0,0 +1,78 @@
+.TH "RSPAMD" "8" "" "Rspamd User Manual" ""
+.SH NAME
+.PP
+rspamd \- main daemon for rapid spam filtering system
+.SH SYNOPSIS
+.PP
+rspamd [\f[I]options\f[]]...
+.PP
+rspamd \-\-help
+.SH DESCRIPTION
+.PP
+Rspamd filtering system is designed to be fast, modular and easily
+scalable system.
+Rspamd core is written in \f[C]C\f[] language using event driven
+processing model.
+Plugins for rspamd can be written in \f[C]Lua\f[] programming language.
+Rspamd is designed to process connections completely asynchronous and do
+not block anywhere in code.
+.SH OPTIONS
+.TP
+.B \-f, \-\-no\-fork
+Do not daemonize main process
+.RS
+.RE
+.TP
+.B \-c \f[I]path\f[], \-\-config=\f[I]path\f[]
+Specify config file(s)
+.RS
+.RE
+.TP
+.B \-u \f[I]username\f[], \-\-user=\f[I]username\f[]
+User to run rspamd as
+.RS
+.RE
+.TP
+.B \-g \f[I]groupname\f[], \-\-group=\f[I]groupname\f[]
+Group to run rspamd as
+.RS
+.RE
+.TP
+.B \-p \f[I]path\f[], \-\-pid=\f[I]path\f[]
+Path to pidfile
+.RS
+.RE
+.TP
+.B \-i, \-\-insecure
+Ignore running workers as privileged users (insecure)
+.RS
+.RE
+.SH EXAMPLES
+.PP
+Run rspamd daemon with default configuration:
+.IP
+.nf
+\f[C]
+rspamd
+\f[]
+.fi
+.PP
+Run rspamd in foreground with custom configuration:
+.IP
+.nf
+\f[C]
+rspamd\ \-f\ \-c\ ~/rspamd.conf
+\f[]
+.fi
+.PP
+Run rspamd specifying user and group:
+.IP
+.nf
+\f[C]
+rspamd\ \-u\ rspamd\ \-g\ rspamd\ \-c\ /etc/rspamd/rspamd.conf
+\f[]
+.fi
+.SH SEE ALSO
+.PP
+Rspamd documentation and source codes may be downloaded from
+<https://rspamd.com/>.
diff --git a/doc/rspamd.8.md b/doc/rspamd.8.md
new file mode 100644
index 0000000..c7c811c
--- /dev/null
+++ b/doc/rspamd.8.md
@@ -0,0 +1,57 @@
+% RSPAMD(8) Rspamd User Manual
+
+# NAME
+
+rspamd - main daemon for rapid spam filtering system
+
+# SYNOPSIS
+
+rspamd [*options*]...
+
+rspamd --help
+
+# DESCRIPTION
+
+Rspamd filtering system is designed to be fast, modular and easily scalable system.
+Rspamd core is written in `C` language using event driven processing model.
+Plugins for rspamd can be written in `Lua` programming language.
+Rspamd is designed to process connections completely asynchronous and do not block anywhere in code.
+
+# OPTIONS
+
+-f, \--no-fork
+: Do not daemonize main process
+
+-c *path*, \--config=*path*
+: Specify config file(s)
+
+-u *username*, \--user=*username*
+: User to run rspamd as
+
+-g *groupname*, \--group=*groupname*
+: Group to run rspamd as
+
+-p *path*, \--pid=*path*
+: Path to pidfile
+
+-i, \--insecure
+: Ignore running workers as privileged users (insecure)
+
+# EXAMPLES
+
+Run rspamd daemon with default configuration:
+
+ rspamd
+
+Run rspamd in foreground with custom configuration:
+
+ rspamd -f -c ~/rspamd.conf
+
+Run rspamd specifying user and group:
+
+ rspamd -u rspamd -g rspamd -c /etc/rspamd/rspamd.conf
+
+# SEE ALSO
+
+Rspamd documentation and source codes may be downloaded from
+<https://rspamd.com/>. \ No newline at end of file