use crate::PplError;
use clap::{Args, ValueEnum};
use directories::BaseDirs;
use std::fs::{self, OpenOptions};
use std::io::{self, Write};
use std::path::PathBuf;
const MARKER: &str = "# ppl motd";
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum Shell {
Bash,
Zsh,
Fish,
}
#[derive(Args, Debug)]
pub struct ShellArgs {
/// Shell to install the motd hook for (defaults to $SHELL)
#[arg(value_enum)]
pub shell: Option<Shell>,
/// Print the snippet instead of writing it to the rc file
#[arg(long)]
pub print: bool,
}
impl Shell {
fn detect() -> Option<Shell> {
let shell = std::env::var("SHELL").ok()?;
match shell.rsplit('/').next()? {
"bash" => Some(Shell::Bash),
"zsh" => Some(Shell::Zsh),
"fish" => Some(Shell::Fish),
_ => None,
}
}
fn rc_path(&self) -> Option<PathBuf> {
let base = BaseDirs::new()?;
Some(match self {
Shell::Bash => base.home_dir().join(".bashrc"),
Shell::Zsh => std::env::var_os("ZDOTDIR")
.map(PathBuf::from)
.unwrap_or_else(|| base.home_dir().to_path_buf())
.join(".zshrc"),
Shell::Fish => base.config_dir().join("fish").join("config.fish"),
})
}
/// Only run in interactive shells, and only if ppl is on the PATH, so
/// scp/rsync sessions and uninstalls don't break the shell.
fn snippet(&self) -> &'static str {
match self {
Shell::Bash => "[[ $- == *i* ]] && command -v ppl >/dev/null 2>&1 && ppl motd",
Shell::Zsh => "[[ -o interactive ]] && (( $+commands[ppl] )) && ppl motd",
Shell::Fish => "if status is-interactive; and command -q ppl; ppl motd; end",
}
}
}
pub fn do_shell(args: &ShellArgs) -> Result<(), PplError> {
let shell = args.shell.or_else(Shell::detect).ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"could not detect shell from $SHELL, pass one of: bash, zsh, fish",
)
})?;
let entry = format!("{}\n{}\n", MARKER, shell.snippet());
if args.print {
print!("{}", entry);
return Ok(());
}
let path = shell
.rc_path()
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "could not find home directory"))?;
let existing = match fs::read_to_string(&path) {
Ok(s) => s,
Err(e) if e.kind() == io::ErrorKind::NotFound => String::new(),
Err(e) => return Err(e.into()),
};
if existing.contains(MARKER) {
println!("motd already installed in {}", path.display());
return Ok(());
}
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let mut file = OpenOptions::new().create(true).append(true).open(&path)?;
let sep = if existing.is_empty() || existing.ends_with("\n\n") {
""
} else if existing.ends_with('\n') {
"\n"
} else {
"\n\n"
};
write!(file, "{}{}", sep, entry)?;
println!("motd installed in {}", path.display());
Ok(())
}
⭐
Log in to mark this repository.
3.1 KiB · text
History
aea77cc