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 #[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}