Dyalog User Commands
Overview
User commands are useful for the Dyalog APL developer. Dyalog comes with a rich set of user commands, but independent parties also develop and maintain user commands. This article discusses how and where to install third-party user commands, and how to load them into ⎕SE
.
This article assumes that you are familiar with the concept of Dyalog user commands, and that you know what a user command script is, and what specific features and properties it has. If you are not familiar with these topics this article is not for you: study Dyalog's "User Commands User Guide" first.
Dyalog installs its own set of user commands into [DYALOG]/SALT/spice
. Installing third-party user commands into this folder has advantages and disadvantages.
Pro:
- The user commands cannot be changed by an ordinary user (admin rights are required for that)
- Every version of Dyalog has its own set of user commands
But what some may consider an advantage can also be viewed as a disadvantage. Installing it into a different place, the folder MyUCMDs/
(we discuss in a second where that folder lives), has some advantages:
- The user has "write" permission to that folder, so updating is easy
- Anything installed into this folder will be available in all installed versions of Dyalog APL
Note that every user has their own folder MyUCMDs/
. Again this might be considered an advantage or a disadvantage.
Like the SALT/spice
folder, MyUCMDs/
is scanned for user command scripts at startup time.
Where does the folder MyUCMDs/ live?
The answer to this question depends on the operating system used.
- Under Windows it's usually
C:\Users\<username>\Documents\
- Under Linux and Mac OS it is
/home/<username>/
Note that this folder is created by the Dyalog APL installer under Windows but it won't exist under Linux and Mac OS in versions before 19.0, so you need to create the folder yourself on non-Windows platforms.
If you have Tatin installed, or you are using version 19.0 or later (those come with Tatin automatically) then you can call this Tatin API function:
⎕se.Tatin.GetMyUCMDsFolder'foo' C:\Users\kai\Documents\MyUCMDs
Availability of user commands
Once a user command script is moved into a MyUCMDs/
folder (or a sub-folder of it) the user command will be available from any version of APL.
And that could be the end of the story in case the user command you want to install is relatively simple, so all the code can go into the script.
It's a different story when the user command relies on, say, a large set of code files that need to be loaded into ⎕SE
in order to execute the user command.
Now of course the user command script could check whether the code is already available in ⎕SE
and if not do the loading, and that would work just fine.
User commands with an API
However, these days it is becoming increasingly popular to add an API to a user command.
For example, the Dyalog APL project manager [Cider](https://github.com/aplteam/Cider) comes with a rich set of user commands:
]cider* -? CIDER: CloseProject Breaks the Link between the workspace and the files on disk CreateProject Makes the given folder a project folder Help Offers to put the HTML files on display ListAliases List all defined aliases with their folders ListOpenProjects List all currently open projects ListTatinPackages Lists all Tatin packages in all install folders Make Build a new version of the project OpenProject Load all source files into the WS and keep it linked by default RunTests Executes the project's test suite (if any) Version Returns name, version number and version date as a three-element vector ViewConfig Puts the config file of a project on display
But Cider also offers a public interface:
↑⎕se.Cider.⎕nl -3 AddAlias CloseProject CreateOpenParms DropAlias GetAliasFileContent GetCiderAliasFilename GetCiderConfigFilename GetMyUCMDsFolder GetProjectPath ListOpenProjects ListTatinPackages OpenProject RenameInfo_url RunMake RunTests Version ViewConfig
If you want to use any of these API functions without the need of first calling any of the Cider user commands (that would allow the user command script to load the code into ⎕SE
), then you have to make sure that the code is loaded, ideally at a very early stage: as part of the instantiating process.
This brings us to the real topic of this article: how to achieve that!
setup.dyalog in MyUCMDs/
We are going to introduce a script setup.dyalog
into the folder MyUCMDs/
. We are doing this because when Dyalog finds such a script then it will check whether it has a function Setup
. If so then this function will be executed.
Notes:
- The name of the script must be lowercase because otherwise it won't be found on non-Windows platforms
- The script can be a class or a namespace
- The function
Setup
must accept a right argument
In our case, the right argument will be ani
which stands forinit
- The function
Setup
must return a result
The result will be ignored by the caller
There is no setup.dyalog yet
Create one that looks like this:
:Namespace SetItUp ∇ {r}←Setup arg;⎕IO;⎕ML;dmx r←⍬ ⎕IO←1 ⋄ ⎕ML←1 :Trap ⎕SE.SALTUtils.DEBUG↓0 ⎕←LoadMyUserCommand ⎕SE.SALTUtils.DEBUG :Else dmx←⎕DMX ⎕←'Setup.dyalog has a problem and was not executed successfully:' ⎕←↑' '∘,¨dmx.DM :EndTrap ∇ ∇ r←LoadMyUserCommand debug;wspath;path2Config r←0 0⍴'' :Trap debug/0 :If ~IfAtLeastVersion 18 r←'MyUserCommand not loaded: not compatible with this version of Dyalog' :ElseIf 80≠⎕DR' ' ⍝ Not in "Classic" r←'MyUserCommand not loaded: not compatible with Classic' :Else ... ⍝ Load your stuff :EndIf :Else r←'Attempt to load MyUserCommand failed with ',⎕DMX.EM :EndTrap ∇ IfAtLeastVersion←{ ⍝ ⍵ is supposed to be a number like 15 or 17.1, representing a version of Dyalog APL. ⍝ Returns a Boolean that is 1 only if the current version is at least as good. ⍵≤{⊃(//)⎕VFI ⍵/⍨2>+\'.'=⍵}2⊃# ⎕WG'APLVersion' } ∇ r←GetMyUCMDsFolder add ⍝ Returns standard path for Dyalog's MyUCMDs folder.\\ ⍝ Works on all platforms but returns different results.\\ add←{(((~'/\'∊⍨⊃⍵)∧0≠≢⍵)/'/'),⍵}add OS←3↑⊃#.APLVersion :If 'Win'≡OS r←⊃{⍺,⍵}/1 ⎕nparts (2⊃4070⌶0),'\..\MyUCMDs' :Else r←(2 ⎕NQ'.' 'GetEnvironment' 'Home'),'/MyUCMDs',add :EndIf ∇ :EndNamespace
Note that this also checks the version of Dyalog APL and whether it's "Classic" or not. Amend this to your needs.
If your user command is not a Tatin package then this will do.
There is already a setup.dyalog
Copy the functions IfAtLeastVersion
, GetMyUCMDsFolder
and </syntaxhighlight>LoadMyUserCommand</syntaxhighlight> from above into your own setup.dyalog
script and then make sure that LoadMyUserCommand
is called from your </syntaxhighlight>Setup</syntaxhighlight> function.
This makes sure that the API of your user command is available right after instantiating Dyalog APL.
User commands that are Tatin packages
Since version 0.86. 0 a Tatin package can be marked as a user command: by specifying the path to a user command script with a project.
Example:
userCommandScript: "APLSource/MyUserCommand,dyalog",
With this line in the file apl-package.json
such a package can be installed and loaded with Tatin:
]Tatin.InstallPackages [tatin]MyUserCommand [MyUCMDs] ]Tatin.LoadDependencies [MyUCMDs]MyUserCommand
Notes:
- There is no name specified after
[MyUCMDs]
in the second argument ofInstallPackages
: this makes the function act on the name of the package, hereMyUserCommand
LoadDependencies
will look for a folderMyUserCommand
in theMyUCMDs/
folder. If there is one, and it contains a fileapl-dependencies.txt
, then the package will be loaded into⎕SE
.- Usually
LoadDependencies
loads packages into#
in case no second argument is specified, but because the folder was specified as an alias ([MyUCMDs]
) the function knows that this is about a user command, and therefore the default target for the load operation is⎕SE
and not#
. - The user command script
MyUserCommand.dyalog
is moved to the top of the folder hosting the user command, here[MyUCMDs]/MyUserCommand
Loading all such user commands
If you want to make sure that all user commands that are Tatin packages are loaded into ⎕SE
at an early stage add this to your setup.dyalog
script and make sure that it is called by your Setup
function in that script:
r←path LoadUserCommandPackages debug;home;name;res;folders;folder;F ⍝ This loads Tatin packages that are user commands installed in MyUCMDs/ r←'' F←⎕SE._Tatin.FilesAndDirs :If 0<≢folders←F.ListDirs path :For folder :In folders :If F.IsFile folder,'/apl-buildlist.json' name←2⊃⎕NPARTS folder :Trap (~debug)/0 {}⎕SE.Tatin.LoadDependencies(path,name)⎕SE :Else r,←⊂'>>> Attempt to load ',name,' failed with ',⎕DMX.EM :EndTrap :EndIf :EndFor :EndIf :If 0=≢r r←0 0⍴'' :Else r←⍪r :EndIf ⍝Done
Note that it uses stuff from Tatin, so it works only with Tatin being available in ⎕SE
, but since you are about to load Tatin packages that must be the case anyway.