├── .gitignore ├── LICENSE ├── README.md └── doxydown.pl /.gitignore: -------------------------------------------------------------------------------- 1 | /blib/ 2 | /.build/ 3 | _build/ 4 | cover_db/ 5 | inc/ 6 | Build 7 | !Build/ 8 | Build.bat 9 | .last_cover_stats 10 | /Makefile 11 | /Makefile.old 12 | /MANIFEST.bak 13 | /META.yml 14 | /META.json 15 | /MYMETA.* 16 | nytprof.out 17 | /pm_to_blib 18 | *.o 19 | *.bs 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Vsevolod Stakhov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Doxydown - documentation utility 2 | 3 | ## Introduction 4 | 5 | Doxydown is an utility to convert Doxygen style comments from the source code to Markdown. 6 | Unlike other documentation systems, `doxydown` is specifically designed to generate Markdown output only. 7 | At the moment, doxydown can work with C, Lua, SQL, and Perl comments and produce kramdown/pandoc or GitHub 8 | flavoured markdown. Doxydown produces output with anchors, links and table of content. 9 | It also can highlight syntax for examples in the documentation. 10 | 11 | ### Why markdown 12 | 13 | Markdown is used by many contemporary engines and can be rendered to HTML using advanced templates, 14 | styles and scripts. Markdown provides excellent formatting capabilities while it doesn't require authors 15 | to be web designers to create documentation. Markdown is rendered by GitHub so doxydown can generate 16 | documentation is easily viewed directly inside GitHub. Moreover, doxydown supports pandoc style of markdown 17 | and that means that markdown output can be converted to all formats supported by pandoc (html, pdf, latex, 18 | man pages and many others). 19 | 20 | ### Why not `other documentation generator` 21 | 22 | Doxydown is extremely simple as it can output markdown only but it is very convenient tool to generate nice 23 | Markdown with all features required from the documentation system. Doxydown uses input format that is almost 24 | identical to Goxygen that allows to you to re-use the existing documentation comments. Currenly, Doxydown 25 | does not support many features but they could be easily added on demand. The source is meant to be easily 26 | hackable and easily extended. 27 | 28 | ## Input format 29 | 30 | Doxydown extracts documentation from the comments blocks. The start of block is indicated by a comment block. 31 | 32 | For example: 33 | 34 | ```c 35 | /*** 36 | * This is a C block 37 | */ 38 | ``` 39 | 40 | ```lua 41 | --[[[ 42 | -- This is a Lua block 43 | --]] 44 | ``` 45 | 46 | ```lua 47 | --[[[ 48 | -- This is a Lua SQL block 49 | --]] 50 | 51 | -------- 52 | -- Alternate Lua and SQL Format 53 | -------- 54 | ``` 55 | 56 | ```perl 57 | ### 58 | # This is a Perl block 59 | ### 60 | ``` 61 | 62 | 63 | Doxydown also strips an initial comment characters if you have used them for decoration. 64 | The following comment blocks are synatically identical... 65 | 66 | ```c 67 | /*** 68 | * some text 69 | * other text 70 | * 71 | */ 72 | ``` 73 | 74 | ```c 75 | /*** 76 | some text 77 | other text 78 | 79 | */ 80 | ``` 81 | 82 | Note that doxydown preserves empty lines and all markdown elements. 83 | 84 | ### Documentation blocks 85 | 86 | Each documentation block describes either module or function/method. Modules are 87 | logical compounds of functions and methods. The difference between method and 88 | function is significant for languages with methods support (e.g. by `lua` via 89 | metatables). To define method or function you can use the following: 90 | 91 | /*** 92 | @function my_awesome_function(param1[, param2]) 93 | This function is awesome. 94 | */ 95 | 96 | All text met in the current documentation block is used as function or method description. 97 | You can also define parameters and return values for functions and methods: 98 | 99 | @param {type} param1 mandatory param 100 | 101 | Here, `{type}` is optional type description for a parameter, `param1` is parameter's name 102 | and the rest of the string is parameter description. Currently, you cannot split 103 | parameter description by newline character. In future versions of doxydown this might 104 | be fixed. 105 | 106 | You can specify return type of your function by using of `@return` tag: 107 | 108 | @return {type} some cool result 109 | 110 | This tag is similar to `@param` and has the same limitation regarding newlines. 111 | You can also add some example code by using of `@example` tag: 112 | 113 | @example 114 | my_awesome_function('hello'); // returns 42 115 | 116 | All text after `@example` tag and until documentation block end is used as an example 117 | and is highlighted in markdown. Also you can switch the language of example by using 118 | the extended `@example` tag: 119 | 120 | @example lua 121 | 122 | In this example, the code will be highlighted as `lua` code. 123 | 124 | Modules descriptions uses the same conventions, but `@param` and `@return` are 125 | meaningless for the modules. Function and methods blocks that follows some `@module` 126 | block are automatically attached to that module. 127 | 128 | Both modules and function can use links to other functions and methods by using of 129 | `@see` tag: 130 | 131 | @see my_awesome_function 132 | 133 | This inserts a hyperlink to the specified function definition to the markdown. 134 | 135 | ## Output format 136 | 137 | Doxydown can generate github flavoured markdown and pandoc/kramdown compatible 138 | markdown. The main difference is in how anchors are organized. In kramdown and 139 | pandoc it is possible to specify an explicit id for each header, whilst in 140 | GH flavoured markdown we can use only implicit anchors. 141 | 142 | ### Examples 143 | 144 | You can see an example of github flavoured markdown render at 145 | [libucl github page](https://github.com/vstakhov/libucl/blob/master/doc/lua_api.md). 146 | The same page bu rendered by kramdown engine in `jekyll` platform can be 147 | accessed by [this address](https://rspamd.com/doc/lua/ucl.html). 148 | 149 | ## Program invocation 150 | 151 | doxydown [-hg] [-e language] [-l language] < input_source > markdown.md 152 | 153 | * `-h`: help message 154 | * `-e`: sets default example language (default: lua) 155 | * `-l`: sets input language (default: c) 156 | * `-g`: use github flavoured markdown (default: kramdown/pandoc) 157 | 158 | ## License 159 | 160 | Doxydown is published by terms of `MIT` license. 161 | -------------------------------------------------------------------------------- /doxydown.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | $VERSION = "0.1.4"; 4 | 5 | use strict; 6 | use warnings; 7 | use Data::Dumper; 8 | use Digest::MD5 qw(md5_hex); 9 | 10 | my @modules; 11 | my %options = (); 12 | my $cur_module; 13 | my $example_language = "lua"; 14 | 15 | my %languages = ( 16 | c => { 17 | start => qr/^\s*\/\*\*\*(?:\s*|(\s+\S.+\s*))$/, 18 | end => qr/^\s*\*+\/\s*$/, 19 | filter => qr/^(?:\s*\*+\s?)?(\s*\S.+)\s*$/, 20 | }, 21 | lua => { 22 | start => qr/^\s*\--(?:\[\[\[+|-+)\s*$/, 23 | end => qr/^\s*--(:?\]\]+|-+)\s*$/, 24 | filter => qr/^(?:\s*--!?\s?)?(\s*\S?.+)\s*$/, 25 | }, 26 | sql => { 27 | start => qr/^\s*\--(?:\[\[+|-+)\s*$/, 28 | end => qr/^\s*--(:?\]\]+|-+)\s*$/, 29 | filter => qr/^(?:\s*--\s?)?(\s*\S.+)\s*$/, 30 | }, 31 | pl => { 32 | start => qr/^\s*\##+\s*$/, 33 | end => qr/^\s*##+\s*/, 34 | filter => qr/^(?:\s*#+\s?)(\s*\S.+)\s*$/, 35 | }, 36 | ); 37 | 38 | my $function_re = qr/^\s*\@(function|fn|method)\s*(\S.+)$/oi; 39 | my $struct_re = qr/^\s*\@(table|struct)\s*(\S.+)$/oi; 40 | my $module_re = qr/^\s*\@(?:module|file)\s*(\S.+)$/oi; 41 | my $language; 42 | 43 | # /function print_module_markdown 44 | sub print_module_markdown { 45 | my ( $mname, $m ) = @_; 46 | my $idline = $options{g} ? "" : " {#$m->{'id'}}"; 47 | 48 | print <{'data'} 52 | EOD 53 | if ( $m->{'example'} ) { 54 | print <{'example_language'} 59 | $m->{'example'} 60 | ~~~ 61 | EOD 62 | } 63 | 64 | sub print_func { 65 | my ($f) = @_; 66 | 67 | my $name = $f->{'name'}; 68 | my $id = $f->{'id'}; 69 | 70 | if ($f->{'brief'}) { 71 | print "> [`$name`](#$id): ". $f->{'brief'} . "\n\n"; 72 | } else { 73 | print "> [`$name`](#$id)\n\n"; 74 | } 75 | } 76 | 77 | sub print_table { 78 | my ($f) = @_; 79 | 80 | my $name = $f->{'name'}; 81 | my $id = $f->{'id'}; 82 | 83 | if ($f->{'brief'}) { 84 | print "> [`$name`](#$id): ". $f->{'brief'} . "\n\n"; 85 | } else { 86 | print "> [`$name`](#$id)\n\n"; 87 | } 88 | } 89 | 90 | print "\n### Brief content:\n\n"; 91 | 92 | if ($m->{'functions'}) { 93 | if (scalar(@{ $m->{'functions'} }) > 0) { 94 | print "**Functions**:\n\n"; 95 | 96 | foreach ( @{ $m->{'functions'} } ) { 97 | print_func($_); 98 | } 99 | } 100 | } 101 | 102 | if ($m->{'methods'}) { 103 | if (scalar(@{ $m->{'methods'} }) > 0) { 104 | print "\n\n**Methods**:\n\n"; 105 | 106 | foreach ( @{ $m->{'methods'} } ) { 107 | print_func($_); 108 | } 109 | } 110 | } 111 | 112 | if ($m->{'tables'}) { 113 | if (scalar(@{ $m->{'tables'} }) > 0) { 114 | print "\n\n**Tables**:\n\n"; 115 | 116 | foreach ( @{ $m->{'tables'} } ) { 117 | print_table($_); 118 | } 119 | } 120 | } 121 | 122 | if ($m->{'structs'}) { 123 | if (scalar(@{ $m->{'structs'} }) > 0) { 124 | print "\n\n**Structs**:\n\n"; 125 | 126 | foreach ( @{ $m->{'structs'} } ) { 127 | print_table($_); 128 | } 129 | } 130 | } 131 | } 132 | 133 | # /function print_function_markdown 134 | sub print_function_markdown { 135 | my ( $type, $fname, $f ) = @_; 136 | 137 | my $idline = $options{g} ? "" : " {#$f->{'id'}}"; 138 | print <{'data'} 142 | EOD 143 | print "\n**Parameters:**\n\n"; 144 | 145 | if ( $f->{'params'} && scalar @{ $f->{'params'} } > 0 ) { 146 | foreach ( @{ $f->{'params'} } ) { 147 | if ( $_->{'type'} ) { 148 | print 149 | "- `$_->{'name'} \{$_->{'type'}\}`: $_->{'description'}\n"; 150 | } else { 151 | print "- `$_->{'name'}`: $_->{'description'}\n"; 152 | } 153 | } 154 | } else { 155 | print "No parameters\n"; 156 | } 157 | 158 | print "\n**Returns:**\n\n"; 159 | 160 | if ( $f->{'returns'} && scalar @{ $f->{'returns'} } > 0 ) { 161 | foreach ( @{ $f->{'returns'} } ) { 162 | if ( $_->{'type'} ) { 163 | print "- `\{$_->{'type'}\}`: $_->{'description'}\n"; 164 | } else { 165 | print "- $_->{'description'}\n"; 166 | } 167 | } 168 | } else { 169 | print "No return\n"; 170 | } 171 | 172 | if ( $f->{'example'} ) { 173 | print <{'example_language'} 178 | $f->{'example'} 179 | ~~~ 180 | EOD 181 | } 182 | } 183 | 184 | # /function print_struct_markdown 185 | sub print_struct_markdown { 186 | my ( $type, $fname, $f ) = @_; 187 | 188 | my $idline = $options{g} ? "" : " {#$f->{'id'}}"; 189 | print <{'data'} 193 | EOD 194 | print "\n**Elements:**\n\n"; 195 | 196 | if ( $f->{'params'} && scalar @{ $f->{'params'} } > 0 ) { 197 | foreach ( @{ $f->{'params'} } ) { 198 | if ( $_->{'type'} ) { 199 | print 200 | "- `$_->{'name'} \{$_->{'type'}\}`: $_->{'description'}\n"; 201 | } else { 202 | print "- `$_->{'name'}`: $_->{'description'}\n"; 203 | } 204 | } 205 | } else { 206 | print "No elements\n"; 207 | } 208 | 209 | if ( $f->{'example'} ) { 210 | print <{'example_language'} 215 | $f->{'example'} 216 | ~~~ 217 | EOD 218 | } 219 | } 220 | 221 | # /function print_markdown 222 | sub print_markdown { 223 | for my $m (@modules) { 224 | my $mname = $m->{name}; 225 | 226 | print_module_markdown( $mname, $m ); 227 | 228 | if ($m->{'functions'}) { 229 | if ( scalar(@{ $m->{'functions'} }) > 0 ) { 230 | print "\n## Functions\n\nThe module `$mname` defines the following functions.\n\n"; 231 | 232 | foreach ( @{ $m->{'functions'} } ) { 233 | print_function_markdown( "Function", $_->{'name'}, $_ ); 234 | 235 | print "\nBack to [module description](#$m->{'id'}).\n\n"; 236 | 237 | } 238 | } 239 | } 240 | 241 | if ($m->{'methods'}) { 242 | if ( scalar(@{ $m->{'methods'} }) > 0 ) { 243 | print "\n## Methods\n\nThe module `$mname` defines the following methods.\n\n"; 244 | 245 | foreach ( @{ $m->{'methods'} } ) { 246 | print_function_markdown( "Method", $_->{'name'}, $_ ); 247 | 248 | print "\nBack to [module description](#$m->{'id'}).\n\n"; 249 | 250 | } 251 | } 252 | } 253 | 254 | if ($m->{'tables'}) { 255 | if ( scalar(@{ $m->{'tables'} }) > 0 ) { 256 | print "\n## Tables\n\nThe module `$mname` defines the following tables.\n\n"; 257 | 258 | foreach ( @{ $m->{'tables'} } ) { 259 | print_struct_markdown( "Table", $_->{'name'}, $_ ); 260 | 261 | print "\nBack to [module description](#$m->{'id'}).\n\n"; 262 | 263 | } 264 | } 265 | } 266 | 267 | if ($m->{'stucts'}) { 268 | if ( scalar(@{ $m->{'stucts'} }) > 0 ) { 269 | print "\n## Stucts\n\nThe module `$mname` defines the following stucts.\n\n"; 270 | 271 | foreach ( @{ $m->{'stucts'} } ) { 272 | print_stuct_markdown( "Stuct", $_->{'name'}, $_ ); 273 | 274 | print "\nBack to [module description](#$m->{'id'}).\n\n"; 275 | 276 | } 277 | } 278 | } 279 | 280 | print "\nBack to [top](#).\n\n"; 281 | } 282 | } 283 | 284 | # /function make_id 285 | sub make_id { 286 | my ( $name, $prefix ) = @_; 287 | 288 | if ( !$prefix ) { 289 | $prefix = "f"; 290 | } 291 | 292 | if ( !$options{g} ) { 293 | 294 | # Kramdown/pandoc version of ID's 295 | $name =~ /^(\S+).*$/; 296 | 297 | return substr( substr( $prefix, 0, 1 ) . md5_hex($1), 0, 6 ); 298 | } else { 299 | my $input = lc $prefix . "-" . $name; 300 | my $id = join '-', split /\s+/, $input; 301 | 302 | $id =~ s/[^\w_-]+//g; 303 | 304 | return $id; 305 | } 306 | } 307 | 308 | # /function substitute_data_keywords 309 | sub substitute_data_keywords { 310 | my ($line) = @_; 311 | 312 | if ( $line =~ /^.*\@see\s+(\S+)\s*.*$/ ) { 313 | my $name = $1; 314 | my $id = make_id($name); 315 | 316 | return $line =~ s/\@see\s+\S+/[`$name`](#$id)/r; 317 | } 318 | 319 | return $line; 320 | } 321 | 322 | # /function parse_function 323 | sub parse_function { 324 | my ( $func, @data ) = @_; 325 | 326 | my ( $type, $name ) = ( $func =~ $function_re ); 327 | chomp $name; 328 | 329 | my $f = { 330 | name => $name, 331 | data => '', 332 | example => undef, 333 | example_language => $example_language, 334 | id => make_id( $name, $type ), 335 | }; 336 | my $example = 0; 337 | 338 | foreach ( @data ) { 339 | if ( /^\s*\@param\s*(?:\{([^}]+)\})?\s*(\S+)\s*(.+)?\s*$/ ) { 340 | my $p = { 341 | name => $2, 342 | type => $1, 343 | description => $3 344 | }; 345 | 346 | push @{ $f->{'params'} }, $p; 347 | } elsif ( /^\s*\@return\s*(?:\{([^}]+)\})?\s*(.+)?\s*$/ ) { 348 | my $r = { 349 | type => $1, 350 | description => $2 351 | }; 352 | 353 | push @{ $f->{'returns'} }, $r; 354 | } elsif ( /^\s*\@brief\s*(\S.+)$/ ) { 355 | $f->{'brief'} = $1; 356 | } 357 | elsif ( /^\s*\@example\s*(\S)?\s*$/ ) { 358 | $example = 1; 359 | if ( $1 ) { 360 | $f->{'example_language'} = $1; 361 | } 362 | } elsif ( $_ ne $func ) { 363 | if ( $example ) { 364 | $f->{'example'} .= $_; 365 | } else { 366 | $f->{'data'} .= substitute_data_keywords($_); 367 | } 368 | } 369 | } 370 | 371 | if ( $f->{'data'} ) { 372 | chomp $f->{'data'}; 373 | } elsif ($f->{'brief'}) { 374 | chomp $f->{'brief'}; 375 | $f->{'data'} = $f->{'brief'}; 376 | } 377 | 378 | if ( $f->{'example'} ) { 379 | chomp $f->{'example'}; 380 | } 381 | 382 | if ( $type eq "method" ) { 383 | push @{ $cur_module->{'methods'} }, $f; 384 | } elsif ( $type eq "function" || $type eq "fn") { 385 | push @{ $cur_module->{'functions'} }, $f; 386 | } 387 | } 388 | 389 | # /function parse_struct 390 | sub parse_struct { 391 | my ( $func, @data ) = @_; 392 | 393 | my ( $type, $name ) = ( $func =~ $struct_re ); 394 | chomp $name; 395 | 396 | my $f = { 397 | name => $name, 398 | data => '', 399 | example => undef, 400 | example_language => $example_language, 401 | id => make_id( $name, $type ), 402 | }; 403 | my $example = 0; 404 | 405 | foreach ( @data ) { 406 | if ( /^\s*\@param\s*(?:\{([^}]+)\})?\s*(\S+)\s*(.+)?\s*$/ ) { 407 | my $p = { 408 | name => $2, 409 | type => $1, 410 | description => $3 411 | }; 412 | 413 | push @{ $f->{'params'} }, $p; 414 | } elsif ( /^\s*\@brief\s*(\S.+)$/ ) { 415 | $f->{'brief'} = $1; 416 | } elsif ( /^\s*\@example\s*(\S)?\s*$/ ) { 417 | $example = 1; 418 | if ( $1 ) { 419 | $f->{'example_language'} = $1; 420 | } 421 | } elsif ( $_ ne $func ) { 422 | if ( $example ) { 423 | $f->{'example'} .= $_; 424 | } else { 425 | $f->{'data'} .= substitute_data_keywords($_); 426 | } 427 | } 428 | } 429 | 430 | if ( $f->{'data'} ) { 431 | chomp $f->{'data'}; 432 | } elsif ($f->{'brief'}) { 433 | chomp $f->{'brief'}; 434 | $f->{'data'} = $f->{'brief'}; 435 | } 436 | 437 | if ( $f->{'example'} ) { 438 | chomp $f->{'example'}; 439 | } 440 | 441 | if ( $type eq "table" ) { 442 | push @{ $cur_module->{'tables'} }, $f; 443 | } elsif ( $type eq "struct" ) { 444 | push @{ $cur_module->{'structs'} }, $f; 445 | } 446 | } 447 | 448 | # /function parse_module 449 | sub parse_module { 450 | my ( $module, @data ) = @_; 451 | my ( $name ) = ( $module =~ $module_re ); 452 | 453 | chomp $name; 454 | 455 | my $f = { 456 | name => $name, 457 | functions => [], 458 | methods => [], 459 | data => '', 460 | example => undef, 461 | example_language => $example_language, 462 | id => make_id( $name, "module" ), 463 | }; 464 | 465 | my $example = 0; 466 | 467 | foreach ( @data ) { 468 | if ( /^\s*\@example\s*(\S)?\s*$/ ) { 469 | $example = 1; 470 | if ($1) { 471 | $f->{'example_language'} = $1; 472 | } 473 | } elsif ( /^\s*\@brief\s*(\S.+)$/ ) { 474 | $f->{'brief'} = $1; 475 | } elsif ( $_ ne $module ) { 476 | if ( $example ) { 477 | $f->{'example'} .= $_; 478 | } else { 479 | $f->{'data'} .= substitute_data_keywords($_); 480 | } 481 | } 482 | } 483 | 484 | if ( $f->{'data'} ) { 485 | chomp $f->{'data'}; 486 | } elsif ( $f->{'brief'} ) { 487 | chomp $f->{'brief'}; 488 | 489 | $f->{'data'} = $f->{'brief'}; 490 | } 491 | 492 | if ( $f->{'example'} ) { 493 | chomp $f->{'example'}; 494 | } 495 | 496 | $cur_module = $f; 497 | push @modules, $f; 498 | } 499 | 500 | # /function parse_content 501 | sub parse_content { 502 | # 503 | my @func = grep /$function_re/, @_; 504 | if ( scalar @func > 0 ) { 505 | parse_function( $func[0], @_ ); 506 | } 507 | 508 | # 509 | my @struct = grep /$struct_re/, @_; 510 | if ( scalar @struct > 0 ) { 511 | parse_struct( $struct[0], @_ ); 512 | } 513 | 514 | # 515 | my @module = grep /$module_re/, @_; 516 | if ( scalar @module > 0 ) { 517 | parse_module( $module[0], @_ ); 518 | } 519 | } 520 | 521 | sub HELP_MESSAGE { 522 | print STDERR < markdown.md 526 | 527 | -h : this (help) message 528 | -e : sets default example language (default: lua) 529 | -l : sets input language (default: c) 530 | -g : use github flavoured markdown (default: kramdown/pandoc) 531 | EOF 532 | 533 | exit; 534 | } 535 | 536 | $Getopt::Std::STANDARD_HELP_VERSION = 1; 537 | use Getopt::Std; 538 | 539 | getopts( 'he:gl:', \%options ); 540 | 541 | HELP_MESSAGE() if $options{h}; 542 | 543 | $example_language = $options{e} if $options{e}; 544 | $language = $languages{ lc $options{l} } if $options{l}; 545 | 546 | if ( !$language ) { 547 | $language = $languages{c}; 548 | } 549 | 550 | ## TODO: select language based on file extention 551 | ## TODO: change calling structure to allow looping through directory 552 | 553 | use constant { 554 | STATE_READ_SKIP => 0, 555 | STATE_READ_CONTENT => 1, 556 | STATE_READ_ENUM => 2, 557 | STATE_READ_STRUCT => 3, 558 | }; 559 | 560 | my $state = STATE_READ_SKIP; 561 | my $content; 562 | 563 | while ( <> ) { 564 | if ( $state == STATE_READ_SKIP ) { 565 | if ( $_ =~ $language->{start} ) { 566 | $state = STATE_READ_CONTENT; 567 | 568 | if (defined($1)) { 569 | chomp($content = $1); 570 | $content =~ tr/\r//d; 571 | $content .= "\n"; 572 | } else { 573 | $content = ""; 574 | } 575 | } 576 | } elsif ( $state == STATE_READ_CONTENT ) { 577 | if ( $_ =~ $language->{end} ) { 578 | $state = STATE_READ_SKIP; 579 | 580 | parse_content( split /^/, $content ); 581 | 582 | $content = ""; 583 | } else { 584 | my ($line) = ( $_ =~ $language->{filter} ); 585 | 586 | if ( $line ) { 587 | $line =~ tr/\r//d; 588 | $content .= $line . "\n"; 589 | } else { 590 | # Preserve empty lines 591 | $content .= "\n"; 592 | } 593 | } 594 | } 595 | } 596 | 597 | #print Dumper( \@modules ); 598 | print_markdown; 599 | --------------------------------------------------------------------------------