unit role Pluggable; use JSON::Fast; use File::Find; # Public Interface # search and load plugins multi method plugins( :$base = Nil, :$plugins-namespace = 'Plugins', :$name-matcher = Nil ) { load-matching-modules( plugin-base($base), $plugins-namespace, $name-matcher ); } # load a list of plugins multi method plugins( @list-of-plugins ) { load-modules( @list-of-plugins ); } multi sub plugins( $base = Nil, :$plugins-namespace = 'Plugins', :$name-matcher = Nil ) is export { load-matching-modules( $base, $plugins-namespace, $name-matcher ); } multi sub plugins( $list-of-plugins ) is export { load-modules( $list-of-plugins ); } method available-plugins(:$base = Nil, :$plugins-namespace = 'Plugins', :$name-matcher = Nil ) { available-plugins( plugin-base($base), :$plugins-namespace, :$name-matcher ); } sub available-plugins( $base, :$plugins-namespace = 'Plugins', :$name-matcher = Nil ) is export { list-modules( $base, $plugins-namespace, $name-matcher ); } # Private stuff my sub plugin-base( $base ) { $base.defined ?? $base !! ::?CLASS.^name } my sub load-matching-modules( $base, $namespace, $name-matcher ) { load-modules( list-modules( $base, $namespace, $name-matcher ) ) } my sub load-modules( $modules ) { $modules .map({ require-module( $_ ) }) # Filter out modules that failed to load. # Note: We have a list of type objects here. # These are themselves "false" and their .defined property is "false" too # So don' replace the grep with something "simpler" like .grep({$_}) or .grep({.defined}) .grep({ !.isa(Nil) }) # Potentially, there same module is installed and locally in "lib" at the same time .unique .Array; } my sub list-modules( $base, $namespace, $name-matcher ) { ( |matching-installed-modules( $base, $namespace, $name-matcher ), |matching-filesystem-modules( $base, $namespace, $name-matcher ) ).unique.Array; } my sub matching-filesystem-modules( $base, $namespace, $name-matcher ) { matching-modules( all-filesystem-modules(), $base, $namespace, $name-matcher ); } my sub matching-installed-modules( $base, $namespace, $name-matcher ) { matching-modules( all-installed-modules(), $base, $namespace, $name-matcher ); } my sub matching-modules( $modules, $base, $namespace, $name-matcher ) { $modules.grep(-> $module-name { is-matching-module( $module-name, $base, $namespace, $name-matcher ); }); } my sub is-matching-module( $module-name, $base, $namespace, $name-matcher ) { ( $module-name.chars > "{$base}::{$namespace}".chars ) && ( $module-name.starts-with("{$base}::{$namespace}") ) && ( (!defined $name-matcher) || ( $module-name ~~ $name-matcher ) ); } my sub modules-in-directory( $directory ) { find(dir => $directory, name => /\.pm6?$/ ) .map({ cleanup-module-name( $_, $directory ) }); } my sub cleanup-module-name ( $module-name, $path-prefix ) { $module-name .substr( $path-prefix.chars + 1 ) # cut off the path .substr( 0, *-4 ) # cut off the extension .subst( /\//, '::', :g ) # for linux et al .subst( /\\/, '::', :g ); # for windows } my sub require-module( $module-name ) { try require ::( $module-name ); return ::( $module-name ) unless $!; say "Warning: Unable to load <$module-name>"; say $! if ( $*DEBUG-PLUGINS//False ); return; } # Black magic my sub all-filesystem-modules() { $*REPO.repo-chain .grep({ .isa( CompUnit::Repository::FileSystem ) }) .map({ |modules-in-directory( .prefix ) }); } my sub all-installed-modules() { # XXX perhaps $r.installed() could be leveraged here, but it # seems broken at the moment $*REPO.repo-chain .map({ say $_; $_ }) .grep({ .isa( CompUnit::Repository::Installation ) }) .map({ |modules-in-dist( .prefix ) }); } my sub modules-in-dist( $directory ) { # Can't replace this with as this mysteriously dies # under Windows ( https://github.com/rakudo/rakudo/issues/3120 ) gather { try { for $directory.child('dist').IO.dir.grep(*.IO.f) -> $idx-file { take modules-in-idx-file( $idx-file ); } } }.flat } sub modules-in-idx-file( $idx-file ) { try { my $data = from-json( $idx-file.IO.slurp ); return $data{'provides'}.keys; } }