Something went wrong. Try again.
A collection of my addons for World of Warcraft
Something went wrong. Try again.
6.1 kB · 215 lines
Rust
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216use std::env;use std::error::Error;use std::fs;use std::os::unix::fs::symlink;use std::path::{Path, PathBuf};use std::process::{Command, ExitCode};
type Result<T> = std::result::Result<T, Box<dyn Error>>;
/// The AddOns directory for the WoW Forever beta client, relative to `$HOME`.////// Override with the `WOW_ADDONS_DIR` environment variable.const DEFAULT_ADDONS_DIR: &str = ".local/share/bottles/bottles/Battle.net/drive_c/Program Files (x86)/World of Warcraft/_classic_beta_/Interface/AddOns";
const ANNOTATIONS_REPO: &str = "https://github.com/Ketho/vscode-wow-api.git";
const USAGE: &str = "\Usage: cargo xtask <command>
Commands: link Symlink each addon in addons/ into the WoW AddOns directory unlink Remove symlinks in the WoW AddOns directory that point into this repo annotations Download or update the WoW API annotations used by LuaLS
Environment: WOW_ADDONS_DIR Overrides the WoW AddOns directory";
fn main() -> ExitCode { let command = env::args().nth(1); let result = match command.as_deref() { Some("link") => link(), Some("unlink") => unlink(), Some("annotations") => annotations(), Some("help" | "-h" | "--help") => { println!("{USAGE}"); Ok(()) } _ => { eprintln!("{USAGE}"); return ExitCode::FAILURE; } };
match result { Ok(()) => ExitCode::SUCCESS, Err(err) => { eprintln!("error: {err}"); ExitCode::FAILURE } }}
fn repo_root() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")) .parent() .expect("xtask should live inside the repo") .to_path_buf()}
fn addons_dir() -> Result<PathBuf> { if let Some(dir) = env::var_os("WOW_ADDONS_DIR") { return Ok(PathBuf::from(dir)); }
let home = env::var_os("HOME").ok_or("$HOME is not set")?; Ok(Path::new(&home).join(DEFAULT_ADDONS_DIR))}
/// Returns the addon directories in `addons_root`.////// A directory is an addon if it contains a TOC file named after it, either/// `<name>.toc` or a flavor-specific `<name>_<flavor>.toc`.fn find_addons(addons_root: &Path) -> Result<Vec<PathBuf>> { let mut addons = Vec::new();
for entry in fs::read_dir(addons_root)? { let path = entry?.path(); if !path.is_dir() { continue; }
let Some(name) = path.file_name().and_then(|name| name.to_str()) else { continue; };
let has_toc = fs::read_dir(&path)? .filter_map(|entry| entry.ok()) .any(|entry| { let file_name = entry.file_name(); let Some(file_name) = file_name.to_str() else { return false; }; let Some(stem) = file_name.strip_suffix(".toc") else { return false; }; stem == name || stem .strip_prefix(name) .is_some_and(|rest| rest.starts_with('_')) });
if has_toc { addons.push(path); } }
addons.sort(); Ok(addons)}
fn ensure_addons_dir_exists(addons_dir: &Path) -> Result<()> { if addons_dir.is_dir() { return Ok(()); }
Err(format!( "AddOns directory not found: {}\nSet WOW_ADDONS_DIR to point at your client's Interface/AddOns directory.", addons_dir.display() ) .into())}
fn link() -> Result<()> { let addons_root = repo_root().join("addons"); let addons_dir = addons_dir()?; ensure_addons_dir_exists(&addons_dir)?;
let addons = find_addons(&addons_root)?; if addons.is_empty() { println!("No addons found in {}", addons_root.display()); return Ok(()); }
println!("Linking addons into {}", addons_dir.display());
for addon in addons { let name = addon.file_name().unwrap(); let dest = addons_dir.join(name); let name = name.to_string_lossy();
match fs::read_link(&dest) { Ok(target) if target == addon => { println!(" {name}: already linked"); } Ok(target) => { println!( " {name}: skipped, already a symlink to {}", target.display() ); } Err(_) if dest.symlink_metadata().is_ok() => { println!(" {name}: skipped, a non-symlink already exists at the destination"); } Err(_) => { symlink(&addon, &dest)?; println!(" {name}: linked"); } } }
Ok(())}
fn unlink() -> Result<()> { let root = repo_root(); let addons_dir = addons_dir()?; ensure_addons_dir_exists(&addons_dir)?;
let mut removed = 0; for entry in fs::read_dir(&addons_dir)? { let path = entry?.path(); let Ok(target) = fs::read_link(&path) else { continue; };
if target.starts_with(&root) { fs::remove_file(&path)?; println!( " {}: unlinked", path.file_name().unwrap().to_string_lossy() ); removed += 1; } }
if removed == 0 { println!("No linked addons found in {}", addons_dir.display()); }
Ok(())}
fn annotations() -> Result<()> { let dest = repo_root().join(".annotations/vscode-wow-api");
let status = if dest.join(".git").is_dir() { println!("Updating annotations in {}", dest.display()); Command::new("git") .args(["pull", "--ff-only", "--depth", "1"]) .current_dir(&dest) .status()? } else { println!("Cloning annotations into {}", dest.display()); Command::new("git") .args(["clone", "--depth", "1", ANNOTATIONS_REPO]) .arg(&dest) .status()? };
if !status.success() { return Err(format!("git exited with {status}").into()); }
Ok(())}