Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Climbing a Tree: Refactoring FindBin::libs for Raku

Climbing a Tree: Refactoring FindBin::libs for Raku

FindBin::libs simplifies module management by automatically including 'lib' dirs using the runtime executable path. This allows, for example, files running from ./t to prefer t/lib modules over normal ones and avoid connecting to production databases for destructive testing. This also simplifies work in sandboxes by using the locally available, in-work, modules automatically.

This talk describes how the Raku version of the module was created from Perl5, including the Raku syntax, module building and management with mi6, and how the modules that make up the new FindBin::libs interact to provide the interface.

Steven Lembark

July 09, 2022
Tweet

More Decks by Steven Lembark

Other Decks in Technology

Transcript

  1. Why? Test or execute project without installing. In-house modules without

    CPAN or zef. Tests use ./t/lib or ./t/etc Avoid destructive tests in production. Resolve path. Use modules relative to ‘real’ path.
  2. Describing Raku logic Not Perl. Coding & testing utilities. Command

    line tools. zef Installer. Releasing with zef.
  3. First Step: FindBin You are here ---> * Interactive vs.

    program. Absolute path. As-is vs. resolved. Native filesystem. &Bin &Script
  4. “unit” Raku uses “compilation units”. This unit is a single

    module. use v6.d; unit module FindBin:ver<0.4.0>:auth<CPAN:lembark>;
  5. “unit” Raku uses “compilation units”. Raku supports multiple versions &

    authors. The version & author are part of the definition. use v6.d; unit module FindBin:ver<0.4.0>:auth<CPAN:lembark>;
  6. Constant’s don’t change Neither does your interactive status: constant IS

    INTERACTIVE ‑ = $*PROGRAM NAME eq '-e'| '-' | 'interactive' ‑ ;
  7. Twigil `tween a sigil and a name. ‘*’ is for

    dynamic. AUTOGENERATED constant IS INTERACTIVE ‑ = $*PROGRAM NAME eq '-e'| '-' | 'interactive' ‑ ;
  8. Module args ‘:’ prefix is part of the language: use

    FindBin; # :DEFAULT use FindBin :resolve; use FindBin :verbose; use FindBin :Bin; use FindBin :Script :resolve;
  9. Module args ‘:’ prefix is part of the language: constant

    _FindBin_RESOLVE-DEF is export( :resolve ) = True; constant _FindBin_VERBOSE-DEF is export( :verbose ) = True;
  10. Slip |( ) converts a list into a Slip. No

    context: Raku lists don't flatten automatically. Slips do. Good examples at doc.raku.org (see references). constant OPTION-TAGS = |( :resolve , :verbose );
  11. Signature An object. Defines input, output. sub Bin ( Bool()

    :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF, Bool() :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF --> IO )
  12. Signature :$resolve stores “:resolve” or “:!resolve” argument. Defaults to caller’s

    lexical. sub Bin ( Bool() :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF, Bool() :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF --> IO )
  13. Signature Bool() coerces the values to type Bool. Avoids undef.

    sub Bin ( Bool() :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF, Bool() :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF --> IO )
  14. export Explicit “:Bin”, no args. Either of “:verbose” or “:resolve”

    via flattening. sub Bin ( Bool() :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF, Bool() :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF --> IO ) is export( :Bin, :DEFAULT, OPTION-TAGS )
  15. Using a constant No sigil. “?? !!” Ternary. my $bin_from

    = IS-INTERACTIVE ?? $*CWD !! $*PROGRAM.IO ;
  16. Using a constant $*CWD is an IO. my $bin_from =

    IS-INTERACTIVE ?? $*CWD !! $*PROGRAM.IO ;
  17. Using a constant $*PROGRAM is Str. .IO calls constructor: Converts

    the string to an IO object. my $bin_from = IS-INTERACTIVE ?? $*CWD !! $*PROGRAM.IO ;
  18. Using an IO .resolve --> IO .absolute --> Str. my

    $path = $resolve ?? $bin_from.resolve !! $bin_from.absolute.IO ;
  19. Returning a directory dirname works on *NIX, fails on MS,

    VMS. Parent includes the volume. IS-INTERACTIVE ?? $path !! $path.parent
  20. Bin Viola! sub Bin ( Bool() :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF, Bool()

    :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF --> IO ) is export( :Bin, :DEFAULT, OPTION-TAGS ) { my $bin_from = IS-INTERACTIVE ?? $*CWD !! $*PROGRAM.IO ; my $path = $resolve ?? $bin_from.resolve !! $bin_from.absolute.IO ; if $verbose { ... } IS-INTERACTIVE ?? $path !! $path.parent }
  21. Viola! Script note is “say” to stderr. sub Script (

    Bool() :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF, Bool() :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF --> Str ) is export( :Script, :DEFAULT, OPTION-TAGS ) { if $verbose { note '# Script()'; note "# Interactive: '{IS-INTERACTIVE}'"; note "# Resolve: $resolve"; note "# Path: '$*PROGRAM-NAME'"; } IS-INTERACTIVE ?? $*PROGRAM-NAME !! $resolve ?? $*PROGRAM.resolve.basename !! $*PROGRAM.basename }
  22. Oops... Bug in v6.d: Signature cannot handle Caller::Lexical sub Script

    ( Bool() :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF, Bool() :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF --> Str ) is export( :Script, :DEFAULT, OPTION-TAGS ) { if $verbose { note '# Script()'; note "# Interactive: '{IS-INTERACTIVE}'"; note "# Resolve: $resolve"; note "# Path: '$*PROGRAM-NAME'"; } IS-INTERACTIVE ?? $*PROGRAM-NAME !! $resolve ?? $*PROGRAM.resolve.basename !! $*PROGRAM.basename }
  23. Viola! Bug in v6.d: Signature cannot handle Caller::Lexical Fix: Assign

    defaults. our sub Script ( Bool() :$resolve is copy, Bool() :$verbose is copy --> Str ) is export( :Script, :DEFAULT, OPTION-TAGS ) { $resolve //= ? CALLER::LEXICAL::_FindBin_RESOLVE-DEF; $verbose //= ? CALLER::LEXICAL::_FindBin_VERBOSE-DEF; if $verbose { note '# Script()'; note "# Interactive: '{IS-INTERACTIVE}'"; note "# Resolve: $resolve"; note "# Path: '$*PROGRAM-NAME'"; } IS-INTERACTIVE ?? $*PROGRAM-NAME !! $resolve ?? $*PROGRAM.resolve.basename !! $*PROGRAM.basename }
  24. Viola! our sub Script ( Bool() :$resolve is copy, Bool()

    :$verbose is copy --> Str ) is export( :Script, :DEFAULT, OPTION-TAGS ) { $resolve //= ? CALLER::LEXICAL::_FindBin_RESOLVE-DEF; $verbose //= ? CALLER::LEXICAL::_FindBin_VERBOSE-DEF; if $verbose { note '# Script()'; note "# Interactive: '{IS-INTERACTIVE}'"; note "# Resolve: $resolve"; note "# Path: '$*PROGRAM-NAME'"; } IS-INTERACTIVE ?? $*PROGRAM-NAME !! $resolve ?? $*PROGRAM.resolve.basename !! $*PROGRAM.basename } ‘?’ coerces value to Bool. Avoids undef.
  25. Generic sanity test use v6.d; use Test; use lib $*PROGRAM.IO.parent(2).add(

    'lib' ); ( $*PROGRAM-NAME.IO.basename ~~ m{^ (\d+ '-')? (.+) '.'t $} ) or bail-out 'Unable to parse test name: ' ~ $*PROGRAM-NAME; my $madness = $1.subst( '-', '::', :g ); use-ok $madness; my $found = ::( $madness ).^name; is $found, $madness, "Package '$found' ($madness)."; done-testing;
  26. Checking exports Pseudo-class MY:: with hardwired name. use v6.d; use

    Test; ok ! MY::<&Bin> , 'Bin not installed before use.'; ok ! MY::<&Script> , 'Script not installed before use.'; ok ! MY::<_FindBin_RESOLVE-DEF>, '! _FindBin_RESOLVE-DEF’; ok ! MY::<_FindBin_VERBOSE-DEF>, '! _FindBin_VERBOSE-DEF’;
  27. Checking exports do { use FindBin; ok MY::<&Bin> , 'Bin

    installed by default.'; ok MY::<&Script> , 'Script installed by default.'; ok ! MY::<_FindBin_RESOLVE-DEF>, '! _FindBin_RESOLVE-DEF'; ok ! MY::<_FindBin_VERBOSE-DEF>, '! _FindBin_VERBOSE-DEF'; note '# Bin is: ' ~ Bin; note '# Script is: ' ~ Script; };
  28. Checking exports do { use FindBin; ... }; ok !

    MY::<&Bin> , 'Bin is lexicaly scoped.'; ok ! MY::<&Script> , 'Script is lexicaly scoped.'; ok ! MY::<_FindBin_RESOLVE-DEF>, '! _FindBin_RESOLVE-DEF'; ok ! MY::<_FindBin_VERBOSE-DEF>, '! _FindBin_RESOLVE-DEF';
  29. Script test program is easy use v6.d; use Test; use

    lib $*PROGRAM.IO.parent(2).add( 'lib' ); use FindBin; my $found = Script; my $expect = $*PROGRAM.basename; ok $found, 'Script returns ' ~ $found; ok $expect, '$*PROGRAM.basename is ' ~ $expect; is $found, $expect, 'Script matches basename.'; done-testing;
  30. Testing -e takes more syntax use v6.d; use Test; my

    $expect = '-e'; my ( $found ) = qx{ raku -I./lib -M'FindBin' -e 'say Script' }.chomp; is $found, $expect, "'raku -e' returns '$found' ($expect)"; done-testing;
  31. Now you got all of the tests... How do you

    run them? prove6; what you are used to mi6 test; command line assistants assixt test;
  32. Getting mi6 $ zef install App::Mi6 ===> Searching for: App::Mi6

    ===> Testing: App::Mi6:ver<0.2.6>:auth<cpan:SKAJI> ===> Testing [OK] for App::Mi6:ver<0.2.6>:auth<cpan:SKAJI> ===> Installing: App::Mi6:ver<0.2.6>:auth<cpan:SKAJI> 1 bin/ script [mi6] installed to: /opt/raku/share/raku/site/bin Note the install dir. Symlinks are your friends...
  33. Changes Revision history for FindBin {{$NEXT}} - Return IO for

    simplicity 0.3.4 2019-05-25T23:02:06-05:00 - use generic 01 test for use/require. - POD, whitespace, comments. 0.3.3 2019-05-20T11:42:45-05:00 ...
  34. META6.json { "auth" : "CPAN:lembark", "authors" : [ "lembark" ],

    "build-depends" : [ ], "depends" : [ ], "description" : "Locate execution directory and basename.", "license" : "Artistic-2.0", "name" : "FindBin", "perl" : "6.d", "provides" : { "FindBin" : "lib/FindBin.pm6" }, "resources" : [ ], "source-url" : "https://gitlab.com/lembark/raku-FindBin.git", "tags" : [ ], "test-depends" : [ ], "version" : "0.3.4" }
  35. POD is still largely POD =begin pod =head1 SYNOPSIS #

    export Bin() and Script() by default. use FindBin; my $dir_from = Bin(); # $dir_from is an IO object my $script_base = Script(); # $script_base is a string. # explicitly export only Bin() or Script(). use FindBin :Bin;
  36. Releasing a module $ mi6 release ==> Release distribution to

    CPAN There are 12 steps: # Rakuholics Anonymous??? <snip> ==> Step 1. CheckChanges ==> Step 2. CheckOrigin ==> Step 3. CheckUntrackedFiles ==> Step 4. BumpVersion Next release version? [0.3.5]: 0.4.0 ==> Step 5. RegenerateFiles ==> Step 6. DistTest <snip> ==> Step 8. UploadToCPAN Are you sure you want to upload FindBin-0.4.0.tar.gz to CPAN? (y/N) y
  37. Releasing a module Housekeeping includes version tag. ==> Step 9.

    RewriteChanges ==> Step10. GitCommit [master 6b2a3e2] 0.4.0 4 files changed, 5 insertions(+), 3 deletions(-) <snip To gitlab.com:lembark/Raku-FindBin.git 607929e..6b2a3e2 master -> master ==> Step11. CreateGitTag Total 0 (delta 0), reused 0 (delta 0) To gitlab.com:lembark/Raku-FindBin.git * [new tag] 0.4.0 -> 0.4.0 ==> Step12. CleanDist
  38. Instant gratification zef can install from a directory. $ zef

    install . ; ===> Testing: FindBin:ver<0.4.0>:auth<CPAN:lembark> # Madness: 'FindBin' (01-) # FindBin # Bin is: /sandbox/lembark/Modules/Raku/FindBin/t <snip> ===> Testing [OK] for FindBin:ver<0.4.0>:auth<CPAN:lembark> ===> Installing: FindBin:ver<0.4.0>:auth<CPAN:lembark>
  39. Bin is the root of all evil... Next Step: Root

    out the rest of it. Originally part of FindBin::libs: Scan up directory tree. Append relative paths. Filter them.
  40. Even more than more than one way to do it

    multi scan-up ( Stringy :$filter = 'all', *%argz --> Seq ) { state %filterz = ( all => True , dir => :d , file => :f , exist => :e , ); my $value = %filterz{ $filter } or fail "Unknown: '$filter'. not "~ %filterz.keys.join(' '); samewith filter => $value, |%argz }
  41. Single named arg “:filter” multi scan-up ( Stringy :$filter =

    'all', *%argz --> Seq ) { state %filterz = ( all => True , dir => :d , file => :f , exist => :e , ); my $value = %filterz{ $filter } or fail "Unknown: '$filter'. not "~ %filterz.keys.join(' '); samewith filter => $value, |%argz }
  42. *%argz slurps remaining args multi scan-up ( Stringy :$filter =

    'all', *%argz --> Seq ) { stage %filterz = ( all => True , dir => :d , file => :f , exist => :e , ); my $value = %filterz{ $filter } or fail "Unknown: '$filter'. not "~ %filterz.keys.join(' '); samewith filter => $value, |%argz }
  43. |%argz flattens them back out multi scan-up ( Stringy :$filter

    = 'all', *%argz --> Seq ) { state %filterz = ( all => True , dir => :d , file => :f , exist => :e , ); my $value = %filterz{ $filter } or fail "Unknown: '$filter'. not "~ %filterz.keys.join(' '); samewith filter => $value, |%argz }
  44. same name, new filter multi scan-up ( Stringy :$filter =

    'all', *%argz --> Seq ) { state %filterz = ( all => True , dir => :d , file => :f , exist => :e , ); my $value = %filterz{ $filter } or fail "Unknown: '$filter'. not "~ %filterz.keys.join(' '); samewith filter => $value, |%argz }
  45. The other way to do it multi scan-up ( Any

    :$filter, Str :$append = '', Bool() :$resolve = False, Bool() :$verbose = False, Bool() :$skip-root = False, IO() :$from = Bin( :$resolve, :$verbose ) --> Seq ) is export( :DEFAULT ) $filter here is anything.
  46. Args are defined in the parameters. multi scan-up ( Any

    :$filter, Str :$append = '', Bool() :$resolve = False, Bool() :$verbose = False, Bool() :$skip-root = False, IO() :$from = Bin( :$resolve, :$verbose ) --> Seq ) is export( :DEFAULT ) Re-cycled from Stringy multi via %args.
  47. Telling them apart multi Foo ( Stringy $filter = ‘all’,

    … ) multi Foo ( Any $filter, … ) Foo() Pick default no viable alternative. Foo(‘X’) Pick Stringy derived from Any. Foo( blah ) Pick Any.
  48. Pick a filter, Any filter... Perl5 used a code block,

    passed as a closure. $filter->( $path ) or next...
  49. Pick a filter, Any filter... Smart matches are better way.

    Pass in a file test, regex, block... $path ~~ $filter or next;
  50. Pick a filter, Any filter... Smart matches are better way.

    Pass in a file test, regex, block... $path ~~ $filter or next; Anything but a string. Which was used up in the other multi.
  51. Finding your path absolute, resolve, parent should be familiar by

    now... my $start = ( $resolve ?? $from.resolve( completely => True ) !! $from ).absolute.IO; my $path = $start ~~ :d ?? $start !! $start.parent ;
  52. Finding your path Fail if the path is not resolvable.

    my $start = ( $resolve ?? $from.resolve( completely => True ) !! $from ).absolute.IO; my $path = $start ~~ :d ?? $start !! $start.parent ;
  53. Gathering for a loop return gather loop { … take

    … if … ; … last if … ; } gather returns a Seq from take. loop runs until a break. Replaces for(;;){ ... push @result... }
  54. Gathering for a loop return gather loop { … take

    … if … ; … last if … ; } gather returns a Seq from take. loop runs until a break. Replaces for(;;){ ... push @result... }
  55. Gathering for a loop return gather loop { … take

    … if … ; … last if … ; } gather returns a Seq from take. loop runs until a break. Replaces for(;;){ ... push @result... }
  56. Find your roots return gather loop { my $next =

    $path.parent; my $at-root = $path ~~ $next; ... $path ~~ $filter and take $path; ... last if $at-root; $path = $next; }
  57. What to take if( $skip-root && $at-root ) { say

    “Root: ‘$path’ (skip)” if $verbose; } else { my $take = $append ?? $path.add( $append ) # add a relative path. !! $path ; $take ~~ $filter # $filter controls the match. and take $take; }
  58. return gather loop { my $next = $path.parent; my $at-root

    = $path ~~ $next; if $at-root && $skip-root { note ‘Skip root’ if $verbose; } else { $take = $append ?? ... ; $take ~~ $filter and take $take; } $at-root and last; $path = $next; } Skip root before append. Supply path to $filter. Exit at root. Working logic
  59. Q: What’s in a Seq from gather? $((IO::Path.new("/sandbox/lembark/Modules/Raku/FileSystem-Parent/ t/lib", :SPEC(IO::Spec::Unix),

    :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent")), IO::Path.new("/sandbox/lembark/Modules/Raku/FileSystem-Parent/lib" , :SPEC(IO::Spec::Unix), :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent")), IO::Path.new("/sandbox/lembark/Modules/Raku/lib", :SPEC(IO::Spec:: Unix), :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent")), IO::Path.new("/sandbox/lembark/Modules/lib", :SPEC(IO::Spec::Unix) , :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent")), IO::Path.new("/sandbox/lembark/lib", :SPEC(IO::Spec::Unix), :CWD(" /sandbox/lembark/Modules/Raku/FileSystem-Parent")), IO::Path.new("/sandbox/lib", :SPEC(IO::Spec::Unix), :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent")), IO::Path.new("/lib", :SPEC(IO::Spec::Unix), :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent"))).Seq)
  60. Testing lazyness One test: Compare it to an array. my

    @found = scan-up.eager; for ( 0 ..^ +@found ) -> $i { … } Another is smart-match it with a seq: my $found = scan-up; ok $found ~~ $expect, ‘Found it.’;
  61. Compare Bin dir with a file Path and Bin results

    should match. my $pass0 = scan-up; ok $pass0, "scan-up = $pass0"; my $pass1 = scan-up :from( $*PROGRAM-NAME ); ok $pass1, "scan-up :from( $*PROGRAM-NAME ) = $pass1"; ok $pass0 ~~ $pass1, 'Bin matches $*Program-Name';
  62. File and dir path work $ Raku t/03-no-subdir.rakutest ok 1

    - scan-up = /sandbox/lembark/Modules/Raku/FileSystem- Parent/t /sandbox/lembark/Modules/Raku/FileSystem-Parent /sandbox/lembark/Modules/Raku /sandbox/lembark/Modules /sandbox/lembark /sandbox / ok 2 - scan-up :from( t/03-no-subdir.t ) = /sandbox/lembark/Modules/Raku/FileSystem-Parent/t /sandbox/lembark/Modules/Raku/FileSystem-Parent /sandbox/lembark/Modules/Raku /sandbox/lembark/Modules /sandbox/lembark /sandbox / ok 3 - Bin and $*Program-Name return same list. 1..3
  63. Adding a lib :$append with variable name. my $append =

    'lib'; my $pass0 = scan-up :$append; ok $pass0, 'scan-up $append'; my $pass1 = scan-up :$append, from => $*PROGRAM-NAME; ok $pass1, 'scan-up $append :from => $*PROGRAM-NAME'; ok $pass0.eager ~~ $pass1.eager, ‘They match’;
  64. Testing :$append ok 1 - scan-up $append ok 2 -

    scan-up $append :from => $*PROGRAM-NAME # Pass0:/sandbox/lembark/Modules/Raku/FileSystem-Parent/t/lib /sandbox/lembark/Modules/Raku/FileSystem-Parent/lib /sandbox/lembark/Modules/Raku/lib /sandbox/lembark/Modules/lib /sandbox/lembark/lib /sandbox/lib /lib # Pass1:/sandbox/lembark/Modules/Raku/FileSystem-Parent/t/lib /sandbox/lembark/Modules/Raku/FileSystem-Parent/lib /sandbox/lembark/Modules/Raku/lib /sandbox/lembark/Modules/lib /sandbox/lembark/lib /sandbox/lib /lib ok 3 - Bin and $*Program-Name return same list.
  65. Test verbose overrides There is, of course, more than one

    way. Shouldn’t affect Bin’s return value. use FindBin :verbose; my $false_1 = Bin( verbose => False ).Str; my $false_2 = Bin( :verbose( False ) ).Str; my $false_3 = Bin( :!verbose ).Str; is $false_1, $false_2, "Match with false."; is $false_2, $false_3, "Match with false.";
  66. “EXPORT” does the deed Outside of the package. Handles positionals

    passed via use. use v6.d; sub EXPORT ( *@args –-> Map ) { my $resolve = ? @args.first( 'resolve' ); my $verbose = ? @args.first( 'verbose' ); # do things... } unit module FindBin::libs:ver<0.0.1>:auth<CPAN:lembark> # ain’t nobody here but us chickens
  67. “EXPORT” does the deed Outside of the package. Handles positionals

    passed via use. use v6.d; sub EXPORT ( *@args –-> Map ) { my $resolve = ? @args.first( 'resolve' ); my $verbose = ? @args.first( 'verbose' ); # do things... } unit module FindBin::libs:ver<0.0.1>:auth<CPAN:lembark> # ain’t nobody here at all
  68. Raku Colon Cleanser Ever loose track of parens in an

    expression? Raku method calls can replace $foo.some_method( $bar ); with $foo.some_method: $bar;
  69. Raku Colon Cleanser Ever loose track of parens in an

    expression? Convert: $foo.frob($bar.nicate($thing.new($blort))); into: $foo.frob: $bar.nicate: $thing.new: $blort
  70. Raku Colon Cleanser Ever loose track of parens in an

    expression? $foo.frob: $bar.nicate: $thing.new: $blort ;
  71. Find non-root ./lib dirs & add them. use FileSystem::Parent; my

    %fixed = ( append => 'lib', filter => 'dir', skip-root => True ); my @found = scan-up( :$resolve, :$verbose, |%fixed ); for @found.reverse -> $dir { for CompUnit::Repository::FileSystem.new( prefix => $dir ) -> $lib { CompUnit::RepositoryRegistry.use-repository( $lib ); } }
  72. Find non-root ./lib dirs & add them. use FileSystem::Parent; my

    %fixed = ( append => 'lib', filter => 'dir', skip-root => True ); my @found = scan-up( :$resolve, :$verbose, |%fixed ); for @found.reverse -> $dir { given CompUnit::Repository::FileSystem.new( prefix => $dir ) -> $lib { CompUnit::RepositoryRegistry.use-repository( $lib ); } }
  73. Find non-root ./lib dirs & add them. use FileSystem::Parent; my

    %fixed = ( append => 'lib', filter => 'dir', skip-root => True ); my @found = scan-up( :$resolve, :$verbose, |%fixed ); for @found.reverse -> $dir { given CompUnit::Repository::FileSystem.new( prefix => $dir ) -> $lib { CompUnit::RepositoryRegistry.use-repository( $lib ); } }
  74. Find non-root ./lib dirs & add them. use FileSystem::Parent; my

    %fixed = ( append => 'lib', filter => 'dir', skip-root => True ); my @found = scan-up( :$resolve, :$verbose, |%fixed ); for @found.reverse -> $prefix { given CompUnit::Repository::FileSystem.new( :$prefix ) -> $lib { CompUnit::RepositoryRegistry.use-repository( $lib ); } }
  75. Find non-root ./lib dirs & add them. use FileSystem::Parent; my

    %fixed = ( append => 'lib', filter => 'dir', skip-root => True ); my @found = scan-up( :$resolve, :$verbose, |%fixed ); for @found.reverse -> $prefix { CompUnit::RepositoryRegistry.use-repository( CompUnit::Repository::FileSystem.new( :$prefix ) ) }
  76. Find non-root ./lib dirs & add them. use FileSystem::Parent; my

    %fixed = ( append => 'lib', filter => 'dir', skip-root => True ); my @found = scan-up( :$resolve, :$verbose, |%fixed ); for @found.reverse -> $prefix { CompUnit::RepositoryRegistry.use-repository: CompUnit::Repository::FileSystem.new: :$prefix }
  77. Named Parameters & Variables Egads! Using “:$prefix” ties your code

    to my API! Signatures allow arbitrary variable names. :$prefix is a shortcut for sub foo( :prefix( $prefix ) ) You can also have: sub foo ( :prefix( $dir_pref ) ) Change internal variable names.
  78. Named Parameters & Variables Egads! Using “:$prefix” ties your code

    to my API! Signatures allow arbitrary variable names. :$prefix is a shortcut for sub foo( :prefix( $prefix ) ) You can also have: sub foo ( :add( $prefix ) ) Change external parameter name.
  79. --> Map sub EXPORT ( *@args --> Map ) {

    # return a Map %( '@FindBin-libs-dirs' => @found ) } Map of ( Name => Value )
  80. --> Map Map of ( Name => Value ) ‘@’

    is part of the name. sub EXPORT ( *@args --> Map ) { # return a Map %( '@FindBin-libs-dirs' => @found ) }
  81. sub EXPORT ( *@args --> Map ) { use FileSystem::Parent;

    my $resolve = ? @_.first( 'resolve' ); my $verbose = ? @_.first( 'verbose' ); my %fixed = ( ... ); my @found = scan-up( :$verbose, :$resolve, |%fixed ); for @found.reverse -> $prefix { CompUnit::RepositoryRegistry.use-repository: CompUnit::Repository::FileSystem.new: :$prefix } %( '@FindBin-libs-dirs' => @found ) } Exporter
  82. Pass1: Check for installed dirs. EXPORT installs the variable. Flatten

    it for printing. use FindBin::libs; ok @FindBin-libs-dirs, 'Exported FindBin-libs-dirs'; note join( "\n#\t", '# libs:', |@FindBin-libs-dirs ); done-testing;
  83. Pass2: Validate directory order. Frobnicate.rakumod in ./t/lib/FindBin & ./lib/FindBin. “FrobPath”

    will be ‘t/lib/...’ or ‘lib/...’. unit module FindBin::Frobnicate:ver<0.1.1>:auth<CPAN:lembark>; constant FrobPath is export( :DEFAULT ) = 't/lib/FindBin/Frobnicate.pm6'; constant FrobPath is export( :DEFAULT ) = 'lib/FindBin/Frobnicate.pm6';
  84. Pass2: Validate directory order. Test in ./t should prefer t/lib.

    use FindBin::libs; use FindBin::Frobnicate; ok MY::<FrobPath>, 'FrobPath exported.'; my $expect = 't/lib/FindBin/Frobnicate.pm6'; my $found := MY::FrobPath; is $found, $expect, "Found: ‘$found’”;
  85. prove6 Looks familiar. $ prove6 -v t/03-use-frobnicate.t; ok 1 -

    FrobPath exported. ok 2 - Found 't/lib/FindBin/Frobnicate.pm6' 1..2 t/03-use-frobnicate.t .. ok All tests successful. Files=1, Tests=2, 0 wallclock secs Result: PASS
  86. Summary Raku is quite doable. mi6 & assixt make it

    manageable. Raku has gotten much better. Raku is quite doable. mi6 & assixt make it more manageable. The doc’s website has gotten much better: Raku Try it... you’ll like it.