Skip to main content

frm/commands/
status.rs

1// Copyright (c) 2025-2026 Michael S. Klishin and Contributors
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::env;
10use std::io::{self, Write};
11
12use crate::Result;
13use crate::common::env_vars::RABBITMQ_HOME;
14use crate::config::Config;
15use crate::paths::Paths;
16use crate::version::Version;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Status {
20    pub active: Option<Version>,
21    pub default: Option<Version>,
22    pub releases: Vec<Version>,
23    pub alphas: Vec<Version>,
24}
25
26impl Status {
27    pub fn collect(paths: &Paths) -> Result<Self> {
28        let active = detect_active_version(paths);
29        let config = Config::load(paths)?;
30        let default = config.default_version;
31
32        let all_versions = paths.installed_versions()?;
33        let (alphas, releases): (Vec<_>, Vec<_>) = all_versions
34            .into_iter()
35            .partition(|v| v.is_distributed_via_server_packages_repository());
36
37        Ok(Self {
38            active,
39            default,
40            releases,
41            alphas,
42        })
43    }
44
45    pub fn format(&self) -> String {
46        let mut out = String::new();
47
48        match (&self.active, &self.default) {
49            (Some(active), Some(default)) if active == default => {
50                out.push_str(&format!("Active:  {} (default)\n", active));
51            }
52            (Some(active), Some(default)) => {
53                out.push_str(&format!("Active:  {}\n", active));
54                out.push_str(&format!("Default: {}\n", default));
55            }
56            (Some(active), None) => {
57                out.push_str(&format!("Active:  {}\n", active));
58            }
59            (None, Some(default)) => {
60                out.push_str(&format!("Default: {}\n", default));
61            }
62            (None, None) => {}
63        }
64
65        if self.releases.is_empty() && self.alphas.is_empty() {
66            if out.is_empty() {
67                out.push_str("No RabbitMQ versions installed\n");
68            }
69            return out;
70        }
71
72        if !out.is_empty() {
73            out.push('\n');
74        }
75
76        out.push_str("Installed:\n\n");
77
78        for version in self.releases.iter().rev() {
79            let marker = self.version_marker(version);
80            out.push_str(&format!("  {} {}\n", marker, version));
81        }
82
83        for version in self.alphas.iter().rev() {
84            let marker = self.version_marker(version);
85            out.push_str(&format!("  {} {}\n", marker, version));
86        }
87
88        out
89    }
90
91    fn version_marker(&self, version: &Version) -> &'static str {
92        let is_active = self.active.as_ref() == Some(version);
93        let is_default = self.default.as_ref() == Some(version);
94
95        match (is_active, is_default) {
96            (true, _) => "🟢",
97            (false, true) => "⚪",
98            (false, false) => "  ",
99        }
100    }
101}
102
103fn detect_active_version(paths: &Paths) -> Option<Version> {
104    let rabbitmq_home = env::var(RABBITMQ_HOME).ok()?;
105    let versions_dir = paths.versions_dir();
106    let versions_prefix = versions_dir.to_string_lossy();
107
108    if !rabbitmq_home.starts_with(versions_prefix.as_ref()) {
109        return None;
110    }
111
112    let version_str = rabbitmq_home
113        .strip_prefix(versions_prefix.as_ref())?
114        .trim_start_matches('/');
115
116    version_str.parse().ok()
117}
118
119pub fn run(paths: &Paths) -> Result<()> {
120    let status = Status::collect(paths)?;
121    let output = status.format();
122    io::stdout().write_all(output.as_bytes())?;
123    Ok(())
124}