1use std::path::Path;
2
3use crate::cli::{AutoUpdateAction, XtazySubcommand};
4use crate::error::{DealerError, DealerResult};
5use crate::state::{DEFAULT_TOOLCHAIN_VERSION, DealerState};
6
7pub(crate) fn run_subcommand(
8 subcommand: XtazySubcommand,
9 workspace_root: &Path,
10) -> DealerResult<String> {
11 let state = DealerState::from_process_env(workspace_root);
12 match subcommand {
13 XtazySubcommand::Install { version } => install(version, &state),
14 XtazySubcommand::Update => update(&state),
15 XtazySubcommand::AutoUpdate { action } => auto_update(action, &state),
16 XtazySubcommand::UseVersion { version } => use_version(&version, &state),
17 XtazySubcommand::Active => active(&state),
18 XtazySubcommand::List => list(&state),
19 XtazySubcommand::Remove { version } => remove(&version, &state),
20 }
21}
22
23fn install(version: Option<String>, state: &DealerState) -> DealerResult<String> {
24 let version = version.unwrap_or_else(|| DEFAULT_TOOLCHAIN_VERSION.to_string());
25 if state.has_complete_toolchain(&version) {
26 state.set_active_version(&version)?;
27 return Ok(format!(
28 "Xtazy toolchain {version} is already installed and active"
29 ));
30 }
31
32 Err(DealerError::NotImplemented {
33 feature: format!(
34 "xtazy install {version} download flow; expected final target '{}'",
35 state.toolchain_dir(&version).display()
36 ),
37 })
38}
39
40fn update(state: &DealerState) -> DealerResult<String> {
41 Err(DealerError::NotImplemented {
42 feature: format!(
43 "xtazy update download flow for active toolchain {}",
44 state.active_version()
45 ),
46 })
47}
48
49fn auto_update(action: Option<AutoUpdateAction>, state: &DealerState) -> DealerResult<String> {
50 match action {
51 Some(AutoUpdateAction::Off) => {
52 state.set_auto_update_enabled(false)?;
53 Ok("Xtazy toolchain auto-update disabled".to_string())
54 }
55 Some(AutoUpdateAction::Status) => Ok(format!(
56 "Xtazy toolchain auto-update is {}",
57 if state.auto_update_enabled() {
58 "enabled"
59 } else {
60 "disabled"
61 }
62 )),
63 None => {
64 state.set_auto_update_enabled(true)?;
65 Ok(
66 "Xtazy toolchain auto-update enabled; update download flow is not implemented yet"
67 .to_string(),
68 )
69 }
70 }
71}
72
73fn use_version(version: &str, state: &DealerState) -> DealerResult<String> {
74 if !state.has_complete_toolchain(version) {
75 return Err(DealerError::Backend(format!(
76 "cannot activate Xtazy toolchain {version}: '{}' is missing or incomplete; expected piko, rusttime/, and std/",
77 state.toolchain_dir(version).display()
78 )));
79 }
80 state.set_active_version(version)?;
81 Ok(format!("Active Xtazy toolchain set to {version}"))
82}
83
84fn active(state: &DealerState) -> DealerResult<String> {
85 let version = state.active_version();
86 Ok(format!(
87 "active_xtazy_toolchain={version}\npath={}\ncomplete={}",
88 state.toolchain_dir(&version).display(),
89 state.has_complete_toolchain(&version)
90 ))
91}
92
93fn list(state: &DealerState) -> DealerResult<String> {
94 let installed = state.installed_toolchains()?;
95 let active = state.active_version();
96 if installed.is_empty() {
97 return Ok(format!(
98 "No Xtazy toolchains installed under {}",
99 state.xtazy_dir().display()
100 ));
101 }
102
103 let mut lines = Vec::new();
104 for toolchain in installed {
105 let active_marker = if toolchain.version == active {
106 " active"
107 } else {
108 ""
109 };
110 let completeness = if toolchain.complete {
111 "complete"
112 } else {
113 "incomplete"
114 };
115 lines.push(format!(
116 "{} [{}]{} {}",
117 toolchain.version,
118 completeness,
119 active_marker,
120 toolchain.path.display()
121 ));
122 }
123 Ok(lines.join("\n"))
124}
125
126fn remove(version: &str, state: &DealerState) -> DealerResult<String> {
127 if version == state.active_version() {
128 return Err(DealerError::Backend(format!(
129 "cannot remove active Xtazy toolchain {version}; switch active version first"
130 )));
131 }
132 state.remove_toolchain(version)?;
133 Ok(format!("Removed Xtazy toolchain {version}"))
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::test_support::TempProject;
140 use std::fs;
141
142 fn state(temp: &TempProject) -> DealerState {
143 DealerState::for_home(temp.path().join("dealer-home"))
144 }
145
146 fn install_fake_toolchain(state: &DealerState, version: &str) {
147 let dir = state.toolchain_dir(version);
148 fs::create_dir_all(dir.join("rusttime")).expect("rusttime dir should be created");
149 fs::create_dir_all(dir.join("std")).expect("std dir should be created");
150 fs::write(dir.join("piko"), "").expect("piko marker should be written");
151 }
152
153 #[test]
154 fn use_active_list_and_remove_local_toolchains() {
155 let temp = TempProject::new("xtazy-local-toolchains");
156 let state = state(&temp);
157 install_fake_toolchain(&state, "0.2.0");
158 install_fake_toolchain(&state, "0.3.0");
159
160 assert_eq!(
161 use_version("0.2.0", &state).expect("use should pass"),
162 "Active Xtazy toolchain set to 0.2.0"
163 );
164 assert!(
165 active(&state)
166 .expect("active should pass")
167 .contains("complete=true")
168 );
169 assert!(
170 list(&state)
171 .expect("list should pass")
172 .contains("0.2.0 [complete] active")
173 );
174
175 assert!(remove("0.2.0", &state).is_err());
176 assert_eq!(
177 remove("0.3.0", &state).expect("remove should pass"),
178 "Removed Xtazy toolchain 0.3.0"
179 );
180 }
181
182 #[test]
183 fn auto_update_status_round_trips() {
184 let temp = TempProject::new("xtazy-auto-update");
185 let state = state(&temp);
186
187 assert_eq!(
188 auto_update(Some(AutoUpdateAction::Status), &state).expect("status should pass"),
189 "Xtazy toolchain auto-update is disabled"
190 );
191 assert!(
192 auto_update(None, &state)
193 .expect("enable should pass")
194 .contains("enabled")
195 );
196 assert_eq!(
197 auto_update(Some(AutoUpdateAction::Status), &state).expect("status should pass"),
198 "Xtazy toolchain auto-update is enabled"
199 );
200 }
201}