use std::path::PathBuf;
use super::{
ChooseTheme,
package::Package,
state::{InstallState, InstalledTheme, STATE_FILE},
workspace::Workspace,
};
use crate::{DEFAULT_REGISTRY, Registry, content_hash};
pub struct InitOptions {
pub components_dir: Option<PathBuf>,
pub theme: Option<String>,
}
pub struct InstalledThemeInfo {
pub name: String,
pub registry: String,
pub file: PathBuf,
}
pub struct Initialized {
pub state_file: PathBuf,
pub components_dir: PathBuf,
pub theme: InstalledThemeInfo,
}
#[track_caller]
pub fn init(
package: &Package,
options: InitOptions,
choose: &mut ChooseTheme<'_>,
) -> Result<Initialized, String> {
if package.state_path().exists() {
return Err(format!(
"{} already exists; the package is already initialized",
package.state_path().display()
));
}
let theme = plan_theme(package, options.theme.as_deref(), choose)?;
let file = package.resolve(&theme.file);
if let Some(parent) = file.parent() {
std::fs::create_dir_all(parent)
.map_err(|error| format!("failed to create {}: {error}", parent.display()))?;
}
std::fs::write(&file, &theme.contents)
.map_err(|error| format!("failed to write {}: {error}", file.display()))?;
let mut state = InstallState::create(package, options.components_dir)?;
state.theme = Some(InstalledTheme {
name: theme.name.clone(),
registry: theme.registry.clone(),
hash: theme.hash,
file: theme.file.clone(),
});
state.save(package)?;
Ok(Initialized {
state_file: PathBuf::from(STATE_FILE),
components_dir: state.components_dir.clone(),
theme: InstalledThemeInfo {
name: theme.name,
registry: theme.registry,
file: theme.file,
},
})
}
struct ThemePlan {
name: String,
registry: String,
hash: String,
file: PathBuf,
contents: String,
}
#[track_caller]
fn plan_theme(
package: &Package,
requested: Option<&str>,
choose: &mut ChooseTheme<'_>,
) -> Result<ThemePlan, String> {
let registry_name = DEFAULT_REGISTRY;
let workspace = Workspace::load(package)?;
let dir = workspace
.registry_dir(registry_name)
.map_err(|error| format!("cannot install a theme: {error}"))?;
let registry = Registry::load(dir)
.map_err(|error| format!("failed to load registry `{registry_name}`: {error}"))?;
let names: Vec<String> = registry.theme_names().map(str::to_string).collect();
if names.is_empty() {
return Err(format!(
"registry `{registry_name}` offers no themes to install"
));
}
let chosen = match requested {
Some(name) if names.iter().any(|known| known == name) => name.to_string(),
Some(name) => {
return Err(format!(
"unknown theme `{name}`; available: {}",
names.join(", ")
));
}
None if names.len() == 1 => names[0].clone(),
None => choose(&names)?,
};
let theme = registry
.theme(&chosen)
.expect("chosen theme came from the registry");
let contents = theme
.read_source()
.map_err(|error| format!("failed to read theme `{chosen}`: {error}"))?;
Ok(ThemePlan {
name: chosen,
registry: registry_name.to_string(),
hash: content_hash(&contents),
file: PathBuf::from(theme.file_name()),
contents,
})
}