11 releases

0.2.3 Nov 12, 2024
0.2.2 Nov 12, 2024
0.1.6 Nov 9, 2024

#92 in Command line utilities

Download history 529/week @ 2024-11-05 245/week @ 2024-11-12 12/week @ 2024-11-19

786 downloads per month

GPL-3.0-only

73KB
1.5K SLoC

XTomate

Your poweful automation ally

Introduction

XTomate is a simple automation tool that allows you to define workflows in TOML files and run them. It is designed to be simple to use and easy to extend.

Installation

The easiest way to install XTomate is to clone the repository and install with cargo.

git clone https://github.com/vyPal/XTomate.git
cd XTomate
cargo install --path .

Usage

To run a workflow, you need to create a TOML file with the workflow definition and run the xt command with the path to the file.

xt run workflow

Plugins

XTomate is designed to be extensible with plugins. Plugins are simple dynamic libraries that implement necessary traits. Each plugin needs to implement a initialize function, a execute function and a teardown function. The initialize function is called when the plugin is loaded, the execute function is called when the plugin is used and the teardown function is called when the plugin is unloaded.

Both the initialize and execute functions take a JSON object encoded as a string as an argument. This object contains the configuration for the plugin. The teardown function does not take any arguments.

All functions return a 32-bit integer. A return value of 0 indicates success, while a non-zero value indicates an error.

Any plugin that will be loaded should be placed in the plugins directory in the root of the project. The user may also specify a custom directory to load plugins from using the --plugins flag.

Writing a plugin

(I will write a more detailed guide later, but for now just extend this template)

// Libraries used by the plugin
use std::ffi::CStr;
use libc::c_char;
use std::sync::{LazyLock, Mutex};
use serde::{Serialize, Deserialize};
use serde_json;

// Configuration struct for the plugin
#[derive(Serialize, Deserialize)]
struct PluginConfig {
    app_name: String,
}

// Input struct for the plugin
#[derive(Serialize, Deserialize)]
struct ExecutionInput {
    message: String,
}

// Global variable to store the app name
static APP_NAME: LazyLock<Mutex<String>> = LazyLock::new(|| Mutex::new(String::new()));

// Plugin initialization function
#[no_mangle]
pub extern "C" fn initialize(config: *const c_char) -> i32 {
    let config_cstr = unsafe { CStr::from_ptr(config) };
    let config_str = config_cstr.to_str().unwrap_or("");
    
    let config: PluginConfig = serde_json::from_str(config_str).unwrap();
    let mut app_name = APP_NAME.lock().unwrap();
    *app_name = config.app_name.clone();
    0
}

// Plugin execution function
#[no_mangle]
pub extern "C" fn execute(input: *const c_char) -> i32 {
    let input_cstr = unsafe { CStr::from_ptr(input) };
    let input_str = input_cstr.to_str().unwrap_or("");
    
    let input_data: ExecutionInput = serde_json::from_str(input_str).unwrap();
    std::process::Command::new("notify-send")
        .arg("-a")
        .arg(APP_NAME.lock().unwrap().clone())
        .arg(input_data.message)
        .output()
        .expect("failed to execute process");
    0
}

// Plugin teardown function
#[no_mangle]
pub extern "C" fn teardown() -> i32 {
    APP_NAME.lock().unwrap().clear();
    0
}

Workflow TOML syntax

(Partially so that I don't forget)

# Basic workflow information
name = "example"
version = "0.1.0" # XTomate version required to run the workflow

# Tasks to run on special events
on_start = ["log_start"]
on_finish = ["notify_send", "log_done"]

# Plugin configurations
[[plugins]]
name = "notify_send" # Plugin name
source = "https://github.com/vyPal/xtomate-plugin-notify-send" # Plugin source
version = "^0.1.0" # Plugin version
config = { app_name = "XTomate" } # Plugin configuration
[[plugins]]
name = "logger"
source = "vyPal/xtomate-plugin-logger" # Shorter way to write source
version = "^0.1.0"
config = { app_name = "XTomate", log_file = "xtomate.log" }

# Task configurations
[tasks.notify_send]
run = false # This will prevent the task to run on its own, it will have to be called explicitly
plugin = "notify_send" # Plugin to use
config = { message = "Workflow finished" } # Plugin configuration

[tasks.log_done]
run = false
plugin = "logger"
config = { message = "Workflow finished", level = "info", sub_app_name = "Status" }

[tasks.log_start]
run = false
plugin = "logger"
config = { message = "Workflow started", level = "info", sub_app_name = "Status" }

[tasks.prepdir]
# Command to run
command = """
mkdir testdir
cd testdir
touch hello.py
"""

[tasks.createprogram]
command = '''
echo "print('hello world')" > testdir/hello.py
'''
dependencies = [{"prepdir" = "success"}] # Dependencies to run before this task

[tasks.writefile]
command = '''
echo "$WORLD $HELLO" > testdir/hello.txt
'''
dependencies = [{"prepdir" = "success"}] # Dependencies to run before this task with a specific status
env = {HELLO = "world", WORLD = "hello"} # Environment variables to set before running the command

[tasks.runprogram]
command = "python testdir/hello.py && cat testdir/hello.txt"
dependencies = [{"createprogram" = "success"}, {"writefile" = "success"}]

Dependencies

~13–26MB
~380K SLoC