diff --git a/.gitignore b/.gitignore
index b9fb92a..454f55b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
original/
output/
+__pycache__
+target
diff --git a/automate-rs/Cargo.lock b/automate-rs/Cargo.lock
new file mode 100644
index 0000000..033c6a8
--- /dev/null
+++ b/automate-rs/Cargo.lock
@@ -0,0 +1,309 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "blobfox"
+version = "0.1.0"
+dependencies = [
+ "clap",
+ "serde",
+ "serde_yaml",
+ "xmltree",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54635806b078b7925d6e36810b1755f2a4b5b4d57560432c1ecf60bcbe10602b"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_derive",
+ "clap_lex",
+ "indexmap",
+ "once_cell",
+ "strsim",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "clap_derive"
+version = "3.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
+[[package]]
+name = "once_cell"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
+
+[[package]]
+name = "os_str_bytes"
+version = "6.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
+
+[[package]]
+name = "serde"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc855a42c7967b7c369eb5860f7164ef1f6f81c20c7cc1141f2a604e18723b03"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f2122636b9fe3b81f1cb25099fcf2d3f542cdb1d45940d56c713158884a05da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
+dependencies = [
+ "indexmap",
+ "ryu",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "xml-rs"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
+
+[[package]]
+name = "xmltree"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb"
+dependencies = [
+ "xml-rs",
+]
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/automate-rs/Cargo.toml b/automate-rs/Cargo.toml
new file mode 100644
index 0000000..ecbc31a
--- /dev/null
+++ b/automate-rs/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "blobfox"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+xmltree = "0.10"
+serde = { version = "1.0", features = ["derive"] }
+serde_yaml = "0.8"
+clap = { version = "3.2.14", features = ["derive"] }
diff --git a/automate-rs/resources/assets/lgbtq_heart.svg b/automate-rs/resources/assets/lgbtq_heart.svg
new file mode 100644
index 0000000..e8f245f
--- /dev/null
+++ b/automate-rs/resources/assets/lgbtq_heart.svg
@@ -0,0 +1,222 @@
+
+
diff --git a/automate-rs/resources/blobfox.yml b/automate-rs/resources/blobfox.yml
new file mode 100644
index 0000000..c920640
--- /dev/null
+++ b/automate-rs/resources/blobfox.yml
@@ -0,0 +1,15 @@
+name: 'blobfox'
+base_off: null
+variants:
+ 'base':
+ src: '../vector/blobfox.svg'
+
+ 'boop':
+ assets:
+ - type: svg
+ src: 'assets/lgbtq_heart.svg'
+
+ overwrites:
+ - id: 'eye-left'
+ fill: 0xffffffff
+ remove: true
diff --git a/automate-rs/resources/neugeme.yml b/automate-rs/resources/neugeme.yml
new file mode 100644
index 0000000..a5a7016
--- /dev/null
+++ b/automate-rs/resources/neugeme.yml
@@ -0,0 +1,11 @@
+name: 'neugeme'
+base: 'blobfox.yml' # all variants from 'blobfox' will be imported in this unit, unless they are already present
+variants:
+ 'base':
+ overwrites:
+ - id: 'body'
+ fill: 0x02FFFD
+ - id: 'hair'
+ fill: 0x02FFFD
+ - id: 'blobfox'
+ remove: true
diff --git a/automate-rs/src/decl.rs b/automate-rs/src/decl.rs
new file mode 100644
index 0000000..a56d84c
--- /dev/null
+++ b/automate-rs/src/decl.rs
@@ -0,0 +1,107 @@
+use std::path::{PathBuf, Path};
+use std::collections::HashMap;
+use serde::{Serialize, Deserialize};
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct Declaration {
+ pub name: String,
+ pub base: Option,
+ pub variants: HashMap,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct Variant {
+ pub base: Option,
+
+ pub src: Option, // Loads every asset from an SVG file
+ #[serde(default)]
+ pub assets: Vec, // Loads individual assets
+
+ #[serde(default)]
+ pub overwrites: Vec, // Operations on assets
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct Asset {
+ pub src: Option,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct Overwrite {
+ pub id: String, // ID of the element to modify
+
+ pub fill: Option,
+ pub stroke: Option,
+
+ #[serde(default)]
+ pub remove: bool,
+}
+
+impl Declaration {
+ pub fn join(self, parent: Self) -> Self {
+ let mut variants = self.variants;
+
+ for (name, parent_variant) in parent.variants {
+ if let Some(variant) = variants.get_mut(&name) {
+ variant.join(parent_variant);
+ } else {
+ variants.insert(name, parent_variant);
+ }
+ }
+
+ Self {
+ name: self.name,
+ base: parent.base,
+ variants
+ }
+ }
+
+ /// Replaces every path relative to the yaml file to paths relative to the cwd
+ pub fn canonicalize(&mut self, path: impl AsRef) {
+ let path = path.as_ref().parent().unwrap_or(&PathBuf::from(".")).to_path_buf();
+ for variant in self.variants.values_mut() {
+ variant.canonicalize(path.as_ref());
+ }
+ }
+}
+
+impl Variant {
+ pub fn join(&mut self, mut parent: Self) {
+ self.assets.append(&mut parent.assets);
+ self.overwrites.append(&mut parent.overwrites);
+
+ if self.base.is_none() {
+ self.base = parent.base;
+ }
+
+ if self.src.is_none() {
+ self.src = parent.src; // TODO: handle relative paths
+ }
+ }
+
+ /// Replaces every path relative to the yaml file to paths relative to the cwd
+ pub fn canonicalize(&mut self, path: &Path) {
+ match &mut self.src {
+ Some(src_path) => *src_path = path.join(&*src_path),
+ None => {}
+ }
+
+ for asset in &mut self.assets {
+ asset.canonicalize(path);
+ }
+
+ // for overwrite in &mut self.overwrites {
+ // overwrite.canonicalize(path);
+ // }
+ }
+}
+
+impl Asset {
+ /// Replaces every path relative to the yaml file to paths relative to the cwd
+ pub fn canonicalize(&mut self, path: &Path) {
+ match &mut self.src {
+ Some(src_path) => *src_path = path.join(&*src_path),
+ None => {}
+ }
+ }
+}
diff --git a/automate-rs/src/main.rs b/automate-rs/src/main.rs
new file mode 100644
index 0000000..36bb8d8
--- /dev/null
+++ b/automate-rs/src/main.rs
@@ -0,0 +1,41 @@
+use clap::Parser;
+use std::path::PathBuf;
+
+pub mod decl;
+use decl::*;
+
+fn main() {
+ let args = Args::parse();
+
+ let mut path = args.decl_file;
+ let raw = std::fs::read_to_string(&path).unwrap();
+ let mut declaration: Declaration = serde_yaml::from_str(&raw).unwrap();
+ declaration.canonicalize(&path);
+
+ while let Some(parent) = std::mem::take(&mut declaration.base) {
+ path = path.parent().unwrap_or(&PathBuf::from(".")).join(parent);
+ let raw = std::fs::read_to_string(&path).unwrap_or_else(|err| {
+ // TODO: print the include stack
+ panic!("Couldn't read {}: {}", path.display(), err);
+ });
+
+ let mut parent_decl: Declaration = serde_yaml::from_str(&raw).unwrap();
+ parent_decl.canonicalize(&path);
+
+ declaration = declaration.join(parent_decl);
+ }
+
+ println!("{:#?}", declaration);
+}
+
+#[derive(Parser, Debug)]
+#[clap(author, version, about, long_about = None)]
+struct Args {
+ /// A YAML file containing the declaration from which the emotes should be generated
+ #[clap(short, long, value_parser)]
+ decl_file: PathBuf,
+
+ /// List of the emote names to export
+ #[clap(value_parser)]
+ names: Vec,
+}