use eyre::{Result, eyre};
use std::path::Path;
mod add;
mod apply;
mod edit;
mod status;
mod unapply;
pub(crate) use add::DotfilesAdd;
pub(crate) use apply::DotfilesApply;
pub(crate) use edit::DotfilesEdit;
pub(crate) use status::DotfilesStatus;
pub(crate) use unapply::DotfilesUnapply;
#[derive(Debug, usage_rs::Args)]
#[usage(verbatim_doc_comment, hide = true)]
pub(crate) struct Dotfiles {
#[usage(subcommand)]
command: Commands,
}
#[derive(Debug, usage_rs::Subcommands)]
enum Commands {
#[usage(hide = true)]
Add(add::DotfilesAdd),
#[usage(hide = true)]
Apply(apply::DotfilesApply),
#[usage(hide = true)]
Edit(edit::DotfilesEdit),
#[usage(hide = true)]
Status(status::DotfilesStatus),
#[usage(hide = true)]
Unapply(unapply::DotfilesUnapply),
}
impl Dotfiles {
pub(crate) async fn run(self) -> Result<()> {
deprecated_at!(
"2027.2.0",
"2028.2.0",
"cli.dotfiles",
"`mise dotfiles ...` is deprecated. Use `mise bootstrap dotfiles ...` instead."
);
match self.command {
Commands::Add(cmd) => cmd.run().await,
Commands::Apply(cmd) => cmd.run().await.map(|_| ()),
Commands::Edit(cmd) => cmd.run().await,
Commands::Status(cmd) => cmd.run().await,
Commands::Unapply(cmd) => cmd.run().await,
}
}
}
pub(crate) fn ignored_configs_with_dotfiles() -> Vec<&'static Path> {
crate::config::IGNORED_CONFIG_FILES
.iter()
.filter(|path| {
crate::file::read_to_string(path)
.ok()
.and_then(|body| body.parse::<toml::Table>().ok())
.is_some_and(|table| table.contains_key("dotfiles"))
})
.map(|path| path.as_path())
.collect()
}
pub(crate) fn warn_if_dotfiles_ignored() {
let ignored = ignored_configs_with_dotfiles();
if ignored.is_empty() {
return;
}
warn!(
"[dotfiles] in these config files was skipped because they are not trusted:\n{}\nRun `mise trust` in that directory to use them.",
ignored
.iter()
.map(|p| format!(" {}", crate::file::display_path(p)))
.collect::<Vec<_>>()
.join("\n")
);
}
fn open_in_editor(file: &Path) -> Result<()> {
let (program, mut args) = split_editor_command(&crate::env::EDITOR)?;
args.push(file.as_os_str().into());
crate::cmd::cmd(&program, args).run()?;
Ok(())
}
fn split_editor_command(editor: &str) -> Result<(String, Vec<std::ffi::OsString>)> {
let mut parts = shell_words::split(editor)
.map_err(|e| eyre!("failed to parse EDITOR/VISUAL value {:?}: {}", editor, e))?
.into_iter();
let program = parts
.next()
.ok_or_else(|| eyre!("EDITOR/VISUAL is empty"))?;
Ok((program, parts.map(Into::into).collect()))
}