1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
use clap::Subcommand;
use colored::Colorize;
use super::Flags;
use crate::{
channel::UpstreamMatch,
config::Config,
report,
state::LocalState,
toolchain::{Toolchain, ToolchainJustification},
};
/// Appends a status marker to a listing row: yellow as one span when color is on, plain otherwise.
fn push_marker(line: &mut String, text: &str, use_color: bool) {
use core::fmt::Write;
if use_color {
write!(line, " {}", text.yellow()).unwrap();
} else {
write!(line, " {text}").unwrap();
}
}
#[derive(Debug, Subcommand)]
pub enum ShowCommand {
/// Show the active toolchain.
#[command(name = "active-toolchain")]
Current {
#[clap(flatten)]
flags: Flags,
},
/// Display the computed value of MIDENUP_HOME
Home,
/// List installed toolchains
List {
#[clap(flatten)]
flags: Flags,
},
}
impl ShowCommand {
pub fn execute(&self, config: &Config, state: &LocalState) -> anyhow::Result<()> {
use core::fmt::Write;
match self {
Self::Current { .. } => {
let (toolchain, justification) = Toolchain::current(config, None)?;
// The justification is commentary, not the result, so it goes to stderr.
if report::verbosity() >= report::Verbosity::Debug {
match justification {
ToolchainJustification::MidenToolchainFile { path } => {
crate::info!("found a miden-toolchain.toml file in {}", path.display())
},
ToolchainJustification::Override => {
crate::info!(
"system default has been overridden via `midenup override`"
)
},
ToolchainJustification::Requested => {
crate::info!("explicitly requested by user")
},
ToolchainJustification::Default => {
crate::info!("current toolchain is system default")
},
}
}
println!("{}", toolchain.channel);
Ok(())
},
Self::Home => {
println!("{}", config.midenup_home.display());
Ok(())
},
Self::List { .. } => {
// Installed toolchains are recorded locally, so this works with no network at all.
// Upstream only adds *markers* -- which networks name a channel, which
// installations have updates or are no longer published -- so when it is
// unavailable they are simply omitted rather than guessed at.
let upstream = config.upstream_manifest().ok();
// The upstream lookup may have emitted a report to stderr, so restore stdout's
// color policy immediately before rendering the result.
let use_color = report::prepare_stdout_color();
// Every `toolchains/<network>` link on this machine: the networks the user
// installed, and what to compare with upstream.
let local_links = crate::networks::links(&config.midenup_home);
let toolchains_display: Vec<_> = state
.installations
.iter()
.map(|installation| {
let name = &installation.channel;
let mut line = format!("{name}");
// The networks the user installed onto this channel, not every network
// upstream says runs it. Several may share one channel, so this is a
// list rather than a single marker.
let networks: Vec<&str> = local_links
.iter()
.filter(|(_, linked)| *linked == name)
.map(|(network, _)| network.as_str())
.collect();
if !networks.is_empty() {
if use_color {
write!(
&mut line,
" {}",
format!("({})", networks.join(", ")).bold()
)
.unwrap();
} else {
write!(&mut line, " ({})", networks.join(", ")).unwrap();
}
}
// If a network whose link still names this channel while upstream has
// moved it, we show it to the user.
if let Some(manifest) = upstream {
for (network, linked) in &local_links {
if linked != name {
continue;
}
if let Some(notice) = crate::networks::drift(
network,
linked,
manifest.network_version(network),
) {
push_marker(&mut line, ¬ice, use_color);
}
}
}
// A migrated record describes a pre-publication tree that no receipt
// covers, so midenup will not execute against it. Saying so is the whole
// point: the user's toolchain still works, but only after it is installed
// properly, and they should not have to infer that from a failure.
if !installation.is_managed() {
push_marker(
&mut line,
&format!("(needs reinstallation) -- run `midenup install {name}`"),
use_color,
);
}
if upstream.is_some() {
// The lookup follows migration lineage the way `update` does, so a
// superseded channel shows as updatable rather than gone. Verbatim
// manifest content: a listing pins nothing and reaches for no source.
match installation.as_channel().upstream_counterpart_raw(config) {
// An unmanaged record already carries its one instruction, the
// reinstall above; a second directive would contradict it.
Some(_) if !installation.is_managed() => {},
Some((channel, upstream_match)) => {
let has_update = match upstream_match {
// Being superseded is the update: running it migrates.
UpstreamMatch::Migrated { .. } => true,
UpstreamMatch::UpstreamCounterpart => {
super::update::needs_update(
config,
installation,
channel,
)
},
};
if has_update {
push_marker(
&mut line,
&format!(
"(update available -- run `midenup update {name}`)"
),
use_color,
);
}
},
// Retained, not deleted: the user may still want `var/` and an
// explicit uninstall (spec section 12.3).
None => push_marker(&mut line, "(unavailable upstream)", use_color),
}
}
line
})
.collect();
if use_color {
println!("{}", "Installed toolchains:".bold().underline());
} else {
println!("Installed toolchains:");
}
for toolchain in toolchains_display {
println!("{toolchain}");
}
Ok(())
},
}
}
}