Climbing a Tree:
Refactoring FindBin::libs for Raku
Steven Lembark
Workhorse Computing
lembark@wrkhors.com
FindBin::libs is a programatic “-I”
FindBin
Scan up the tree.
Use non-root ./lib dirs.
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.
Describing Raku logic
Not Perl.
Coding & testing utilities.
Command line tools.
zef Installer.
Releasing with zef.
First Step: FindBin
You are here ---> *
Interactive vs. program.
Absolute path.
As-is vs. resolved.
Native filesystem.
&Bin &Script
Thinking Raku
FindBin used to export $Bin along with subs.
Now exports Bin & Script subs only.
“unit”
Raku uses “compilation units”.
This unit is a single module.
use v6.d;
unit module FindBin:ver<0.4.0>:auth<CPAN:lembark>;
“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>;
Constant’s don’t change
Neither does your interactive status.
Constant’s don’t change
Neither does your interactive status:
constant IS‑INTERACTIVE
= $*PROGRAM‑NAME eq '-e'| '-' | 'interactive'
;
Twigil
`tween a sigil and a name.
‘*’ is for dynamic.
AUTOGENERATED
constant IS‑INTERACTIVE
= $*PROGRAM‑NAME eq '-e'| '-' | 'interactive'
;
Module args
‘:’ prefix is part of the language:
use FindBin; # :DEFAULT
use FindBin :resolve;
use FindBin :verbose;
use FindBin :Bin;
use FindBin :Script :resolve;
Module args
‘:’ prefix is part of the language:
use FindBin; # :DEFAULT
use FindBin :resolve;
use FindBin :verbose;
use FindBin :Bin;
use FindBin :Script :resolve;
constant _FindBin_RESOLVE-DEF is export( :resolve ) = True;
constant _FindBin_VERBOSE-DEF is export( :verbose ) = True;
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 );
Pseudo-Packages
CALLER::
LEXCAL::
CALLER::LEXICAL::YourNameHere
Signature
An object.
Defines input, output.
sub Bin
(
Bool() :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF,
Bool() :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF
--> IO
)
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
)
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
)
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 )
Using a constant
No sigil.
“?? !!” Ternary.
my $bin_from
= IS-INTERACTIVE
?? $*CWD
!! $*PROGRAM.IO
;
Using a constant
$*CWD is an IO. my $bin_from
= IS-INTERACTIVE
?? $*CWD
!! $*PROGRAM.IO
;
Using a constant
$*PROGRAM is Str.
.IO calls constructor:
Converts the string to
an IO object.
my $bin_from
= IS-INTERACTIVE
?? $*CWD
!! $*PROGRAM.IO
;
Using an IO
.resolve --> IO
.absolute --> Str.
my $path
= $resolve
?? $bin_from.resolve
!! $bin_from.absolute.IO
;
Returning a directory
dirname works on
*NIX, fails on
MS, VMS.
Parent includes
the volume.
IS-INTERACTIVE
?? $path
!! $path.parent
Bin
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
}
Viola!
Viola!
Script
note is
“say” to stderr.
our 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
}
Oops...
Bug in v6.d:
Signature
cannot handle
Caller::Lexical
our 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
}
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
}
Viola!
‘?’ coerces
value to Bool.
Avoids undef.
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
}
How to test it.
Test
ok
use_ok
require_ok
is
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;
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’;
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;
};
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';
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;
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;
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;
Releasing a module
“mi6 release;”
Changes entry
META6.json
Content pushed.
Assign a version tag.
Upload to CPAN.
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...
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
...
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"
}
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;
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
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
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>
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.
FileSystem::Parent
Exports “scan-up”.
Stringy standard filters.
Roll your own: block, regex...
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
}
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
}
*%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
}
|%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
}
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
}
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.
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.
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.
Pick a filter, Any filter...
Perl5 used a code block, passed as a closure.
$filter->( $path ) or next...
Pick a filter, Any filter...
Smart matches are better way.
Pass in a file test, regex, block...
$path ~~ $filter or next;
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.
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
;
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
;
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... }
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... }
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... }
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;
}
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;
}
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
Q: What’s in a Seq from gather?
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),
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
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.
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.
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.
(
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
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.’;
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';
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
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’;
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.
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.";
pragmata
Finally!
Given libs, use them.
pragmata
Finally!
Given libs, use them.
Runs at compile time.
Calling a sub is too late.
“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
“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
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;
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
Raku Colon Cleanser
Ever loose track of parens in an expression?
$foo.frob:
$bar.nicate:
$thing.new: $blort
;
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 );
}
}
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 );
}
}
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 );
}
}
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 );
}
}
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
)
)
}
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
}
Named Parameters & Variables
Egads! Using “:$prefix” ties your code to an API!
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.
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.
--> Map
sub EXPORT
(
*@args --> Map
)
{
# return a Map
%( '@FindBin-libs-dirs' => @found )
}
Map of ( Name => Value )
--> Map
Map of ( Name => Value )
‘@’ is part of the name.
sub EXPORT
(
*@args --> Map
)
{
# return a Map
%( '@FindBin-libs-dirs' => @found )
}
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
“unit” is a placeholder.
Export does all the work.
unit module FindBin::libs has no subs.
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;
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';
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’”;
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
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.
References
https://gitlab.com/lembark
https://github.com/skaji/mi6
https://www.tyil.nl/post/2018/03/20/Raku-
introduction-to-application-programming/
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

Findbin libs

  • 1.
    Climbing a Tree: RefactoringFindBin::libs for Raku Steven Lembark Workhorse Computing [email protected]
  • 2.
    FindBin::libs is aprogramatic “-I” FindBin Scan up the tree. Use non-root ./lib dirs.
  • 3.
    Why? Test or executeproject 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.
  • 4.
    Describing Raku logic NotPerl. Coding & testing utilities. Command line tools. zef Installer. Releasing with zef.
  • 5.
    First Step: FindBin Youare here ---> * Interactive vs. program. Absolute path. As-is vs. resolved. Native filesystem. &Bin &Script
  • 6.
    Thinking Raku FindBin usedto export $Bin along with subs. Now exports Bin & Script subs only.
  • 7.
    “unit” Raku uses “compilationunits”. This unit is a single module. use v6.d; unit module FindBin:ver<0.4.0>:auth<CPAN:lembark>;
  • 8.
    “unit” Raku uses “compilationunits”. 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>;
  • 9.
    Constant’s don’t change Neitherdoes your interactive status.
  • 10.
    Constant’s don’t change Neitherdoes your interactive status: constant IS‑INTERACTIVE = $*PROGRAM‑NAME eq '-e'| '-' | 'interactive' ;
  • 11.
    Twigil `tween a sigiland a name. ‘*’ is for dynamic. AUTOGENERATED constant IS‑INTERACTIVE = $*PROGRAM‑NAME eq '-e'| '-' | 'interactive' ;
  • 12.
    Module args ‘:’ prefixis part of the language: use FindBin; # :DEFAULT use FindBin :resolve; use FindBin :verbose; use FindBin :Bin; use FindBin :Script :resolve;
  • 13.
    Module args ‘:’ prefixis part of the language: use FindBin; # :DEFAULT use FindBin :resolve; use FindBin :verbose; use FindBin :Bin; use FindBin :Script :resolve; constant _FindBin_RESOLVE-DEF is export( :resolve ) = True; constant _FindBin_VERBOSE-DEF is export( :verbose ) = True;
  • 14.
    Slip |( ) convertsa 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 );
  • 15.
  • 16.
    Signature An object. Defines input,output. sub Bin ( Bool() :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF, Bool() :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF --> IO )
  • 17.
    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 )
  • 18.
    Signature Bool() coerces thevalues to type Bool. Avoids undef. sub Bin ( Bool() :$resolve = CALLER::LEXICAL::_FindBin_RESOLVE-DEF, Bool() :$verbose = CALLER::LEXICAL::_FindBin_VERBOSE-DEF --> IO )
  • 19.
    export Explicit “:Bin”, noargs. 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 )
  • 20.
    Using a constant Nosigil. “?? !!” Ternary. my $bin_from = IS-INTERACTIVE ?? $*CWD !! $*PROGRAM.IO ;
  • 21.
    Using a constant $*CWDis an IO. my $bin_from = IS-INTERACTIVE ?? $*CWD !! $*PROGRAM.IO ;
  • 22.
    Using a constant $*PROGRAMis Str. .IO calls constructor: Converts the string to an IO object. my $bin_from = IS-INTERACTIVE ?? $*CWD !! $*PROGRAM.IO ;
  • 23.
    Using an IO .resolve--> IO .absolute --> Str. my $path = $resolve ?? $bin_from.resolve !! $bin_from.absolute.IO ;
  • 24.
    Returning a directory dirnameworks on *NIX, fails on MS, VMS. Parent includes the volume. IS-INTERACTIVE ?? $path !! $path.parent
  • 25.
    Bin 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 } Viola!
  • 26.
    Viola! Script note is “say” tostderr. our 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 }
  • 27.
    Oops... Bug in v6.d: Signature cannothandle Caller::Lexical our 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 }
  • 28.
    Viola! Bug in v6.d: Signature cannothandle 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 }
  • 29.
    Viola! ‘?’ coerces value toBool. Avoids undef. 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 }
  • 30.
    How to testit. Test ok use_ok require_ok is
  • 31.
    Generic sanity test usev6.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;
  • 32.
    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’;
  • 33.
    Checking exports do { use FindBin; okMY::<&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; };
  • 34.
    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';
  • 35.
    Script test programis 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;
  • 36.
    Testing -e takesmore 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;
  • 37.
    Now you gotall of the tests... How do you run them? prove6; what you are used to mi6 test; command line assistants assixt test;
  • 38.
    Releasing a module “mi6release;” Changes entry META6.json Content pushed. Assign a version tag. Upload to CPAN.
  • 39.
    Getting mi6 $ zefinstall 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...
  • 40.
    Changes Revision history forFindBin {{$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 ...
  • 41.
    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" }
  • 42.
    POD is stilllargely 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;
  • 43.
    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
  • 44.
    Releasing a module Housekeepingincludes 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
  • 45.
    Instant gratification zef caninstall 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>
  • 46.
    Bin is theroot 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.
  • 47.
    FileSystem::Parent Exports “scan-up”. Stringy standardfilters. Roll your own: block, regex...
  • 48.
    Even more thanmore 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 }
  • 49.
    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 }
  • 50.
    *%argz slurps remainingargs 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 }
  • 51.
    |%argz flattens themback 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 }
  • 52.
    same name, newfilter 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 }
  • 53.
    The other wayto 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.
  • 54.
    Args are definedin 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.
  • 55.
    Telling them apart multiFoo ( Stringy $filter = ‘all’, … ) multi Foo ( Any $filter, … ) Foo() Pick default no viable alternative. Foo(‘X’) Pick Stringy derived from Any. Foo( blah ) Pick Any.
  • 56.
    Pick a filter,Any filter... Perl5 used a code block, passed as a closure. $filter->( $path ) or next...
  • 57.
    Pick a filter,Any filter... Smart matches are better way. Pass in a file test, regex, block... $path ~~ $filter or next;
  • 58.
    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.
  • 59.
    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 ;
  • 60.
    Finding your path Failif the path is not resolvable. my $start = ( $resolve ?? $from.resolve( completely => True ) !! $from ).absolute.IO; my $path = $start ~~ :d ?? $start !! $start.parent ;
  • 61.
    Gathering for aloop return gather loop { … take … if … ; … last if … ; } gather returns a Seq from take. loop runs until a break. Replaces for(;;){ ... push @result... }
  • 62.
    Gathering for aloop return gather loop { … take … if … ; … last if … ; } gather returns a Seq from take. loop runs until a break. Replaces for(;;){ ... push @result... }
  • 63.
    Gathering for aloop return gather loop { … take … if … ; … last if … ; } gather returns a Seq from take. loop runs until a break. Replaces for(;;){ ... push @result... }
  • 64.
    Find your roots returngather loop { my $next = $path.parent; my $at-root = $path ~~ $next; ... $path ~~ $filter and take $path; ... last if $at-root; $path = $next; }
  • 65.
    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; }
  • 66.
    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
  • 67.
    Q: What’s ina Seq from gather?
  • 68.
    Q: What’s ina 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),
  • 69.
  • 70.
    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.
  • 71.
    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.
  • 72.
    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.
  • 73.
  • 74.
    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.’;
  • 75.
    Compare Bin dirwith 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';
  • 76.
    File and dirpath 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
  • 77.
    Adding a lib :$appendwith 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’;
  • 78.
    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.
  • 79.
    Test verbose overrides Thereis, 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.";
  • 80.
  • 81.
    pragmata Finally! Given libs, usethem. Runs at compile time. Calling a sub is too late.
  • 82.
    “EXPORT” does thedeed 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
  • 83.
    “EXPORT” does thedeed 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
  • 84.
    Raku Colon Cleanser Everloose track of parens in an expression? Raku method calls can replace $foo.some_method( $bar ); with $foo.some_method: $bar;
  • 85.
    Raku Colon Cleanser Everloose track of parens in an expression? Convert: $foo.frob($bar.nicate($thing.new($blort))); into: $foo.frob: $bar.nicate: $thing.new: $blort
  • 86.
    Raku Colon Cleanser Everloose track of parens in an expression? $foo.frob: $bar.nicate: $thing.new: $blort ;
  • 87.
    Find non-root ./libdirs & 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 ); } }
  • 88.
    Find non-root ./libdirs & 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 ); } }
  • 89.
    Find non-root ./libdirs & 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 ); } }
  • 90.
    Find non-root ./libdirs & 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 ); } }
  • 91.
    Find non-root ./libdirs & 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 ) ) }
  • 92.
    Find non-root ./libdirs & 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 }
  • 93.
    Named Parameters &Variables Egads! Using “:$prefix” ties your code to an API!
  • 94.
    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.
  • 95.
    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.
  • 96.
    --> Map sub EXPORT ( *@args--> Map ) { # return a Map %( '@FindBin-libs-dirs' => @found ) } Map of ( Name => Value )
  • 97.
    --> Map Map of( Name => Value ) ‘@’ is part of the name. sub EXPORT ( *@args --> Map ) { # return a Map %( '@FindBin-libs-dirs' => @found ) }
  • 98.
    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
  • 99.
    “unit” is aplaceholder. Export does all the work. unit module FindBin::libs has no subs.
  • 100.
    Pass1: Check forinstalled 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;
  • 101.
    Pass2: Validate directoryorder. 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';
  • 102.
    Pass2: Validate directoryorder. 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’”;
  • 103.
    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
  • 104.
    Summary Raku is quitedoable. 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.
  • 105.
  • 106.