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

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