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

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