You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

doxydown.pl 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. #!/usr/bin/env perl
  2. $VERSION = "0.1";
  3. use strict;
  4. use warnings;
  5. use Data::Dumper;
  6. use Digest::MD5 qw(md5_hex);
  7. my @modules;
  8. my %options = ();
  9. my $cur_module;
  10. my $example_language = "lua";
  11. my %languages = (
  12. c => {
  13. start => qr/^\s*\/\*\*\*(?:\s*|(\s+\S.+\s*))$/,
  14. end => qr/^\s*\*+\/\s*$/,
  15. filter => qr/^(?:\s*\*+\s?)?(\s*[^*].+)\s*$/,
  16. },
  17. lua => {
  18. start => qr/^\s*\--\[\[\[\s*$/,
  19. end => qr/^\s*--\]\]\s*/,
  20. filter => qr/^(?:\s*--\s)?(\s*\S.+)\s*$/,
  21. },
  22. );
  23. my $function_re = qr/^\s*\@(function|fn|method)\s*(\S.+)$/oi;
  24. my $module_re = qr/^\s*\@(?:module|file)\s*(\S.+)$/oi;
  25. my $language;
  26. sub print_module_markdown {
  27. my ( $mname, $m ) = @_;
  28. my $idline = $options{g} ? "" : " {#$m->{'id'}}";
  29. print <<EOD;
  30. ## Module `$mname`$idline
  31. $m->{'data'}
  32. EOD
  33. if ( $m->{'example'} ) {
  34. print <<EOD;
  35. Example:
  36. ~~~$m->{'example_language'}
  37. $m->{'example'}
  38. ~~~
  39. EOD
  40. }
  41. sub print_func {
  42. my ($f) = @_;
  43. my $name = $f->{'name'};
  44. my $id = $f->{'id'};
  45. if ($f->{'brief'}) {
  46. print "> [`$name`](#$id): ". $f->{'brief'} . "\n\n";
  47. }
  48. else {
  49. print "> [`$name`](#$id)\n\n";
  50. }
  51. }
  52. print "\n### Brief content:\n\n";
  53. if (scalar(@{ $m->{'functions'} }) > 0) {
  54. print "**Functions**:\n\n";
  55. foreach ( @{ $m->{'functions'} } ) {
  56. print_func($_);
  57. }
  58. }
  59. if (scalar(@{ $m->{'methods'} }) > 0) {
  60. print "\n\n**Methods**:\n\n";
  61. foreach (@{ $m->{'methods'} }) {
  62. print_func($_);
  63. }
  64. }
  65. }
  66. sub print_function_markdown {
  67. my ( $type, $fname, $f ) = @_;
  68. my $idline = $options{g} ? "" : " {#$f->{'id'}}";
  69. print <<EOD;
  70. ### $type `$fname`$idline
  71. $f->{'data'}
  72. EOD
  73. print "\n**Parameters:**\n\n";
  74. if ( $f->{'params'} && scalar @{ $f->{'params'} } > 0 ) {
  75. foreach ( @{ $f->{'params'} } ) {
  76. if ( $_->{'type'} ) {
  77. print
  78. "- `$_->{'name'} \{$_->{'type'}\}`: $_->{'description'}\n";
  79. }
  80. else {
  81. print "- `$_->{'name'}`: $_->{'description'}\n";
  82. }
  83. }
  84. }
  85. else {
  86. print "No parameters\n";
  87. }
  88. print "\n**Returns:**\n\n";
  89. if ( $f->{'return'} && $f->{'return'}->{'description'} ) {
  90. $_ = $f->{'return'};
  91. if ( $_->{'type'} ) {
  92. print "- `\{$_->{'type'}\}`: $_->{'description'}\n";
  93. }
  94. else {
  95. print "- $_->{'description'}\n";
  96. }
  97. }
  98. else {
  99. print "No return\n";
  100. }
  101. if ( $f->{'example'} ) {
  102. print <<EOD;
  103. Example:
  104. ~~~$f->{'example_language'}
  105. $f->{'example'}
  106. ~~~
  107. EOD
  108. }
  109. }
  110. sub print_markdown {
  111. for my $m (@modules) {
  112. my $mname = $m->{name};
  113. print_module_markdown( $mname, $m );
  114. if (scalar(@{ $m->{'functions'} }) > 0) {
  115. print
  116. "\n## Functions\n\nThe module `$mname` defines the following functions.\n\n";
  117. foreach (@{ $m->{'functions'} }) {
  118. print_function_markdown( "Function", $_->{'name'}, $_ );
  119. print "\nBack to [module description](#$m->{'id'}).\n\n";
  120. }
  121. }
  122. if (scalar(@{ $m->{'methods'} }) > 0) {
  123. print
  124. "\n## Methods\n\nThe module `$mname` defines the following methods.\n\n";
  125. foreach (@{ $m->{'methods'} }) {
  126. print_function_markdown( "Method", $_->{'name'}, $_ );
  127. print "\nBack to [module description](#$m->{'id'}).\n\n";
  128. }
  129. }
  130. print "\nBack to [top](#).\n\n";
  131. }
  132. }
  133. sub make_id {
  134. my ( $name, $prefix ) = @_;
  135. if ( !$prefix ) {
  136. $prefix = "f";
  137. }
  138. if ( !$options{g} ) {
  139. # Kramdown/pandoc version of ID's
  140. $name =~ /^(\S+).*$/;
  141. return substr( substr( $prefix, 0, 1 ) . md5_hex($1), 0, 6 );
  142. }
  143. else {
  144. my $input = lc $prefix . "-" . $name;
  145. my $id = join '-', split /\s+/, $input;
  146. $id =~ s/[^\w_-]+//g;
  147. return $id;
  148. }
  149. }
  150. sub substitute_data_keywords {
  151. my ($line) = @_;
  152. if ( $line =~ /^.*\@see\s+(\S+)\s*.*$/ ) {
  153. my $name = $1;
  154. my $id = make_id($name);
  155. return $line =~ s/\@see\s+\S+/[`$name`](#$id)/r;
  156. }
  157. return $line;
  158. }
  159. sub parse_function {
  160. my ( $func, @data ) = @_;
  161. my ( $type, $name ) = ( $func =~ $function_re );
  162. chomp $name;
  163. my $f = {
  164. name => $name,
  165. data => '',
  166. example => undef,
  167. example_language => $example_language,
  168. id => make_id( $name, $type ),
  169. };
  170. my $example = 0;
  171. foreach (@data) {
  172. if (/^\s*\@param\s*(?:\{([^}]+)\})?\s*(\S+)\s*(.+)?\s*$/) {
  173. my $p = { name => $2, type => $1, description => $3 };
  174. push @{ $f->{'params'} }, $p;
  175. }
  176. elsif (/^\s*\@return\s*(?:\{([^}]+)\})?\s*(.+)?\s*$/) {
  177. my $r = { type => $1, description => $2 };
  178. $f->{'return'} = $r;
  179. }
  180. elsif (/^\s*\@brief\s*(\S.+)$/) {
  181. $f->{'brief'} = $1;
  182. }
  183. elsif (/^\s*\@example\s*(\S)?\s*$/) {
  184. $example = 1;
  185. if ($1) {
  186. $f->{'example_language'} = $1;
  187. }
  188. }
  189. elsif ( $_ ne $func ) {
  190. if ($example) {
  191. $f->{'example'} .= $_;
  192. }
  193. else {
  194. $f->{'data'} .= substitute_data_keywords($_);
  195. }
  196. }
  197. }
  198. if ( $f->{'data'} ) {
  199. chomp $f->{'data'};
  200. }
  201. elsif ($f->{'brief'}) {
  202. chomp $f->{'brief'};
  203. $f->{'data'} = $f->{'brief'};
  204. }
  205. if ( $f->{'example'} ) {
  206. chomp $f->{'example'};
  207. }
  208. if ( $type eq "method" ) {
  209. push @{ $cur_module->{'methods'} }, $f;
  210. }
  211. else {
  212. push @{ $cur_module->{'functions'} }, $f;
  213. }
  214. }
  215. sub parse_module {
  216. my ( $module, @data ) = @_;
  217. my ( $name ) = ( $module =~ $module_re );
  218. chomp $name;
  219. my $f = {
  220. name => $name,
  221. functions => [],
  222. methods => [],
  223. data => '',
  224. example => undef,
  225. example_language => $example_language,
  226. id => make_id( $name, "module" ),
  227. };
  228. my $example = 0;
  229. foreach (@data) {
  230. if (/^\s*\@example\s*(\S)?\s*$/) {
  231. $example = 1;
  232. if ($1) {
  233. $f->{'example_language'} = $1;
  234. }
  235. }
  236. elsif (/^\s*\@brief\s*(\S.+)$/) {
  237. $f->{'brief'} = $1;
  238. }
  239. elsif ( $_ ne $module ) {
  240. if ($example) {
  241. $f->{'example'} .= $_;
  242. }
  243. else {
  244. $f->{'data'} .= substitute_data_keywords($_);
  245. }
  246. }
  247. }
  248. if ( $f->{'data'} ) {
  249. chomp $f->{'data'};
  250. }
  251. elsif ($f->{'brief'}) {
  252. chomp $f->{'brief'};
  253. $f->{'data'} = $f->{'brief'};
  254. }
  255. if ( $f->{'example'} ) {
  256. chomp $f->{'example'};
  257. }
  258. $cur_module = $f;
  259. push @modules, $f;
  260. }
  261. sub parse_content {
  262. my @func = grep /$function_re/, @_;
  263. if ( scalar @func > 0 ) {
  264. parse_function( $func[0], @_ );
  265. }
  266. my @module = grep /$module_re/, @_;
  267. if ( scalar @module > 0 ) {
  268. parse_module( $module[0], @_ );
  269. }
  270. }
  271. sub HELP_MESSAGE {
  272. print STDERR <<EOF;
  273. Utility to convert doxygen comments to markdown.
  274. usage: $0 [-hg] [-l language] < input_source > markdown.md
  275. -h : this (help) message
  276. -e : sets default example language (default: lua)
  277. -l : sets input language (default: c)
  278. -g : use github flavoured markdown (default: kramdown/pandoc)
  279. EOF
  280. exit;
  281. }
  282. $Getopt::Std::STANDARD_HELP_VERSION = 1;
  283. use Getopt::Std;
  284. getopts( 'he:gl:', \%options );
  285. HELP_MESSAGE() if $options{h};
  286. $example_language = $options{e} if $options{e};
  287. $language = $languages{ lc $options{l} } if $options{l};
  288. if ( !$language ) {
  289. $language = $languages{c};
  290. }
  291. use constant {
  292. STATE_READ_SKIP => 0,
  293. STATE_READ_CONTENT => 1,
  294. STATE_READ_ENUM => 2,
  295. STATE_READ_STRUCT => 3,
  296. };
  297. my $state = STATE_READ_SKIP;
  298. my $content;
  299. while (<>) {
  300. if ( $state == STATE_READ_SKIP ) {
  301. if ( $_ =~ $language->{start} ) {
  302. $state = STATE_READ_CONTENT;
  303. if (defined($1)) {
  304. chomp($content = $1);
  305. $content =~ tr/\r//d;
  306. $content .= "\n";
  307. }
  308. else {
  309. $content = "";
  310. }
  311. }
  312. }
  313. elsif ( $state == STATE_READ_CONTENT ) {
  314. if ( $_ =~ $language->{end} ) {
  315. $state = STATE_READ_SKIP;
  316. parse_content( split /^/, $content );
  317. $content = "";
  318. }
  319. else {
  320. my ($line) = ( $_ =~ $language->{filter} );
  321. if ($line) {
  322. $line =~ tr/\r//d;
  323. $content .= $line . "\n";
  324. }
  325. else {
  326. # Preserve empty lines
  327. $content .= "\n";
  328. }
  329. }
  330. }
  331. }
  332. #print Dumper( \@modules );
  333. print_markdown;