phpup/commands/
use.rs

1use super::{Command, Config, ConfigError};
2use crate::decorized::Decorized;
3use crate::symlink;
4use crate::version;
5use crate::version::Alias;
6use crate::version::Local;
7use crate::version::Version;
8use clap;
9use std::fmt::Display;
10use std::path::{Path, PathBuf};
11use std::str::FromStr;
12use thiserror::Error;
13
14#[derive(clap::Parser, Debug)]
15pub struct Use {
16    #[clap(
17        name = "version | alias | system",
18        help = "installed version or alias name or system"
19    )]
20    request_version: Option<RequestVersion>,
21
22    #[clap(flatten)]
23    version_file: version::File,
24
25    /// Don't output a message to stdout
26    #[clap(long)]
27    quiet: bool,
28}
29
30#[derive(Debug)]
31enum RequestVersion {
32    Installed(Version),
33    Alias(Alias),
34    System,
35}
36
37#[derive(Error, Debug)]
38pub enum Error {
39    #[error("Can't find installed version '{version}' {source}")]
40    NotInstalled { version: Version, source: Source },
41
42    #[error(transparent)]
43    NoMultiShellPath(#[from] ConfigError),
44
45    #[error(transparent)]
46    NotFoundAlias(#[from] version::alias::Error),
47
48    #[error("Can't detect a version: {0}")]
49    NoVersionFromFile(#[from] version::file::Error),
50
51    #[error("Can't find a system version")]
52    NoSystemVersion,
53}
54
55macro_rules! outln {
56    ($disp:expr, $($arg:tt)*) => {
57        if $disp {
58            println!($($arg)*);
59        };
60    };
61}
62
63impl Command for Use {
64    type Error = Error;
65
66    fn run(&self, config: &Config) -> Result<(), Error> {
67        let (use_version, source) = match &self.request_version {
68            Some(request_version) => match request_version {
69                RequestVersion::Installed(version) => {
70                    (Local::Installed(*version), Source::Installed(*version))
71                }
72                RequestVersion::Alias(alias) => {
73                    let version = alias.resolve(config.aliases_dir())?;
74                    outln!(
75                        !self.quiet,
76                        "Resolve alias {}@ -> {}",
77                        alias.decorized(),
78                        version.decorized()
79                    );
80                    (version, Source::Alias(alias.clone()))
81                }
82                RequestVersion::System => (Local::System, Source::System),
83            },
84            None => {
85                let info = match self.version_file.get_version_info() {
86                    Err(version::file::Error::NoVersionFile(_)) if self.quiet => return Ok(()),
87                    other => other,
88                }?;
89                outln!(
90                    !self.quiet,
91                    "{} has been specified from {}",
92                    info.version.decorized(),
93                    info.filepath.display().decorized()
94                );
95                (info.version, Source::File(info.filepath))
96            }
97        };
98
99        match use_version {
100            Local::Installed(version) => {
101                let use_version = version::latest_installed_by(&version, config)
102                    .ok_or(Error::NotInstalled { version, source })?;
103                let version_dir = config.versions_dir().join(use_version.to_string());
104                replace_multishell_path(version_dir.join("bin"), config)?;
105
106                outln!(!self.quiet, "Using {}", use_version.decorized_with_prefix());
107            }
108            Local::System => {
109                let system_path = version::system::path().ok_or(Error::NoSystemVersion)?;
110                replace_multishell_path(&system_path, config)?;
111
112                outln!(
113                    !self.quiet,
114                    "Using {} -> {}",
115                    Local::System.decorized_with_prefix(),
116                    system_path.display().decorized()
117                );
118            }
119        }
120
121        Ok(())
122    }
123}
124
125fn replace_multishell_path(new_path: impl AsRef<Path>, config: &Config) -> Result<(), Error> {
126    let multishell_path = config.multishell_path()?;
127    symlink::remove(multishell_path).expect("Can't remove symlink!");
128    symlink::link(new_path, multishell_path).expect("Can't create symlink!");
129    Ok(())
130}
131
132impl FromStr for RequestVersion {
133    type Err = Error;
134    fn from_str(s: &str) -> Result<Self, Self::Err> {
135        if s == "system" {
136            Ok(Self::System)
137        } else {
138            s.parse::<Version>()
139                .map(Self::Installed)
140                .or_else(|_| Ok(Self::Alias(s.parse().unwrap())))
141        }
142    }
143}
144
145#[derive(Error, Debug)]
146pub enum Source {
147    Installed(Version),
148    Alias(Alias),
149    File(PathBuf),
150    System,
151}
152
153impl Display for Source {
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        match self {
156            Source::Installed(_) => String::new().fmt(f),
157            Source::Alias(alias) => format!("specified by alias '{}'", alias).fmt(f),
158            Source::File(path) => format!("specified by version-file '{}'", path.display()).fmt(f),
159            Source::System => String::new().fmt(f),
160        }
161    }
162}