max / alloy
- Co-Authored-By
- Claude Opus 4.8 (1M context) <noreply@anthropic.com>
14 files changed,
+182 insertions,
-143 deletions
| @@ -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"] |