11 releases
0.2.3 | Nov 12, 2024 |
---|---|
0.2.2 | Nov 12, 2024 |
0.1.6 | Nov 9, 2024 |
#83 in Command line utilities
776 downloads per month
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–23MB
~382K SLoC