Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use a wrapper script for a Raku CLI

I am wondering what steps, (such as shebang lines and wrapper scripts) are recommended when creating a CLI application in Raku. I am interested in info both for scripts that will be installed with Zef and those that will be distributed separately.

The docs provide the example frobnicate program:

# inside "frobnicate.raku" 
sub MAIN(Str $file where *.IO.f, Int  :$length = 24, Bool :$verbose) { #`(body omitted)}

which I could run with raku frobnicate.raku – a fine solution for scripts, but not great for a real program.

If I want this to be a bit more of a standard program, I could create a frobnicate file such as this:

#!/usr/bin/env raku
sub MAIN(Str $file where *.IO.f, Int  :$length = 24, Bool :$verbose) { #`(body omitted)}

I can make that file executable with chmod +x and move it to a directory in my $PATH; then I can frobnicate with the command frobnicate. So far, all of this makes perfect sense and is just like any other scripting language.

However, none of this takes advantage of Raku's compilation. As a result, CLI apps that do more processing in MAIN can get a bit slow (even for generating --help output). So, the next step is to allow the app to be precomplied, but I'm not quite sure how to do so.

With Zef

When I look at the scripts that I execute after installing a Raku program with Zef, they pretty much all have the form:

#!/usr/bin/env perl6
sub MAIN(:$name, :$auth, :$ver, *@, *%) {
    CompUnit::RepositoryRegistry.run-script("frobnicate", :$name, :$auth, :$ver);
}

And, when I check the package's source, this CompUnit::RepositoryRegistry.run-script line isn't there. So I'm guessing Zef adds it? If so, is there anything I need to do when writing my script to make sure that Zef will use this wrapper and that the wrapper will work?

(This comment was helpful in understanding what was going on, though I still am not sure I 100% get it).

Without Zef

I would like to write scripts that users can run without needing to install them through Zef (though that would be my recommended installation method). Is there some way to use the run-script method shown above without Zef? Or should I do something like the following, which I`ve also seen:

#!/usr/bin/env raku
use Frobnicate::CLI

(And then move the actual functionality into a Frobnicate/CLI.rakumod file).

If I go this route, I'll need to instruct users to download both the frobnicate wrapper script above and the Frobnicate/CLI.rakumod file, right? (That is, there's no way to do this in a single file, is there?)

Assuming I do need to have my users download two files, where should I have them install the files? frobnicate needs to go into a directory in their PATH, but what about Frobnicate/CLI.rakumod? Does it need to be copied into their Raku module search path (and, if so, what command would show them what that path is)? Or can I modify the frobnicate wrapper in some way (maybe by changing raku to raku -Ilib or something like that?) in a way that would let them install both to directory on their PATH?

After typing all this out, it strikes me as possible that I'm dramatically over-thinking this. If so, please let me know that as well! In any event, I'd be happy to add a few more details to the relevant section of the docs if I come to understand this a bit better.

like image 804
codesections Avatar asked Feb 24 '21 18:02

codesections


2 Answers

So I'm guessing Zef adds it?

CompUnit::Repository::Installation adds it

Why is it there? One reason is because some wrapper needs to manage bin scripts the same name that would otherwise clash if they were in the same directory.

Is there some way to use the run-script method shown above without Zef?

run-script is for CompUnit::Repository::Installation only -- if you aren't installing a module then run-script won't be of interest

Assuming I do need to have my users download two files, where should I have them install the files?

Well the recommended/idiomatic way would be to use the core raku functionality to install things (i.e. use zef). Otherwise where you should put some code is either a) not going to matter or b) going to be mostly dependendant on your environment, what is idiomatic for your os, etc.

Does it need to be copied into their Raku module search path (and, if so, what command would show them what that path is)

echo $RAKULIB should suffice for showing the module search path in most cases, especially if they aren't interested in where the installation paths are. And as such you can instruct users to set e.g. RAKULIB=$FROB_LIB_DIR to point wherever your library is if you want them to be able to run your script without manually specifying it via raku -I../ frobnicate (so they don't copy the code anywhere special, they just point to wherever they e.g. clone your repo). Ditto for working with $PATH.

Or can I modify the frobnicate wrapper in some way (maybe by changing raku to raku -Ilib or something like that?) in a way that would let them install both to directory on their PATH?

I would advise against installing things based on some value in $PATH. Instruct users to set $PATH, don't install things to $PATH.

Technically you could add use lib '../' to your script, but using use lib in a script that you also want users to install normally is less than ideal since its adding an unused, potentially hijackable module search path when being run from such an install.

If you want your code to precompile then I suggest putting it in a module, and instructing your users that, if they don't intent to install it, to invoke it via raku -I../ ./frobnicate on a per-use basis or something like export RAKULIB="$FROB_LIB_DIR,$RAKULIB" followed by ./frobnicate for something more permanent. Alternatively if someone evenetually implements precompilation of scripts then you could just use the single file approach.

like image 143
ugexe Avatar answered Nov 19 '22 11:11

ugexe


Some comments on this topic.

  1. It is optimal, I think, to use zef because zef will also load dependencies. It is already unusual to be able to write a Raku program without using other modules and I would expect this to become even more unusual as more Raku modules get developed.

  2. I forget which modules I have installed on my system, and included in a program. By specifying everything in META6.json, then running zef test ., the chance of ensuring someone else can download the module is improved. Actually, I have found the best way to ensure this is to create a docker file and try to install a new module in a docker image/container - but that's another topic.

  3. I have found (for Linux, and I cannot comment about Windows) that if I:

    • write an executable in Raku (see below), lets call it MyWonder,
    • place it in the <distribution>/bin folder,
    • give it +x permission(s), and
    • specify it in the distribution's root META6.json file
    • (publish the distribution)

    then when zef installs the distribution, MyWonder will work on the command line (assuming that zef itself works on the command line, implying that PATH contains the directory to zef).

  4. The optimal (for me) way of invoking a program that is meant to be used on command line, or invoked by a desktop, is:

    • put the following into the MyWonder file (no need for extension)
    use v6.d;
    use MyWonderLife;
    
    • put all of the functionality into MyWonderLife.rakumod
    • make sure there is at least one sub MAIN in MyWonderLife.rakumod
    • specify MyWonderLife in the META6.json file (which allows a lot of flexibility about which directory you can put the actual MyWonderLife.rakummod file under; it doesn't have to be lib/)
    • create a simple test t/basic.rakutest for the distribution, with just the test use-ok 'MyWonderLife;

    This recipe means that all the functionality is precompiled by zef, so that when the executable is called by the user, there is a much quicker response. The slowest part when using Raku is compiling a program. By installing with zef, this is done once during installation. Programs in all languages are slow to install, so a Raku program does not cause a user to wonder what is happening at the moment it is being used.

    Secondly, it is possible to use several multi sub MAIN to handle a variety of calling situations, and to use the range of command line options that can now be handled. Whilst this is obviously possible in any script, putting them all in a .rakumod file seems (to me) to be more natural.

    I've found that extensive tests are a pain to watch when modules are being installed, so I have begun to move most of the development and maintenance testing to xt/ and only have simple installation tests in t/.

    Finally, with this recipe (and assuming you have given the distribution the name MyWonderLife in the META6.json file, the installation instructions for MyWonder, assuming it is possible to call the program with no arguments are simply:

    Use zef to install MyWonderLife, eg.,

    zef install MyWonderLife
    

    and use it on a Command Line as follows:

    MyWonder
    
like image 33
Richard Hainsworth Avatar answered Nov 19 '22 11:11

Richard Hainsworth