use Zef:ver($?DISTRIBUTION.meta // $?DISTRIBUTION.meta// '*'):api($?DISTRIBUTION.meta // '*'):auth($?DISTRIBUTION.meta // ''); use Zef::Distribution:ver(Zef.^ver):api(Zef.^api):auth(Zef.^auth); class Zef::Distribution::Local is Zef::Distribution { =begin pod =title class Zef::Distribution::Local =subtitle A local file system Distribution implementation =head1 Synopsis =begin code :lang use Zef::Distribution::Local; my $dist = Zef::Distribution::Local.new($*CWD); # Show the meta data say $dist.meta.raku; # Output the content of the first item in provides with $dist.meta.hash.values.head -> $name-path { say $dist.content($name-path).open.slurp; } # Output if the $dist contains a namespace matching Foo::Bar:ver<1> say $dist.contains-spec("Foo::Bar:ver<1>"); =end code =head1 Description A C implementation that is used to represent locally downloaded and extracted distributions. =head1 Methods =head2 method new method new(IO() $path) Create a C from a local distribution via its C file. If C<$path> is a directory then it will assume there is a C file it can use. If C<$path> is a file it will assume it a json file containing meta data (formatted like C). =head2 method meta method meta(--> Hash:D) Returns the meta data that represents the distribution. =head2 method content method content($name-path --> IO::Handle:D) Returns an unopened C that can be used to get the content of the C<$name-path>, where C<$name-path> is a value of the distributions C e.g. C, C<$dist.content($dist.meta{"Foo"})>. =end pod has $.path; has $.IO; #| Create a distribution from $path. #| If $path = dir/meta6.json, $.path is set to dir. #| If $path = dir/, $.path is set to the first meta file (if any) thats found. method new(IO() $path) { die "Cannot create a Zef::Distribution from non-existent path: {$path}" unless $path.e; my $meta-path = self!find-meta($path) || die "No meta file? Path: {$path}"; my $abspath = $meta-path.parent.absolute; my %meta = try { %(Zef::from-json($meta-path.slurp)) } || die "Invalid json? File: {$meta-path}"; my $IO = $abspath.IO; self.bless(:path($abspath), :$IO, |%(%meta.grep(?*.value.elems)), :meta(%meta)); } has %!meta-cache; #| Get the meta data this distribution provides method meta(--> Hash:D) { return %!meta-cache if %!meta-cache; my %hash = self.Zef::Distribution::meta; # These are required for installation, but not part of META6 spec # Eventually there needs to be a spec for authors to declare their bin scripts, # and CUR should probably handle the resources file mapping itself (since all # data needed to calculate it exists under the 'resources' field). %hash{"resources/" ~ .key} = .value for self!resources(:meta(%hash)).list; %hash{"bin/" ~ .key} = .value for self!scripts.list; return %!meta-cache := %hash; } #| Get a handle used to read/slurp data from files this distribution contains method content($name-path --> IO::Handle:D) { my $handle = IO::Handle.new: path => IO::Path.new($name-path, :CWD(self.IO)); return $handle // $handle.throw; } #| Given a path that might be a file or directory it makes a best guess at what the implied META6.json is. method !find-meta(Zef::Distribution::Local: $path? is copy --> IO::Path) { my $dir = $path ~~ IO::Path # Purpose: Turn whatever the user gives us to a IO::Path if possible ?? $path # - Already IO::Path !! $path.?chars # - If $path is Any it won't have .chars (hence .?chars) ?? $path.IO # - A string with at least 1 char is needed to call `.IO` !! self.IO; # - Assume its meant to be called on itself (todo: check $path.defined) # If a file was passed in then we assume its a metafile. Normally you'd pass # in a directory containing the meta file, but for convience we'll do this for files return $dir if !$dir || $dir.IO.f; # The windows path size check is for windows symlink wonkiness. # "12" is the minimum size required for a valid meta that # rakudos internal json parser can understand (and is longer than # what the symlink issue noted above usually involves) my $meta-file = $dir.add('META6.json'); return $meta-file.IO.e ?? $meta-file !! IO::Path; } #| Get all files in resources/ directory and map them into a hash CURI.install understands. method !resources(:%meta, Bool :$absolute --> Hash:D) { my $res-path = self.IO.child('resources'); # resources/libraries is treated differently than everything else. # It uses the internal platform-library-name method to apply an # automatic platform naming scheme to the paths. It maps the original # path to this new path so that CURI.install can understand it. # Example: # META FILE: 'resources/libraries/mylib' # GENERATED: 'resources/libraries/mylib' => 'resources/libaries/libmylib.so' # or 'resources/libraries/mylib' => 'resources/libaries/mylib.dll' # Note that it does not add the "lib" prefix on Windows. Whether the generated file has the "lib" prefix is platform dependent. my $lib-path = $res-path.child('libraries'); return %meta.grep(*.defined).map(-> $resource { my $resource-path = $resource ~~ m/^libraries\/(.*)/ ?? $lib-path.child($*VM.platform-library-name(IO::Path.new($0, :CWD($!path)))) !! $res-path.child($resource); $resource => $resource-path.IO.is-relative ?? ( ?$absolute ?? $resource-path.IO.absolute($!path) !! $resource-path ) !! ( !$absolute ?? $resource-path.IO.relative($!path) !! $resource-path ); }).hash; } #| Get all files in bin/ directory and map them into a hash CURI.install understands. method !scripts(Bool :$absolute --> Hash:D) { do with $.IO.child('bin') -> $bin { return $bin.dir.grep(*.IO.f).map({ $_.IO.basename => $_.IO.is-relative ?? ( ?$absolute ?? $_.IO.absolute($!path) !! $_ ) !! ( !$absolute ?? $_.IO.relative($!path) !! $_ ) }).hash if $bin.IO.d } return {}; } }