onpath 0.2.0

Get your tools on the PATH — cross-shell, cross-platform, zero fuss
Documentation
use std::path::{Path, PathBuf};

use crate::config::Position;
use crate::context::SystemContext;
use crate::shell::{Shell, ShellKind};

pub struct PowerShell;

impl Shell for PowerShell {
    fn kind(&self) -> ShellKind {
        ShellKind::PowerShell
    }

    fn env_extension(&self) -> &'static str {
        ".ps1"
    }

    fn env_script(&self, dir: &Path, position: Position) -> String {
        let dir = dir.display();
        let mutation = match position {
            Position::Prepend => {
                format!("$env:PATH = \"{dir}\" + [IO.Path]::PathSeparator + $env:PATH")
            }
            Position::Append => {
                format!("$env:PATH = $env:PATH + [IO.Path]::PathSeparator + \"{dir}\"")
            }
        };
        format!(
            r#"# Generated by onpath. Do not edit.
if (-not ($env:PATH -split [IO.Path]::PathSeparator | Where-Object {{ $_ -eq "{dir}" }})) {{
    {mutation}
}}
"#
        )
    }

    fn source_line(&self, env_script_path: &Path) -> String {
        format!(". \"{}\"", env_script_path.display())
    }

    fn rc_candidates(&self, ctx: &SystemContext) -> Vec<PathBuf> {
        let config = ctx.xdg_config_home();
        vec![
            // PowerShell Core (pwsh) on Unix
            config
                .join("powershell")
                .join("Microsoft.PowerShell_profile.ps1"),
            // Also check home-based profile
            ctx.home_dir()
                .join(".config")
                .join("powershell")
                .join("Microsoft.PowerShell_profile.ps1"),
        ]
    }

    fn primary_rc(&self, ctx: &SystemContext) -> PathBuf {
        ctx.xdg_config_home()
            .join("powershell")
            .join("Microsoft.PowerShell_profile.ps1")
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn env_script_uses_powershell_syntax() {
        let script = PowerShell.env_script(Path::new("/app/bin"), Position::Prepend);
        assert!(script.contains("$env:PATH"));
        assert!(script.contains("[IO.Path]::PathSeparator"));
    }

    #[test]
    fn env_script_append() {
        let script = PowerShell.env_script(Path::new("/app/bin"), Position::Append);
        assert!(script.contains("$env:PATH + [IO.Path]::PathSeparator"));
    }

    #[test]
    fn source_line_uses_dot_sourcing() {
        let line = PowerShell.source_line(Path::new("/home/user/.myapp/env.ps1"));
        assert_eq!(line, ". \"/home/user/.myapp/env.ps1\"");
    }
}