use crate::cli::{ConfigCommand, ConfigSetArgs, DirectoryAddArgs, DirectoryRemoveArgs};
use crate::config::{Config, DirectoryEntry};
use crate::error::{PrefixloadError, Result};
use syntect::easy::HighlightLines;
use syntect::highlighting::ThemeSet;
use syntect::parsing::SyntaxSet;
use syntect::util::{LinesWithEndings, as_24_bit_terminal_escaped};
fn handle_config_show() -> Result<String> {
let content = Config::read_to_string()?;
let ps = SyntaxSet::load_defaults_newlines();
let ts = ThemeSet::load_defaults();
let syntax = ps
.find_syntax_by_extension("yml")
.or_else(|| ps.find_syntax_by_extension("yaml"))
.unwrap_or_else(|| ps.find_syntax_plain_text());
let theme = &ts.themes["base16-ocean.dark"];
let mut h = HighlightLines::new(syntax, theme);
let mut buf = String::with_capacity(content.len() * 2);
for line in LinesWithEndings::from(&content) {
let ranges = h.highlight_line(line, &ps)?;
let escaped = as_24_bit_terminal_escaped(&ranges[..], false);
buf.push_str(&escaped);
}
Ok(buf)
}
fn handle_config_edit() -> Result<String> {
Config::edit()?;
Ok("".to_string())
}
fn update_config<F, R>(operation: F) -> Result<R>
where
F: FnOnce(&mut Config) -> Result<R>,
{
let mut config = Config::load()?;
let result = operation(&mut config)?;
config.save()?;
Ok(result)
}
fn handle_config_set(args: &ConfigSetArgs) -> Result<String> {
update_config(|config| {
if let Some(val) = &args.endpoint {
config.endpoint = val.clone();
}
if let Some(val) = &args.bucket {
config.bucket = val.clone();
}
if let Some(val) = args.part_size {
config.part_size = val;
}
if let Some(val) = &args.local_directory_path {
config.local_directory_path = val.clone();
}
Ok("Config updated!".to_string())
})
}
fn handle_config_dir_add(args: &DirectoryAddArgs) -> Result<String> {
update_config(|config| {
if config
.directory_struct
.iter()
.any(|e| e.local_name_prefix == args.local_name_prefix)
{
return Err(PrefixloadError::Custom(
"Entry with this local_name_prefix already exists.".to_string(),
));
}
config.directory_struct.push(DirectoryEntry {
local_name_prefix: args.local_name_prefix.clone(),
remote_path: args.remote_path.clone(),
});
Ok("Directory entry added.".to_string())
})
}
fn handle_config_dir_rm(args: &DirectoryRemoveArgs) -> Result<String> {
update_config(|config| {
let old_len = config.directory_struct.len();
config
.directory_struct
.retain(|entry| entry.local_name_prefix != args.local_name_prefix);
if config.directory_struct.len() < old_len {
return Ok("Directory entry removed.".to_string());
}
Err(PrefixloadError::Custom(
"No entry with such local_name_prefix found.".to_string(),
))
})
}
pub async fn run(cmd: ConfigCommand) -> Result<String> {
match cmd {
ConfigCommand::Show => handle_config_show(),
ConfigCommand::Edit => handle_config_edit(),
ConfigCommand::Set(args) => handle_config_set(&args),
ConfigCommand::DirAdd(args) => handle_config_dir_add(&args),
ConfigCommand::DirRm(args) => handle_config_dir_rm(&args),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
use std::env;
use std::path::PathBuf;
use tempfile::TempDir;
#[cfg(windows)]
const CONFIG_ENV: &str = "APPDATA";
#[cfg(not(windows))]
const CONFIG_ENV: &str = "XDG_CONFIG_HOME";
fn temp_config_dir() -> TempDir {
let tmp = TempDir::new().expect("temp dir");
unsafe { env::set_var(CONFIG_ENV, tmp.path()) };
tmp
}
#[test]
#[serial]
fn config_set_updates_requested_fields() {
let _guard = temp_config_dir();
let args = ConfigSetArgs {
endpoint: Some("http://example.com".into()),
bucket: Some("mybucket".into()), part_size: Some(123),
local_directory_path: Some("/tmp/data".into()),
};
let msg = handle_config_set(&args).expect("set");
assert_eq!(msg, "Config updated!");
let cfg = Config::load().unwrap();
assert_eq!(cfg.endpoint, "http://example.com");
assert_eq!(cfg.bucket, "mybucket");
assert_eq!(cfg.part_size, 123);
assert_eq!(cfg.local_directory_path, PathBuf::from("/tmp/data"));
}
#[test]
#[serial]
fn dir_add_appends_and_skips_duplicate() {
let _guard = temp_config_dir();
let add_args = DirectoryAddArgs {
local_name_prefix: "PRE".into(),
remote_path: "dir1/".into(),
};
let msg1 = handle_config_dir_add(&add_args).expect("dir add 1");
assert_eq!(msg1, "Directory entry added.");
let cfg_after_first = Config::load().unwrap();
let initial_len = cfg_after_first.directory_struct.len();
assert!(
cfg_after_first
.directory_struct
.iter()
.any(|e| e.local_name_prefix == "PRE" && e.remote_path == "dir1/"),
"New directory mapping not found in config"
);
let err_msg = handle_config_dir_add(&add_args).unwrap_err().to_string();
assert!(err_msg.contains("Entry with this local_name_prefix already exists."));
let cfg_after_second = Config::load().unwrap();
assert_eq!(
cfg_after_second.directory_struct.len(),
initial_len,
"Duplicate entry unexpectedly modified directory_struct"
);
}
#[test]
#[serial]
fn dir_rm_removes_and_reports_missing() {
let _guard = temp_config_dir();
let add_args = DirectoryAddArgs {
local_name_prefix: "DEL".into(),
remote_path: "to/delete".into(),
};
handle_config_dir_add(&add_args).unwrap();
let rm_args = DirectoryRemoveArgs {
local_name_prefix: "DEL".into(),
};
let msg1 = handle_config_dir_rm(&rm_args).expect("dir rm 1");
assert_eq!(msg1, "Directory entry removed.");
let cfg = Config::load().unwrap();
assert!(
cfg.directory_struct
.iter()
.all(|e| e.local_name_prefix != "DEL"),
"Entry was not actually removed"
);
let msg_err = handle_config_dir_rm(&rm_args).unwrap_err().to_string();
assert!(msg_err.contains("No entry with such local_name_prefix found."));
}
}