Custom Commands

Core vs Non-core

There are 2 types of zet commands: core and non-core. Core commands are built into zet. They cannot be changed or replaced. Their list is available by running zet --help. Some examples of core commands are:

  • zet init
  • zet api
  • zet env
  • zet commands

Non-core commands are all the other commands, including the ones bundled with zet. They can be removed or replaced. Some examples are:

  • zet ls
  • zet mv
  • zet note
  • zet open
  • zet sync

Search Paths

When you run a non-core zet command, zet searches for it inside the following directories: ${ZET_MODULES_PATH}:${PATH}. If $ZET_MODULES_PATH environment variable is not defined, zet defines it by itself:

  • (Linux) ${XDG_DATA_HOME:-$HOME/.local/share}/zet/modules
  • (macOS) $HOME/Library/Application Support/zet/modules
  • (Windows) {FOLDERID_RoamingAppData}/zet/modules
  • (Linux, macOS) /usr/share/zet/modules

You can see the values of $PATH and $ZET_MODULES_PATH by running zet env.

If you redefine $ZET_MODULES_PATH, zet might loose the possibility to run bundled non-core commands. If it isn't what you intended, first check the default $ZET_MODULES_PATH calculated by zet and use it:

export ZET_MODULES_PATH=/my/dir1:/my/dir2:$HOME/.local/share/zet/modules:/usr/share/zet/modules

All executables found in these directories which start with zet- prefix are considered zet non-core commands and will be listed on zet commands screen.

Writing own commands

To add your own non-core command, simply put an executable (for example a shell script) in your $ZET_MODULES_PATH or $PATH. You should prefer $ZET_MODULES_PATH to not clobber the $PATH and to avoid the risk of accidentally running a command outside of zet's execution environment.

Environment

When Zet calls any non-core command, it parses zet.toml and exports all variables in each [[notebooks]] section into command's environment. Variables are named after notebook's name and prefixed with ZET_NOTEBOOK_. For example, if there exists a notebook named "My Notebook", there will be at least ZET_NOTEBOOK_MY_NOTEBOOK_NAME and ZET_NOTEBOOK_MY_NOTEBOOK_PATH exported.

List of notebook names will be exported as ZET_NOTEBOOKS colon-separated list.

You can inspect all Zet-specific environment variables by running zet env. For easier access to environment variables, you may use zet api getenvnb and zet api getenvnbsel commands.

API

To ease the task of writing your own commands, zet provides the api core command, which you should use for some common tasks. See zet api --help and zet api <api-command> --help for in-depth description of what's availale.

For example, your command might need an access to the files using the selector syntax, defaulting to all the files inside all the notebooks. You can use zet api list and zet api notebooks to access all relevant informations:

#!/bin/bash
# usage: zet command [selectors...]

if (( ! $# )); then
    # readarray handles possible spaces inside the paths returned by api calls
    readarray -t args <<< $(zet api notebooks --selector)
    set -- "${args[@]}"
fi

readarray -t paths <<< $(zet api list --absolute "$@")

for p in "${paths[@]}"; do
    echo "$p"
done

Libraries

Zet exposes libraries for some languages inside $ZET_LIB_DIR directory. These libraries are language-specific and contain helper functions which are useful for commands written in a particular language. Libraries are stored in separate directories, e.g. Bash library is in $ZET_LIB_DIR/bash and Python library is in $ZET_LIB_DIR/python.

Descriptions

zet commands lists all found non-core commands. Each command may additionally have a short, one-line description which will be shown to user by zet commands. These descriptions are gathered from command-list.txt files found in $ZET_MODULES_PATH directories. Typically, each command-list.txt should have descriptions of all zet commands in the same directory.

Only directories in $ZET_MODULES_PATH (and not in $PATH) are used to gather command-list.txt files. It means that if you use $PATH to maintain your collection of submodules, you won't be able to set their descriptions.

Format of each line in these files is:

command: description

Example

Suppose that we set ZET_MODULES_PATH=$HOME/zetmodules. Inside this directory there are 2 commands: zet-foo and zet-bar. To add descriptions for them, we should create $HOME/zetmodules/command-list.txt file with the following contents:

foo: foo your notes by fooing them all at once
bar: make barring sound from your notes

Command Templates

Bash

#!/bin/bash

#/ usage: zet mycommand [options] <selector>
#/
#/ Foo a bar
#/
#/ Arguments:
#/   selector
#/          foos a bar without blahing
#/
#/
#/ Options:
#/   --help
#/          show this help message

set -o errexit
set -o nounset
set -o pipefail

. "${ZET_LIB_DIR}/bash/zetlib.bash"

opt_short="h"
opt_long=("help")
parse_opts "$@"
set -- "${OPTRET[@]}"; unset OPTRET

while (( $# )); do
    case $1 in
        -h|--help)              usage ; exit 0 ;;
        --)                     posargs=() ;;
        *)                      posargs+=("$1") ;;
    esac
    shift
done

zet api paths "${posargs[@]}"

Python

#!/usr/bin/env python3

import os
import sys
import argparse

sys.path.append(os.path.join(os.environ["ZET_LIB_DIR"], "python"))
from pyzet import api_paths


def prepare_args():
    parser = argparse.ArgumentParser(description="Foo a bar")
    parser.add_argument("selector", help="foos a bar without blahing")
    return parser.parse_args()


def main():
    args = prepare_args()
    lines = api_paths(args.selector)
    print(*lines)


try:
    sys.exit(main())
except KeyboardInterrupt:
    sys.exit(15)