Invoke is a Python package used to make tasks executed by CLI.
Your Python code looks like this:
from invoke import task
@task
def hello(ctx, user):
print(f'Hello {user} !')
ctx.run('curl parrot.live')
And you can invoke your task like this:
$ inv hello Celian
> Hello Celian !
> (nice animation)
Invoke makes it easy to provide arguments to your tasks, see the documentation for details !
Instead of using Shell scripts, Python3 may automate more complex workflows and is multi-platform.
Personaly, I have a Windows / multiple Linux distributions on a laptop and a
desktop. I may want to have different behaviours for a task given a specific
platform. For instance, I can have an update
task that works on Debian,
Arch Linux and Nixos.
Fruthermore, as tasks are written in a Python package, it is useful to have a utility module that can provide system-wide information such as:
is_debian()
is_laptop()
In order to properly use Invoke in my systems, I wanted to be able to run any task anywhere on the system. For example I can do:
task update-discord
And this will update Discord on my system, even if I'm outside my configuration directory.
To do so, here is the layout of this tiny project:
- debian.py: Debian related commands
- apps.py: Software related commands
- utils.py: Some invoke / system related utilities used in tasks
- setup.py: Commands to setup / configure this module
- tasks.py: Imports all the tasks
- task.sh: Stub to use
task
as a command that executes my Invoke tasks anywhere on the system (put in/usr/bin/task
after the setup task)
apps.py
from invoke import task
from utils import sudo_cmd
@task
def update_discord(ctx):
# Debian
sudo_cmd(ctx, "apt install ./Downloads/discord-*.deb")
ctx.run("rm ./Downloads/discord-*.deb")
# I can add other behaviours depending on the system
utils.py
def sudo_cmd(ctx, cmd, *args, **kwargs):
"""
Make sudo command
"""
return ctx.run('sudo ' + cmd, *args, pty=True, **kwargs)
tasks.py
from apps import *
# I can also choose to import this based on some environment variables...
from debian import *
from setup import *
The setup will add a script to /usr/bin/task
which runs Invoke to the
target task directory.
Here is the top-level script:
task.sh
#!/bin/sh
invoke -r '{{TASKS_DIR}}' "$@"
And the setup script:
setup.py
import os
from invoke import task
from utils import sudo_cmd
@task
def setup(ctx, dst = '/usr/bin/task'):
"""
Setup these tasks to this computer
"""
assert not os.path.exists(dst), f'{dst} exists'
with open('task.sh') as f:
script = f.read()
script = script.replace('{{TASKS_DIR}}', os.path.realpath('.'))
with open('/tmp/task.sh', 'w') as f:
f.write(script)
ctx.run('chmod +x /tmp/task.sh')
sudo_cmd(ctx, f'cp /tmp/task.sh {dst}')
print('Installed to', dst)
@task
def hello(ctx):
"""
Hello world !
"""
print('World !')
print(f'({__file__})')
To install all the tasks, I just have to go to my tasks repository and run:
inv setup
Now, I can list my tasks anywhere on my system using:
task -l
In my honest opinion, I find that using Python is way better than Shell script
for some tasks but might be inconvenient. Hopefully Invoke makes it a way
easier to make simple tasks. It is also important to be able to see which tasks
are on the system and what they are doing, Invoke automatically generates
help documentations for each task so combining inv -l
and inv -h <task-name>
makes a wonderful quality of life for this.
Célian Cc618
Raimbault 💚 - https://github.com/Cc618 https://celian.dev