Skip to main content

dealer/
xtazy.rs

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}