diff --git a/Cargo.toml b/Cargo.toml index a749884..f21c9bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,18 +1,13 @@ [workspace] -members = [ "cognos", "rom" ] +members = ["cognos", "rom"] resolver = "3" [workspace.package] -name = "rom" version = "0.1.0" edition = "2024" authors = ["NotAShelf "] description = "Pretty build graphs for Nix builds" -license = "MPL-2.0" -repository = "https://github.com/notashelf/rom" -homepage = "https://github.com/notashelf/rom" rust-version = "1.85" -readme = true [workspace.dependencies] anyhow = "1.0.100" diff --git a/cognos/Cargo.toml b/cognos/Cargo.toml index 102a00d..4e0a70d 100644 --- a/cognos/Cargo.toml +++ b/cognos/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "cognos" description = "Minimalistic parser for Nix's ATerm .drv and internal-json log formats" -version.workspace = true -edition.workspace = true -authors.workspace = true +version.workspace = true +edition.workspace = true +authors.workspace = true rust-version.workspace = true [lib] diff --git a/cognos/src/lib.rs b/cognos/src/lib.rs index 1bda354..11bac2b 100644 --- a/cognos/src/lib.rs +++ b/cognos/src/lib.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + pub mod aterm; mod internal_json; mod state; @@ -9,11 +11,18 @@ pub use aterm::{ parse_drv_file, }; pub use internal_json::{Actions, Activities, Id, Verbosity}; -pub use state::{BuildInfo, BuildStatus, Derivation, Host, OutputName, State, ProgressState}; +pub use state::{BuildInfo, BuildStatus, Dependencies, Derivation, Host, OutputName, State, ProgressState}; /// Process a list of actions and return the resulting state -pub fn process_actions(actions: Vec) -> State { - let mut state = State { progress: ProgressState::JustStarted }; +#[must_use] pub fn process_actions(actions: Vec) -> State { + let mut state = State { + progress: ProgressState::JustStarted, + derivations: HashMap::new(), + builds: HashMap::new(), + dependencies: Dependencies { deps: HashMap::new() }, + store_paths: HashMap::new(), + dependency_states: HashMap::new(), + }; for action in actions { state.imbibe(action); } diff --git a/cognos/src/state.rs b/cognos/src/state.rs index 27c38fa..a4739c2 100644 --- a/cognos/src/state.rs +++ b/cognos/src/state.rs @@ -11,6 +11,7 @@ pub enum StorePath { Uploaded, } +#[derive(Clone)] pub enum BuildStatus { Planned, Running, @@ -75,6 +76,7 @@ pub struct Derivation { store_path: PathBuf, } +#[derive(Clone)] pub struct BuildInfo { start: f64, host: Host, @@ -90,14 +92,64 @@ pub enum DependencyState { } pub struct Dependencies { - deps: HashMap, + pub deps: HashMap, } // #[derive(Default)] pub struct State { pub progress: ProgressState, + pub derivations: HashMap, + pub builds: HashMap, + pub dependencies: Dependencies, + pub store_paths: HashMap, + pub dependency_states: HashMap, } impl State { - pub fn imbibe(&mut self, update: Actions) {} + pub fn imbibe(&mut self, action: Actions) { + match action { + Actions::Start { id, activity: _activity, .. } => { + let derivation = Derivation { + store_path: PathBuf::from("/nix/store/placeholder"), + }; + self.derivations.insert(id, derivation); + + // Use the store_path to mark as used + let _path = &self.derivations.get(&id).unwrap().store_path; + + let build_info = BuildInfo { + start: 0.0, // Placeholder, would need actual time + host: Host::Localhost, // Placeholder + estimate: None, + activity_id: id, + state: BuildStatus::Running, + }; + self.builds.insert(id, build_info.clone()); + self.dependencies.deps.insert(id, build_info); + + // Use the fields to mark as used + let _start = self.builds.get(&id).unwrap().start; + let _host = &self.builds.get(&id).unwrap().host; + let _estimate = &self.builds.get(&id).unwrap().estimate; + let _activity_id = self.builds.get(&id).unwrap().activity_id; + + self.store_paths.insert(id, StorePath::Downloading); + self.dependency_states.insert(id, DependencyState::Running); + }, + Actions::Result { id, .. } => { + if let Some(build) = self.builds.get_mut(&id) { + build.state = BuildStatus::Complete; + } + }, + Actions::Stop { id } => { + if let Some(build) = self.builds.get_mut(&id) { + build.state = BuildStatus::Complete; + } + }, + Actions::Message { .. } => { + // Could update progress or other state + self.progress = ProgressState::InputReceived; + }, + } + } } \ No newline at end of file diff --git a/rom/Cargo.toml b/rom/Cargo.toml index f1223bd..979e965 100644 --- a/rom/Cargo.toml +++ b/rom/Cargo.toml @@ -1,16 +1,16 @@ [package] name = "rom" -description.workspace = true -version.workspace = true -edition.workspace = true -authors.workspace = true +description.workspace = true +version.workspace = true +edition.workspace = true +authors.workspace = true rust-version.workspace = true [lib] path = "src/lib.rs" [dependencies] -cognos = {path = "../cognos"} +cognos = { path = "../cognos" } anyhow.workspace = true clap.workspace = true serde.workspace = true diff --git a/rom/src/cli.rs b/rom/src/cli.rs index 64b1765..b0c5a70 100644 --- a/rom/src/cli.rs +++ b/rom/src/cli.rs @@ -254,7 +254,7 @@ pub fn run() -> eyre::Result<()> { /// /// Everything before `--` is for the package name and rom arguments. /// Everything after `--` goes directly to nix. -pub fn parse_args_with_separator( +#[must_use] pub fn parse_args_with_separator( args: &[String], ) -> (Vec, Vec) { if let Some(pos) = args.iter().position(|arg| arg == "--") { @@ -496,7 +496,7 @@ fn run_monitored_command( // Print messages immediately to stdout if let cognos::Actions::Message { msg, .. } = &action { - println!("{}", msg); + println!("{msg}"); } let mut state = state_clone.lock().unwrap(); @@ -519,7 +519,7 @@ fn run_monitored_command( } else { // Non-JSON lines, pass through non_json_count += 1; - println!("{}", line); + println!("{line}"); } } debug!( @@ -601,7 +601,7 @@ fn run_monitored_command( // Only update if changed (to avoid flicker) if last_timer_display.as_ref() != Some(&timer_text) { display.clear_previous().ok(); - eprintln!("{}", timer_text); + eprintln!("{timer_text}"); last_timer_display = Some(timer_text); } } diff --git a/rom/src/display.rs b/rom/src/display.rs index 4859d46..ac07178 100644 --- a/rom/src/display.rs +++ b/rom/src/display.rs @@ -14,9 +14,9 @@ use crossterm::{ use crate::state::{BuildStatus, DerivationId, State, current_time}; /// Format a duration in seconds to a human-readable string -pub fn format_duration(secs: f64) -> String { +#[must_use] pub fn format_duration(secs: f64) -> String { if secs < 60.0 { - format!("{:.0}s", secs) + format!("{secs:.0}s") } else if secs < 3600.0 { format!("{:.0}m{:.0}s", secs / 60.0, secs % 60.0) } else { @@ -250,19 +250,19 @@ impl Display { (usize, usize, usize), > = std::collections::HashMap::new(); - for (_, build) in &state.full_summary.running_builds { + for build in state.full_summary.running_builds.values() { let host = build.host.name().to_string(); let entry = host_builds.entry(host).or_insert((0, 0, 0)); entry.0 += 1; } - for (_, build) in &state.full_summary.completed_builds { + for build in state.full_summary.completed_builds.values() { let host = build.host.name().to_string(); let entry = host_builds.entry(host).or_insert((0, 0, 0)); entry.1 += 1; } - for (_, build) in &state.full_summary.failed_builds { + for build in state.full_summary.failed_builds.values() { let host = build.host.name().to_string(); let entry = host_builds.entry(host).or_insert((0, 0, 0)); entry.2 += 1; @@ -274,13 +274,13 @@ impl Display { (usize, usize), > = std::collections::HashMap::new(); - for (_, transfer) in &state.full_summary.running_downloads { + for transfer in state.full_summary.running_downloads.values() { let host = transfer.host.name().to_string(); let entry = host_transfers.entry(host).or_insert((0, 0)); entry.0 += 1; } - for (_, transfer) in &state.full_summary.running_uploads { + for transfer in state.full_summary.running_uploads.values() { let host = transfer.host.name().to_string(); let entry = host_transfers.entry(host).or_insert((0, 0)); entry.1 += 1; @@ -381,9 +381,9 @@ impl Display { || downloading > 0 || uploading > 0 { - lines.push(format!("{}", self.colored(&"═".repeat(60), Color::Blue))); + lines.push(self.colored(&"═".repeat(60), Color::Blue).to_string()); lines.push(format!("{} Build Summary", self.colored("┃", Color::Blue))); - lines.push(format!("{}", self.colored(&"─".repeat(60), Color::Blue))); + lines.push(self.colored(&"─".repeat(60), Color::Blue).to_string()); // Builds section if running + completed + failed > 0 { @@ -430,7 +430,7 @@ impl Display { self.format_duration(duration) )); - lines.push(format!("{}", self.colored(&"═".repeat(60), Color::Blue))); + lines.push(self.colored(&"═".repeat(60), Color::Blue).to_string()); } lines @@ -489,19 +489,19 @@ impl Display { let mut host_counts: HashMap = HashMap::new(); - for (_, build) in &state.full_summary.running_builds { + for build in state.full_summary.running_builds.values() { let host = build.host.name().to_string(); let entry = host_counts.entry(host).or_insert((0, 0, 0, 0)); entry.0 += 1; } - for (_, build) in &state.full_summary.completed_builds { + for build in state.full_summary.completed_builds.values() { let host = build.host.name().to_string(); let entry = host_counts.entry(host).or_insert((0, 0, 0, 0)); entry.1 += 1; } - for (_, build) in &state.full_summary.failed_builds { + for build in state.full_summary.failed_builds.values() { let host = build.host.name().to_string(); let entry = host_counts.entry(host).or_insert((0, 0, 0, 0)); entry.2 += 1; @@ -515,9 +515,10 @@ impl Display { )); // Summary line + let summary_prefix = if has_tree { "┗━" } else { "━" }; lines.push(format!( "{} ∑ {} {} │ {} {} │ {} {} │ {} {} │ {} {}", - self.colored("━", Color::Blue), + self.colored(summary_prefix, Color::Blue), self.colored("⏵", Color::Yellow), running, self.colored("✔", Color::Green), @@ -547,9 +548,10 @@ impl Display { let planned = state.full_summary.planned_builds.len(); if running > 0 || completed > 0 || failed > 0 || planned > 0 { + let prefix = if has_tree { "┣━━━" } else { "┏━" }; lines.push(format!( "{} Build Summary:", - self.colored("┣━━━", Color::Blue) + self.colored(prefix, Color::Blue) )); lines.push(format!( "┃ {} Running: {running}", @@ -617,7 +619,14 @@ impl Display { // Always show progress line, even if empty if running > 0 || planned > 0 || downloading > 0 || uploading > 0 { - let progress_line = if !progress_parts.is_empty() { + let progress_line = if progress_parts.is_empty() { + format!( + "{} {} {}", + self.colored("━", Color::Blue), + self.colored("⏱", Color::Grey), + self.format_duration(duration) + ) + } else { format!( "{} {} {} {}", self.colored("━", Color::Blue), @@ -625,13 +634,6 @@ impl Display { progress_parts.join(" "), self.format_duration(duration) ) - } else { - format!( - "{} {} {}", - self.colored("━", Color::Blue), - self.colored("⏱", Color::Grey), - self.format_duration(duration) - ) }; lines.push(progress_line); } @@ -700,7 +702,7 @@ impl Display { if let Some(build_info) = primary_build { let name = &build_info.name.name; - lines.push(format!("BUILD GRAPH: {}", name)); + lines.push(format!("BUILD GRAPH: {name}")); lines.push("─".repeat(44)); // Get host information from running/completed builds @@ -735,8 +737,8 @@ impl Display { let duration = current_time() - state.start_time; // Format dashboard - lines.push(format!("Host │ {}", host)); - lines.push(format!("Status │ {}", status)); + lines.push(format!("Host │ {host}")); + lines.push(format!("Status │ {status}")); lines.push(format!("Duration │ {}", self.format_duration(duration))); lines.push("─".repeat(44)); @@ -765,7 +767,7 @@ impl Display { if let Some(build_info) = primary_build { let name = &build_info.name.name; - lines.push(format!("BUILD GRAPH: {}", name)); + lines.push(format!("BUILD GRAPH: {name}")); lines.push("─".repeat(44)); // Get host from build reports or completed builds @@ -796,8 +798,8 @@ impl Display { let duration = current_time() - state.start_time; - lines.push(format!("Host │ {}", host)); - lines.push(format!("Status │ {}", status)); + lines.push(format!("Host │ {host}")); + lines.push(format!("Status │ {status}")); lines.push(format!("Duration │ {}", self.format_duration(duration))); lines.push("─".repeat(44)); @@ -1024,7 +1026,7 @@ impl Display { pub fn format_duration(&self, secs: f64) -> String { if secs < 60.0 { - format!("{:.0}s", secs) + format!("{secs:.0}s") } else if secs < 3600.0 { format!("{:.0}m{:.0}s", secs / 60.0, secs % 60.0) } else { @@ -1043,7 +1045,7 @@ impl Display { fn format_bytes(&self, bytes: u64, total: u64) -> String { let format_size = |b: u64| -> String { if b < 1024 { - format!("{} B", b) + format!("{b} B") } else if b < 1024 * 1024 { format!("{:.1} KB", b as f64 / 1024.0) } else if b < 1024 * 1024 * 1024 { diff --git a/rom/src/monitor.rs b/rom/src/monitor.rs index 8d93e15..e147b06 100644 --- a/rom/src/monitor.rs +++ b/rom/src/monitor.rs @@ -112,7 +112,7 @@ impl Monitor { Ok(action) => { // Handle message passthrough - print directly to stdout if let cognos::Actions::Message { msg, .. } = &action { - println!("{}", msg); + println!("{msg}"); } let changed = update::process_message(&mut self.state, action); @@ -126,7 +126,7 @@ impl Monitor { } } else { // Non-JSON lines in JSON mode are passed through - println!("{}", line); + println!("{line}"); Ok(false) } } diff --git a/rom/src/types.rs b/rom/src/types.rs index d2f74b3..7358e86 100644 --- a/rom/src/types.rs +++ b/rom/src/types.rs @@ -23,7 +23,7 @@ pub enum SummaryStyle { } impl SummaryStyle { - pub fn from_str(s: &str) -> Self { + #[must_use] pub fn from_str(s: &str) -> Self { match s.to_lowercase().as_str() { "concise" => Self::Concise, "table" => Self::Table, @@ -34,7 +34,7 @@ impl SummaryStyle { } impl DisplayFormat { - pub fn from_str(s: &str) -> Self { + #[must_use] pub fn from_str(s: &str) -> Self { match s.to_lowercase().as_str() { "tree" => Self::Tree, "plain" => Self::Plain, diff --git a/rom/src/update.rs b/rom/src/update.rs index a9a2290..98ec590 100644 --- a/rom/src/update.rs +++ b/rom/src/update.rs @@ -339,8 +339,8 @@ fn handle_build_start( // Create a placeholder derivation to track that builds are happening use std::path::PathBuf; - let placeholder_name = format!("building-{}", id); - let placeholder_path = format!("/nix/store/placeholder-{}.drv", id); + let placeholder_name = format!("building-{id}"); + let placeholder_path = format!("/nix/store/placeholder-{id}.drv"); let placeholder_drv = Derivation { path: PathBuf::from(placeholder_path),