Using LuaRocks dependencies

It is hard and time-consuming to write everything from scratch. Usually, we rely on libraries for common tasks. The libraries themselves bring some dependencies, and so on. Thankfully, there is no need in figuring out the dependency graph and managing dependency versions manually – package managers do the boring work for us.

LuaRocks is the most commonly used package manager for Lua. It is also a repository of libraries for all occasions. Marta supports LuaRocks out-of-the-box.

In this tutorial, we will create a plugin that creates files with arbitrary names. We will use the uuid library for name generation.

Multi-file plugins

Single-file plugins does not support LuaRocks dependencies, so we need to create a multi-file plugin.

🚩 Create the plugin structure as in the snippet below. Declare the plugin and the action.

Plugins/
    uuid/
        init.lua

marta module

If you read the previous tutorials, you know that plugins are declared with a plugin { ... } block, and actions – with an action { ... } block. Well, this is only a part of truth.

Marta API comes in two modules: marta contains core API functionality, and martax provides utilities and helpers. We never used marta directly as its contents is implicitly exposed in single-file plugins. The reason is simplicity: if should be as fast as possible to write a trivial plugin.

However, this is not always the desirable behavior for complex plugins: they may want to set global variables with the same name as some marta member. So in multi-file plugins we need either to replace plugin { ... } with marta.plugin { ... }, or expose marta members explicitly:

marta.plugin { ... }
marta.action { ... }

or

marta.expose()
plugin { ... }
action { ... }

You are free to choose the way you prefer. In this tutorials we will use marta.expose() as it avoids code duplication.

🚩 Check if your multi-file plugin loads successfully. Fix issues if needed.

Downloading LuaRocks dependencies

Marta supports LuaRocks dependency trees, but does not download dependencies itself. Instead, external libraries have to be bundled with the plugin. We use the luarocks command-line utility to download them.

  • Install LuaRocks. If you use Homebrew, the easiest way to install luarocks is to install the lua package.
  • Open the Terminal, navigate to the plugin directory (~/Library/Application Support/org.yanex.marta/Plugins/uuid).
  • Run luarocks install --tree rocks uuid. This will download the uuid library and all its dependencies (if any) to ./rocks.

The resulting tree (find . -type f) should look like this:

./init.lua
./rocks/lib/luarocks/rocks-5.3/manifest
./rocks/lib/luarocks/rocks-5.3/uuid/0.2-1/rock_manifest
./rocks/lib/luarocks/rocks-5.3/uuid/0.2-1/doc/index.html
./rocks/lib/luarocks/rocks-5.3/uuid/0.2-1/doc/ldoc.css
./rocks/lib/luarocks/rocks-5.3/uuid/0.2-1/doc/topics/readme.md.html
./rocks/lib/luarocks/rocks-5.3/uuid/0.2-1/uuid-0.2-1.rockspec
./rocks/share/lua/5.3/uuid.lua

While you can remove the ./rocks/lib/luarocks/rocks-5.3 directory, do not forget to include third-party library licenses if you plan to distribute the plugin.

Attaching LuaRocks dependencies

Call marta.useRocks() in the beginning of init.lua to import LuaRocks dependencies. By default, useRocks() will import the rocks directory contents. You can provide a custom directory name, e.g. useRocks("dependencies").

Let’s add an action to check if the library is attached correctly:

marta.useRocks()
local uuid = require("uuid")

action {
    id = "check",
    name = "Check UUID",
    apply = function()
        martax.alert(uuid())
    end
}

🚩 Restart Marta and check if the action shows an alert with an UUID.

File creation

Our action creates new files. So we need to check if the current file system is writable, and notify the user if it isn’t.

action {
    id = "create.file",
    name = "Create UUID file",
    apply = function(context)
        local parent = context.activePane.model.folder
        if not parent then return end
        
        if not parent.fileSystem:supports("writeAccess") then
            martax.alert("Can not create a file.", "File system is read only.")
            return
        end
    end
}
  • context.activePane.model.folder returns a File for the current pane folder.
  • martax.alert() accepts up to four arguments:
    • alert(message)
    • alert(message, informativeText)
    • alert(message, informativeText, buttons)
      • For buttons you can pass a single name or a string array.
      • alert() returns an index of the pressed button.
    • alert(message, informativeText, buttons, style)
      • style can be warning, critical or informational (default).

We need also a bit of code to actually create a file:

local file = parent:append(uuid())
local access = martax.access("rwxr--r--")
file:writeText("File content.", "create", access)
file.fileSystem:flush()

We could attach luaposix and call open(), but it would only work for the local file system. Our approach is FS-agnostic.

The final flush() call is not required. However, it instructs a file system to write pending data to disk immediately and flush the caches.

Here’s the final version of the plugin:

marta.expose()
marta.useRocks()

plugin { 
    id = "marta.example.deps",
    name = "LuaRocks experiments",
    apiVersion = "2.0"
}

local uuid = require("uuid")

action {
    id = "check",
    name = "Check UUID",
    apply = function()
        martax.alert(uuid())
    end
}

action {
    id = "create.file",
    name = "Create UUID file",
    apply = function(context)
        local parent = context.activePane.model.folder
        if not parent then return end

        if not parent.fileSystem:supports("writeAccess") then
            martax.alert("Can not create a file.", "File system is read only.")
            return
        end

        local file = parent:append(uuid())
        local access = martax.access("rwxr--r--")
        file:writeText("File content.", "create", access)
        file.fileSystem:flush()
    end
}