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 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. #!/usr/bin/env perl
  2. $VERSION = "0.1.4";
  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.+)\s*$/,
  16. },
  17. lua => {
  18. start => qr/^\s*\--(?:\[\[\[+|-+)\s*$/,
  19. end => qr/^\s*--(:?\]\]+|-+)\s*$/,
  20. filter => qr/^(?:\s*--!?\s)?(\s*\S.+)\s*$/,
  21. },
  22. sql => {
  23. start => qr/^\s*\--(?:\[\[+|-+)\s*$/,
  24. end => qr/^\s*--(:?\]\]+|-+)\s*$/,
  25. filter => qr/^(?:\s*--\s)?(\s*\S.+)\s*$/,
  26. },
  27. pl => {
  28. start => qr/^\s*\##+\s*$/,
  29. end => qr/^\s*##+\s*/,
  30. filter => qr/^(?:\s*#+\s?)(\s*\S.+)\s*$/,
  31. },
  32. );
  33. my $function_re = qr/^\s*\@(function|fn|method)\s*(\S.+)$/oi;
  34. my $struct_re = qr/^\s*\@(table|struct)\s*(\S.+)$/oi;
  35. my $module_re = qr/^\s*\@(?:module|file)\s*(\S.+)$/oi;
  36. my $language;
  37. # /function print_module_markdown
  38. sub print_module_markdown {
  39. my ( $mname, $m ) = @_;
  40. my $idline = $options{g} ? "" : " {#$m->{'id'}}";
  41. print <<EOD;
  42. ## Module `$mname`$idline
  43. $m->{'data'}
  44. EOD
  45. if ( $m->{'example'} ) {
  46. print <<EOD;
  47. ### Example:
  48. ~~~$m->{'example_language'}
  49. $m->{'example'}
  50. ~~~
  51. EOD
  52. }
  53. sub print_func {
  54. my ($f) = @_;
  55. my $name = $f->{'name'};
  56. my $id = $f->{'id'};
  57. if ($f->{'brief'}) {
  58. print "* [`$name`](#$id): ". $f->{'brief'} . "\n";
  59. } else {
  60. print "* [`$name`](#$id)\n";
  61. }
  62. }
  63. sub print_table {
  64. my ($f) = @_;
  65. my $name = $f->{'name'};
  66. my $id = $f->{'id'};
  67. if ($f->{'brief'}) {
  68. print "> [`$name`](#$id): ". $f->{'brief'} . "\n\n";
  69. } else {
  70. print "> [`$name`](#$id)\n\n";
  71. }
  72. }
  73. print "\n### Brief content:\n\n";
  74. if ($m->{'functions'}) {
  75. if (scalar(@{ $m->{'functions'} }) > 0) {
  76. print "**Functions**:\n\n";
  77. foreach ( @{ $m->{'functions'} } ) {
  78. print_func($_);
  79. }
  80. }
  81. }
  82. if ($m->{'methods'}) {
  83. if (scalar(@{ $m->{'methods'} }) > 0) {
  84. print "\n\n**Methods**:\n\n";
  85. foreach ( @{ $m->{'methods'} } ) {
  86. print_func($_);
  87. }
  88. }
  89. }
  90. if ($m->{'tables'}) {
  91. if (scalar(@{ $m->{'tables'} }) > 0) {
  92. print "\n\n**Tables**:\n\n";
  93. foreach ( @{ $m->{'tables'} } ) {
  94. print_table($_);
  95. }
  96. }
  97. }
  98. if ($m->{'structs'}) {
  99. if (scalar(@{ $m->{'structs'} }) > 0) {
  100. print "\n\n**Structs**:\n\n";
  101. foreach ( @{ $m->{'structs'} } ) {
  102. print_table($_);
  103. }
  104. }
  105. }
  106. }
  107. # /function print_function_markdown
  108. sub print_function_markdown {
  109. my ( $type, $fname, $f ) = @_;
  110. my $idline = $options{g} ? "" : " {#$f->{'id'}}";
  111. print <<EOD;
  112. ### $type `$fname`$idline
  113. $f->{'data'}
  114. EOD
  115. print "\n**Parameters:**\n\n";
  116. if ( $f->{'params'} && scalar @{ $f->{'params'} } > 0 ) {
  117. foreach ( @{ $f->{'params'} } ) {
  118. if ( $_->{'type'} ) {
  119. print
  120. "- `$_->{'name'} \{$_->{'type'}\}`: $_->{'description'}\n";
  121. } else {
  122. print "- `$_->{'name'}`: $_->{'description'}\n";
  123. }
  124. }
  125. } else {
  126. print "No parameters\n";
  127. }
  128. print "\n**Returns:**\n\n";
  129. if ( $f->{'returns'} && scalar @{ $f->{'returns'} } > 0 ) {
  130. foreach ( @{ $f->{'returns'} } ) {
  131. if ( $_->{'type'} ) {
  132. print "- `\{$_->{'type'}\}`: $_->{'description'}\n";
  133. } else {
  134. print "- $_->{'description'}\n";
  135. }
  136. }
  137. } else {
  138. print "No return\n";
  139. }
  140. if ( $f->{'example'} ) {
  141. print <<EOD;
  142. ### Example:
  143. ~~~$f->{'example_language'}
  144. $f->{'example'}
  145. ~~~
  146. EOD
  147. }
  148. }
  149. # /function print_struct_markdown
  150. sub print_struct_markdown {
  151. my ( $type, $fname, $f ) = @_;
  152. my $idline = $options{g} ? "" : " {#$f->{'id'}}";
  153. print <<EOD;
  154. ### $type `$fname`$idline
  155. $f->{'data'}
  156. EOD
  157. print "\n**Elements:**\n\n";
  158. if ( $f->{'params'} && scalar @{ $f->{'params'} } > 0 ) {
  159. foreach ( @{ $f->{'params'} } ) {
  160. if ( $_->{'type'} ) {
  161. print
  162. "- `$_->{'name'} \{$_->{'type'}\}`: $_->{'description'}\n";
  163. } else {
  164. print "- `$_->{'name'}`: $_->{'description'}\n";
  165. }
  166. }
  167. } else {
  168. print "No elements\n";
  169. }
  170. if ( $f->{'example'} ) {
  171. print <<EOD;
  172. ### Example:
  173. ~~~$f->{'example_language'}
  174. $f->{'example'}
  175. ~~~
  176. EOD
  177. }
  178. }
  179. # /function print_markdown
  180. sub print_markdown {
  181. for my $m (@modules) {
  182. my $mname = $m->{name};
  183. print_module_markdown( $mname, $m );
  184. if ($m->{'functions'}) {
  185. if ( scalar(@{ $m->{'functions'} }) > 0 ) {
  186. print "\n## Functions\n\nThe module `$mname` defines the following functions.\n\n";
  187. foreach ( @{ $m->{'functions'} } ) {
  188. print_function_markdown( "Function", $_->{'name'}, $_ );
  189. print "\nBack to [module description](#$m->{'id'}).\n\n";
  190. }
  191. }
  192. }
  193. if ($m->{'methods'}) {
  194. if ( scalar(@{ $m->{'methods'} }) > 0 ) {
  195. print "\n## Methods\n\nThe module `$mname` defines the following methods.\n\n";
  196. foreach ( @{ $m->{'methods'} } ) {
  197. print_function_markdown( "Method", $_->{'name'}, $_ );
  198. print "\nBack to [module description](#$m->{'id'}).\n\n";
  199. }
  200. }
  201. }
  202. if ($m->{'tables'}) {
  203. if ( scalar(@{ $m->{'tables'} }) > 0 ) {
  204. print "\n## Tables\n\nThe module `$mname` defines the following tables.\n\n";
  205. foreach ( @{ $m->{'tables'} } ) {
  206. print_struct_markdown( "Table", $_->{'name'}, $_ );
  207. print "\nBack to [module description](#$m->{'id'}).\n\n";
  208. }
  209. }
  210. }
  211. if ($m->{'stucts'}) {
  212. if ( scalar(@{ $m->{'stucts'} }) > 0 ) {
  213. print "\n## Stucts\n\nThe module `$mname` defines the following stucts.\n\n";
  214. foreach ( @{ $m->{'stucts'} } ) {
  215. print_stuct_markdown( "Stuct", $_->{'name'}, $_ );
  216. print "\nBack to [module description](#$m->{'id'}).\n\n";
  217. }
  218. }
  219. }
  220. print "\nBack to [top](#).\n\n";
  221. }
  222. }
  223. # /function make_id
  224. sub make_id {
  225. my ( $name, $prefix ) = @_;
  226. if ( !$prefix ) {
  227. $prefix = "f";
  228. }
  229. if ( !$options{g} ) {
  230. # Kramdown/pandoc version of ID's
  231. $name =~ /^(\S+).*$/;
  232. return substr( substr( $prefix, 0, 1 ) . md5_hex($1), 0, 6 );
  233. } else {
  234. my $input = lc $prefix . "-" . $name;
  235. my $id = join '-', split /\s+/, $input;
  236. $id =~ s/[^\w_-]+//g;
  237. return $id;
  238. }
  239. }
  240. # /function substitute_data_keywords
  241. sub substitute_data_keywords {
  242. my ($line) = @_;
  243. if ( $line =~ /^.*\@see\s+(\S+)\s*.*$/ ) {
  244. my $name = $1;
  245. my $id = make_id($name);
  246. return $line =~ s/\@see\s+\S+/[`$name`](#$id)/r;
  247. }
  248. return $line;
  249. }
  250. # /function parse_function
  251. sub parse_function {
  252. my ( $func, @data ) = @_;
  253. my ( $type, $name ) = ( $func =~ $function_re );
  254. chomp $name;
  255. my $f = {
  256. name => $name,
  257. data => '',
  258. example => undef,
  259. example_language => $example_language,
  260. id => make_id( $name, $type ),
  261. };
  262. my $example = 0;
  263. foreach ( @data ) {
  264. if ( /^\s*\@param\s*(?:\{([^}]+)\})?\s*(\S+)\s*(.+)?\s*$/ ) {
  265. my $p = {
  266. name => $2,
  267. type => $1 || "no type",
  268. description => $3 || "no description"
  269. };
  270. push @{ $f->{'params'} }, $p;
  271. } elsif ( /^\s*\@return\s*(?:\{([^}]+)\})?\s*(.+)?\s*$/ ) {
  272. my $r = {
  273. type => $1,
  274. description => $2 || "no description"
  275. };
  276. push @{ $f->{'returns'} }, $r;
  277. } elsif ( /^\s*\@brief\s*(\S.+)$/ ) {
  278. $f->{'brief'} = $1;
  279. }
  280. elsif ( /^\s*\@example\s*(\S)?\s*$/ ) {
  281. $example = 1;
  282. if ( $1 ) {
  283. $f->{'example_language'} = $1;
  284. }
  285. } elsif ( $_ ne $func ) {
  286. if ( $example ) {
  287. $f->{'example'} .= $_;
  288. } else {
  289. $f->{'data'} .= substitute_data_keywords($_);
  290. }
  291. }
  292. }
  293. if ( $f->{'data'} ) {
  294. chomp $f->{'data'};
  295. } elsif ($f->{'brief'}) {
  296. chomp $f->{'brief'};
  297. $f->{'data'} = $f->{'brief'};
  298. }
  299. if ( $f->{'example'} ) {
  300. chomp $f->{'example'};
  301. }
  302. if ( !$f->{'brief'} && $f->{'data'} ) {
  303. if ( $f->{'data'} =~ /^(.*?)(?:(?:[.:]\s|$)|\n).*/ ) {
  304. $f->{'brief'} = "$1";
  305. chomp $f->{'brief'};
  306. if ( $f->{'brief'} !~ /\.$/) {
  307. $f->{'brief'} .= ".";
  308. }
  309. }
  310. }
  311. if ( $type eq "method" ) {
  312. push @{ $cur_module->{'methods'} }, $f;
  313. } elsif ( $type eq "function" || $type eq "fn") {
  314. push @{ $cur_module->{'functions'} }, $f;
  315. }
  316. }
  317. # /function parse_struct
  318. sub parse_struct {
  319. my ( $func, @data ) = @_;
  320. my ( $type, $name ) = ( $func =~ $struct_re );
  321. chomp $name;
  322. my $f = {
  323. name => $name,
  324. data => '',
  325. example => undef,
  326. example_language => $example_language,
  327. id => make_id( $name, $type ),
  328. };
  329. my $example = 0;
  330. foreach ( @data ) {
  331. if ( /^\s*\@param\s*(?:\{([^}]+)\})?\s*(\S+)\s*(.+)?\s*$/ ) {
  332. my $p = {
  333. name => $2,
  334. type => $1,
  335. description => $3
  336. };
  337. push @{ $f->{'params'} }, $p;
  338. } elsif ( /^\s*\@brief\s*(\S.+)$/ ) {
  339. $f->{'brief'} = $1;
  340. } elsif ( /^\s*\@example\s*(\S)?\s*$/ ) {
  341. $example = 1;
  342. if ( $1 ) {
  343. $f->{'example_language'} = $1;
  344. }
  345. } elsif ( $_ ne $func ) {
  346. if ( $example ) {
  347. $f->{'example'} .= $_;
  348. } else {
  349. $f->{'data'} .= substitute_data_keywords($_);
  350. }
  351. }
  352. }
  353. if ( $f->{'data'} ) {
  354. chomp $f->{'data'};
  355. } elsif ($f->{'brief'}) {
  356. chomp $f->{'brief'};
  357. $f->{'data'} = $f->{'brief'};
  358. }
  359. if ( $f->{'example'} ) {
  360. chomp $f->{'example'};
  361. }
  362. if ( $type eq "table" ) {
  363. push @{ $cur_module->{'tables'} }, $f;
  364. } elsif ( $type eq "struct" ) {
  365. push @{ $cur_module->{'structs'} }, $f;
  366. }
  367. }
  368. # /function parse_module
  369. sub parse_module {
  370. my ( $module, @data ) = @_;
  371. my ( $name ) = ( $module =~ $module_re );
  372. chomp $name;
  373. my $f = {
  374. name => $name,
  375. functions => [],
  376. methods => [],
  377. data => '',
  378. example => undef,
  379. example_language => $example_language,
  380. id => make_id( $name, "module" ),
  381. };
  382. my $example = 0;
  383. foreach ( @data ) {
  384. if ( /^\s*\@example\s*(\S)?\s*$/ ) {
  385. $example = 1;
  386. if ($1) {
  387. $f->{'example_language'} = $1;
  388. }
  389. } elsif ( /^\s*\@brief\s*(\S.+)$/ ) {
  390. $f->{'brief'} = $1;
  391. } elsif ( $_ ne $module ) {
  392. if ( $example ) {
  393. $f->{'example'} .= $_;
  394. } else {
  395. $f->{'data'} .= substitute_data_keywords($_);
  396. }
  397. }
  398. }
  399. if ( $f->{'data'} ) {
  400. chomp $f->{'data'};
  401. } elsif ( $f->{'brief'} ) {
  402. chomp $f->{'brief'};
  403. $f->{'data'} = $f->{'brief'};
  404. }
  405. if ( $f->{'example'} ) {
  406. chomp $f->{'example'};
  407. }
  408. $cur_module = $f;
  409. push @modules, $f;
  410. }
  411. # /function parse_content
  412. sub parse_content {
  413. #
  414. my @func = grep /$function_re/, @_;
  415. if ( scalar @func > 0 ) {
  416. parse_function( $func[0], @_ );
  417. }
  418. #
  419. my @struct = grep /$struct_re/, @_;
  420. if ( scalar @struct > 0 ) {
  421. parse_struct( $struct[0], @_ );
  422. }
  423. #
  424. my @module = grep /$module_re/, @_;
  425. if ( scalar @module > 0 ) {
  426. parse_module( $module[0], @_ );
  427. }
  428. }
  429. sub HELP_MESSAGE {
  430. print STDERR <<EOF;
  431. Utility to convert doxygen comments to markdown.
  432. usage: $0 [-hg] [-l language] < input_source > markdown.md
  433. -h : this (help) message
  434. -e : sets default example language (default: lua)
  435. -l : sets input language (default: c)
  436. -g : use github flavoured markdown (default: kramdown/pandoc)
  437. EOF
  438. exit;
  439. }
  440. $Getopt::Std::STANDARD_HELP_VERSION = 1;
  441. use Getopt::Std;
  442. getopts( 'he:gl:', \%options );
  443. HELP_MESSAGE() if $options{h};
  444. $example_language = $options{e} if $options{e};
  445. $language = $languages{ lc $options{l} } if $options{l};
  446. if ( !$language ) {
  447. $language = $languages{c};
  448. }
  449. ## TODO: select language based on file extension
  450. ## TODO: change calling structure to allow looping through directory
  451. use constant {
  452. STATE_READ_SKIP => 0,
  453. STATE_READ_CONTENT => 1,
  454. STATE_READ_ENUM => 2,
  455. STATE_READ_STRUCT => 3,
  456. };
  457. my $state = STATE_READ_SKIP;
  458. my $content;
  459. while ( <> ) {
  460. if ( $state == STATE_READ_SKIP ) {
  461. if ( $_ =~ $language->{start} ) {
  462. $state = STATE_READ_CONTENT;
  463. if (defined($1)) {
  464. chomp($content = $1);
  465. $content =~ tr/\r//d;
  466. $content .= "\n";
  467. } else {
  468. $content = "";
  469. }
  470. }
  471. } elsif ( $state == STATE_READ_CONTENT ) {
  472. if ( $_ =~ $language->{end} ) {
  473. $state = STATE_READ_SKIP;
  474. parse_content( split /^/, $content );
  475. $content = "";
  476. } else {
  477. my ($line) = ( $_ =~ $language->{filter} );
  478. if ( $line ) {
  479. $line =~ tr/\r//d;
  480. $content .= $line . "\n";
  481. } else {
  482. # Preserve empty lines
  483. $content .= "\n";
  484. }
  485. }
  486. }
  487. }
  488. #print Dumper( \@modules );
  489. print_markdown;