blib/lib/Liveman.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 168 | 227 | 74.0 |
branch | 45 | 110 | 40.9 |
condition | 12 | 59 | 20.3 |
subroutine | 20 | 29 | 68.9 |
pod | 8 | 8 | 100.0 |
total | 253 | 433 | 58.4 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Liveman; 2: use 5.22.0; 3: use common::sense; 4: 5: our $VERSION = "1.0"; 6: 7: use Term::ANSIColor qw/colored/; 8: use File::Slurper qw/read_text write_text/; 9: use Markdown::To::POD qw/markdown_to_pod/; 10: 11: 12: # Конструктор 13: sub new { 14: my $cls = shift; 15: my $self = bless {@_}, $cls; 16: delete $self->{files} if $self->{files} && !scalar @{$self->{files}}; 17: $self 18: } 19: 20: # Пакет из пути 21: sub _pkg($) { 22: ( shift =~ s!^lib/(.*)\.\w+$!$1!r ) =~ s!/!::!gr 23: } 24: 25: # Переменная из пакета 26: sub _var($) { 27: '$' . lcfirst( shift =~ s!::(\w)?!_${\lc $1}!gr ) 28: } 29: 30: # Для метода для вставки 31: sub _md_method(@) { 32: my ($pkg, $sub, $args, $remark) = @_; 33: my $sub_args = "$sub ($args)"; 34: $args = "($args)" if $args; 35: 36: $remark = "." unless defined $remark; 37: my $var = _var $pkg; 38: << "END"; 39: ## $sub_args 40: 41: $remark 42: 43: ```perl 44: my $var = $pkg->new; 45: ${var}->$sub$args # -> .3 46: ``` 47: 48: END 49: } 50: 51: # Для фичи для вставки 52: sub _md_feature(@) { 53: my ($pkg, $has, $remark) = @_; 54: 55: $remark = "." unless defined $remark; 56: my $var = _var $pkg; 57: << "END"; 58: ## $has 59: 60: $remark 61: 62: ```perl 63: my $var = $pkg->new; 64: 65: ${var}->$has\t# -> .5 66: ``` 67: 68: END 69: } 70: 71: 72: # Добавить разделы функций в *.md из *.pm 73: sub appends { 74: my ($self) = @_; 75: my $files = $self->{files} // [split /\n/, `find lib -name '*.pm' -a -type f`]; 76: $self->append($_) for @$files; 77: $self 78: } 79: 80: # Добавить разделы функций в *.md из *.pm 81: sub append { 82: my ($self, $pm) = @_; 83: 84: my $md = $pm =~ s!(\.\w+)?$!.md!r; 85: 86: die "Not file $pm!" if !-f $pm; 87: $self->mkmd($md) if !-f $md; 88: 89: local $_ = read_text $pm; 90: my %sub; my %has; 91: while(m! (^\# [\ \t]* (?<remark> .*?) [\ \t]* )? \n ( 92: sub \s+ (?<sub> (\w+|::)+ ) .* 93: ( \s* my \s* \( \s* (\$self,? \s* )? (?<args>.*?) \s* \) \s* = \s* \@_; )? 94: | has \s+ (?<has> (\w+|'\w+'|"\w+"|\[ \s* ([^\[\]]*?) \s* \])+ ) 95: ) !mgxn) { 96: $sub{$+{sub}} = {%+} if exists $+{sub} and "_" ne substr $+{sub}, 0, 1; 97: $has{$+{has}} = {%+} if exists $+{has} and "_" ne substr $+{has}, 0, 1; 98: } 99: 100: return $self if !keys %sub && !keys %has; 101: 102: $_ = read_text $md; 103: 104: my $pkg = _pkg $md; 105: 106: my $added = 0; 107: 108: s{^\#[\ \t]+( (?<is>METHODS|SUBROUTINES) | DESCRIPTION ) 109: (^```.*?^```|.)*? (?= ^\#\s) 110: }{ 111: my $x = $&; my $is = $+{is}; 112: if($is) { 113: while($x =~ /^\#\#[\ \t]+(\w+)/gm) { 114: delete $sub{$1}; 115: } 116: } 117: $added += keys %sub; 118: join "", $x, $is? (): "# SUBROUTINES/METHODS\n\n", map { _md_method $pkg, $_, $sub{$_}{args}, $sub{$_}{remark} } sort keys %sub; 119: }emsx or die "Нет секции DESCRIPTION!" if keys %sub; 120: 121: s{^\#[\ \t]+((?<is>FEATURES) | DESCRIPTION) 122: (^```.*?^```|.)*? (?= ^\#\s) 123: }{ 124: my $x = $&; my $is = $+{is}; 125: if($is) { 126: while($x =~ /^\#\#[\ \t]+([^\n]+?)[\ \t]*/gm) { 127: delete $has{$1}; 128: } 129: } 130: $added += keys %has; 131: join "", $x, $is? (): "# FEATURES\n\n", map { _md_feature $pkg, $_, $sub{$_}{remark} } sort keys %has; 132: }emsx or die "Нет секции DESCRIPTION!" if keys %has; 133: 134: 135: if ($added) { 136: write_text $md, $_; 137: print "🔖 $pm ", colored("⊂", "BRIGHT_GREEN"), " $md ", "\n", 138: " ", scalar keys %has? (colored("FEATURES ", "BRIGHT_WHITE"), join(colored(", ", "red"), sort keys %has), "\n"): (), 139: " ", scalar keys %sub? (colored("SUBROUTINES ", "BRIGHT_WHITE"), join(colored(", ", "red"), sort keys %sub), "\n"): (), 140: ; 141: } else { 142: print "🔖 $pm\n"; 143: } 144: 145: $self->{count}++; 146: $self->{added} = $added; 147: $self 148: } 149: 150: sub _git_user_name { shift->{_git_user_name} //= _trim(`git config user.name`) } 151: sub _git_user_email { shift->{_git_user_email} //= _trim(`git config user.email`) } 152: sub _year { shift->{_year} //= _trim(`date +%Y`) } 153: sub _license { shift->{_license} //= -r "minil.toml" && read_text("minil.toml") =~ /^\s*license\s*=\s*"([^"\n]*)"/m ? ($1 eq "perl_5"? "Perl5": uc($1) =~ s/_/v/r): "Perl5" } 154: sub _land { shift->{_land} //= `curl "https://ipapi.co/\$(curl https://2ip.ru --connect-timeout 3 --max-time 3 -Ss)/json/" --connect-timeout 3 --max-time 3 -Ss` =~ /country_name": "([^"\n]*)"/ ? ($1 eq "Russia" ? "Rusland" : $1) : 'Rusland' } 155: 156: # Добавить разделы функций в *.md из *.pm 157: sub mkmd { 158: my ($self, $md) = @_; 159: 160: my $pkg = _pkg $md; 161: 162: my $author = $self->_git_user_name; 163: my $email = $self->_git_user_email; 164: my $year = $self->_year; 165: my $license = $self->_license; 166: my $land = $self->_land; 167: 168: write_text $md, << "END"; 169: # NAME 170: 171: $pkg - 172: 173: # SYNOPSIS 174: 175: ```perl 176: use $pkg; 177: 178: my ${\_var $pkg} = $pkg->new; 179: ``` 180: 181: # DESCRIPION 182: 183: . 184: 185: # SUBROUTINES 186: 187: # INSTALL 188: 189: For install this module in your system run next [command](https://metacpan.org/pod/App::cpm): 190: 191: ```sh 192: sudo cpm install -gvv $pkg 193: ``` 194: 195: # AUTHOR 196: 197: $author [$email](mailto:$email) 198: 199: # LICENSE 200: 201: ⚖ **$license** 202: 203: # COPYRIGHT 204: 205: The $pkg module is copyright © $year $author. $land. All rights reserved. 206: END 207: } 208: 209: # Получить путь к тестовому файлу из пути к md-файлу 210: sub test_path { 211: my ($self, $md) = @_; 212: $md =~ s!^lib/(.*)\.md$! 213: join "", "t/", join("/", map { 214: lcfirst($_) =~ s/[A-Z]/"-" . lc $&/gre 215: } split /\//, $1), ".t" 216: !e; 217: $md 218: } 219: 220: # Трансформирует md-файлы 221: sub transforms { 222: my ($self) = @_; 223: my $mds = $self->{files} // [split /\n/, `find lib -name '*.md'`]; 224: 225: $self->{count} = 0; 226: 227: if($self->{compile_force}) { 228: $self->transform($_) for @$mds; 229: } else { 230: for my $md (@$mds) { 231: my $test = $self->test_path($md); 232: my $mdmtime = (stat $md)[9]; 233: die "Нет файла $md" if !$mdmtime; 234: $self->transform($md, $test) if !-e $test || -e $test && $mdmtime > (stat $test)[9]; 235: } 236: } 237: 238: if(-f "minil.toml" && -r "minil.toml") { 239: my $is_copy; my $name; 240: eval { 241: my $minil = read_text("minil.toml"); 242: ($name) = $minil =~ /^name = "([\w:-]+)"/m; 243: $name =~ s!(-|::)!/!g; 244: $name = "lib/$name.md"; 245: if(-f $name && -r $name) { 246: if(!-e "README.md" || -e "README.md" 247: && (stat $name)[9] > (stat "README.md")[9]) { 248: write_text "README.md", read_text $name; 249: $is_copy = 1; 250: } 251: } 252: }; 253: if($@) {warn $@} 254: elsif($is_copy) { 255: print "📘 $name ", colored("↦", "white"), " README.md ", colored("...", "white"), " ", colored("ok", "bright_green"), "\n"; 256: } 257: } 258: 259: $self 260: } 261: 262: # Эскейпинг для qr!! 263: sub _qr_esc { 264: $_[0] =~ s/!/\\!/gr 265: } 266: 267: # Эскейпинг для строки в двойных кавычках 268: sub _qq_esc { 269: $_[0] =~ s!"!\\"!gr 270: } 271: 272: # Эскейпинг для строки в одинарных кавычках 273: sub _q_esc { 274: $_[0] =~ s!'!\\'!gr 275: } 276: 277: # Обрезает пробельные символы 278: sub _trim { 279: $_[0] =~ s!^\s*(.*?)\s*\z!$1!sr 280: } 281: 282: # Создаёт путь 283: sub _mkpath { 284: my ($p) = @_; 285: mkdir $`, 0755 while $p =~ /\//g; 286: } 287: 288: # Строка кода для тестирования 289: sub _to_testing { 290: my ($line, %x) = @_; 291: 292: return $x{code} if $x{code} =~ /^\s*#/; 293: 294: my $expected = $x{expected}; 295: my $q = _q_esc($line =~ s!\s*$!!r); 296: my $code = _trim($x{code}); 297: 298: if(exists $x{is_deeply}) { "::is_deeply scalar do {$code}, scalar do {$expected}, '$q';\n" } 299: elsif(exists $x{is}) { "::is scalar do {$code}, scalar do{$expected}, '$q';\n" } 300: elsif(exists $x{qqis}) { my $ex = _qq_esc($expected); "::is scalar do {$code}, \"$ex\", '$q';\n" } 301: elsif(exists $x{qis}) { my $ex = _q_esc($expected); "::is scalar do {$code}, '$ex', '$q';\n" } 302: elsif(exists $x{like}) { my $ex = _qr_esc($expected); "::like scalar do {$code}, qr!$ex!, '$q';\n" } 303: elsif(exists $x{unlike}) { my $ex = _qr_esc($expected); "::unlike scalar do {$code}, qr!$ex!, '$q';\n" } 304: else { # Что-то ужасное вырвалось на волю! 305: "???" 306: } 307: } 308: 309: # Трансформирует md-файл в тест и документацию 310: sub transform { 311: my ($self, $md, $test) = @_; 312: $test //= $self->test_path($md); 313: 314: print "🔖 $md ", colored("↦", "white"), " $test ", colored("...", "white"), " "; 315: 316: my $markdown = read_text($md); 317: 318: my @pod; my @test; my $title = 'Start'; my $close_subtest; my $use_title = 1; 319: 320: my @text = split /^(```\w*[ \t]*(?:\n|\z))/mo, $markdown; 321: 322: for(my $i=0; $i<@text; $i+=4) { 323: my ($mark, $sec1, $code, $sec2) = @text[$i..$i+4]; 324: 325: push @pod, markdown_to_pod($mark); 326: push @test, $mark =~ s/^/# /rmg; 327: 328: last unless defined $sec1; 329: $i--, $sec2 = $code, $code = "" if $code =~ /^```[ \t]*$/; 330: 331: die "=== mark ===\n$mark\n=== sec1 ===\n$sec1\n=== code ===\n$code\n=== sec2 ===\n$sec2\n\nsec2 ne ```" if $sec2 ne "```\n"; 332: 333: $title = _trim($1) while $mark =~ /^#+[ \t]+(.*)/gm; 334: 335: push @pod, "\n", ($code =~ s/^/\t/gmr), "\n"; 336: 337: my ($infile, $is) = $mark =~ /^(?:File|Файл)[ \t]+(.*?)([\t ]+(?:is|является))?:[\t ]*\n\z/m; 338: if($infile) { 339: my $real_code = $code =~ s/^\\(```\w*[\t ]*$)/$1/mgro; 340: if($is) { # тестируем, что текст совпадает 341: push @test, "\n{ my \$s = '${\_q_esc($infile)}'; open my \$__f__, '<:utf8', \$s or die \"Read \$s: \$!\"; my \$n = join '', <\$__f__>; close \$__f__; ::is \$n, '${\_q_esc($real_code)}', \"File \$s\"; }\n"; 342: } 343: else { # записываем тект в файл 344: #push @test, "\n{ my \$s = main::_mkpath_('${\_q_esc($infile)}'); open my \$__f__, '>:utf8', \$s or die \"Read \$s: \$!\"; print \$__f__ '${\_q_esc($real_code)}'; close \$__f__ }\n"; 345: push @test, "#\@> $infile\n", $real_code =~ s/^/#>> /rgm, "#\@< EOF\n"; 346: } 347: } elsif($sec1 =~ /^```(?:perl)?[ \t]*$/) { 348: 349: if($use_title ne $title) { 350: push @test, "done_testing; }; " if $close_subtest; 351: $close_subtest = 1; 352: push @test, "subtest '${\ _q_esc($title)}' => sub { "; 353: $use_title = $title; 354: } 355: 356: my $test = $code =~ s{^(?<code>.*)#[ \t]*((?<is_deeply>-->|⟶)|(?<is>->|→)|(?<qqis>=>|⇒)|(?<qis>\\>|↦)|(?<like>~>|↬)|(?<unlike><~|↫))\s*(?<expected>.+?)[ \t]*\n}{ _to_testing($&, %+) }grme; 357: push @test, "\n", $test, "\n"; 358: } 359: else { 360: push @test, "\n", $code =~ s/^/# /rmg, "\n"; 361: } 362: } 363: 364: push @test, "\n\tdone_testing;\n};\n" if $close_subtest; 365: push @test, "\ndone_testing;\n"; 366: 367: _mkpath($test); 368: my $mkpath = q{sub _mkpath_ { my ($p) = @_; length($`) && !-e $`? mkdir($`, 0755) || die "mkdir $`: $!": () while $p =~ m!/!g; $p }}; 369: my $write_files = q{open my $__f__, "<:utf8", $t or die "Read $t: $!"; read $__f__, $s, -s $__f__; close $__f__; while($s =~ /^#\\@> (.*)\n((#>> .*\n)*)#\\@< EOF\n/gm) { my ($file, $code) = ($1, $2); $code =~ s/^#>> //mg; open my $__f__, ">:utf8", _mkpath_($file) or die "Write $file: $!"; print $__f__ $code; close $__f__; }}; 370: #my @symbol = ('a'..'z', 'A'..'Z', '0' .. '9', '-', '_'); 371: # "-" . join("", map $symbol[rand(scalar @symbol)], 1..6) 372: my $test_path = join "", "/tmp/.liveman/", 373: `pwd` =~ s/^.*?([^\/]+)\n$/$1/rs, 374: $test =~ s!^t/(.*)\.t$!/$1!r =~ y/\//!/r, "/"; 375: my $chdir = "my \$t = `pwd`; chop \$t; \$t .= '/' . __FILE__; my \$s = '${\ _q_esc($test_path)}'; `rm -fr '\$s'` if -e \$s; chdir _mkpath_(\$s) or die \"chdir \$s: \$!\";"; 376: # use Carp::Always::Color ::Term; 377: my $die = 'use Scalar::Util qw//; use Carp qw//; $SIG{__DIE__} = sub { my ($s) = @_; if(ref $s) { $s->{STACKTRACE} = Carp::longmess "?" if "HASH" eq Scalar::Util::reftype $s; die $s } else {die Carp::longmess defined($s)? $s: "undef" }};'; 378: write_text $test, join "", "use common::sense; use open qw/:std :utf8/; use Test::More 0.98; $mkpath BEGIN { $die $chdir $write_files } ", @test; 379: 380: # Создаём модуль, если его нет 381: my $pm = $md =~ s/\.md$/.pm/r; 382: if(!-e $pm) { 383: my $pkg = ($pm =~ s!^lib/(.*)\.pm$!$1!r) =~ s!/!::!gr; 384: write_text $pm, "package $pkg;\n\n1;"; 385: } 386: 387: # Трансформируем модуль (pod и версия): 388: my $pod = join "", @pod; 389: my $module = read_text $pm; 390: $module =~ s!(\s*\n__END__[\t ]*\n.*)?$!\n\n__END__\n\n=encoding utf-8\n\n$pod!sn; 391: 392: # Меняем версию: 393: my $v = uc "version"; 394: my ($version) = $markdown =~ /^#[ \t]+$v\s+([\w\.-]{1,32})\s/m; 395: $module =~ s!^(our\s*\$$v\s*=\s*)["']?[\w.-]{1,32}["']?!$1"$version"!m if defined $version; 396: write_text $pm, $module; 397: 398: $self->{count}++; 399: 400: print colored("ok", "bright_green"), "\n"; 401: 402: $self 403: } 404: 405: # Запустить тесты 406: sub tests { 407: my ($self) = @_; 408: 409: my $cover = "/usr/bin/site_perl/cover"; 410: $cover = 'cover' if !-e $cover; 411: 412: my $yath = "/usr/bin/site_perl/yath"; 413: $yath = 'yath' if !-e $yath; 414: 415: my $options = $self->{options}; 416: 417: if($self->{files}) { 418: my @tests = map $self->test_path($_), @{$self->{files}}; 419: local $, = " "; 420: $self->{exit_code} = system $self->{prove} 421: ? "prove -Ilib $options @tests" 422: : "$yath test -j4 $options @tests"; 423: return $self; 424: } 425: 426: my $perl5opt = $ENV{PERL5OPT}; 427: 428: system "$cover -delete"; 429: if($self->{prove}) { 430: local $ENV{PERL5OPT} = "$perl5opt -MDevel::Cover"; 431: $self->{exit_code} = system "env | grep PERL5OPT; prove -Ilib -r t $options"; 432: #$self->{exit_code} = system "prove --exec 'echo `pwd`/lib && perl -MDevel::Cover -I`pwd`/lib' -r t"; 433: } else { 434: $self->{exit_code} = system "$yath test -j4 --cover $options"; 435: } 436: return $self if $self->{exit_code}; 437: system "$cover -report html_basic"; 438: system "(opera cover_db/coverage.html || xdg-open cover_db/coverage.html) &> /dev/null" if $self->{open}; 439: return $self; 440: } 441: 442: 1; 443: 444: __END__ 445: 446: =encoding utf-8 447: 448: =head1 NAME 449: 450: Liveman - markdown compiller to test and pod. 451: 452: =head1 VERSION 453: 454: 1.0 455: 456: =head1 SYNOPSIS 457: 458: File lib/Example.md: 459: 460: Twice two: 461: \```perl 462: 2*2 # -> 2+2 463: \``` 464: 465: Test: 466: 467: use Liveman; 468: 469: my $liveman = Liveman->new(prove => 1); 470: 471: # compile lib/Example.md file to t/example.t and added pod to lib/Example.pm 472: $liveman->transform("lib/Example.md"); 473: 474: $liveman->{count} # => 1 475: -f "t/example.t" # => 1 476: -f "lib/Example.pm" # => 1 477: 478: # compile all lib/**.md files with a modification time longer than their corresponding test files (t/**.t) 479: $liveman->transforms; 480: $liveman->{count} # => 0 481: 482: # compile without check modification time 483: Liveman->new(compile_force => 1)->transforms->{count} # => 1 484: 485: # start tests with yath 486: my $yath_return_code = $liveman->tests->{exit_code}; 487: 488: $yath_return_code # => 0 489: -f "cover_db/coverage.html" # => 1 490: 491: # limit liveman to these files for operations transforms and tests (without cover) 492: my $liveman2 = Liveman->new(files => [], force_compile => 1); 493: 494: =head1 DESCRIPION 495: 496: The problem with modern projects is that the documentation is disconnected from testing. 497: This means that the examples in the documentation may not work, and the documentation itself may lag behind the code. 498: 499: Liveman compile C<lib/**>.md files to C<t/**.t> files 500: and it added pod-documentation to section C<__END__> to C<lib/**.pm> files. 501: 502: Use C<liveman> command for compile the documentation to the tests in catalog of your project and starts the tests: 503: 504: liveman 505: 506: Run it with coverage. 507: 508: Option C<-o> open coverage in browser (coverage file: C<cover_db/coverage.html>). 509: 510: Liveman replace C<our $VERSION = "...";> in C<lib/**.pm> from C<lib/**.md> if it exists in pm and in md. 511: 512: If exists file B<minil.toml>, then Liveman read C<name> from it, and copy file with this name and extension C<.md> to README.md. 513: 514: =head2 TYPES OF TESTS 515: 516: Section codes C<noname> or C<perl> writes as code to C<t/**.t>-file. And comment with arrow translates on test from module C<Test::More>. 517: 518: The test name set as the code-line. 519: 520: =head3 C<is> 521: 522: Compare two expressions for equivalence: 523: 524: "hi!" # -> "hi" . "!" 525: "hi!" # → "hi" . "!" 526: 527: =head3 C<is_deeply> 528: 529: Compare two expressions for structures: 530: 531: "hi!" # --> "hi" . "!" 532: "hi!" # ⟶ "hi" . "!" 533: 534: =head3 C<is> with extrapolate-string 535: 536: Compare expression with extrapolate-string: 537: 538: my $exclamation = "!"; 539: "hi!2" # => hi${exclamation}2 540: "hi!2" # ⇒ hi${exclamation}2 541: 542: =head3 C<is> with nonextrapolate-string 543: 544: Compare expression with nonextrapolate-string: 545: 546: 'hi${exclamation}3' # \> hi${exclamation}3 547: 'hi${exclamation}3' # ↦ hi${exclamation}3 548: 549: =head3 C<like> 550: 551: It check a regular expression included in the expression: 552: 553: 'abbc' # ~> b+ 554: 'abc' # ↬ b+ 555: 556: =head3 C<unlike> 557: 558: It check a regular expression excluded in the expression: 559: 560: 'ac' # <~ b+ 561: 'ac' # ↫ b+ 562: 563: =head2 EMBEDDING FILES 564: 565: Each test is executed in a temporary directory, which is erased and created when the test is run. 566: 567: This directory format is /tmp/.liveman/I<project>/I<path-to-test>/. 568: 569: Code section in md-file prefixed line B<< File C<path>: >> write to file in rintime testing. 570: 571: Code section in md-file prefixed line B<< File C<path> is: >> will be compared with the file by the method C<Test::More::is>. 572: 573: File experiment/test.txt: 574: 575: hi! 576: 577: File experiment/test.txt is: 578: 579: hi! 580: 581: B<Attention!> An empty string between the prefix and the code is not allowed! 582: 583: Prefixes maybe on russan: C<Файл path:> and C<Файл path является:>. 584: 585: =head1 METHODS 586: 587: =head2 new (%param) 588: 589: Constructor. Has arguments: 590: 591: =over 592: 593: =item 1. C<files> (array_ref) — list of md-files for methods C<transforms> and C<tests>. 594: 595: =item 2. C<open> (boolean) — open coverage in browser. If is B<opera> browser — open in it. Else — open via C<xdg-open>. 596: 597: =item 3. C<force_compile> (boolean) — do not check the md-files modification time. 598: 599: =item 4. C<options> — add options in command line to yath or prove. 600: 601: =item 5. C<prove> — use prove, but use'nt yath. 602: 603: =back 604: 605: =head2 test_path ($md_path) 606: 607: Get the path to the C<t/**.t>-file from the path to the C<lib/**.md>-file: 608: 609: Liveman->new->test_path("lib/PathFix/RestFix.md") # => t/path-fix/rest-fix.t 610: 611: =head2 transform ($md_path, [$test_path]) 612: 613: Compile C<lib/**.md>-file to C<t/**.t>-file. 614: 615: And method C<transform> replace the B<pod>-documentation in section C<__END__> in C<lib/**.pm>-file. And create C<lib/**.pm>-file if it not exists. 616: 617: File lib/Example.pm is: 618: 619: package Example; 620: 621: 1; 622: 623: __END__ 624: 625: =encoding utf-8 626: 627: Twice two: 628: 629: 2*2 # -> 2+2 630: 631: 632: File C<lib/Example.pm> was created from file C<lib/Example.md> described in section C<SINOPSIS> in this document. 633: 634: =head2 transforms () 635: 636: Compile C<lib/**.md>-files to C<t/**.t>-files. 637: 638: All if C<< $self-E<gt>{files} >> is empty, or C<< $self-E<gt>{files} >>. 639: 640: =head2 tests () 641: 642: Tests C<t/**.t>-files. 643: 644: All if C<< $self-E<gt>{files} >> is empty, or C<< $self-E<gt>{files} >> only. 645: 646: =head2 mkmd ($md) 647: 648: It make md-file. 649: 650: =head2 appends () 651: 652: Append to C<lib/**.md> from C<lib/**.pm> subroutines and features. 653: 654: =head2 append ($path) 655: 656: Append subroutines and features from the module with C<$path> into its documentation in the its sections. 657: 658: File lib/Alt/The/Plan.pm: 659: 660: package Alt::The::Plan; 661: 662: sub planner { 663: my ($self) = @_; 664: } 665: 666: # This is first! 667: sub miting { 668: my ($self, $meet, $man, $woman) = @_; 669: } 670: 671: sub _exquise_me { 672: my ($self, $meet, $man, $woman) = @_; 673: } 674: 675: 1; 676: 677: 678: 679: -e "lib/Alt/The/Plan.md" # -> undef 680: 681: # Set the mocks: 682: *Liveman::_git_user_name = sub {'Yaroslav O. Kosmina'}; 683: *Liveman::_git_user_email = sub {'dart@cpan.org'}; 684: *Liveman::_year = sub {2023}; 685: *Liveman::_license = sub {"Perl5"}; 686: *Liveman::_land = sub {"Rusland"}; 687: 688: my $liveman = Liveman->new->append("lib/Alt/The/Plan.pm"); 689: $liveman->{count} # -> 1 690: $liveman->{added} # -> 2 691: 692: -e "lib/Alt/The/Plan.md" # -> 1 693: 694: # And again: 695: $liveman = Liveman->new->append("lib/Alt/The/Plan.pm"); 696: $liveman->{count} # -> 1 697: $liveman->{added} # -> 0 698: 699: File lib/Alt/The/Plan.md is: 700: 701: # NAME 702: 703: Alt::The::Plan - 704: 705: # SYNOPSIS 706: 707: \```perl 708: use Alt::The::Plan; 709: 710: my $alt_the_plan = Alt::The::Plan->new; 711: \``` 712: 713: # DESCRIPION 714: 715: . 716: 717: # SUBROUTINES 718: 719: ## miting ($meet, $man, $woman) 720: 721: This is first! 722: 723: \```perl 724: my $alt_the_plan = Alt::The::Plan->new; 725: $alt_the_plan->miting($meet, $man, $woman) # -> .3 726: \``` 727: 728: ## planner () 729: 730: . 731: 732: \```perl 733: my $alt_the_plan = Alt::The::Plan->new; 734: $alt_the_plan->planner # -> .3 735: \``` 736: 737: # INSTALL 738: 739: For install this module in your system run next [command](https://metacpan.org/pod/App::cpm): 740: 741: \```sh 742: sudo cpm install -gvv Alt::The::Plan 743: \``` 744: 745: # AUTHOR 746: 747: Yaroslav O. Kosmina [dart@cpan.org](mailto:dart@cpan.org) 748: 749: # LICENSE 750: 751: ⚖ **Perl5** 752: 753: # COPYRIGHT 754: 755: The Alt::The::Plan module is copyright © 2023 Yaroslav O. Kosmina. Rusland. All rights reserved. 756: 757: =head1 INSTALL 758: 759: Add to B<cpanfile> in your project: 760: 761: on 'test' => sub { 762: requires 'Liveman', 763: git => 'https://github.com/darviarush/perl-liveman.git', 764: ref => 'master', 765: ; 766: }; 767: 768: And run command: 769: 770: $ sudo cpm install -gvv 771: 772: =head1 AUTHOR 773: 774: Yaroslav O. Kosmina LL<mailto:dart@cpan.org> 775: 776: =head1 LICENSE 777: 778: ⚖ B<GPLv3> 779: 780: =head1 COPYRIGHT 781: 782: The Alt::The::Plan module is copyright © 2023 Yaroslav O. Kosmina. Rusland. All rights reserved. 783: |