Skip to main content

max / alloy

Adopt lint block, pin stable toolchain, cargo fmt, fix clippy Wire the shared clippy::pedantic block, pin channel=stable (rustfmt + clippy), normalize with cargo fmt, and reach green under -D warnings: associated-fn conversion for self-less helpers, write! over format!-push, find_map over filter_map().next(). No suppressions.
Co-Authored-By
Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-24 15:05 UTC
Signed with PGP, not checked
Commit: 779f26c2205661f15d33cd86c20a946cd6515c8a
Parent: 028c9dd
14 files changed, +182 insertions, -143 deletions
M Cargo.toml +32
@@ -17,3 +17,35 @@
17 17 lto = "thin"
18 18 codegen-units = 1
19 19 strip = "symbols"
20 +
21 + [workspace.lints.rust]
22 + unused = "warn"
23 + unreachable_pub = "warn"
24 +
25 + [workspace.lints.clippy]
26 + pedantic = { level = "warn", priority = -1 }
27 + # Allow-list tuned from a measured breakdown across server/multithreaded/pter
28 + # (2026-07-22). These are the high-churn / low-signal pedantic lints; everything
29 + # else in `pedantic` stays a warning. Keep this block identical across repos.
30 + module_name_repetitions = "allow"
31 + # Doc lints. No docs-completeness push is underway.
32 + missing_errors_doc = "allow"
33 + missing_panics_doc = "allow"
34 + doc_markdown = "allow"
35 + # Numeric casts. Endemic and mostly intentional in size and byte math.
36 + cast_possible_truncation = "allow"
37 + cast_sign_loss = "allow"
38 + cast_precision_loss = "allow"
39 + cast_possible_wrap = "allow"
40 + cast_lossless = "allow"
41 + # Subjective structure and style nags. High churn, low signal.
42 + must_use_candidate = "allow"
43 + too_many_lines = "allow"
44 + struct_excessive_bools = "allow"
45 + similar_names = "allow"
46 + items_after_statements = "allow"
47 + single_match_else = "allow"
48 + # Frequent false-positives in TUI and router-heavy code.
49 + match_same_arms = "allow"
50 + unnecessary_wraps = "allow"
51 + type_complexity = "allow"
@@ -23,3 +23,6 @@
23 23 makeover.workspace = true
24 24 sha-crypt = "0.6.0"
25 25 getrandom = "0.4.3"
26 +
27 + [lints]
28 + workspace = true
@@ -61,7 +61,7 @@
61 61 const PANE_DEVICES: usize = 1;
62 62
63 63 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
64 - pub enum Direction {
64 + pub(crate) enum Direction {
65 65 Output,
66 66 Input,
67 67 }
@@ -94,7 +94,7 @@
94 94 }
95 95
96 96 #[derive(Debug, Clone)]
97 - pub struct Device {
97 + pub(crate) struct Device {
98 98 pub index: u32,
99 99 pub name: String,
100 100 pub description: String,
@@ -125,7 +125,7 @@
125 125
126 126 /// An application or service moving audio through a device.
127 127 #[derive(Debug, Clone)]
128 - pub struct Stream {
128 + pub(crate) struct Stream {
129 129 pub index: u32,
130 130 /// Who is playing. `media.name` (what is playing) is deliberately not kept
131 131 /// alongside it: the streams pane is the narrower of the two and has no
@@ -165,7 +165,7 @@
165 165 /// different `pactl` nouns and identifiers, so the backend takes one of these
166 166 /// rather than duplicating every method.
167 167 #[derive(Debug, Clone, Copy)]
168 - pub enum Target<'a> {
168 + pub(crate) enum Target<'a> {
169 169 Device(&'a Device),
170 170 Stream(&'a Stream),
171 171 }
@@ -197,7 +197,7 @@
197 197 }
198 198 }
199 199
200 - pub trait Backend {
200 + pub(crate) trait Backend {
201 201 fn name(&self) -> &'static str;
202 202 fn list_devices(&self, log: &mut CommandLog) -> Result<Vec<Device>>;
203 203 fn list_streams(&self, log: &mut CommandLog) -> Result<Vec<Stream>>;
@@ -216,7 +216,7 @@
216 216 }
217 217
218 218 /// Pick a backend: `pactl` when it answers, the mock otherwise.
219 - pub fn detect() -> Box<dyn Backend> {
219 + pub(crate) fn detect() -> Box<dyn Backend> {
220 220 if Invocation::new("pactl").arg("--version").probe() {
221 221 Box::new(PaCtl)
222 222 } else {
@@ -224,7 +224,7 @@
224 224 }
225 225 }
226 226
227 - pub struct PaCtl;
227 + pub(crate) struct PaCtl;
228 228
229 229 impl Backend for PaCtl {
230 230 fn name(&self) -> &'static str {
@@ -304,7 +304,7 @@
304 304 }
305 305
306 306 /// Fixed sample state, for machines without PipeWire.
307 - pub struct Mock;
307 + pub(crate) struct Mock;
308 308
309 309 impl Backend for Mock {
310 310 fn name(&self) -> &'static str {
@@ -535,7 +535,7 @@
535 535 }
536 536
537 537 /// The `alloy audio` screen.
538 - pub struct AudioView {
538 + pub(crate) struct AudioView {
539 539 backend: Box<dyn Backend>,
540 540 devices: Vec<Device>,
541 541 streams: Vec<Stream>,
@@ -547,7 +547,7 @@
547 547 }
548 548
549 549 impl AudioView {
550 - pub fn new(log: &mut CommandLog) -> Self {
550 + pub(crate) fn new(log: &mut CommandLog) -> Self {
551 551 let mut view = Self {
552 552 backend: detect(),
553 553 devices: Vec::new(),
@@ -683,7 +683,7 @@
683 683 }
684 684 }
685 685
686 - fn stream_row<'a>(&self, theme: &Theme, stream: &'a Stream) -> Line<'a> {
686 + fn stream_row<'a>(theme: &Theme, stream: &'a Stream) -> Line<'a> {
687 687 let volume = if stream.muted {
688 688 " --".to_string()
689 689 } else {
@@ -696,7 +696,7 @@
696 696 ])
697 697 }
698 698
699 - fn device_row<'a>(&self, theme: &Theme, device: &'a Device) -> Line<'a> {
699 + fn device_row<'a>(theme: &Theme, device: &'a Device) -> Line<'a> {
700 700 let volume = if device.muted {
701 701 " --".to_string()
702 702 } else {
@@ -784,7 +784,7 @@
784 784 let stream_rows: Vec<Line> = self
785 785 .streams
786 786 .iter()
787 - .map(|stream| self.stream_row(theme, stream))
787 + .map(|stream| Self::stream_row(theme, stream))
788 788 .collect();
789 789 let left = render_pane(
790 790 frame,
@@ -809,7 +809,7 @@
809 809 let device_rows: Vec<Line> = self
810 810 .devices
811 811 .iter()
812 - .map(|device| self.device_row(theme, device))
812 + .map(|device| Self::device_row(theme, device))
813 813 .collect();
814 814 let right = render_pane(
815 815 frame,
@@ -886,7 +886,7 @@
886 886 KeyCode::Char('j') | KeyCode::Down => cursor.next(),
887 887 KeyCode::Char('k') | KeyCode::Up => cursor.prev(),
888 888 // `=` alongside `+` so the shifted key is not required.
889 - KeyCode::Char('+') | KeyCode::Char('=') => {
889 + KeyCode::Char('+' | '=') => {
890 890 self.set_volume(log, i16::from(VOLUME_STEP));
891 891 }
892 892 KeyCode::Char('-') => self.set_volume(log, -i16::from(VOLUME_STEP)),
@@ -1279,7 +1279,7 @@
1279 1279 view.route(&mut log);
1280 1280 assert!(view.error.is_some(), "the route was refused");
1281 1281
1282 - for _ in 0..DEVICE_POLL_TICKS + 1 {
1282 + for _ in 0..=DEVICE_POLL_TICKS {
1283 1283 view.tick(&mut log);
1284 1284 }
1285 1285 assert!(
@@ -46,17 +46,17 @@
46 46 /// two-row pane, and the command the user actually pressed a key for scrolls
47 47 /// off before they can read it.
48 48 #[derive(Debug, Default)]
49 - pub struct CommandLog {
49 + pub(crate) struct CommandLog {
50 50 entries: VecDeque<LogEntry>,
51 51 muted: bool,
52 52 }
53 53
54 54 impl CommandLog {
55 - pub fn new() -> Self {
55 + pub(crate) fn new() -> Self {
56 56 Self::default()
57 57 }
58 58
59 - pub fn record(&mut self, command: impl Into<String>, outcome: Severity) {
59 + pub(crate) fn record(&mut self, command: impl Into<String>, outcome: Severity) {
60 60 if self.muted {
61 61 return;
62 62 }
@@ -74,7 +74,7 @@
74 74 /// Scoped rather than a pair of set-muted calls so the suppression cannot
75 75 /// leak: an early return or a `?` inside `f` still restores the previous
76 76 /// state. Nesting restores to the enclosing state rather than to unmuted.
77 - pub fn quiet<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
77 + pub(crate) fn quiet<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
78 78 let was_muted = self.muted;
79 79 self.muted = true;
80 80 let out = f(self);
@@ -87,7 +87,7 @@
87 87 /// `VecDeque` is not contiguous, so the slice view needs the ring
88 88 /// straightened first; this is called once per frame, and after the first
89 89 /// call the deque is already contiguous.
90 - pub fn entries(&mut self) -> &[LogEntry] {
90 + pub(crate) fn entries(&mut self) -> &[LogEntry] {
91 91 self.entries.make_contiguous();
92 92 self.entries.as_slices().0
93 93 }
@@ -109,10 +109,10 @@
109 109 /// Held as bytes, not a `String`, for two reasons. A pipe takes bytes anyway, so
110 110 /// no conversion happens at the moment of use, and a `Vec<u8>` can be zeroed on
111 111 /// drop without `unsafe`, which a `String` cannot.
112 - pub struct Secret(Vec<u8>);
112 + pub(crate) struct Secret(Vec<u8>);
113 113
114 114 impl Secret {
115 - pub fn new(value: impl Into<Vec<u8>>) -> Self {
115 + pub(crate) fn new(value: impl Into<Vec<u8>>) -> Self {
116 116 Self(value.into())
117 117 }
118 118
@@ -120,7 +120,7 @@
120 120 ///
121 121 /// Named to stand out in review: a call to this is the only place a secret
122 122 /// can leave the type, so it is the only place worth checking.
123 - pub fn expose(&self) -> &[u8] {
123 + pub(crate) fn expose(&self) -> &[u8] {
124 124 &self.0
125 125 }
126 126 }
@@ -150,7 +150,7 @@
150 150 /// A command line, held as argv rather than a string so it is executed exactly
151 151 /// as displayed — no shell, no quoting round-trip, no injection surface.
152 152 #[derive(Debug)]
153 - pub struct Invocation {
153 + pub(crate) struct Invocation {
154 154 program: String,
155 155 args: Vec<String>,
156 156 /// Piped to the child on stdin. Never displayed, never logged.
@@ -163,7 +163,7 @@
163 163 }
164 164
165 165 impl Invocation {
166 - pub fn new(program: impl Into<String>) -> Self {
166 + pub(crate) fn new(program: impl Into<String>) -> Self {
167 167 Self {
168 168 program: program.into(),
169 169 args: Vec::new(),
@@ -177,17 +177,17 @@
177 177 /// reaches the log pane. What the pane shows instead is the argv plus a note
178 178 /// that input was withheld, so the line stays honest about the fact that
179 179 /// something was piped in without being honest about what.
180 - pub fn stdin(mut self, secret: Secret) -> Self {
180 + pub(crate) fn stdin(mut self, secret: Secret) -> Self {
181 181 self.stdin = Some(secret);
182 182 self
183 183 }
184 184
185 - pub fn arg(mut self, arg: impl Into<String>) -> Self {
185 + pub(crate) fn arg(mut self, arg: impl Into<String>) -> Self {
186 186 self.args.push(arg.into());
187 187 self
188 188 }
189 189
190 - pub fn args<I, S>(mut self, args: I) -> Self
190 + pub(crate) fn args<I, S>(mut self, args: I) -> Self
191 191 where
192 192 I: IntoIterator<Item = S>,
193 193 S: Into<String>,
@@ -205,7 +205,7 @@
205 205 /// The `#` marks commentary the way the mock backends already do, and the
206 206 /// same reasoning applies as for [`Effect::Write`]: being honest about the
207 207 /// shape beats contorting the pane into showing something it must not.
208 - pub fn display(&self) -> String {
208 + pub(crate) fn display(&self) -> String {
209 209 let mut out = String::from(&self.program);
210 210 for arg in &self.args {
211 211 out.push(' ');
@@ -225,7 +225,7 @@
225 225
226 226 /// Run the command and return its stdout, recording the invocation and its
227 227 /// outcome in `log`.
228 - pub fn run(&self, log: &mut CommandLog) -> Result<String> {
228 + pub(crate) fn run(&self, log: &mut CommandLog) -> Result<String> {
229 229 let result = self.capture();
230 230 log.record(
231 231 self.display(),
@@ -241,7 +241,7 @@
241 241 /// Run without logging — for probes, which run before the user has asked
242 242 /// for anything and would otherwise fill the pane with noise the user did
243 243 /// not trigger.
244 - pub fn probe(&self) -> bool {
244 + pub(crate) fn probe(&self) -> bool {
245 245 self.capture().is_ok()
246 246 }
247 247
@@ -253,7 +253,7 @@
253 253 /// effect — this is for calls that should never be logged at all, such as
254 254 /// `debug` subcommands the console reads but no user should be told to
255 255 /// run.
256 - pub fn capture_quiet(&self) -> Result<String> {
256 + pub(crate) fn capture_quiet(&self) -> Result<String> {
257 257 self.capture()
258 258 }
259 259
@@ -268,7 +268,7 @@
268 268 /// stdio, so there is no pipe to write into, and silently dropping the
269 269 /// input would hand the child a command missing the half that mattered.
270 270 /// Nothing does this today; the assertion is here so nothing starts to.
271 - pub fn command(&self) -> Command {
271 + pub(crate) fn command(&self) -> Command {
272 272 debug_assert!(
273 273 self.stdin.is_none(),
274 274 "a suspended command inherits stdio and cannot carry a secret"
@@ -311,7 +311,7 @@
311 311 /// nothing more is known. Whether it succeeded is the sequence's to report,
312 312 /// and it does so through the run screen rather than by amending a log line
313 313 /// that has already scrolled.
314 - pub fn spawn_streaming(&self, log: &mut CommandLog) -> Result<std::process::Child> {
314 + pub(crate) fn spawn_streaming(&self, log: &mut CommandLog) -> Result<std::process::Child> {
315 315 let mut command = Command::new(&self.program);
316 316 command
317 317 .args(&self.args)
@@ -412,7 +412,7 @@
412 412 /// Not `Clone`, since [`Invocation`] is not: duplicating something that may
413 413 /// hold a [`Secret`] would mean another buffer to scrub, and nothing needs it.
414 414 #[derive(Debug)]
415 - pub enum Effect {
415 + pub(crate) enum Effect {
416 416 /// Run a command.
417 417 Run(Invocation),
418 418 /// Write a file, replacing whatever was there.
@@ -434,7 +434,7 @@
434 434 /// shell. A [`Effect::Write`] cannot round-trip that way — the contents are
435 435 /// a whole file — so it names the verb and the path instead. Being honest
436 436 /// about the shape beats contorting a heredoc into the pane.
437 - pub fn display(&self) -> String {
437 + pub(crate) fn display(&self) -> String {
438 438 match self {
439 439 Effect::Run(invocation) => invocation.display(),
440 440 Effect::Write { path, .. } => format!("write {}", contract_home(path)),
@@ -442,7 +442,7 @@
442 442 }
443 443
444 444 /// Perform it, recording what was done and whether it worked.
445 - pub fn apply(&self, log: &mut CommandLog) -> Result<()> {
445 + pub(crate) fn apply(&self, log: &mut CommandLog) -> Result<()> {
446 446 match self {
447 447 Effect::Run(invocation) => invocation.run(log).map(drop),
448 448 Effect::Write {
@@ -18,7 +18,7 @@
18 18
19 19 /// A line of text with a caret in it.
20 20 #[derive(Debug, Default, Clone)]
21 - pub struct TextField {
21 + pub(crate) struct TextField {
22 22 value: String,
23 23 /// Caret position, in `char`s from the start. Equal to the char count when
24 24 /// the caret is past the last character, which is where typing appends.
@@ -26,11 +26,11 @@
26 26 }
27 27
28 28 impl TextField {
29 - pub fn new() -> Self {
29 + pub(crate) fn new() -> Self {
30 30 Self::default()
31 31 }
32 32
33 - pub fn value(&self) -> &str {
33 + pub(crate) fn value(&self) -> &str {
34 34 &self.value
35 35 }
36 36
@@ -41,7 +41,7 @@
41 41 /// asserting that through `split` would describe the text either side of it
42 42 /// rather than the position itself.
43 43 #[allow(dead_code)]
44 - pub fn caret(&self) -> usize {
44 + pub(crate) fn caret(&self) -> usize {
45 45 self.caret
46 46 }
47 47
@@ -62,14 +62,14 @@
62 62 }
63 63
64 64 /// Type a character at the caret.
65 - pub fn insert(&mut self, c: char) {
65 + pub(crate) fn insert(&mut self, c: char) {
66 66 let at = self.byte_of(self.caret);
67 67 self.value.insert(at, c);
68 68 self.caret += 1;
69 69 }
70 70
71 71 /// Delete the character before the caret.
72 - pub fn backspace(&mut self) {
72 + pub(crate) fn backspace(&mut self) {
73 73 if self.caret == 0 {
74 74 return;
75 75 }
@@ -79,7 +79,7 @@
79 79 }
80 80
81 81 /// Delete the character under the caret.
82 - pub fn delete(&mut self) {
82 + pub(crate) fn delete(&mut self) {
83 83 if self.caret >= self.chars() {
84 84 return;
85 85 }
@@ -90,21 +90,21 @@
90 90 /// Clamped rather than wrapping: a caret that jumps to the far end of the
91 91 /// line when you press Left once too often is the kind of thing that gets
92 92 /// a character typed into the wrong place.
93 - pub fn left(&mut self) {
93 + pub(crate) fn left(&mut self) {
94 94 self.caret = self.caret.saturating_sub(1);
95 95 }
96 96
97 - pub fn right(&mut self) {
97 + pub(crate) fn right(&mut self) {
98 98 if self.caret < self.chars() {
99 99 self.caret += 1;
100 100 }
101 101 }
102 102
103 - pub fn home(&mut self) {
103 + pub(crate) fn home(&mut self) {
104 104 self.caret = 0;
105 105 }
106 106
107 - pub fn end(&mut self) {
107 + pub(crate) fn end(&mut self) {
108 108 self.caret = self.chars();
109 109 }
110 110
@@ -112,7 +112,7 @@
112 112 ///
113 113 /// For seeding a field with a default the user is expected to edit rather
114 114 /// than retype, which is what the hostname step does.
115 - pub fn set(&mut self, value: impl Into<String>) {
115 + pub(crate) fn set(&mut self, value: impl Into<String>) {
116 116 self.value = value.into();
117 117 self.caret = self.chars();
118 118 }
@@ -124,7 +124,7 @@
124 124 /// the theme, and this type deliberately knows nothing about one. The
125 125 /// middle is `None` when the caret is past the end, where a renderer draws
126 126 /// a block on empty space.
127 - pub fn split(&self) -> (&str, Option<char>, &str) {
127 + pub(crate) fn split(&self) -> (&str, Option<char>, &str) {
128 128 let at = self.byte_of(self.caret);
129 129 let (before, rest) = self.value.split_at(at);
130 130 let mut chars = rest.chars();
@@ -220,7 +220,7 @@
220 220 /// this is what the final `bootc install to-disk` invocation is built from,
221 221 /// and what the summary step reads back. Nothing else survives to the end.
222 222 #[derive(Debug, Default)]
223 - pub struct Answers {
223 + pub(crate) struct Answers {
224 224 /// Device path of the install target, e.g. `/dev/nvme0n1`.
225 225 pub disk: Option<String>,
226 226 /// Written to `/etc/hostname` on the installed system.
@@ -522,13 +522,12 @@
522 522 fn passwd_ids(listing: &str, username: &str) -> Result<String, String> {
523 523 listing
524 524 .lines()
525 - .filter_map(|line| {
525 + .find_map(|line| {
526 526 let mut fields = line.split(':');
527 527 (fields.next()? == username).then_some(())?;
528 528 let (_password, uid, gid) = (fields.next()?, fields.next()?, fields.next()?);
529 529 Some(format!("{uid}:{gid}"))
530 530 })
531 - .next()
532 531 .ok_or_else(|| format!("useradd left no passwd entry for {username}"))
533 532 }
534 533
@@ -802,7 +801,7 @@
802 801 Stage::Resolve {
803 802 invocation: partition_types(disk),
804 803 then: Box::new(move |listing| {
805 - let partition = root_partition(listing)?.to_string();
804 + let partition = root_partition(listing)?.clone();
806 805 Ok(vec![
807 806 // Release whatever bootc left mounted on the partition
808 807 // before mounting it. Its own leftover is read-only, and a
@@ -854,7 +853,7 @@
854 853
855 854 /// A whole disk, as an install target.
856 855 #[derive(Debug, Clone, PartialEq, Eq)]
857 - pub struct Disk {
856 + pub(crate) struct Disk {
858 857 /// Device path, which is what `bootc install to-disk` takes.
859 858 pub path: String,
860 859 pub name: String,
@@ -876,7 +875,7 @@
876 875 /// and "your install medium is not a target" is a thing worth saying once
877 876 /// rather than a row silently absent.
878 877 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
879 - pub enum Blocked {
878 + pub(crate) enum Blocked {
880 879 /// Something on it is mounted, so it is the running system or the medium
881 880 /// booted from. In a live install both of those are exactly the disk a
882 881 /// user must not overwrite.
@@ -885,7 +884,7 @@
885 884 }
886 885
887 886 impl Blocked {
888 - pub const fn label(self) -> &'static str {
887 + pub(crate) const fn label(self) -> &'static str {
889 888 match self {
890 889 Self::InUse => "in use",
891 890 Self::ReadOnly => "read-only",
@@ -896,7 +895,7 @@
896 895 ///
897 896 /// Phrased to follow the device path, so the whole message reads as one
898 897 /// sentence: "/dev/nvme0n1 is mounted; it holds the running system".
899 - pub const fn reason(self) -> &'static str {
898 + pub(crate) const fn reason(self) -> &'static str {
900 899 match self {
901 900 Self::InUse => "is mounted; it holds the running system or the install medium",
902 901 Self::ReadOnly => "is read-only",
@@ -909,7 +908,7 @@
909 908 ///
910 909 /// Read-only is reported ahead of in-use so a write-protected medium says
911 910 /// so, rather than blaming the mount it also has.
912 - pub fn blocker(&self) -> Option<Blocked> {
911 + pub(crate) fn blocker(&self) -> Option<Blocked> {
913 912 if self.read_only {
914 913 Some(Blocked::ReadOnly)
915 914 } else if !self.mountpoints.is_empty() {
@@ -950,7 +949,7 @@
950 949 ("kB", 1_000),
951 950 ];
952 951
953 - pub fn format_size(bytes: u64) -> String {
952 + pub(crate) fn format_size(bytes: u64) -> String {
954 953 for (unit, scale) in UNITS {
955 954 if bytes >= scale {
956 955 return format!("{:.1} {unit}", bytes as f64 / scale as f64);
@@ -990,7 +989,7 @@
990 989 // ---- backend ----
991 990
992 991 /// A source of disks.
993 - pub trait Backend {
992 + pub(crate) trait Backend {
994 993 fn name(&self) -> &'static str;
995 994 fn list(&self, log: &mut CommandLog) -> Result<Vec<Disk>>;
996 995 }
@@ -998,7 +997,7 @@
998 997 /// Pick a backend: the real one when `lsblk` answers, the mock otherwise.
999 998 ///
1000 999 /// A `--version` probe rather than a `which` check, matching `net` and `mesh`.
1001 - pub fn detect() -> Box<dyn Backend> {
1000 + pub(crate) fn detect() -> Box<dyn Backend> {
1002 1001 if Invocation::new("lsblk").arg("--version").probe() {
1003 1002 Box::new(LsBlk)
1004 1003 } else {
@@ -1006,7 +1005,7 @@
1006 1005 }
1007 1006 }
1008 1007
1009 - pub struct LsBlk;
1008 + pub(crate) struct LsBlk;
1010 1009
1011 1010 impl LsBlk {
1012 1011 /// `-b` for bytes, so the size arrives as a number to format rather than a
@@ -1034,7 +1033,7 @@
1034 1033 }
1035 1034
1036 1035 /// Fixed sample disks, for machines without lsblk.
1037 - pub struct Mock;
1036 + pub(crate) struct Mock;
1038 1037
1039 1038 impl Backend for Mock {
1040 1039 fn name(&self) -> &'static str {
@@ -1176,7 +1175,7 @@
1176 1175 // ---- the view ----
1177 1176
1178 1177 /// The `alloy install` screen.
1179 - pub struct InstallView {
1178 + pub(crate) struct InstallView {
1180 1179 steps: Steps,
1181 1180 backend: Box<dyn Backend>,
1182 1181 disks: Vec<Disk>,
@@ -1204,7 +1203,7 @@
1204 1203 }
1205 1204
1206 1205 impl InstallView {
1207 - pub fn new(log: &mut CommandLog) -> Self {
1206 + pub(crate) fn new(log: &mut CommandLog) -> Self {
1208 1207 let mut hostname = TextField::new();
1209 1208 // Seeded rather than blank: the default is what most installs want, and
1210 1209 // a field arriving pre-filled says what shape of answer is expected.
@@ -1625,7 +1624,7 @@
1625 1624 }
1626 1625
1627 1626 /// One disk, as a row: the columns from [`row_columns`], styled.
1628 - fn row<'a>(&self, theme: &Theme, disk: &'a Disk) -> Line<'a> {
1627 + fn row<'a>(theme: &Theme, disk: &'a Disk) -> Line<'a> {
1629 1628 let (status, severity) = match disk.blocker() {
1630 1629 Some(blocked) => (blocked.label(), Severity::Warn),
1631 1630 None => ("", Severity::Info),
@@ -1710,7 +1709,7 @@
1710 1709 let rows: Vec<Line> = self
1711 1710 .disks
1712 1711 .iter()
1713 - .map(|disk| self.row(theme, disk))
1712 + .map(|disk| Self::row(theme, disk))
1714 1713 .collect();
1715 1714 frame.render_widget(
1716 1715 AlloyList::new(theme, rows).selected(self.cursor.selected()),
@@ -57,7 +57,7 @@
57 57 const GO_ZERO_TIME_PREFIX: &str = "0001-01-01";
58 58
59 59 #[derive(Debug, Clone)]
60 - pub struct Peer {
60 + pub(crate) struct Peer {
61 61 pub hostname: String,
62 62 pub os: String,
63 63 /// First tailnet address. Peers can hold both a v4 and a v6; the v4 is
@@ -111,7 +111,7 @@
111 111 /// control plane am I on" is exactly the question someone running Headscale
112 112 /// wants answered without dropping to a shell.
113 113 #[derive(Debug, Clone, PartialEq, Eq)]
114 - pub enum ControlPlane {
114 + pub(crate) enum ControlPlane {
115 115 /// The vendor's own control plane.
116 116 Hosted,
117 117 /// A self-hosted control server, named by host.
@@ -133,7 +133,7 @@
133 133 }
134 134
135 135 #[derive(Debug, Clone)]
136 - pub struct MeshStatus {
136 + pub(crate) struct MeshStatus {
137 137 /// `Running`, `Stopped`, `NeedsLogin`, and friends. Reported verbatim
138 138 /// rather than mapped to an enum: it is a Tailscale-owned vocabulary that
139 139 /// gains members, and showing an unfamiliar one is better than collapsing
@@ -149,7 +149,7 @@
149 149 }
150 150 }
151 151
152 - pub trait Backend {
152 + pub(crate) trait Backend {
153 153 fn name(&self) -> &'static str;
154 154 fn status(&self, log: &mut CommandLog) -> Result<MeshStatus>;
155 155
@@ -173,7 +173,7 @@
173 173 /// Tailscale is the only real implementation today, and covers Headscale too
174 174 /// since Headscale drives this same client. A different mesh would be another
175 175 /// arm here.
176 - pub fn detect() -> Box<dyn Backend> {
176 + pub(crate) fn detect() -> Box<dyn Backend> {
177 177 if Invocation::new("tailscale").arg("version").probe() {
178 178 Box::new(Tailscale)
179 179 } else {
@@ -181,7 +181,7 @@
181 181 }
182 182 }
183 183
184 - pub struct Tailscale;
184 + pub(crate) struct Tailscale;
185 185
186 186 impl Backend for Tailscale {
187 187 fn name(&self) -> &'static str {
@@ -246,7 +246,7 @@
246 246 }
247 247
248 248 /// Fixed sample state, for machines without Tailscale.
249 - pub struct Mock;
249 + pub(crate) struct Mock;
250 250
251 251 impl Backend for Mock {
252 252 fn name(&self) -> &'static str {
@@ -461,7 +461,7 @@
461 461 }
462 462
463 463 /// The `alloy tail` screen.
464 - pub struct MeshView {
464 + pub(crate) struct MeshView {
465 465 backend: Box<dyn Backend>,
466 466 status: Option<MeshStatus>,
467 467 control_plane: ControlPlane,
@@ -471,7 +471,7 @@
471 471 }
472 472
473 473 impl MeshView {
474 - pub fn new(log: &mut CommandLog) -> Self {
474 + pub(crate) fn new(log: &mut CommandLog) -> Self {
475 475 let backend = detect();
476 476 // Once, at startup: changing the control server requires
477 477 // re-authenticating, so it cannot change under a running view.
@@ -542,7 +542,7 @@
542 542 }
543 543 }
544 544
545 - fn row<'a>(&self, theme: &Theme, peer: &'a Peer) -> Line<'a> {
545 + fn row<'a>(theme: &Theme, peer: &'a Peer) -> Line<'a> {
546 546 Line::from(vec![
547 547 text::bold(theme, format!("{:<18}", truncate(&peer.hostname, 17))),
548 548 text::muted(theme, format!("{:<8}", truncate(&peer.os, 7))),
@@ -608,7 +608,7 @@
608 608 return;
609 609 }
610 610
611 - let rows: Vec<Line> = peers.iter().map(|peer| self.row(theme, peer)).collect();
611 + let rows: Vec<Line> = peers.iter().map(|peer| Self::row(theme, peer)).collect();
612 612 frame.render_widget(
613 613 AlloyList::new(theme, rows).selected(self.cursor.selected()),
614 614 inner,
@@ -17,7 +17,7 @@
17 17 use crate::shell::{Flow, View, block_title};
18 18
19 19 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
20 - pub enum Kind {
20 + pub(crate) enum Kind {
21 21 Wired,
22 22 Wireless,
23 23 Loopback,
@@ -50,7 +50,7 @@
50 50 }
51 51
52 52 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
53 - pub enum State {
53 + pub(crate) enum State {
54 54 Connected,
55 55 Disconnected,
56 56 Unavailable,
@@ -94,7 +94,7 @@
94 94 }
95 95
96 96 #[derive(Debug, Clone)]
97 - pub struct Interface {
97 + pub(crate) struct Interface {
98 98 pub name: String,
99 99 pub kind: Kind,
100 100 pub state: State,
@@ -103,7 +103,7 @@
103 103 }
104 104
105 105 /// A source of interface state.
106 - pub trait Backend {
106 + pub(crate) trait Backend {
107 107 fn name(&self) -> &'static str;
108 108 fn list(&self, log: &mut CommandLog) -> Result<Vec<Interface>>;
109 109 }
@@ -113,7 +113,7 @@
113 113 /// The probe is a real invocation rather than a `which` check — an `nmcli`
114 114 /// binary that cannot reach a NetworkManager daemon (a container, a live ISO
115 115 /// mid-boot) is worse than no `nmcli` at all, and only running it reveals that.
116 - pub fn detect() -> Box<dyn Backend> {
116 + pub(crate) fn detect() -> Box<dyn Backend> {
117 117 if Invocation::new("nmcli").arg("--version").probe() {
118 118 Box::new(NmCli)
119 119 } else {
@@ -121,7 +121,7 @@
121 121 }
122 122 }
123 123
124 - pub struct NmCli;
124 + pub(crate) struct NmCli;
125 125
126 126 impl NmCli {
127 127 /// One invocation for the whole device table. `nmcli device show` with no
@@ -149,7 +149,7 @@
149 149 }
150 150
151 151 /// Fixed sample state, for machines without NetworkManager.
152 - pub struct Mock;
152 + pub(crate) struct Mock;
153 153
154 154 impl Backend for Mock {
155 155 fn name(&self) -> &'static str {
@@ -260,7 +260,7 @@
260 260 }
261 261
262 262 /// The `alloy net` screen.
263 - pub struct NetView {
263 + pub(crate) struct NetView {
264 264 backend: Box<dyn Backend>,
265 265 interfaces: Vec<Interface>,
266 266 cursor: Cursor,
@@ -268,7 +268,7 @@
268 268 }
269 269
270 270 impl NetView {
271 - pub fn new(log: &mut CommandLog) -> Self {
271 + pub(crate) fn new(log: &mut CommandLog) -> Self {
272 272 let mut view = Self {
273 273 backend: detect(),
274 274 interfaces: Vec::new(),
@@ -292,7 +292,7 @@
292 292 }
293 293 }
294 294
295 - fn row<'a>(&self, theme: &Theme, iface: &'a Interface) -> Line<'a> {
295 + fn row<'a>(theme: &Theme, iface: &'a Interface) -> Line<'a> {
296 296 let address = iface
297 297 .addresses
298 298 .first()
@@ -343,7 +343,7 @@
343 343 let rows: Vec<Line> = self
344 344 .interfaces
345 345 .iter()
346 - .map(|iface| self.row(theme, iface))
346 + .map(|iface| Self::row(theme, iface))
347 347 .collect();
348 348 frame.render_widget(
349 349 AlloyList::new(theme, rows).selected(self.cursor.selected()),
@@ -56,6 +56,7 @@
56 56 //! <!-- wiki: alloy-package-ux -->
57 57
58 58 use std::collections::{BTreeMap, BTreeSet};
59 + use std::fmt::Write as _;
59 60
60 61 use alloy_tui::keys::{Action, classify};
61 62 use alloy_tui::{
@@ -88,7 +89,7 @@
88 89 /// [`Level::Host`].
89 90 #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
90 91 #[serde(rename_all = "lowercase")]
91 - pub enum Level {
92 + pub(crate) enum Level {
92 93 /// Full home, devices, D-Bus, host integration.
93 94 Host,
94 95 /// Container-private home, explicit mounts only, network on, no devices,
@@ -124,7 +125,7 @@
124 125 /// teaching for free: the boxes that survive a rebuild are visibly distinct
125 126 /// from the ones that do not.
126 127 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
127 - pub enum Origin {
128 + pub(crate) enum Origin {
128 129 /// Named in the box spec, so it can be rebuilt.
129 130 Declared,
130 131 /// Found on the system but not in the spec.
@@ -147,7 +148,7 @@
147 148 /// an unfamiliar one beats mapping it to "unknown", the same reasoning as
148 149 /// `mesh` reporting `BackendState` verbatim.
149 150 #[derive(Debug, Clone, PartialEq, Eq)]
150 - pub enum BoxState {
151 + pub(crate) enum BoxState {
151 152 Running,
152 153 /// A sandboxed box is one app; it is installed, not started and stopped.
153 154 Installed,
@@ -194,7 +195,7 @@
194 195 /// the verb — and the domain type earns the name here. The two places that want
195 196 /// the pointer say `std::boxed::Box` explicitly.
196 197 #[derive(Debug, Clone)]
197 - pub struct Box {
198 + pub(crate) struct Box {
198 199 pub name: String,
199 200 /// Absent when the box was not made by Alloy and its backend does not imply
200 201 /// a level.
@@ -255,7 +256,7 @@
255 256 /// file", not a heuristic. Absent file means every box is ad-hoc, which is the
256 257 /// correct reading of a system with no spec.
257 258 #[derive(Debug, Default)]
258 - pub struct Spec {
259 + pub(crate) struct Spec {
259 260 boxes: BTreeMap<String, SpecBox>,
260 261 /// App id to declared name, so a sandboxed row finds its entry.
261 262 ///
@@ -273,7 +274,7 @@
273 274 /// Failures are silent by design. A missing spec is the ordinary case, and
274 275 /// a malformed one must not stop the view from showing the inventory —
275 276 /// which is the half of the screen that does not depend on the file at all.
276 - pub fn load() -> Self {
277 + pub(crate) fn load() -> Self {
277 278 let Some(path) = spec_path() else {
278 279 return Self::default();
279 280 };
@@ -351,7 +352,7 @@
351 352 /// parse time would cost every other box its declared marker over one bad entry,
352 353 /// and the inventory is the half of the screen that does not depend on the spec.
353 354 #[derive(Debug, Deserialize)]
354 - pub struct SpecBox {
355 + pub(crate) struct SpecBox {
355 356 level: Level,
356 357 /// Image reference for `host` and `workspace`.
357 358 image: Option<String>,
@@ -420,7 +421,7 @@
420 421 ///
421 422 /// Every method returns an [`Invocation`] rather than running one. See the
422 423 /// module docs.
423 - pub trait Backend {
424 + pub(crate) trait Backend {
424 425 /// The tool this fronts, for the view title.
425 426 fn name(&self) -> &'static str;
426 427
@@ -482,7 +483,7 @@
482 483 /// teaches nothing; here "no container backend installed" is itself the true
483 484 /// and useful answer, and inventing boxes would undercut a screen whose whole
484 485 /// claim is that it does not lie about what is on the system.
485 - pub fn detect() -> Vec<std::boxed::Box<dyn Backend>> {
486 + pub(crate) fn detect() -> Vec<std::boxed::Box<dyn Backend>> {
486 487 let mut backends: Vec<std::boxed::Box<dyn Backend>> = Vec::new();
487 488 if Invocation::new("podman").arg("--version").probe() {
488 489 backends.push(std::boxed::Box::new(Podman));
@@ -495,7 +496,7 @@
495 496
496 497 // ---- podman: the host and workspace levels ----
497 498
498 - pub struct Podman;
499 + pub(crate) struct Podman;
499 500
500 501 impl Backend for Podman {
501 502 fn name(&self) -> &'static str {
@@ -858,7 +859,7 @@
858 859
859 860 // ---- flatpak: the sandboxed level ----
860 861
861 - pub struct Flatpak;
862 + pub(crate) struct Flatpak;
862 863
863 864 /// Columns requested from `flatpak list`, in the order the parser reads them.
864 865 ///
@@ -1142,7 +1143,7 @@
1142 1143
1143 1144 /// The three tabs, in bar order.
1144 1145 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1145 - pub enum Tab {
1146 + pub(crate) enum Tab {
1146 1147 Installed,
1147 1148 Boxes,
1148 1149 System,
@@ -1211,7 +1212,7 @@
1211 1212 }
1212 1213
1213 1214 /// The `alloy pkg` screen.
1214 - pub struct PkgView {
1215 + pub(crate) struct PkgView {
1215 1216 backends: Vec<std::boxed::Box<dyn Backend>>,
1216 1217 spec: Spec,
1217 1218 tabs: FocusRing,
@@ -1232,7 +1233,7 @@
1232 1233 }
1233 1234
1234 1235 impl PkgView {
1235 - pub fn new(tab: Tab, log: &mut CommandLog) -> Self {
1236 + pub(crate) fn new(tab: Tab, log: &mut CommandLog) -> Self {
1236 1237 let mut tabs = FocusRing::new(Tab::ALL.len());
1237 1238 tabs.focus(tab.slot());
1238 1239
@@ -1514,7 +1515,7 @@
1514 1515 }
1515 1516 }
1516 1517
1517 - fn row<'a>(&self, theme: &Theme, boxed: &'a Box) -> Line<'a> {
1518 + fn row<'a>(theme: &Theme, boxed: &'a Box) -> Line<'a> {
1518 1519 Line::from(vec![
1519 1520 text::bold(theme, format!("{:<20}", truncate(&boxed.name, 19))),
1520 1521 text::secondary(theme, format!("{:<11}", boxed.level_label())),
@@ -1554,7 +1555,7 @@
1554 1555 // The gap between the spec and the machine, which is the one thing here
1555 1556 // the user can close with a keypress.
1556 1557 if absent > 0 {
1557 - summary.push_str(&format!(", {absent} not created yet"));
1558 + let _ = write!(summary, ", {absent} not created yet");
1558 1559 }
1559 1560 summary
1560 1561 }
@@ -1569,7 +1570,7 @@
1569 1570 let rows: Vec<Line> = self
1570 1571 .boxes
1571 1572 .iter()
1572 - .map(|row| self.row(theme, &row.boxed))
1573 + .map(|row| Self::row(theme, &row.boxed))
1573 1574 .collect();
1574 1575 frame.render_widget(
1575 1576 AlloyList::new(theme, rows).selected(self.cursor.selected()),
@@ -1655,19 +1656,19 @@
1655 1656 let rows: Vec<Line> = status
1656 1657 .deployments
1657 1658 .iter()
1658 - .map(|dep| self.deployment_row(theme, dep))
1659 + .map(|dep| Self::deployment_row(theme, dep))
1659 1660 .collect();
1660 1661 frame.render_widget(AlloyList::new(theme, rows), list_area);
1661 1662
1662 1663 let pinned = status.deployments.iter().filter(|d| d.pinned).count();
1663 1664 let mut summary = format!("{} deployments", status.deployments.len());
1664 1665 if pinned > 0 {
1665 - summary.push_str(&format!(", {pinned} pinned"));
1666 + let _ = write!(summary, ", {pinned} pinned");
1666 1667 }
1667 1668 frame.render_widget(Line::from(text::muted(theme, summary)), summary_area);
1668 1669 }
1669 1670
1670 - fn deployment_row<'a>(&self, theme: &Theme, dep: &'a Deployment) -> Line<'a> {
1671 + fn deployment_row<'a>(theme: &Theme, dep: &'a Deployment) -> Line<'a> {
1671 1672 let (state, severity) = dep.state();
1672 1673 Line::from(vec![
1673 1674 Span::styled(format!("{state:<9}"), severity.style(theme)),
@@ -71,7 +71,7 @@
71 71 /// A resolver returns [`Stage`]s rather than [`Invocation`]s so discovery can
72 72 /// nest: finding the partition produces a mount, and mounting makes the second
73 73 /// discovery possible.
74 - pub enum Stage {
74 + pub(crate) enum Stage {
75 75 /// Run it. Nothing downstream depends on what it prints.
76 76 Run(Invocation),
77 77 /// Run it, then let `then` build what follows from its stdout.
@@ -98,13 +98,13 @@
98 98 /// decides has not been decided yet, and inventing a line for a command
99 99 /// whose arguments do not exist would be the summary lying about what it
100 100 /// knows. The installer's summary says so in the surrounding copy instead.
101 - pub fn display(&self) -> String {
101 + pub(crate) fn display(&self) -> String {
102 102 self.invocation().display()
103 103 }
104 104 }
105 105
106 106 /// Builds the stages that follow, from the captured stdout of the one before.
107 - pub type Resolver = Box<dyn FnOnce(&str) -> Result<Vec<Stage>, String>>;
107 + pub(crate) type Resolver = Box<dyn FnOnce(&str) -> Result<Vec<Stage>, String>>;
108 108
109 109 /// One running child and the lines it has produced.
110 110 struct Running {
@@ -289,7 +289,7 @@
289 289 /// sequence and the failure rule spans it: a `useradd` that runs after a failed
290 290 /// deploy would be writing into a tree that is not there, so a failure stops
291 291 /// everything after it.
292 - pub struct Sequence {
292 + pub(crate) struct Sequence {
293 293 queue: VecDeque<Stage>,
294 294 current: Option<Running>,
295 295 output: Vec<String>,
@@ -305,7 +305,7 @@
305 305 /// the first tick after the view appears rather than during construction.
306 306 /// That way the run screen is on screen before anything runs, instead of
307 307 /// the first command's output arriving for a pane nobody has seen yet.
308 - pub fn new(stages: Vec<Stage>) -> Self {
308 + pub(crate) fn new(stages: Vec<Stage>) -> Self {
309 309 Self {
310 310 queue: stages.into(),
311 311 current: None,
@@ -316,16 +316,16 @@
316 316 }
317 317
318 318 /// Lines produced so far, oldest first.
319 - pub fn output(&self) -> &[String] {
319 + pub(crate) fn output(&self) -> &[String] {
320 320 &self.output
321 321 }
322 322
323 323 /// `Some` once the sequence has stopped, whether it finished or failed.
324 - pub fn outcome(&self) -> Option<&Result<(), String>> {
324 + pub(crate) fn outcome(&self) -> Option<&Result<(), String>> {
325 325 self.outcome.as_ref()
326 326 }
327 327
328 - pub fn is_done(&self) -> bool {
328 + pub(crate) fn is_done(&self) -> bool {
329 329 self.outcome.is_some()
330 330 }
331 331
@@ -342,7 +342,7 @@
342 342 /// Declaring the count up front would be the alternative, and it would put
343 343 /// the number in one place and the stages that have to match it in another.
344 344 /// That is the coupling this file has already been bitten by twice.
345 - pub fn completed(&self) -> usize {
345 + pub(crate) fn completed(&self) -> usize {
346 346 self.done_count
347 347 }
348 348
@@ -351,7 +351,7 @@
351 351 ///
352 352 /// Called from the view's tick. Does no waiting, so a tick costs the same
353 353 /// whether the child is producing output or has been silent for a minute.
354 - pub fn poll(&mut self, log: &mut CommandLog) {
354 + pub(crate) fn poll(&mut self, log: &mut CommandLog) {
355 355 if self.outcome.is_some() {
356 356 return;
357 357 }
@@ -782,7 +782,7 @@
782 782
783 783 match finished.recv_timeout(Duration::from_secs(30)) {
784 784 Ok(last) => assert_eq!(last.as_deref(), Some("read-it-all")),
785 - Err(_) => panic!("writing stdin deadlocked against the child's stdout"),
785 + Err(err) => panic!("writing stdin deadlocked against the child's stdout: {err}"),
786 786 }
787 787 }
788 788
@@ -28,7 +28,7 @@
28 28 /// via [`View::cancel`]'s default, and is what a view returns from that hook
29 29 /// when it has nothing left to back out of.
30 30 #[derive(Debug)]
31 - pub enum Flow {
31 + pub(crate) enum Flow {
32 32 Continue,
33 33 Exit,
34 34 /// Open a confirmation modal. The view keeps whatever it was about to do
@@ -47,7 +47,7 @@
47 47 /// keeps [`View`] object-safe and means the shell never has to understand what
48 48 /// it is confirming.
49 49 #[derive(Debug)]
50 - pub struct Confirm {
50 + pub(crate) struct Confirm {
51 51 pub title: String,
52 52 pub message: String,
53 53 pub severity: Severity,
@@ -55,7 +55,7 @@
55 55
56 56 impl Confirm {
57 57 /// A destructive confirm: the common case, and the reason this exists.
58 - pub fn destructive(title: impl Into<String>, message: impl Into<String>) -> Self {
58 + pub(crate) fn destructive(title: impl Into<String>, message: impl Into<String>) -> Self {
59 59 Self {
60 60 title: title.into(),
61 61 message: message.into(),
@@ -66,7 +66,7 @@
66 66
67 67 /// A console screen. Views own their data and their body; the shell owns the
68 68 /// frame around it.
69 - pub trait View {
69 + pub(crate) trait View {
70 70 /// Title for the body block.
71 71 fn title(&self) -> String;
72 72
@@ -152,13 +152,13 @@
152 152 /// immediately. One second is slow enough that a view polling a couple of
153 153 /// commands per tick stays cheap, and fast enough that an app starting
154 154 /// playback shows up before the user wonders whether the console noticed.
155 - pub const TICK: Duration = Duration::from_secs(1);
155 + pub(crate) const TICK: Duration = Duration::from_secs(1);
156 156
157 157 /// Run a view to completion: set up the terminal, loop, and restore.
158 158 ///
159 159 /// The terminal is restored even when the loop fails, so a backend error does
160 160 /// not strand the user in raw mode with no echo.
161 - pub fn run(theme: &Theme, view: &mut dyn View, log: &mut CommandLog) -> Result<()> {
161 + pub(crate) fn run(theme: &Theme, view: &mut dyn View, log: &mut CommandLog) -> Result<()> {
162 162 let mut terminal = ratatui::init();
163 163 let result = event_loop(&mut terminal, theme, view, log);
164 164 ratatui::restore();
@@ -362,7 +362,7 @@
362 362
363 363 /// Title text for a view's body block, padded so it does not sit flush against
364 364 /// the border corner.
365 - pub fn block_title(title: &str) -> String {
365 + pub(crate) fn block_title(title: &str) -> String {
366 366 format!(" {title} ")
367 367 }
368 368
@@ -373,7 +373,7 @@
373 373 /// beside [`block_title`] rather than in a view because `pkg` is the second
374 374 /// screen to want it, which is the point docs/CONSOLE.md sets for extracting
375 375 /// shared machinery.
376 - pub fn truncate(text: &str, width: usize) -> String {
376 + pub(crate) fn truncate(text: &str, width: usize) -> String {
377 377 if text.chars().count() <= width {
378 378 return text.to_string();
379 379 }
@@ -11,10 +11,10 @@
11 11 use anyhow::{Context, Result};
12 12
13 13 /// Default light theme (docs/TOKENS.md).
14 - pub const DEFAULT_LIGHT: &str = "akari-dawn";
14 + pub(crate) const DEFAULT_LIGHT: &str = "akari-dawn";
15 15
16 16 /// Default dark theme (docs/TOKENS.md).
17 - pub const DEFAULT_DARK: &str = "akari-night";
17 + pub(crate) const DEFAULT_DARK: &str = "akari-night";
18 18
19 19 /// Theme search path, highest precedence first: the user's own themes, then
20 20 /// the ones the image ships, then the in-repo checkout when running from a dev
@@ -51,8 +51,8 @@
51 51 }
52 52
53 53 /// Load a theme by id, or the mode-appropriate default when `id` is `None`.
54 - pub fn load(id: Option<&str>) -> Result<Theme> {
55 - let id = id.map(str::to_string).unwrap_or_else(default_theme_id);
54 + pub(crate) fn load(id: Option<&str>) -> Result<Theme> {
55 + let id = id.map_or_else(default_theme_id, str::to_string);
56 56 let dirs = search_path();
57 57
58 58 let colors = makeover::load_theme(&dirs, &id)
@@ -28,7 +28,7 @@
28 28 /// lets the whole navigation model be tested without a terminal, a form, or a
29 29 /// disk.
30 30 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
31 - pub struct Steps {
31 + pub(crate) struct Steps {
32 32 index: usize,
33 33 len: usize,
34 34 /// The furthest step reached, which is not always the current one.
@@ -49,7 +49,7 @@
49 49 /// error, but it is not one worth taking the process down for, and
50 50 /// [`Cursor`](alloy_tui::Cursor) sets the precedent of staying total over
51 51 /// an empty range.
52 - pub const fn new(len: usize) -> Self {
52 + pub(crate) const fn new(len: usize) -> Self {
53 53 Self {
54 54 index: 0,
55 55 len,
@@ -57,11 +57,11 @@
57 57 }
58 58 }
59 59
60 - pub const fn current(&self) -> usize {
60 + pub(crate) const fn current(&self) -> usize {
61 61 self.index
62 62 }
63 63
64 - pub const fn len(&self) -> usize {
64 + pub(crate) const fn len(&self) -> usize {
65 65 self.len
66 66 }
67 67
@@ -72,13 +72,13 @@
72 72 /// says "step 2 of 4" instead, so this is the last of the type still
73 73 /// waiting on its consumer, and the allow comes off with it.
74 74 #[allow(dead_code)]
75 - pub const fn furthest(&self) -> usize {
75 + pub(crate) const fn furthest(&self) -> usize {
76 76 self.furthest
77 77 }
78 78
79 79 /// On the first step, so Esc leaves the installer rather than stepping
80 80 /// back. Also true of an empty sequence, which is nowhere.
81 - pub const fn is_first(&self) -> bool {
81 + pub(crate) const fn is_first(&self) -> bool {
82 82 self.index == 0
83 83 }
84 84
@@ -87,7 +87,7 @@
87 87 /// The `len == 0` guard matters: without it an empty sequence reports
88 88 /// "not on the last step" while having no steps to advance through, and a
89 89 /// view driving off that would offer a next that never arrives.
90 - pub const fn is_last(&self) -> bool {
90 + pub(crate) const fn is_last(&self) -> bool {
91 91 self.len == 0 || self.index + 1 == self.len
92 92 }
93 93
@@ -97,7 +97,7 @@
97 97 /// step's Enter as "advance, and if it did not move, run the install"
98 98 /// without asking [`is_last`](Self::is_last) separately and racing its own
99 99 /// state.
100 - pub const fn advance(&mut self) -> bool {
100 + pub(crate) const fn advance(&mut self) -> bool {
101 101 if self.is_last() {
102 102 return false;
103 103 }
@@ -112,7 +112,7 @@
112 112 ///
113 113 /// `false` on the first step is the shell's cue to close the view: Esc
114 114 /// backs out until there is nothing left to back out of, then it leaves.
115 - pub const fn back(&mut self) -> bool {
115 + pub(crate) const fn back(&mut self) -> bool {
116 116 if self.is_first() {
117 117 return false;
118 118 }
@@ -1,0 +1,4 @@
1 + [toolchain]
2 + channel = "stable"
3 + profile = "minimal"
4 + components = ["rustfmt", "clippy"]