use std::io;
use std::path::Path;
use clap::ArgMatches;
use super::utils::{require_instance, required_arg};
use crate::cli::output::print_table;
use crate::instance::ContentEntry;
type CliResult = Result<(), Box<dyn std::error::Error>>;
type Scanner = fn(&Path, &str) -> Vec<ContentEntry>;
pub fn handle_mod(matches: &ArgMatches) -> CliResult {
handle_content(matches, "mod", crate::instance::content::mods::scan_mods)
}
pub fn handle_pack(matches: &ArgMatches) -> CliResult {
handle_content(
matches,
"pack",
crate::instance::content::resource_packs::scan_resource_packs,
)
}
pub fn handle_shader(matches: &ArgMatches) -> CliResult {
handle_content(
matches,
"shader",
crate::instance::content::shaders::scan_shaders,
)
}
fn handle_content(matches: &ArgMatches, kind: &str, scan: Scanner) -> CliResult {
match matches.subcommand() {
Some(("list", sub_matches)) => list_entries(required_arg(sub_matches, "instance")?, scan),
Some(("enable", sub_matches)) => toggle_entry(
required_arg(sub_matches, "instance")?,
required_arg(sub_matches, kind)?,
true,
kind,
scan,
),
Some(("disable", sub_matches)) => toggle_entry(
required_arg(sub_matches, "instance")?,
required_arg(sub_matches, kind)?,
false,
kind,
scan,
),
_ => Ok(()),
}
}
pub(crate) fn find_entry_by_stem<'a>(
entries: &'a [ContentEntry],
target: &str,
) -> Option<&'a ContentEntry> {
entries
.iter()
.find(|entry| entry.file_stem.eq_ignore_ascii_case(target))
}
fn list_entries(instance: &str, scan: Scanner) -> CliResult {
let instances_dir = crate::config::SETTINGS.paths.resolve_instances_dir();
require_instance(&instances_dir, instance)?;
let rows = scan(&instances_dir, instance)
.into_iter()
.map(|entry| {
vec![
entry.name,
if entry.enabled {
"enabled".to_string()
} else {
"disabled".to_string()
},
]
})
.collect::<Vec<_>>();
print_table(&["Name", "State"], &rows);
Ok(())
}
fn toggle_entry(
instance: &str,
target: &str,
should_enable: bool,
kind: &str,
scan: Scanner,
) -> CliResult {
let instances_dir = crate::config::SETTINGS.paths.resolve_instances_dir();
require_instance(&instances_dir, instance)?;
let entries = scan(&instances_dir, instance);
let entry = find_entry_by_stem(&entries, target)
.ok_or_else(|| io::Error::other(format!("{} '{}' not found", kind, target)))?;
if entry.enabled == should_enable {
println!(
"Already {}d.",
if should_enable { "enable" } else { "disable" }
);
return Ok(());
}
crate::instance::content::mods::toggle_entry(entry)?;
println!(
"{}d '{}'.",
if should_enable { "Enable" } else { "Disable" },
entry.name
);
Ok(())
}
#[cfg(test)]
mod tests {
use super::find_entry_by_stem;
use crate::instance::ContentEntry;
use std::path::PathBuf;
fn entry(file_stem: &str) -> ContentEntry {
ContentEntry {
file_stem: file_stem.to_string(),
name: file_stem.to_string(),
description: String::new(),
enabled: true,
icon_bytes: None,
path: PathBuf::from(file_stem),
icon_lines: None,
}
}
#[test]
fn matches_by_stem_case_insensitively() {
let entries = vec![entry("Sodium"), entry("Lithium")];
let found = find_entry_by_stem(&entries, "sOdIuM").expect("entry should match");
assert_eq!(found.file_stem, "Sodium");
}
#[test]
fn returns_none_for_missing_stem() {
let entries = vec![entry("Sodium")];
assert!(find_entry_by_stem(&entries, "iris").is_none());
}
}