Custom Commands

Core vs Non-core

There are 2 types of zet subcommands: 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. This includes some commands which are bundled with zet. These subcommands 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 one as a set of local-specific data directory and system-specific data directory:

  • (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 subcommands. 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 subcommands and will be listed on zet commands screen.

Writing own subcommands

To add your own subcommand, simply drop 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 subcommands outside of zet's execution environment.

API

To ease the task of writing your own subcommands, 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 subcommand 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 --as-selectors)
    set -- "${args[@]}"
fi

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

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 subcommands. Each subcommand 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:

subcommand: description

Example

Suppose that ZET_MODULES_PATH=$HOME/zetmodules and that inside this directory there are 2 subcommands: zet-foo and zet-bar. To add descriptions for them, have to 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

Subcommand 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)