Slide 1

Slide 1 text

Climbing a Tree: Refactoring FindBin::libs for Raku Steven Lembark Workhorse Computing [email protected]

Slide 2

Slide 2 text

FindBin::libs is a programatic “-I” FindBin Scan up the tree. Use non-root ./lib dirs.

Slide 3

Slide 3 text

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.

Slide 4

Slide 4 text

Describing Raku logic Not Perl. Coding & testing utilities. Command line tools. zef Installer. Releasing with zef.

Slide 5

Slide 5 text

First Step: FindBin You are here ---> * Interactive vs. program. Absolute path. As-is vs. resolved. Native filesystem. &Bin &Script

Slide 6

Slide 6 text

Thinking Raku FindBin used to export $Bin along with subs. Now exports Bin & Script subs only.

Slide 7

Slide 7 text

“unit” Raku uses “compilation units”. This unit is a single module. use v6.d; unit module FindBin:ver<0.4.0>:auth;

Slide 8

Slide 8 text

“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;

Slide 9

Slide 9 text

Constant’s don’t change Neither does your interactive status.

Slide 10

Slide 10 text

Constant’s don’t change Neither does your interactive status: constant IS INTERACTIVE ‑ = $*PROGRAM NAME eq '-e'| '-' | 'interactive' ‑ ;

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Module args ‘:’ prefix is part of the language: constant _FindBin_RESOLVE-DEF is export( :resolve ) = True; constant _FindBin_VERBOSE-DEF is export( :verbose ) = True;

Slide 14

Slide 14 text

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 );

Slide 15

Slide 15 text

Pseudo-Packages CALLER:: LEXCAL:: CALLER::LEXICAL::YourNameHere

Slide 16

Slide 16 text

Signature An object. Defines input, output. sub Bin ( Bool() :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF, Bool() :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF --> IO )

Slide 17

Slide 17 text

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 )

Slide 18

Slide 18 text

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 )

Slide 19

Slide 19 text

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 )

Slide 20

Slide 20 text

Using a constant No sigil. “?? !!” Ternary. my $bin_from = IS-INTERACTIVE ?? $*CWD !! $*PROGRAM.IO ;

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Using a constant $*PROGRAM is Str. .IO calls constructor: Converts the string to an IO object. my $bin_from = IS-INTERACTIVE ?? $*CWD !! $*PROGRAM.IO ;

Slide 23

Slide 23 text

Using an IO .resolve --> IO .absolute --> Str. my $path = $resolve ?? $bin_from.resolve !! $bin_from.absolute.IO ;

Slide 24

Slide 24 text

Returning a directory dirname works on *NIX, fails on MS, VMS. Parent includes the volume. IS-INTERACTIVE ?? $path !! $path.parent

Slide 25

Slide 25 text

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 }

Slide 26

Slide 26 text

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 }

Slide 27

Slide 27 text

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 }

Slide 28

Slide 28 text

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 }

Slide 29

Slide 29 text

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.

Slide 30

Slide 30 text

How to test it. Test ok use_ok require_ok is

Slide 31

Slide 31 text

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;

Slide 32

Slide 32 text

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’;

Slide 33

Slide 33 text

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; };

Slide 34

Slide 34 text

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';

Slide 35

Slide 35 text

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;

Slide 36

Slide 36 text

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;

Slide 37

Slide 37 text

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;

Slide 38

Slide 38 text

Releasing a module “mi6 release;” Changes entry META6.json Content pushed. Assign a version tag. Upload to CPAN.

Slide 39

Slide 39 text

Getting mi6 $ zef install App::Mi6 ===> Searching for: App::Mi6 ===> Testing: App::Mi6:ver<0.2.6>:auth ===> Testing [OK] for App::Mi6:ver<0.2.6>:auth ===> Installing: App::Mi6:ver<0.2.6>:auth 1 bin/ script [mi6] installed to: /opt/raku/share/raku/site/bin Note the install dir. Symlinks are your friends...

Slide 40

Slide 40 text

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 ...

Slide 41

Slide 41 text

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" }

Slide 42

Slide 42 text

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;

Slide 43

Slide 43 text

Releasing a module $ mi6 release ==> Release distribution to CPAN There are 12 steps: # Rakuholics Anonymous??? ==> 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 ==> Step 8. UploadToCPAN Are you sure you want to upload FindBin-0.4.0.tar.gz to CPAN? (y/N) y

Slide 44

Slide 44 text

Releasing a module Housekeeping includes version tag. ==> Step 9. RewriteChanges ==> Step10. GitCommit [master 6b2a3e2] 0.4.0 4 files changed, 5 insertions(+), 3 deletions(-) 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

Slide 45

Slide 45 text

Instant gratification zef can install from a directory. $ zef install . ; ===> Testing: FindBin:ver<0.4.0>:auth # Madness: 'FindBin' (01-) # FindBin # Bin is: /sandbox/lembark/Modules/Raku/FindBin/t ===> Testing [OK] for FindBin:ver<0.4.0>:auth ===> Installing: FindBin:ver<0.4.0>:auth

Slide 46

Slide 46 text

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.

Slide 47

Slide 47 text

FileSystem::Parent Exports “scan-up”. Stringy standard filters. Roll your own: block, regex...

Slide 48

Slide 48 text

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 }

Slide 49

Slide 49 text

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 }

Slide 50

Slide 50 text

*%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 }

Slide 51

Slide 51 text

|%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 }

Slide 52

Slide 52 text

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 }

Slide 53

Slide 53 text

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.

Slide 54

Slide 54 text

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.

Slide 55

Slide 55 text

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.

Slide 56

Slide 56 text

Pick a filter, Any filter... Perl5 used a code block, passed as a closure. $filter->( $path ) or next...

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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.

Slide 59

Slide 59 text

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 ;

Slide 60

Slide 60 text

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 ;

Slide 61

Slide 61 text

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... }

Slide 62

Slide 62 text

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... }

Slide 63

Slide 63 text

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... }

Slide 64

Slide 64 text

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; }

Slide 65

Slide 65 text

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; }

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Q: What’s in a Seq from gather?

Slide 68

Slide 68 text

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)

Slide 69

Slide 69 text

A: Lazyness ( IO::Path.new ( "/sandbox/lembark/Modules/Raku/FileSystem-Parent/t/lib" , :SPEC(IO::Spec::Unix) , :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent") ), ... ).Seq

Slide 70

Slide 70 text

A: Lazyness ( IO::Path.new ( "/sandbox/lembark/Modules/Raku/FileSystem-Parent/t/lib" , :SPEC(IO::Spec::Unix) , :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent") ), ... ).Seq Untyped until the value is extracted.

Slide 71

Slide 71 text

A: Lazyness ( IO::Path.new ( "/sandbox/lembark/Modules/Raku/FileSystem-Parent/t/lib" , :SPEC(IO::Spec::Unix) , :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent") ), ... ).Seq It may fail: Filesystem is [usually] stable.

Slide 72

Slide 72 text

A: Lazyness ( IO::Path.new ( "/sandbox/lembark/Modules/Raku/FileSystem-Parent/t/lib" , :SPEC(IO::Spec::Unix) , :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent") ), ... ).Seq It may fail: Database or network queries may not be.

Slide 73

Slide 73 text

( IO::Path.new ( "/sandbox/lembark/Modules/Raku/FileSystem-Parent/t/lib" , :SPEC(IO::Spec::Unix) , :CWD("/sandbox/lembark/Modules/Raku/FileSystem-Parent") ), ... ).Seq Caveat: hoc comedit seriem! A: Lazyness

Slide 74

Slide 74 text

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.’;

Slide 75

Slide 75 text

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';

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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’;

Slide 78

Slide 78 text

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.

Slide 79

Slide 79 text

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.";

Slide 80

Slide 80 text

pragmata Finally! Given libs, use them.

Slide 81

Slide 81 text

pragmata Finally! Given libs, use them. Runs at compile time. Calling a sub is too late.

Slide 82

Slide 82 text

“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 # ain’t nobody here but us chickens

Slide 83

Slide 83 text

“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 # ain’t nobody here at all

Slide 84

Slide 84 text

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;

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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 ); } }

Slide 88

Slide 88 text

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 ); } }

Slide 89

Slide 89 text

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 ); } }

Slide 90

Slide 90 text

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 ); } }

Slide 91

Slide 91 text

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 ) ) }

Slide 92

Slide 92 text

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 }

Slide 93

Slide 93 text

Named Parameters & Variables Egads! Using “:$prefix” ties your code to an API!

Slide 94

Slide 94 text

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.

Slide 95

Slide 95 text

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.

Slide 96

Slide 96 text

--> Map sub EXPORT ( *@args --> Map ) { # return a Map %( '@FindBin-libs-dirs' => @found ) } Map of ( Name => Value )

Slide 97

Slide 97 text

--> Map Map of ( Name => Value ) ‘@’ is part of the name. sub EXPORT ( *@args --> Map ) { # return a Map %( '@FindBin-libs-dirs' => @found ) }

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

“unit” is a placeholder. Export does all the work. unit module FindBin::libs has no subs.

Slide 100

Slide 100 text

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;

Slide 101

Slide 101 text

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; constant FrobPath is export( :DEFAULT ) = 't/lib/FindBin/Frobnicate.pm6'; constant FrobPath is export( :DEFAULT ) = 'lib/FindBin/Frobnicate.pm6';

Slide 102

Slide 102 text

Pass2: Validate directory order. Test in ./t should prefer t/lib. use FindBin::libs; use FindBin::Frobnicate; ok MY::, 'FrobPath exported.'; my $expect = 't/lib/FindBin/Frobnicate.pm6'; my $found := MY::FrobPath; is $found, $expect, "Found: ‘$found’”;

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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.

Slide 105

Slide 105 text

References https://gitlab.com/lembark https://github.com/skaji/mi6 https://www.tyil.nl/post/2018/03/20/Raku-introduction-to- application-programming/

Slide 106

Slide 106 text

https://docs.Raku.org/ routine/| type/Slip type/Signature#Coercion_type type/Stringy language/variables#index-entry-Twigil language/variables#The_*_twigil language/testing language/modules#EXPORT syntax/state#The_$_variable