Disclaimer: This design could change before zef plugins is thrown into the world but this is the initial look at the plugin paradigm and something you can follow along and actually use today.
Let's take a look at implementing a plugin to let us edit/view config from the CLI without firing up a text editor and editing zef's config manually.
set up
Right now the plugin code is all residing in a branch other than zef master so we need to set up an alternate version of zef for us to use for the tutorial. This tutorial will use the alias zef-p
to denote the version of zef capable of plugins. Doing a basic install using zef-p install
will install modules the same as your system/local zef.
$ git clone https://github.com/ugexe/zef.git zef-p
# clones zef into directory zef-p
$ cd zef-p
zef-p$ git fetch origin allow-runtime-interface-plugins
# fetch the WIP plugins branch
zef-p$ git checkout allow-runtime-interface-plugins
# check out the plugins branch
zef-p$ alias zef-p="perl6 -I$(pwd)/lib $(pwd)/bin/zef"
# make an alias to make using our plugin capable zef easy to use
zef-p$ cd ..
$ # now we have zef-p available and the rest of the tutorial
writing a plugin
The example we're going to use is a basic view, add, remove for zef-p test
configuration. We're going to add the commands zef-p config
and zef-p config.test
to zef in the rest of this tutorial.
the skeleton
Creating your command is as easy as writing a script and putting it in the bin/
directory of your module. We'll go through the creation of zef-p config
and zef-p config.test
.
zef-p config
Create a file in your bin/
directory called zef-config
.
zef-p config.test
Create a file in your bin/
directory called zef-config.test
.
all done!
Not really. Let's add some functionality to zef-config
!
implementing zef-p config
Open your bin/zef-config
file and add the following
multi MAIN('config') {
note qq:to/END_USAGE/
zef config - a plugin for zef config management
zef config.test
ls
List all of the plugins zef has available for testing
push --module (--comment?) (--short-name?)
Appends the specified module to the test plugins
remove index(Int)
Removes the module from the config with index of Int
prepend --module (--comment?) (--short-name?)
Prepends the specified module to the test plugins
END_USAGE
}
The part above to be most interested in is the use of multi MAIN('config')
. This is the function that brings the functionality to zef-p config
and it behaves as you'd expect of MAIN. The key part is the keyed in 'config'
. When an unknown $command is supplied to zef, say zef-p config
out of the box, zef will search for any modules providing bin/zef-$command
and will load them so if your module provides three different $command then you will need three bin scripts (or one depending on how symlink handy you are and expect your users to be).
A second note, these plugins are not installed to your perl6 compunit repository. This is to save zef from combing your normal CURs every time a command it doesn't recognize is received.
This will just print out our usage for zef-p config.test
. Just like that, when we zef-p install --plugin .
(this plugin installation interface will likely change) we can then run zef-p config
and it will show our usage. The --plugin
option is what tells zef-p to install in its private plugin CUR.
implementing zef-p config.test
To keep the tutorial informative and less about how to write perl, we'll implement the ls
feature and add the stubs for push|remove|prepend
as described in our usage above.
In your bin/zef-config.test
use Zef;
sub pad (Int $w, Str $val) {
sprintf(" %-{$w - 1}s", $val);
}
multi MAIN('config.test', 'ls') {
my $idx = 0;
my @widths =
max(7, $*zef.config.hash<Test>.elems.Str.chars), #indexes
24,
20,
28,
;
my @headers = 'index', 'module', 'short-name', 'comment';
for |$*zef.config.hash<Test> -> $info {
@widths[1] = max(@widths[1], ($info<module>//'').chars + 2);
@widths[2] = max(@widths[2], ($info<short-name>//'').chars + 2);
@widths[3] = max(@widths[3], ($info<comment>//'').chars + 2);
}
say '-' x 2 + ([+] @widths);
say (0..^@headers.elems).map({ pad(@widths[$_], @headers[$_]) }).join('|');
say '-' x 2 + ([+] @widths);
for |$*zef.config.hash<Test> -> $info {
say (
pad(@widths[0], $idx++.Str),
|(1..^@headers.elems).map({ pad(@widths[$_], $info{@headers[$_]}//'') })
).join('|');
}
say '-' x 2 + ([+] @widths);
}
multi MAIN('config.test', 'remove', Int $index) { ... }
multi MAIN('config.test', 'prepend', Str:D $module, Str :$short-name?, Str :$comment?) { ... }
multi MAIN('config.test', 'push', Str:D $module, Str :$short-name?, Str :$comment?) { ... }
Looking deeper into our ls
command, you may notice $*zef
. That variable gives access to the config and setup that happens in Zef::CLI
. In the ls
command we're using it to look at the config that was loaded from zef's configuration file, getting the width of columns to print out, and finally printing out all of the data we find in zef's config regarding Test
plugins. The output of that command is displayed later in this post.
make our plugin installable
To make this plugin installable, we need a META6.json file. Here is a barebones template (please, if you're releasing real modules into the ecosystem then put some effort and time into your META6.json files so others can read/understand them).
{
"perl": "6.d+",
"name": "Zef::Plugin::PluginManager",
"auth": "deathbyperl6:the-viewer",
"description": "a zef plugin that supplies a partial cli to managing zef's config",
"provides": { }
}
install the plugin
So, we've created our bin/
scripts to provide zef-p config|config.test
. Now it's time to install and test it out.
zef-p install --plugin .
As stated previously, --plugin
will go away but at this time it's letting zef-p
know that it should install our scripts to zef's private plugin CUR. The other thing of note, shown below, is that a plugin's USAGE will override zef's if the plugin successfully loads.
At this point you should be able to run the following commands:
λ ~/projects/zef-plugin-tutorial$ zef-p config
zef config - a plugin for zef config management
zef config.test
ls
List all of the plugins zef has available for testing
push --module (--comment?) (--short-name?)
Appends the specified module to the test plugins
remove index(Int)
Removes the module from the config with index of Int
prepend --module (--comment?) (--short-name?)
Prepends the specified module to the test plugins
λ ~/projects/zef-plugin-tutorial$ zef-p config.test ls
-------------------------------------------------------------------------------------
index | module | short-name | comment
-------------------------------------------------------------------------------------
0 | Zef::Service::TAP | tap-harness | Perl6 TAP::Harness adapter
1 | Zef::Service::Shell::prove | prove |
2 | Zef::Service::Shell::Test | perl6-test |
-------------------------------------------------------------------------------------
λ ~/projects/zef-plugin-tutorial$ zef-p config.test
Usage:
/Users/tonyo/projects/zef-plugin-tutorial/zef/bin/zef config.test ls
/Users/tonyo/projects/zef-plugin-tutorial/zef/bin/zef config.test remove <index>
/Users/tonyo/projects/zef-plugin-tutorial/zef/bin/zef [--short-name=<Str>] [--comment=<Str>] config.test prepend <module>
/Users/tonyo/projects/zef-plugin-tutorial/zef/bin/zef [--short-name=<Str>] [--comment=<Str>] config.test push <module>
clean up
If you have installed any modules not using --plugin
then they likely went into one of your [pre]configured repositories and you can remove them with zef uninstall
if you need to. From here, to get rid of zef-p
or start anew, just remove the zef-p
directory we created when cloning zef
in the setup phase of this post.
conclusion
The code in this repo can be found here
This is a barebones primer to extending the functionality of zef. If you have questions, thoughts, concerns please reach out to me @tony-o in #perl6 on freenode. A .tell
in that channel is helpful because I'm not always able to monitor.