use std::path::{Path, PathBuf};
use anyhow::{Context, bail};
use toml_edit::{Array, DocumentMut, Item, Value};
use trusty_mpm::core::config::MpmConfig;
use trusty_mpm::core::project_config::ProjectLevelConfig;
use trusty_mpm::core::session_profile::{self, SUPERVISOR_PROFILE_ID};
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Edit {
Unchanged,
Changed(String),
}
pub(crate) fn parse_user_config(
raw: &str,
path: &Path,
) -> anyhow::Result<(DocumentMut, MpmConfig)> {
let doc = raw
.parse::<DocumentMut>()
.with_context(|| malformed(path))?;
let typed = toml::from_str::<MpmConfig>(raw).with_context(|| malformed(path))?;
Ok((doc, typed))
}
fn malformed(path: &Path) -> String {
format!(
"{} is malformed; fix it by hand and re-run — `tm fleet init` never rewrites a file it \
cannot parse",
path.display()
)
}
pub(crate) fn add_allowlist_entry(raw: &str, dir: &Path, path: &Path) -> anyhow::Result<Edit> {
let (mut doc, typed) = parse_user_config(raw, path)?;
if session_profile::is_allow_listed(dir, &typed.supervisor) {
return Ok(Edit::Unchanged);
}
let entry = absolute_utf8(dir)?;
let table = doc
.entry("supervisor")
.or_insert_with(toml_edit::table)
.as_table_like_mut()
.with_context(|| format!("`supervisor` in {} is not a table", path.display()))?;
match table.get_mut("projects") {
None => {
table.insert(
"projects",
Item::Value(Value::Array(Array::from_iter([entry]))),
);
}
Some(item) => item
.as_array_mut()
.with_context(|| {
format!(
"`supervisor.projects` in {} is not an array",
path.display()
)
})?
.push(entry),
}
let text = doc.to_string();
let (_, check) = parse_user_config(&text, path)?;
if !session_profile::is_allow_listed(dir, &check.supervisor) {
bail!(
"internal error: the edited {} does not list {}",
path.display(),
dir.display()
);
}
Ok(Edit::Changed(text))
}
pub(crate) fn request_supervisor_profile(raw: &str, path: &Path) -> anyhow::Result<Edit> {
let typed = ProjectLevelConfig::from_toml(raw, path).with_context(|| malformed(path))?;
if session_profile::profile_from_config(Some(&typed)).is_supervisor() {
return Ok(Edit::Unchanged);
}
let mut doc = raw
.parse::<DocumentMut>()
.with_context(|| malformed(path))?;
doc["profile"] = toml_edit::value(SUPERVISOR_PROFILE_ID);
Ok(Edit::Changed(doc.to_string()))
}
pub(crate) fn other_architects(config: &MpmConfig, dir: &Path) -> Vec<PathBuf> {
architects(config)
.into_iter()
.filter(|entry| !session_profile::path_is_listed(dir, std::slice::from_ref(entry)))
.collect()
}
pub(crate) fn architects(config: &MpmConfig) -> Vec<PathBuf> {
config
.supervisor
.projects
.iter()
.filter(|entry| entry.is_absolute() && session_profile::requested(entry).is_supervisor())
.cloned()
.collect()
}
fn absolute_utf8(dir: &Path) -> anyhow::Result<String> {
if !dir.is_absolute() {
bail!("allowlist entry {} is not an absolute path", dir.display());
}
dir.to_str()
.map(str::to_owned)
.with_context(|| format!("{} is not valid UTF-8", dir.display()))
}