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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598
  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\n";
  59. } else {
  60. print "> [`$name`](#$id)\n\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,
  268. description => $3
  269. };
  270. push @{ $f->{'params'} }, $p;
  271. } elsif ( /^\s*\@return\s*(?:\{([^}]+)\})?\s*(.+)?\s*$/ ) {
  272. my $r = {
  273. type => $1,
  274. description => $2
  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 ( $type eq "method" ) {
  303. push @{ $cur_module->{'methods'} }, $f;
  304. } elsif ( $type eq "function" || $type eq "fn") {
  305. push @{ $cur_module->{'functions'} }, $f;
  306. }
  307. }
  308. # /function parse_struct
  309. sub parse_struct {
  310. my ( $func, @data ) = @_;
  311. my ( $type, $name ) = ( $func =~ $struct_re );
  312. chomp $name;
  313. my $f = {
  314. name => $name,
  315. data => '',
  316. example => undef,
  317. example_language => $example_language,
  318. id => make_id( $name, $type ),
  319. };
  320. my $example = 0;
  321. foreach ( @data ) {
  322. if ( /^\s*\@param\s*(?:\{([^}]+)\})?\s*(\S+)\s*(.+)?\s*$/ ) {
  323. my $p = {
  324. name => $2,
  325. type => $1,
  326. description => $3
  327. };
  328. push @{ $f->{'params'} }, $p;
  329. } elsif ( /^\s*\@brief\s*(\S.+)$/ ) {
  330. $f->{'brief'} = $1;
  331. } elsif ( /^\s*\@example\s*(\S)?\s*$/ ) {
  332. $example = 1;
  333. if ( $1 ) {
  334. $f->{'example_language'} = $1;
  335. }
  336. } elsif ( $_ ne $func ) {
  337. if ( $example ) {
  338. $f->{'example'} .= $_;
  339. } else {
  340. $f->{'data'} .= substitute_data_keywords($_);
  341. }
  342. }
  343. }
  344. if ( $f->{'data'} ) {
  345. chomp $f->{'data'};
  346. } elsif ($f->{'brief'}) {
  347. chomp $f->{'brief'};
  348. $f->{'data'} = $f->{'brief'};
  349. }
  350. if ( $f->{'example'} ) {
  351. chomp $f->{'example'};
  352. }
  353. if ( $type eq "table" ) {
  354. push @{ $cur_module->{'tables'} }, $f;
  355. } elsif ( $type eq "struct" ) {
  356. push @{ $cur_module->{'structs'} }, $f;
  357. }
  358. }
  359. # /function parse_module
  360. sub parse_module {
  361. my ( $module, @data ) = @_;
  362. my ( $name ) = ( $module =~ $module_re );
  363. chomp $name;
  364. my $f = {
  365. name => $name,
  366. functions => [],
  367. methods => [],
  368. data => '',
  369. example => undef,
  370. example_language => $example_language,
  371. id => make_id( $name, "module" ),
  372. };
  373. my $example = 0;
  374. foreach ( @data ) {
  375. if ( /^\s*\@example\s*(\S)?\s*$/ ) {
  376. $example = 1;
  377. if ($1) {
  378. $f->{'example_language'} = $1;
  379. }
  380. } elsif ( /^\s*\@brief\s*(\S.+)$/ ) {
  381. $f->{'brief'} = $1;
  382. } elsif ( $_ ne $module ) {
  383. if ( $example ) {
  384. $f->{'example'} .= $_;
  385. } else {
  386. $f->{'data'} .= substitute_data_keywords($_);
  387. }
  388. }
  389. }
  390. if ( $f->{'data'} ) {
  391. chomp $f->{'data'};
  392. } elsif ( $f->{'brief'} ) {
  393. chomp $f->{'brief'};
  394. $f->{'data'} = $f->{'brief'};
  395. }
  396. if ( $f->{'example'} ) {
  397. chomp $f->{'example'};
  398. }
  399. $cur_module = $f;
  400. push @modules, $f;
  401. }
  402. # /function parse_content
  403. sub parse_content {
  404. #
  405. my @func = grep /$function_re/, @_;
  406. if ( scalar @func > 0 ) {
  407. parse_function( $func[0], @_ );
  408. }
  409. #
  410. my @struct = grep /$struct_re/, @_;
  411. if ( scalar @struct > 0 ) {
  412. parse_struct( $struct[0], @_ );
  413. }
  414. #
  415. my @module = grep /$module_re/, @_;
  416. if ( scalar @module > 0 ) {
  417. parse_module( $module[0], @_ );
  418. }
  419. }
  420. sub HELP_MESSAGE {
  421. print STDERR <<EOF;
  422. Utility to convert doxygen comments to markdown.
  423. usage: $0 [-hg] [-l language] < input_source > markdown.md
  424. -h : this (help) message
  425. -e : sets default example language (default: lua)
  426. -l : sets input language (default: c)
  427. -g : use github flavoured markdown (default: kramdown/pandoc)
  428. EOF
  429. exit;
  430. }
  431. $Getopt::Std::STANDARD_HELP_VERSION = 1;
  432. use Getopt::Std;
  433. getopts( 'he:gl:', \%options );
  434. HELP_MESSAGE() if $options{h};
  435. $example_language = $options{e} if $options{e};
  436. $language = $languages{ lc $options{l} } if $options{l};
  437. if ( !$language ) {
  438. $language = $languages{c};
  439. }
  440. ## TODO: select language based on file extension
  441. ## TODO: change calling structure to allow looping through directory
  442. use constant {
  443. STATE_READ_SKIP => 0,
  444. STATE_READ_CONTENT => 1,
  445. STATE_READ_ENUM => 2,
  446. STATE_READ_STRUCT => 3,
  447. };
  448. my $state = STATE_READ_SKIP;
  449. my $content;
  450. while ( <> ) {
  451. if ( $state == STATE_READ_SKIP ) {
  452. if ( $_ =~ $language->{start} ) {
  453. $state = STATE_READ_CONTENT;
  454. if (defined($1)) {
  455. chomp($content = $1);
  456. $content =~ tr/\r//d;
  457. $content .= "\n";
  458. } else {
  459. $content = "";
  460. }
  461. }
  462. } elsif ( $state == STATE_READ_CONTENT ) {
  463. if ( $_ =~ $language->{end} ) {
  464. $state = STATE_READ_SKIP;
  465. parse_content( split /^/, $content );
  466. $content = "";
  467. } else {
  468. my ($line) = ( $_ =~ $language->{filter} );
  469. if ( $line ) {
  470. $line =~ tr/\r//d;
  471. $content .= $line . "\n";
  472. } else {
  473. # Preserve empty lines
  474. $content .= "\n";
  475. }
  476. }
  477. }
  478. }
  479. #print Dumper( \@modules );
  480. print_markdown;