overcast/src/main.rs

173 lines
4.7 KiB
Rust

use std::path::PathBuf;
use argh::FromArgs;
use game::NonSteamGame;
use itertools::Itertools;
use vdf::{Dictionary, VdfValue};
mod game;
mod user;
mod vdf;
#[derive(Debug, FromArgs)]
/// Manage non-Steam games
struct Args {
#[argh(subcommand)]
cmd: Commands,
}
#[derive(Debug, FromArgs)]
#[argh(subcommand)]
enum Commands {
Add(AddArgs),
/// list all shortcuts
List(ListArgs),
}
/// add a new shortcut
#[derive(Debug, FromArgs)]
#[argh(subcommand, name = "add")]
struct AddArgs {
/// starting directory
#[argh(option, short = 'd')]
start_dir: Option<String>,
/// path to the game icon
#[argh(option, short = 'i')]
icon: Option<PathBuf>,
/// name of the app as it'll show in Steam
#[argh(option, short = 'n')]
app_name: String,
/// what command and args to run to execute the app
#[argh(positional, greedy)]
command: Vec<String>,
}
/// list all shortcuts
#[derive(Debug, FromArgs)]
#[argh(subcommand, name = "list")]
struct ListArgs {
/// print too much info about apps
#[argh(switch, short = 'v')]
verbose: bool,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Args = argh::from_env();
let shortcuts_path = {
let mut dir = user::get_userdir()?;
dir.push("config/shortcuts.vdf");
dir
};
match args.cmd {
Commands::Add(args) => add_shortcut(shortcuts_path, args),
Commands::List(args) => list_shortcuts(shortcuts_path, args),
}
}
fn list_shortcuts(
shortcuts_path: PathBuf,
args: ListArgs,
) -> Result<(), Box<dyn std::error::Error>> {
if shortcuts_path.exists() {
for shortcut in get_shortcuts(shortcuts_path)? {
if args.verbose {
println!("App {} (ID {}):", shortcut.app_name, shortcut.app_id);
println!(" Start from: {}", shortcut.start_dir);
println!(" Target: {}", shortcut.executable);
if !shortcut.launch_options.is_empty() {
println!(" Launch Args: {}", shortcut.launch_options);
}
if !shortcut.tags.is_empty() {
println!(" Tags: {}", shortcut.tags.join(", "));
}
if !shortcut.icon.is_empty() {
println!(" Icon Path: {}", shortcut.icon);
}
} else {
println!("App ID {}: {}", shortcut.app_id, shortcut.app_name);
}
}
} else {
println!("shortcuts.vdf doesn't exist!");
}
Ok(())
}
fn add_shortcut(shortcuts_path: PathBuf, args: AddArgs) -> Result<(), Box<dyn std::error::Error>> {
let mut shortcuts = if shortcuts_path.exists() {
get_shortcuts(shortcuts_path.clone())?
} else {
Vec::new()
};
for sc in &shortcuts {
println!("App {}: {}, {}", sc.app_id, sc.app_name, sc.executable);
}
let executable = prepare_arg(&args.command[0]);
let exe_opts: Option<String> = if args.command.len() > 1 {
Some(args.command[1..].to_vec().iter().map(prepare_arg).join(" "))
} else {
None
};
let mut new_game = NonSteamGame::new(args.app_name, executable, exe_opts, None);
if let Some(start_dir) = &args.start_dir {
new_game.start_dir = format!("\"{}\"", start_dir);
}
if let Some(icon) = &args.icon {
new_game.icon = icon.display().to_string();
}
shortcuts.push(new_game);
let vdf_out: vdf::Dictionary = {
let mut map = Dictionary::new();
let arr: vdf::Dictionary = shortcuts
.into_iter()
.enumerate()
.map(|(idx, game)| (idx.to_string(), VdfValue::Dict(game.into())))
.collect();
map.insert("shortcuts".to_owned(), VdfValue::Dict(arr));
map
};
vdf::write_file(vdf_out, shortcuts_path)?;
Ok(())
}
fn get_shortcuts(path: PathBuf) -> Result<Vec<NonSteamGame>, String> {
let vdf_map =
vdf::read_file(&path).map_err(|x| format!("Error opening {}: {}", &path.display(), x))?;
let vec: Result<Vec<NonSteamGame>, String> = vdf_map["shortcuts"]
.clone()
.into_vec()
.map_or_else(|| Err("Malformed shortcuts.vdf".to_owned()), Ok)?
.into_iter()
.map(|x| {
if let vdf::VdfValue::Dict(dict) = x {
Ok(NonSteamGame::try_from(dict).unwrap())
} else {
Err("Couldn't parse shortcuts.vdf".to_owned())
}
})
.collect();
vec
}
fn prepare_arg<S: AsRef<str>>(input: S) -> String {
let input = input.as_ref();
if input.contains(char::is_whitespace) {
format!("\"{}\"", input)
} else {
input.to_owned()
}
}