From fc39717ab7445f06cb95813f3fd7b7ce8ef18dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 14 Jun 2025 08:08:47 +0000 Subject: [PATCH 01/13] fix tests --- crates/rust-project-goals-cli/src/cfp.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/rust-project-goals-cli/src/cfp.rs b/crates/rust-project-goals-cli/src/cfp.rs index c4eda632..09318e50 100644 --- a/crates/rust-project-goals-cli/src/cfp.rs +++ b/crates/rust-project-goals-cli/src/cfp.rs @@ -472,8 +472,9 @@ mod tests { let content = "# Project goals\n\n## Current goal period (2025H1)\n\nThe 2025H1 goal period runs from Jan 1 to Jun 30."; let result = process_readme_content(content, "2026h1", "2026h1"); + println!("{}", result); assert!(result.contains("## Next goal period (2026H1)")); - assert!(result.contains("running from January 1 to June 30")); + assert!(result.contains("running from the start of January to the end of June")); assert!(result.contains("[Click here](./2026h1/goals.md)")); } @@ -484,7 +485,7 @@ mod tests { let result = process_readme_content(content, "2026h1", "2026h1"); assert!(result.contains("## Next goal period (2026H1)")); - assert!(result.contains("running from January 1 to June 30")); + assert!(result.contains("running from the start of January to the end of June")); assert!(!result.contains("Old content.")); } @@ -495,7 +496,7 @@ mod tests { let result = process_readme_content(content, "2026h1", "2026h1"); assert!(result.contains("## Next goal period (2026H1)")); - assert!(result.contains("running from January 1 to June 30")); + assert!(result.contains("running from the start of January to the end of June")); assert!(!result.contains("Old content.")); assert!(result.contains("## Extra section\nsome content")); } @@ -508,7 +509,7 @@ mod tests { assert!(result.contains("## Next goal period (2026H1)")); assert!(!result.contains("## Next goal period (2025H2)")); - assert!(result.contains("running from January 1 to June 30")); + assert!(result.contains("running from the start of January to the end of June")); } #[test] From 57181691c71880a0a72774e7913735d12bcb181f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 14 Jun 2025 08:09:54 +0000 Subject: [PATCH 02/13] fix formatting --- crates/rust-project-goals-cli/src/cfp.rs | 330 ++++++++++++++-------- crates/rust-project-goals-cli/src/main.rs | 8 +- 2 files changed, 222 insertions(+), 116 deletions(-) diff --git a/crates/rust-project-goals-cli/src/cfp.rs b/crates/rust-project-goals-cli/src/cfp.rs index 09318e50..0dd0a879 100644 --- a/crates/rust-project-goals-cli/src/cfp.rs +++ b/crates/rust-project-goals-cli/src/cfp.rs @@ -1,27 +1,31 @@ +use anyhow::{Context, Result}; +use regex::Regex; use std::fs::{self, File}; use std::io::Write; use std::path::{Path, PathBuf}; -use anyhow::{Context, Result}; -use regex::Regex; /// Module containing pure functions for text processing pub mod text_processing { use regex::Regex; - + /// Process template content by replacing placeholders and removing notes - pub fn process_template_content(content: &str, timeframe: &str, lowercase_timeframe: &str) -> String { + pub fn process_template_content( + content: &str, + timeframe: &str, + lowercase_timeframe: &str, + ) -> String { // Remove note sections (starting with '> **NOTE:**' and continuing until a non-'>' line) // But preserve other content starting with '>' let lines: Vec<&str> = content.lines().collect(); let mut filtered_lines = Vec::new(); let mut in_note_section = false; - + for line in lines.iter() { if line.trim().starts_with("> **NOTE:**") { in_note_section = true; continue; } - + if in_note_section { if line.trim().starts_with(">") { continue; // Skip this line as it's part of the note section @@ -29,14 +33,14 @@ pub mod text_processing { in_note_section = false; // End of note section } } - + filtered_lines.push(*line); } - + // Join the filtered lines and clean up consecutive empty lines let mut cleaned_lines = Vec::new(); let mut last_was_empty = false; - + for line in filtered_lines { let is_empty = line.trim().is_empty(); if !is_empty || !last_was_empty { @@ -44,9 +48,10 @@ pub mod text_processing { } last_was_empty = is_empty; } - + // Join and process placeholders - cleaned_lines.join("\n") + cleaned_lines + .join("\n") .replace("YYYYHN", timeframe) .replace("YYYY", &timeframe[0..4]) .replace("HN", &timeframe[4..]) @@ -55,11 +60,15 @@ pub mod text_processing { .replace("yyyy", &lowercase_timeframe[0..4]) .replace("hn", &lowercase_timeframe[4..]) } - + /// Process SUMMARY.md content to add or update a timeframe section - pub fn process_summary_content(content: &str, timeframe: &str, lowercase_timeframe: &str) -> String { + pub fn process_summary_content( + content: &str, + timeframe: &str, + lowercase_timeframe: &str, + ) -> String { let mut new_content = content.to_string(); - + // Create the new section content with capitalized H let capitalized_timeframe = format!("{}H{}", &timeframe[0..4], &timeframe[5..]); let new_section_content = format!( @@ -69,31 +78,37 @@ pub mod text_processing { - [Goals not accepted](./{}/not_accepted.md)\n", capitalized_timeframe, lowercase_timeframe, lowercase_timeframe, lowercase_timeframe ); - + // Check if the timeframe is already in the SUMMARY.md let section_header = format!("# ⏳ {} goal process", capitalized_timeframe); - + if content.contains(§ion_header) { // The section exists, but it might have placeholder content // Find the section header and its content, but be careful not to include the next section header - let section_header_pattern = format!(r"# ⏳ {} goal process", regex::escape(&capitalized_timeframe)); + let section_header_pattern = format!( + r"# ⏳ {} goal process", + regex::escape(&capitalized_timeframe) + ); let re = Regex::new(§ion_header_pattern).unwrap(); - + if let Some(section_match) = re.find(&content) { // Find the end of the section header line if let Some(header_end) = content[section_match.end()..].find('\n') { let content_start = section_match.end() + header_end + 1; - + // Find the start of the next section header (if any) - let next_section_start = content[content_start..].find("\n# ") + let next_section_start = content[content_start..] + .find("\n# ") .map(|pos| content_start + pos) .unwrap_or(content.len()); - + // Extract the section content let section_content = &content[content_start..next_section_start]; - + // Check if it contains placeholder content like "- [Not yet started]()" - if section_content.contains("[Not yet started]") || section_content.trim().is_empty() { + if section_content.contains("[Not yet started]") + || section_content.trim().is_empty() + { // Format the replacement content (just the links, not the section header) let replacement_content = format!( "\n- [Overview](./{}/README.md)\n\ @@ -101,9 +116,10 @@ pub mod text_processing { - [Goals not accepted](./{}/not_accepted.md)\n", lowercase_timeframe, lowercase_timeframe, lowercase_timeframe ); - + // Replace just the section content, preserving the section header and any following sections - new_content.replace_range(content_start..next_section_start, &replacement_content); + new_content + .replace_range(content_start..next_section_start, &replacement_content); return new_content; } else { // Section already has non-placeholder content, don't modify it @@ -112,15 +128,15 @@ pub mod text_processing { } } } - + // If we get here, the section doesn't exist, so we need to add it let new_section = format!("\n{}", new_section_content); - + // Find a good place to insert the new section // Look for the last timeframe section or insert at the beginning // Match both lowercase and uppercase H let re = Regex::new(r"# ⏳ \d{4}[hH][12] goal process").unwrap(); - + if let Some(last_match) = re.find_iter(&content).last() { // Find the end of this section (next section or end of file) if let Some(next_section_pos) = content[last_match.end()..].find("\n# ") { @@ -134,25 +150,29 @@ pub mod text_processing { // No existing timeframe sections, insert at the beginning new_content = new_section + &content; } - + new_content } - + /// Process README.md content to add or update a timeframe section - pub fn process_readme_content(content: &str, timeframe: &str, lowercase_timeframe: &str) -> String { + pub fn process_readme_content( + content: &str, + timeframe: &str, + lowercase_timeframe: &str, + ) -> String { let mut new_content = content.to_string(); - + // Extract year and half from timeframe let _year = &timeframe[0..4]; let half = &timeframe[4..].to_lowercase(); - + // Determine the months based on the half let (start_month, end_month) = if half == "h1" { ("January", "June") } else { ("July", "December") }; - + // Create the new section to add with capitalized H let capitalized_timeframe = format!("{}H{}", &timeframe[0..4], &timeframe[5..]); let new_section = format!( @@ -163,22 +183,30 @@ pub mod text_processing { If you'd like to propose a goal, [instructions can be found here](./how_to/propose_a_goal.md).\n", capitalized_timeframe, capitalized_timeframe, start_month, end_month, lowercase_timeframe ); - + // First check for an existing entry for this specific timeframe - let this_period_pattern = Regex::new(&format!(r"## Next goal period(?:\s*\({}\))?\s*\n", regex::escape(&capitalized_timeframe))).unwrap(); - + let this_period_pattern = Regex::new(&format!( + r"## Next goal period(?:\s*\({}\))?\s*\n", + regex::escape(&capitalized_timeframe) + )) + .unwrap(); + // If not found, look for any "Next goal period" section - let next_period_pattern = Regex::new(r"## Next goal period(?:\s*\([^\)]*\))?\s*\n").unwrap(); - + let next_period_pattern = + Regex::new(r"## Next goal period(?:\s*\([^\)]*\))?\s*\n").unwrap(); + // Also look for "Current goal period" to place after it if no "Next goal period" exists - let current_period_pattern = Regex::new(r"## Current goal period(?:\s*\([^\)]*\))?\s*\n").unwrap(); - + let current_period_pattern = + Regex::new(r"## Current goal period(?:\s*\([^\)]*\))?\s*\n").unwrap(); + // First try to find and replace an existing entry for this specific timeframe if let Some(this_period_match) = this_period_pattern.find(&content) { // Found an existing section for this specific timeframe // Find the end of this section (next section or end of file) if let Some(next_section_pos) = content[this_period_match.end()..].find("\n## ") { - let end_pos = this_period_match.start() + next_section_pos + this_period_match.end() - this_period_match.start(); + let end_pos = + this_period_match.start() + next_section_pos + this_period_match.end() + - this_period_match.start(); new_content.replace_range(this_period_match.start()..end_pos, &new_section); } else { // No next section, replace until the end @@ -188,7 +216,9 @@ pub mod text_processing { // Found an existing "Next goal period" section // Find the end of this section (next section or end of file) if let Some(next_section_pos) = content[next_period_match.end()..].find("\n## ") { - let end_pos = next_period_match.start() + next_section_pos + next_period_match.end() - next_period_match.start(); + let end_pos = + next_period_match.start() + next_section_pos + next_period_match.end() + - next_period_match.start(); new_content.replace_range(next_period_match.start()..end_pos, &new_section); } else { // No next section, replace until the end @@ -198,7 +228,8 @@ pub mod text_processing { // No existing "Next goal period" section, try to add after "Current goal period" section if let Some(current_period_match) = current_period_pattern.find(&content) { // Find the end of the current period section - if let Some(next_section_pos) = content[current_period_match.end()..].find("\n## ") { + if let Some(next_section_pos) = content[current_period_match.end()..].find("\n## ") + { let insert_pos = current_period_match.end() + next_section_pos; new_content.insert_str(insert_pos, &new_section); } else { @@ -215,7 +246,7 @@ pub mod text_processing { } } } - + new_content } } @@ -227,17 +258,20 @@ pub fn create_cfp(timeframe: &str, force: bool, dry_run: bool) -> Result<()> { } // Validate the timeframe format validate_timeframe(timeframe)?; - + // Ensure the timeframe is lowercase for directory and file paths let lowercase_timeframe = timeframe.to_lowercase(); - + // Create the directory for the new timeframe (always lowercase) let dir_path = PathBuf::from("src").join(&lowercase_timeframe); if dir_path.exists() { if !force && !dry_run { - println!("Directory {} already exists. Continuing will overwrite files.", dir_path.display()); + println!( + "Directory {} already exists. Continuing will overwrite files.", + dir_path.display() + ); println!("Do you want to continue? [y/N]"); - + let mut input = String::new(); std::io::stdin().read_line(&mut input)?; if !input.trim().eq_ignore_ascii_case("y") { @@ -245,35 +279,62 @@ pub fn create_cfp(timeframe: &str, force: bool, dry_run: bool) -> Result<()> { return Ok(()); } } else if force { - println!("Directory {} already exists. Force flag set, continuing without confirmation.", dir_path.display()); + println!( + "Directory {} already exists. Force flag set, continuing without confirmation.", + dir_path.display() + ); } else { // dry_run mode println!("Would create/overwrite directory: {}", dir_path.display()); } } else if !dry_run { - fs::create_dir_all(&dir_path).with_context(|| format!("Failed to create directory {}", dir_path.display()))?; + fs::create_dir_all(&dir_path) + .with_context(|| format!("Failed to create directory {}", dir_path.display()))?; println!("Created directory: {}", dir_path.display()); } else { println!("Would create directory: {}", dir_path.display()); } - + // Copy and process template files - copy_and_process_template("src/admin/samples/rfc.md", &dir_path.join("README.md"), timeframe, &lowercase_timeframe, dry_run)?; - copy_and_process_template("src/admin/samples/goals.md", &dir_path.join("goals.md"), timeframe, &lowercase_timeframe, dry_run)?; - copy_and_process_template("src/admin/samples/not_accepted.md", &dir_path.join("not_accepted.md"), timeframe, &lowercase_timeframe, dry_run)?; - + copy_and_process_template( + "src/admin/samples/rfc.md", + &dir_path.join("README.md"), + timeframe, + &lowercase_timeframe, + dry_run, + )?; + copy_and_process_template( + "src/admin/samples/goals.md", + &dir_path.join("goals.md"), + timeframe, + &lowercase_timeframe, + dry_run, + )?; + copy_and_process_template( + "src/admin/samples/not_accepted.md", + &dir_path.join("not_accepted.md"), + timeframe, + &lowercase_timeframe, + dry_run, + )?; + // Update SUMMARY.md update_summary_md(timeframe, &lowercase_timeframe, dry_run)?; - + // Update main README.md with the new timeframe section update_main_readme(timeframe, &lowercase_timeframe, dry_run)?; - + println!("\nCFP setup for {} completed successfully!", timeframe); println!("\nNext steps:"); - println!("1. Review and customize the generated files in src/{}/", lowercase_timeframe); - println!("2. Prepare a Call For Proposals blog post based on the sample in src/admin/samples/cfp.md"); + println!( + "1. Review and customize the generated files in src/{}/", + lowercase_timeframe + ); + println!( + "2. Prepare a Call For Proposals blog post based on the sample in src/admin/samples/cfp.md" + ); println!("3. Run 'mdbook build' to generate the book with the new content"); - + Ok(()) } @@ -287,21 +348,31 @@ fn validate_timeframe(timeframe: &str) -> Result<()> { } /// Copies a template file to the destination and replaces placeholders -fn copy_and_process_template(template_path: &str, dest_path: &Path, timeframe: &str, lowercase_timeframe: &str, dry_run: bool) -> Result<()> { +fn copy_and_process_template( + template_path: &str, + dest_path: &Path, + timeframe: &str, + lowercase_timeframe: &str, + dry_run: bool, +) -> Result<()> { // Read the template file let template_content = fs::read_to_string(template_path) .with_context(|| format!("Failed to read template file: {}", template_path))?; - + // Use the pure function to process the content - let processed_content = text_processing::process_template_content(&template_content, timeframe, lowercase_timeframe); - + let processed_content = text_processing::process_template_content( + &template_content, + timeframe, + lowercase_timeframe, + ); + // Write to destination file if !dry_run { File::create(dest_path) .with_context(|| format!("Failed to create file: {}", dest_path.display()))? .write_all(processed_content.as_bytes()) .with_context(|| format!("Failed to write to file: {}", dest_path.display()))?; - + println!("Created file: {}", dest_path.display()); } else { println!("Would create file: {}", dest_path.display()); @@ -312,74 +383,105 @@ fn copy_and_process_template(template_path: &str, dest_path: &Path, timeframe: & /// Updates the SUMMARY.md file to include the new timeframe section fn update_summary_md(timeframe: &str, lowercase_timeframe: &str, dry_run: bool) -> Result<()> { let summary_path = "src/SUMMARY.md"; - let content = fs::read_to_string(summary_path) - .with_context(|| format!("Failed to read SUMMARY.md"))?; - + let content = + fs::read_to_string(summary_path).with_context(|| format!("Failed to read SUMMARY.md"))?; + // Use the pure function to process the content - let new_content = text_processing::process_summary_content(&content, timeframe, lowercase_timeframe); - + let new_content = + text_processing::process_summary_content(&content, timeframe, lowercase_timeframe); + // Check if the content was modified if new_content == content { if !dry_run { - println!("SUMMARY.md already contains a non-placeholder entry for {}. Skipping update.", timeframe); + println!( + "SUMMARY.md already contains a non-placeholder entry for {}. Skipping update.", + timeframe + ); } else { - println!("Would skip updating SUMMARY.md (already contains a non-placeholder entry for {})", timeframe); + println!( + "Would skip updating SUMMARY.md (already contains a non-placeholder entry for {})", + timeframe + ); } return Ok(()); } - + // Write the updated content back to SUMMARY.md if !dry_run { fs::write(summary_path, new_content) .with_context(|| format!("Failed to write to SUMMARY.md"))?; - + println!("Updated SUMMARY.md with {} section", timeframe); } else { println!("Would update SUMMARY.md with {} section", timeframe); } - + Ok(()) } /// Updates the src/README.md with information about the new timeframe fn update_main_readme(timeframe: &str, lowercase_timeframe: &str, dry_run: bool) -> Result<()> { let readme_path = "src/README.md"; - let content = fs::read_to_string(readme_path) - .with_context(|| format!("Failed to read README.md"))?; - + let content = + fs::read_to_string(readme_path).with_context(|| format!("Failed to read README.md"))?; + // Use the pure function to process the content - let new_content = text_processing::process_readme_content(&content, timeframe, lowercase_timeframe); - + let new_content = + text_processing::process_readme_content(&content, timeframe, lowercase_timeframe); + // Check if the content was modified if new_content == content { if !dry_run { - println!("README.md already contains up-to-date information for {}. Skipping update.", timeframe); + println!( + "README.md already contains up-to-date information for {}. Skipping update.", + timeframe + ); } else { - println!("Would skip updating README.md (already contains up-to-date information for {})", timeframe); + println!( + "Would skip updating README.md (already contains up-to-date information for {})", + timeframe + ); } return Ok(()); } - + // Determine what kind of update was made for better logging let capitalized_timeframe = format!("{}H{}", &timeframe[0..4], &timeframe[5..]); - let specific_timeframe_pattern = format!(r"## Next goal period(?:\s*\({}\))", regex::escape(&capitalized_timeframe)); + let specific_timeframe_pattern = format!( + r"## Next goal period(?:\s*\({}\))", + regex::escape(&capitalized_timeframe) + ); let specific_re = Regex::new(&specific_timeframe_pattern).unwrap(); - + if specific_re.is_match(&content) && specific_re.is_match(&new_content) { if !dry_run { - println!("Updated existing 'Next goal period ({})' section in src/README.md", capitalized_timeframe); + println!( + "Updated existing 'Next goal period ({})' section in src/README.md", + capitalized_timeframe + ); } else { - println!("Would update existing 'Next goal period ({})' section in src/README.md", capitalized_timeframe); + println!( + "Would update existing 'Next goal period ({})' section in src/README.md", + capitalized_timeframe + ); } - } else if Regex::new(r"## Next goal period(?:\s*\([^\)]*\))").unwrap().is_match(&content) { + } else if Regex::new(r"## Next goal period(?:\s*\([^\)]*\))") + .unwrap() + .is_match(&content) + { if !dry_run { println!("Updated existing 'Next goal period' section in src/README.md"); } else { println!("Would update existing 'Next goal period' section in src/README.md"); } - } else if Regex::new(r"## Current goal period(?:\s*\([^\)]*\))").unwrap().is_match(&content) { + } else if Regex::new(r"## Current goal period(?:\s*\([^\)]*\))") + .unwrap() + .is_match(&content) + { if !dry_run { - println!("Added new 'Next goal period' section after 'Current goal period' in src/README.md"); + println!( + "Added new 'Next goal period' section after 'Current goal period' in src/README.md" + ); } else { println!("Would add new 'Next goal period' section after 'Current goal period' in src/README.md"); } @@ -390,13 +492,13 @@ fn update_main_readme(timeframe: &str, lowercase_timeframe: &str, dry_run: bool) println!("Would add new 'Next goal period' section to src/README.md"); } } - + // Write the updated content back to README.md if !dry_run { fs::write(readme_path, new_content) .with_context(|| format!("Failed to write to src/README.md"))?; } - + Ok(()) } @@ -410,80 +512,80 @@ mod tests { let content = "# YYYYHN Goals\n\nThis is for YYYY and HN."; let result = process_template_content(content, "2026H1", "2026h1"); assert_eq!(result, "# 2026H1 Goals\n\nThis is for 2026 and H1."); - + // Test note removal let content_with_notes = "# YYYYHN Goals\n\n> **NOTE:** This is a note that should be removed.\n> More note content.\n\nThis should stay."; let result = process_template_content(content_with_notes, "2026H1", "2026h1"); assert_eq!(result, "# 2026H1 Goals\n\nThis should stay."); - + // Test that other blockquotes are preserved let content_with_blockquote = "# YYYYHN Goals\n\n> This is a regular blockquote that should stay.\n\n> **NOTE:** This is a note that should be removed.\n> More note content.\n\nThis should stay."; let result = process_template_content(content_with_blockquote, "2026H1", "2026h1"); assert_eq!(result, "# 2026H1 Goals\n\n> This is a regular blockquote that should stay.\n\nThis should stay."); } - + #[test] fn test_process_summary_content_no_existing_section() { // Test adding a new section when no timeframe sections exist let content = "# Summary\n\n[Introduction](./README.md)\n"; let result = process_summary_content(content, "2026h1", "2026h1"); - + assert!(result.contains("# ⏳ 2026H1 goal process")); assert!(result.contains("- [Overview](./2026h1/README.md)")); assert!(result.contains("- [Proposed goals](./2026h1/goals.md)")); assert!(result.contains("- [Goals not accepted](./2026h1/not_accepted.md)")); } - + #[test] fn test_process_summary_content_with_placeholder() { // Test updating a section that has placeholder content let content = "# Summary\n\n[Introduction](./README.md)\n\n# ⏳ 2026H1 goal process\n\n- [Not yet started]()\n"; let result = process_summary_content(content, "2026h1", "2026h1"); - + assert!(result.contains("# ⏳ 2026H1 goal process")); assert!(result.contains("- [Overview](./2026h1/README.md)")); assert!(!result.contains("- [Not yet started]()")); } - + #[test] fn test_process_summary_content_with_existing_content() { // Test that existing non-placeholder content is not modified let content = "# Summary\n\n[Introduction](./README.md)\n\n# ⏳ 2026H1 goal process\n\n- [Already populated](./2026h1/README.md)\n"; let result = process_summary_content(content, "2026h1", "2026h1"); - + // Should not change existing non-placeholder content assert_eq!(result, content); } - + #[test] fn test_process_summary_content_with_other_timeframes() { // Test adding a new section when other timeframe sections exist let content = "# Summary\n\n[Introduction](./README.md)\n\n# ⏳ 2025H1 goal process\n\n- [Overview](./2025h1/README.md)\n"; let result = process_summary_content(content, "2026h1", "2026h1"); - + assert!(result.contains("# ⏳ 2025H1 goal process")); assert!(result.contains("# ⏳ 2026H1 goal process")); assert!(result.contains("- [Overview](./2026h1/README.md)")); } - + #[test] fn test_process_readme_content_no_existing_section() { // Test adding a new section when no next goal period section exists let content = "# Project goals\n\n## Current goal period (2025H1)\n\nThe 2025H1 goal period runs from Jan 1 to Jun 30."; let result = process_readme_content(content, "2026h1", "2026h1"); - + println!("{}", result); assert!(result.contains("## Next goal period (2026H1)")); assert!(result.contains("running from the start of January to the end of June")); assert!(result.contains("[Click here](./2026h1/goals.md)")); } - + #[test] fn test_process_readme_content_with_existing_section() { // Test updating an existing section for the same timeframe let content = "# Project goals\n\n## Current goal period (2025H1)\n\nThe 2025H1 goal period runs from Jan 1 to Jun 30.\n\n## Next goal period (2026H1)\n\nOld content."; let result = process_readme_content(content, "2026h1", "2026h1"); - + assert!(result.contains("## Next goal period (2026H1)")); assert!(result.contains("running from the start of January to the end of June")); assert!(!result.contains("Old content.")); @@ -494,40 +596,40 @@ mod tests { // Test updating an existing section while preserving unrelated sections let content = "# Project goals\n\n## Current goal period (2025H1)\n\nThe 2025H1 goal period runs from Jan 1 to Jun 30.\n\n## Next goal period (2026H1)\n\nOld content.\n\n## Extra section\nsome content"; let result = process_readme_content(content, "2026h1", "2026h1"); - + assert!(result.contains("## Next goal period (2026H1)")); assert!(result.contains("running from the start of January to the end of June")); assert!(!result.contains("Old content.")); assert!(result.contains("## Extra section\nsome content")); } - + #[test] fn test_process_readme_content_with_different_timeframe() { // Test replacing an existing next goal period section with a different timeframe let content = "# Project goals\n\n## Current goal period (2025H1)\n\nThe 2025H1 goal period runs from Jan 1 to Jun 30.\n\n## Next goal period (2025H2)\n\nOld content."; let result = process_readme_content(content, "2026h1", "2026h1"); - + assert!(result.contains("## Next goal period (2026H1)")); assert!(!result.contains("## Next goal period (2025H2)")); assert!(result.contains("running from the start of January to the end of June")); } - + #[test] fn test_process_readme_content_second_half() { // Test that the correct months are used for the second half of the year let content = "# Project goals\n\n## Current goal period (2025H2)\n\nThe 2025H2 goal period runs from Jul 1 to Dec 31."; let result = process_readme_content(content, "2026h2", "2026h2"); - + assert!(result.contains("## Next goal period (2026H2)")); assert!(result.contains("running from the start of July to the end of December")); } - + #[test] fn test_process_readme_content_no_current_period() { // Test adding a section when there's no current goal period section let content = "# Project goals\n\n## About the process\n\nSome content."; let result = process_readme_content(content, "2026h1", "2026h1"); - + assert!(result.contains("## Next goal period (2026H1)")); assert!(result.contains("running from the start of January to the end of June")); } diff --git a/crates/rust-project-goals-cli/src/main.rs b/crates/rust-project-goals-cli/src/main.rs index 08e93bdc..9accb36a 100644 --- a/crates/rust-project-goals-cli/src/main.rs +++ b/crates/rust-project-goals-cli/src/main.rs @@ -30,7 +30,7 @@ enum Command { /// Print the RFC text to stdout RFC { path: PathBuf }, - + /// Set up a new Call For Proposals (CFP) period CFP { /// Timeframe for the new CFP period (e.g., 2025h1) @@ -101,7 +101,11 @@ fn main() -> anyhow::Result<()> { rfc::generate_comment(&path)?; } - Command::CFP { timeframe, force, dry_run } => { + Command::CFP { + timeframe, + force, + dry_run, + } => { cfp::create_cfp(timeframe, *force, *dry_run)?; } From 454d7d355f6e1c075542e21ce04e873e706c94dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 14 Jun 2025 08:20:14 +0000 Subject: [PATCH 03/13] remove unused dependencies --- Cargo.lock | 572 +------------------ crates/rust-project-goals-cli-llm/Cargo.toml | 3 - crates/rust-project-goals-cli/Cargo.toml | 2 - 3 files changed, 6 insertions(+), 571 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ac44185..be7f033b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,374 +127,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "aws-config" -version = "1.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b49afaa341e8dd8577e1a2200468f98956d6eda50bcf4a53246cc00174ba924" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-sdk-sso", - "aws-sdk-ssooidc", - "aws-sdk-sts", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-json 0.60.7", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "hex", - "http 0.2.12", - "ring", - "time", - "tokio", - "tracing", - "url", - "zeroize", -] - -[[package]] -name = "aws-credential-types" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e8f6b615cb5fc60a98132268508ad104310f0cfb25a1c22eee76efdf9154da" -dependencies = [ - "aws-smithy-async", - "aws-smithy-runtime-api", - "aws-smithy-types", - "zeroize", -] - -[[package]] -name = "aws-runtime" -version = "1.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5ac934720fbb46206292d2c75b57e67acfc56fe7dfd34fb9a02334af08409ea" -dependencies = [ - "aws-credential-types", - "aws-sigv4", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "http 0.2.12", - "http-body 0.4.6", - "once_cell", - "percent-encoding", - "pin-project-lite", - "tracing", - "uuid", -] - -[[package]] -name = "aws-sdk-bedrock" -version = "1.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73778742601b8345891eb5a3733d48e447f61601438d18222c4ba399cb75560c" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-json 0.61.1", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "http 0.2.12", - "once_cell", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sdk-bedrockruntime" -version = "1.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ed488cf4e3f3acd56ead725dc01a60afcad0161b46c692aa605d63c679e7d0" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-eventstream", - "aws-smithy-http", - "aws-smithy-json 0.61.1", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "http 0.2.12", - "once_cell", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sdk-sso" -version = "1.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ca43a4ef210894f93096039ef1d6fa4ad3edfabb3be92b80908b9f2e4b4eab" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-json 0.61.1", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "http 0.2.12", - "once_cell", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sdk-ssooidc" -version = "1.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abaf490c2e48eed0bb8e2da2fb08405647bd7f253996e0f93b981958ea0f73b0" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-json 0.61.1", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-types", - "bytes", - "http 0.2.12", - "once_cell", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sdk-sts" -version = "1.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68fde0d69c8bfdc1060ea7da21df3e39f6014da316783336deff0a9ec28f4bf" -dependencies = [ - "aws-credential-types", - "aws-runtime", - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-json 0.61.1", - "aws-smithy-query", - "aws-smithy-runtime", - "aws-smithy-runtime-api", - "aws-smithy-types", - "aws-smithy-xml", - "aws-types", - "http 0.2.12", - "once_cell", - "regex-lite", - "tracing", -] - -[[package]] -name = "aws-sigv4" -version = "1.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3820e0c08d0737872ff3c7c1f21ebbb6693d832312d6152bf18ef50a5471c2" -dependencies = [ - "aws-credential-types", - "aws-smithy-http", - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "form_urlencoded", - "hex", - "hmac", - "http 0.2.12", - "http 1.1.0", - "once_cell", - "percent-encoding", - "sha2", - "time", - "tracing", -] - -[[package]] -name = "aws-smithy-async" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" -dependencies = [ - "futures-util", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "aws-smithy-eventstream" -version = "0.60.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef7d0a272725f87e51ba2bf89f8c21e4df61b9e49ae1ac367a6d69916ef7c90" -dependencies = [ - "aws-smithy-types", - "bytes", - "crc32fast", -] - -[[package]] -name = "aws-smithy-http" -version = "0.60.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8bc3e8fdc6b8d07d976e301c02fe553f72a39b7a9fea820e023268467d7ab6" -dependencies = [ - "aws-smithy-eventstream", - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", - "http 0.2.12", - "http-body 0.4.6", - "once_cell", - "percent-encoding", - "pin-project-lite", - "pin-utils", - "tracing", -] - -[[package]] -name = "aws-smithy-json" -version = "0.60.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" -dependencies = [ - "aws-smithy-types", -] - -[[package]] -name = "aws-smithy-json" -version = "0.61.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4e69cc50921eb913c6b662f8d909131bb3e6ad6cb6090d3a39b66fc5c52095" -dependencies = [ - "aws-smithy-types", -] - -[[package]] -name = "aws-smithy-query" -version = "0.60.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" -dependencies = [ - "aws-smithy-types", - "urlencoding", -] - -[[package]] -name = "aws-smithy-runtime" -version = "1.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f20685047ca9d6f17b994a07f629c813f08b5bce65523e47124879e60103d45" -dependencies = [ - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-runtime-api", - "aws-smithy-types", - "bytes", - "fastrand", - "h2 0.3.26", - "http 0.2.12", - "http-body 0.4.6", - "http-body 1.0.0", - "httparse", - "hyper 0.14.29", - "hyper-rustls 0.24.2", - "once_cell", - "pin-project-lite", - "pin-utils", - "rustls 0.21.12", - "tokio", - "tracing", -] - -[[package]] -name = "aws-smithy-runtime-api" -version = "1.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92165296a47a812b267b4f41032ff8069ab7ff783696d217f0994a0d7ab585cd" -dependencies = [ - "aws-smithy-async", - "aws-smithy-types", - "bytes", - "http 0.2.12", - "http 1.1.0", - "pin-project-lite", - "tokio", - "tracing", - "zeroize", -] - -[[package]] -name = "aws-smithy-types" -version = "1.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd94a32b3a7d55d3806fe27d98d3ad393050439dd05eb53ece36ec5e3d3510" -dependencies = [ - "base64-simd", - "bytes", - "bytes-utils", - "futures-core", - "http 0.2.12", - "http 1.1.0", - "http-body 0.4.6", - "http-body 1.0.0", - "http-body-util", - "itoa", - "num-integer", - "pin-project-lite", - "pin-utils", - "ryu", - "serde", - "time", - "tokio", - "tokio-util", -] - -[[package]] -name = "aws-smithy-xml" -version = "0.60.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" -dependencies = [ - "xmlparser", -] - -[[package]] -name = "aws-types" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" -dependencies = [ - "aws-credential-types", - "aws-smithy-async", - "aws-smithy-runtime-api", - "aws-smithy-types", - "rustc_version", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.73" @@ -522,16 +154,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "base64-simd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" -dependencies = [ - "outref", - "vsimd", -] - [[package]] name = "bincode" version = "1.3.3" @@ -631,16 +253,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" -[[package]] -name = "bytes-utils" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" -dependencies = [ - "bytes", - "either", -] - [[package]] name = "caseless" version = "0.2.1" @@ -908,15 +520,8 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", - "subtle", ] -[[package]] -name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - [[package]] name = "elasticlunr-rs" version = "3.0.2" @@ -1268,21 +873,6 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "html5ever" version = "0.27.0" @@ -1415,22 +1005,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http 0.2.12", - "hyper 0.14.29", - "log", - "rustls 0.21.12", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.24.1", -] - [[package]] name = "hyper-rustls" version = "0.27.2" @@ -1441,10 +1015,10 @@ dependencies = [ "http 1.1.0", "hyper 1.4.0", "hyper-util", - "rustls 0.23.11", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls", "tower-service", ] @@ -1874,15 +1448,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - [[package]] name = "num-modular" version = "0.6.1" @@ -2000,12 +1565,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "outref" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" - [[package]] name = "parking_lot" version = "0.12.3" @@ -2347,12 +1906,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "regex-lite" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" - [[package]] name = "regex-syntax" version = "0.8.5" @@ -2376,7 +1929,7 @@ dependencies = [ "http-body 1.0.0", "http-body-util", "hyper 1.4.0", - "hyper-rustls 0.27.2", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", @@ -2387,7 +1940,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile 2.1.2", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", @@ -2441,14 +1994,12 @@ name = "rust-project-goals-cli" version = "0.1.0" dependencies = [ "anyhow", - "chrono", "clap", "progress_bar", "regex", "rust-project-goals", "rust-project-goals-json", "rust-project-goals-llm", - "serde", "serde_json", "walkdir", ] @@ -2458,9 +2009,6 @@ name = "rust-project-goals-cli-llm" version = "0.1.0" dependencies = [ "anyhow", - "aws-config", - "aws-sdk-bedrock", - "aws-sdk-bedrockruntime", "chrono", "clap", "comrak", @@ -2505,15 +2053,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.38.34" @@ -2527,18 +2066,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.11" @@ -2547,32 +2074,11 @@ checksum = "4828ea528154ae444e5a642dbb7d5623354030dc9822b83fd9bb79683c7399d0" dependencies = [ "once_cell", "rustls-pki-types", - "rustls-webpki 0.102.5", + "rustls-webpki", "subtle", "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile 1.0.4", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.1.2" @@ -2589,16 +2095,6 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.102.5" @@ -2652,16 +2148,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "2.11.0" @@ -3074,23 +2560,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.11", + "rustls", "rustls-pki-types", "tokio", ] @@ -3204,21 +2680,9 @@ checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tracing-core" version = "0.1.32" @@ -3324,12 +2788,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf-8" version = "0.7.6" @@ -3342,12 +2800,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "uuid" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" - [[package]] name = "vcpkg" version = "0.2.15" @@ -3360,12 +2812,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vsimd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" - [[package]] name = "walkdir" version = "2.5.0" @@ -3728,12 +3174,6 @@ version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" -[[package]] -name = "xmlparser" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" - [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/crates/rust-project-goals-cli-llm/Cargo.toml b/crates/rust-project-goals-cli-llm/Cargo.toml index cd1d2229..a252b0cf 100644 --- a/crates/rust-project-goals-cli-llm/Cargo.toml +++ b/crates/rust-project-goals-cli-llm/Cargo.toml @@ -5,9 +5,6 @@ edition = "2021" [dependencies] anyhow = "1.0.94" -aws-config = "1.5.8" -aws-sdk-bedrock = "1.57.0" -aws-sdk-bedrockruntime = "1.55.0" chrono = "0.4.39" clap = { version = "4.5.23", features = ["derive"] } rust-project-goals = { version = "0.1.0", path = "../rust-project-goals" } diff --git a/crates/rust-project-goals-cli/Cargo.toml b/crates/rust-project-goals-cli/Cargo.toml index 44b912ee..40bb94a5 100644 --- a/crates/rust-project-goals-cli/Cargo.toml +++ b/crates/rust-project-goals-cli/Cargo.toml @@ -8,10 +8,8 @@ anyhow = "1.0.94" regex = "1.11.1" rust-project-goals = { version = "0.1.0", path = "../rust-project-goals" } rust-project-goals-llm = { version = "0.1.0", path = "../rust-project-goals-llm" } -serde = "1.0.216" walkdir = "2.5.0" serde_json = "1.0.133" -chrono = "0.4.39" progress_bar = "1.0.6" clap = { version = "4.5.23", features = ["derive"] } rust-project-goals-json = { version = "0.1.0", path = "../rust-project-goals-json" } From 10404d115d7b9af02a190f65287110f16bdea1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 14 Jun 2025 08:59:16 +0000 Subject: [PATCH 04/13] fix comments --- crates/rust-project-goals-cli-llm/src/main.rs | 3 +-- crates/rust-project-goals-cli/src/main.rs | 4 +--- crates/rust-project-goals-llm/src/lib.rs | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/rust-project-goals-cli-llm/src/main.rs b/crates/rust-project-goals-cli-llm/src/main.rs index 93921924..911bf16a 100644 --- a/crates/rust-project-goals-cli-llm/src/main.rs +++ b/crates/rust-project-goals-cli-llm/src/main.rs @@ -1,5 +1,4 @@ -//! Code to invoke a LLM to summarize content and generate blog posts. -//! Currently based on AWS bedrock. +//! Code to collect updates on tracking issues and generate blog posts. use clap::Parser; use rust_project_goals::gh::issue_id::Repository; diff --git a/crates/rust-project-goals-cli/src/main.rs b/crates/rust-project-goals-cli/src/main.rs index 9accb36a..2b4f80b2 100644 --- a/crates/rust-project-goals-cli/src/main.rs +++ b/crates/rust-project-goals-cli/src/main.rs @@ -86,7 +86,7 @@ enum Command { }, /// Generate markdown with the list of updates for each tracking issue. - /// Collects updates + /// Collects goal updates. Updates { #[command(flatten)] updates: UpdateArgs, @@ -140,8 +140,6 @@ fn main() -> anyhow::Result<()> { generate_json::generate_json(&opt.repository, &milestone, json_path)?; } Command::Updates { updates } => { - // The updates command is compiled separately so that we don't have to - // build all the LLM stuff if we are not using it. let status = std::process::Command::new("cargo") .arg("run") .arg("-p") diff --git a/crates/rust-project-goals-llm/src/lib.rs b/crates/rust-project-goals-llm/src/lib.rs index 4060dc8d..3de479a9 100644 --- a/crates/rust-project-goals-llm/src/lib.rs +++ b/crates/rust-project-goals-llm/src/lib.rs @@ -1,4 +1,4 @@ -//! Library for the LLM execution -- just encodes the command-line arguments. +//! Library for generating updates -- just encodes the command-line arguments. //! Most of the work is in the `main.rs` binary. use std::path::PathBuf; @@ -11,7 +11,7 @@ pub struct UpdateArgs { /// Milestone for which we generate tracking issue data (e.g., `2024h2`). pub milestone: String, - /// Quick mode does not use an LLM to generate a summary. + /// Open the generated summary in vscode. #[arg(long)] pub vscode: bool, From 3492db64a89b9962579b52279ed339c80b6f4c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 14 Jun 2025 09:03:48 +0000 Subject: [PATCH 05/13] remove async --- Cargo.lock | 12 ------------ crates/rust-project-goals-cli-llm/Cargo.toml | 1 - crates/rust-project-goals-cli-llm/src/main.rs | 7 ++----- crates/rust-project-goals-cli-llm/src/updates.rs | 8 ++++---- 4 files changed, 6 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be7f033b..4883ceba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2019,7 +2019,6 @@ dependencies = [ "rust-project-goals-llm", "serde", "serde_json", - "tokio", ] [[package]] @@ -2264,15 +2263,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[package]] -name = "signal-hook-registry" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" -dependencies = [ - "libc", -] - [[package]] name = "siphasher" version = "0.3.11" @@ -2531,9 +2521,7 @@ dependencies = [ "bytes", "libc", "mio 1.0.2", - "parking_lot", "pin-project-lite", - "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", diff --git a/crates/rust-project-goals-cli-llm/Cargo.toml b/crates/rust-project-goals-cli-llm/Cargo.toml index a252b0cf..c4233ae2 100644 --- a/crates/rust-project-goals-cli-llm/Cargo.toml +++ b/crates/rust-project-goals-cli-llm/Cargo.toml @@ -14,5 +14,4 @@ serde = "1.0.216" serde_json = "1.0.133" comrak = "0.31.0" progress_bar = "1.0.6" -tokio = { version = "1.42.0", features = ["full"] } rust-project-goals-json = { version = "0.1.0", path = "../rust-project-goals-json" } diff --git a/crates/rust-project-goals-cli-llm/src/main.rs b/crates/rust-project-goals-cli-llm/src/main.rs index 911bf16a..6452704c 100644 --- a/crates/rust-project-goals-cli-llm/src/main.rs +++ b/crates/rust-project-goals-cli-llm/src/main.rs @@ -14,8 +14,7 @@ struct Opt { updates_json: String, } -#[tokio::main] -async fn main() -> anyhow::Result<()> { +fn main() -> anyhow::Result<()> { let Opt { repository, updates_json, @@ -27,7 +26,7 @@ async fn main() -> anyhow::Result<()> { start_date, end_date, } = &serde_json::from_str(&updates_json)?; - updates::updates( + updates::generate_updates( &repository, milestone, output_file.as_deref(), @@ -35,6 +34,4 @@ async fn main() -> anyhow::Result<()> { end_date, *vscode, ) - .await?; - Ok(()) } diff --git a/crates/rust-project-goals-cli-llm/src/updates.rs b/crates/rust-project-goals-cli-llm/src/updates.rs index 255e1011..c4ae147a 100644 --- a/crates/rust-project-goals-cli-llm/src/updates.rs +++ b/crates/rust-project-goals-cli-llm/src/updates.rs @@ -15,7 +15,7 @@ use rust_project_goals::gh::{ issues::{checkboxes, list_issues_in_milestone, ExistingGithubComment}, }; -pub async fn updates( +pub(crate) fn generate_updates( repository: &Repository, milestone: &str, output_file: Option<&Path>, @@ -44,8 +44,8 @@ pub async fn updates( progress_bar::Style::Bold, ); - let flagship_goals = prepare_goals(repository, &issues, &filter, true).await?; - let other_goals = prepare_goals(repository, &issues, &filter, false).await?; + let flagship_goals = prepare_goals(repository, &issues, &filter, true)?; + let other_goals = prepare_goals(repository, &issues, &filter, false)?; let updates = templates::Updates::new(milestone.to_string(), flagship_goals, other_goals); progress_bar::finalize_progress_bar(); @@ -79,7 +79,7 @@ pub async fn updates( Ok(()) } -async fn prepare_goals( +fn prepare_goals( repository: &Repository, issues: &[ExistingGithubIssue], filter: &Filter<'_>, From 97b37d826eb7c91cebbf132fbebc9f81d8902b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 14 Jun 2025 09:11:48 +0000 Subject: [PATCH 06/13] move update generation to main crate --- Cargo.lock | 5 +- crates/rust-project-goals-cli/Cargo.toml | 5 +- crates/rust-project-goals-cli/src/main.rs | 52 ++-- crates/rust-project-goals-cli/src/updates.rs | 253 ++++++++++++++++++ .../src/updates/templates.rs | 120 +++++++++ 5 files changed, 415 insertions(+), 20 deletions(-) create mode 100644 crates/rust-project-goals-cli/src/updates.rs create mode 100644 crates/rust-project-goals-cli/src/updates/templates.rs diff --git a/Cargo.lock b/Cargo.lock index 4883ceba..dbc249ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1994,12 +1994,15 @@ name = "rust-project-goals-cli" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "clap", + "comrak", + "handlebars", "progress_bar", "regex", "rust-project-goals", "rust-project-goals-json", - "rust-project-goals-llm", + "serde", "serde_json", "walkdir", ] diff --git a/crates/rust-project-goals-cli/Cargo.toml b/crates/rust-project-goals-cli/Cargo.toml index 40bb94a5..66ad46c5 100644 --- a/crates/rust-project-goals-cli/Cargo.toml +++ b/crates/rust-project-goals-cli/Cargo.toml @@ -7,9 +7,12 @@ edition = "2021" anyhow = "1.0.94" regex = "1.11.1" rust-project-goals = { version = "0.1.0", path = "../rust-project-goals" } -rust-project-goals-llm = { version = "0.1.0", path = "../rust-project-goals-llm" } +serde = "1.0.216" walkdir = "2.5.0" serde_json = "1.0.133" +chrono = "0.4.39" progress_bar = "1.0.6" clap = { version = "4.5.23", features = ["derive"] } rust-project-goals-json = { version = "0.1.0", path = "../rust-project-goals-json" } +handlebars = { version = "6.2.0", features = ["dir_source"] } +comrak = "0.31.0" diff --git a/crates/rust-project-goals-cli/src/main.rs b/crates/rust-project-goals-cli/src/main.rs index 2b4f80b2..ebc3a601 100644 --- a/crates/rust-project-goals-cli/src/main.rs +++ b/crates/rust-project-goals-cli/src/main.rs @@ -1,8 +1,7 @@ -use anyhow::{bail, Context}; +use anyhow::Context; use clap::Parser; use regex::Regex; use rust_project_goals::gh::issue_id::Repository; -use rust_project_goals_llm::UpdateArgs; use std::path::PathBuf; use walkdir::WalkDir; @@ -10,6 +9,7 @@ mod cfp; mod generate_json; mod rfc; mod team_repo; +mod updates; #[derive(clap::Parser, Debug)] #[structopt(about = "Project goal preprocessor")] @@ -88,8 +88,24 @@ enum Command { /// Generate markdown with the list of updates for each tracking issue. /// Collects goal updates. Updates { - #[command(flatten)] - updates: UpdateArgs, + /// Milestone for which we generate tracking issue data (e.g., `2024h2`). + milestone: String, + + /// Open the generated summary in vscode. + #[arg(long)] + vscode: bool, + + /// If specified, write the output into the given file. + #[arg(long)] + output_file: Option, + + /// Start date for comments. + /// If not given, defaults to 1 week before the start of this month. + start_date: Option, + + /// End date for comments. + /// If not given, no end date. + end_date: Option, }, } @@ -139,20 +155,20 @@ fn main() -> anyhow::Result<()> { } => { generate_json::generate_json(&opt.repository, &milestone, json_path)?; } - Command::Updates { updates } => { - let status = std::process::Command::new("cargo") - .arg("run") - .arg("-p") - .arg("rust-project-goals-cli-llm") - .arg("-q") - .arg("--") - .arg(&opt.repository.to_string()) - .arg(&serde_json::to_string(updates).unwrap()) - .status()?; - if !status.success() { - bail!("subcommand failed"); - } - } + Command::Updates { + milestone, + vscode, + output_file, + start_date, + end_date, + } => updates::generate_updates( + &opt.repository, + milestone, + output_file.as_deref(), + start_date, + end_date, + *vscode, + )?, } Ok(()) diff --git a/crates/rust-project-goals-cli/src/updates.rs b/crates/rust-project-goals-cli/src/updates.rs new file mode 100644 index 00000000..181c6f75 --- /dev/null +++ b/crates/rust-project-goals-cli/src/updates.rs @@ -0,0 +1,253 @@ +use anyhow::Context; +use chrono::{Datelike, NaiveDate}; +use rust_project_goals::markwaydown; +use rust_project_goals::re::{HELP_WANTED, TLDR}; +use rust_project_goals::util::comma; +use rust_project_goals_json::GithubIssueState; +use std::io::Write; +use std::path::Path; +use std::process::{Command, Stdio}; + +mod templates; +use rust_project_goals::gh::issues::ExistingGithubIssue; +use rust_project_goals::gh::{ + issue_id::{IssueId, Repository}, + issues::{checkboxes, list_issues_in_milestone, ExistingGithubComment}, +}; +use templates::{HelpWanted, UpdatesGoal}; + +pub(crate) fn generate_updates( + repository: &Repository, + milestone: &str, + output_file: Option<&Path>, + start_date: &Option, + end_date: &Option, + vscode: bool, +) -> anyhow::Result<()> { + if output_file.is_none() && !vscode { + anyhow::bail!("either `--output-file` or `--vscode` must be specified"); + } + + let issues = list_issues_in_milestone(repository, milestone)?; + + let filter = Filter { + start_date: match start_date { + Some(date) => date.clone(), + None => default_start_date(), + }, + end_date, + }; + + progress_bar::init_progress_bar(issues.len()); + progress_bar::set_progress_bar_action( + "Executing", + progress_bar::Color::Blue, + progress_bar::Style::Bold, + ); + + let flagship_goals = prepare_goals(repository, &issues, &filter, true)?; + let other_goals = prepare_goals(repository, &issues, &filter, false)?; + let updates = templates::Updates::new(milestone.to_string(), flagship_goals, other_goals); + + progress_bar::finalize_progress_bar(); + + // Render the output using handlebars and write it to the file. + let output = updates.render()?; + + if let Some(output_file) = output_file { + std::fs::write(&output_file, output) + .with_context(|| format!("failed to write to `{}`", output_file.display()))?; + } else if vscode { + let mut child = Command::new("code") + .arg("-") + .stdin(Stdio::piped()) + .spawn() + .with_context(|| "failed to spawn `code` process")?; + + if let Some(stdin) = child.stdin.as_mut() { + stdin + .write_all(output.as_bytes()) + .with_context(|| "failed to write to `code` stdin")?; + } + + child + .wait() + .with_context(|| "failed to wait on `code` process")?; + } else { + println!("{output}"); + } + + Ok(()) +} + +fn prepare_goals( + repository: &Repository, + issues: &[ExistingGithubIssue], + filter: &Filter<'_>, + flagship: bool, +) -> anyhow::Result> { + let mut result = vec![]; + // We process flagship and regular goals in two passes, and capture comments differently for flagship goals. + for issue in issues { + if flagship != issue.has_flagship_label() { + continue; + } + + let issue_id = IssueId { + repository: repository.clone(), + number: issue.number, + }; + + let title = &issue.title; + + progress_bar::print_progress_bar_info( + &format!("Issue #{number}", number = issue.number), + title, + progress_bar::Color::Green, + progress_bar::Style::Bold, + ); + + let progress = checkboxes(&issue); + + let mut comments = issue.comments.clone(); + comments.sort_by_key(|c| c.created_at.clone()); + comments.retain(|c| !c.is_automated_comment() && filter.matches(c)); + // Prettify the comments' timestamp after using it for sorting. + for comment in comments.iter_mut() { + comment.created_at = format!("{}", comment.created_at_date()); + } + + let tldr = tldr(&issue_id, &mut comments)?; + + let (has_help_wanted, help_wanted) = help_wanted(&issue_id, &tldr, &comments)?; + + let why_this_goal = why_this_goal(&issue_id, issue)?; + + let details_summary = match comments.len() { + 0 => String::from("No detailed updates available."), + 1 => String::from("1 detailed update available."), + len => format!("{len} detailed updates available."), + }; + result.push(UpdatesGoal { + title: title.clone(), + issue_number: issue.number, + issue_assignees: comma(&issue.assignees), + issue_url: issue_id.url(), + progress, + has_help_wanted, + help_wanted, + is_closed: issue.state == GithubIssueState::Closed, + details_summary, + comments, + tldr, + why_this_goal, + needs_separator: true, // updated after sorting + }); + + progress_bar::inc_progress_bar(); + } + + // Updates are in a random order, sort them. + result.sort_by_cached_key(|update| update.title.to_lowercase()); + + // Mark the last entry as not needing a separator from its following sibling, it has none. + if let Some(last) = result.last_mut() { + last.needs_separator = false; + } + + Ok(result) +} + +/// Search for a TL;DR comment. If one is found, remove it and return the text. +fn tldr( + _issue_id: &IssueId, + comments: &mut Vec, +) -> anyhow::Result> { + // `comments` are sorted by creation date in an ascending order, so we look for the most recent + // TL;DR comment from the end. + let Some(index) = comments.iter().rposition(|c| c.body.starts_with(TLDR)) else { + return Ok(None); + }; + + let comment = comments.remove(index); + Ok(Some(comment.body[TLDR.len()..].trim().to_string())) +} + +/// Search for comments that talk about help being wanted and extract that +fn help_wanted( + _issue_id: &IssueId, + tldr: &Option, + comments: &[ExistingGithubComment], +) -> anyhow::Result<(bool, Vec)> { + use std::fmt::Write; + + let mut help_wanted = vec![]; + + let tldr_has_help_wanted = tldr + .as_deref() + .unwrap_or("") + .lines() + .any(|line| HELP_WANTED.is_match(line)); + + for comment in comments { + let mut lines = comment.body.split('\n').peekable(); + + // Look for a line that says "Help wanted" at the front. + // Then extract the rest of that line along with subsequent lines until we find a blank line. + while lines.peek().is_some() { + while let Some(line) = lines.next() { + if let Some(c) = HELP_WANTED.captures(line) { + let text = c["text"].trim().to_string(); + if !text.is_empty() { + help_wanted.push(HelpWanted { text }); + break; + } + } + } + + while let Some(line) = lines.next() { + if line.trim().is_empty() { + break; + } else { + let last = help_wanted.len() - 1; + writeln!(&mut help_wanted[last].text, "{line}")?; + } + } + } + } + + Ok((tldr_has_help_wanted || !help_wanted.is_empty(), help_wanted)) +} + +fn why_this_goal(issue_id: &IssueId, issue: &ExistingGithubIssue) -> anyhow::Result { + let sections = markwaydown::parse_text(issue_id.url(), &issue.body)?; + for section in sections { + if section.title == "Why this goal?" { + return Ok(section.text.trim().to_string()); + } + } + return Ok("".to_string()); +} + +struct Filter<'f> { + start_date: NaiveDate, + end_date: &'f Option, +} + +impl Filter<'_> { + fn matches(&self, comment: &ExistingGithubComment) -> bool { + let date = comment.created_at_date(); + + date >= self.start_date + && match self.end_date { + Some(end_date) => date <= *end_date, + None => true, + } + } +} + +fn default_start_date() -> NaiveDate { + let date = chrono::Utc::now().date_naive(); + let start_of_month = NaiveDate::from_ymd_opt(date.year(), date.month(), 1).unwrap(); + start_of_month - chrono::Duration::days(7) +} diff --git a/crates/rust-project-goals-cli/src/updates/templates.rs b/crates/rust-project-goals-cli/src/updates/templates.rs new file mode 100644 index 00000000..6e8b8e15 --- /dev/null +++ b/crates/rust-project-goals-cli/src/updates/templates.rs @@ -0,0 +1,120 @@ +use std::path::{Path, PathBuf}; + +use handlebars::{DirectorySourceOptions, Handlebars}; +use rust_project_goals::gh::issues::ExistingGithubComment; +use serde::Serialize; + +use rust_project_goals_json::Progress; + +pub struct Templates<'h> { + reg: Handlebars<'h>, +} + +impl<'h> Templates<'h> { + pub fn new() -> anyhow::Result { + let templates = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../templates"); + Self::from_templates_dir(&templates) + } + + pub fn from_templates_dir(dir_path: impl AsRef) -> anyhow::Result { + let dir_path = dir_path.as_ref(); + let mut reg = Handlebars::new(); + + reg.set_strict_mode(true); + + reg.register_templates_directory(dir_path, DirectorySourceOptions::default())?; + assert!(reg.get_template("updates").is_some()); + + reg.register_helper("markdown_to_html", Box::new(markdown_to_html)); + reg.register_helper("is_complete", Box::new(is_complete)); + + Ok(Templates { reg }) + } +} + +handlebars::handlebars_helper!(markdown_to_html: |md: String| comrak::markdown_to_html(&md, &Default::default())); + +handlebars::handlebars_helper!(is_complete: |p: Progress| match p { + Progress::Binary { is_closed } => is_closed, + Progress::Tracked { completed, total } => completed == total, + Progress::Error { .. } => false, +}); + +/// The parameters expected by the `updates.md` template. +#[derive(Serialize, Debug)] +pub struct Updates { + pub milestone: String, + pub flagship_goals: Vec, + pub other_goals: Vec, + pub goal_count: usize, + pub flagship_goal_count: usize, +} + +impl Updates { + pub fn new( + milestone: String, + flagship_goals: Vec, + other_goals: Vec, + ) -> Self { + Updates { + milestone, + flagship_goal_count: flagship_goals.len(), + goal_count: flagship_goals.len() + other_goals.len(), + flagship_goals, + other_goals, + } + } + pub fn render(self) -> anyhow::Result { + let templates = Templates::new()?; + Ok(templates.reg.render("updates", &self)?) + } +} + +/// Part of the parameters expected by the `updates.md` template. +#[derive(Serialize, Debug)] +pub struct UpdatesGoal { + /// Title of the tracking issue + pub title: String, + + /// Tracking issue number on the project goals repository + pub issue_number: u64, + + /// Comma-separated list of assignees + pub issue_assignees: String, + + /// URL of the tracking issue + pub issue_url: String, + + /// True if the issue is closed. + pub is_closed: bool, + + /// True if there are "help wanted" comments OR the TL;DR includes a help wanted request. + pub has_help_wanted: bool, + + /// If there are comments that include ["help wanted"](`rust_project_goals::re::HELP_WANTED`) + /// comments, those comments are included here. + pub help_wanted: Vec, + + /// Markdown with update text (bullet list) + pub comments: Vec, + + /// The "
" summary, a prettified version of comments.len(). + pub details_summary: String, + + /// Progress towards the goal + pub progress: Progress, + + /// TL;DR comment (if any, empty string if none) + pub tldr: Option, + + /// Contents of a "Why this goal?" section in the tracking issue (empty string if not present) + pub why_this_goal: String, + + /// If this goal needs to be separated from its following sibling by an empty line. + pub needs_separator: bool, +} + +#[derive(Serialize, Debug)] +pub struct HelpWanted { + pub text: String, +} From bb02a562a8a38c31170980db216f3cdfbbba076f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 14 Jun 2025 09:13:54 +0000 Subject: [PATCH 07/13] remove unneeded crates --- Cargo.lock | 27 -- Cargo.toml | 2 +- crates/rust-project-goals-cli-llm/Cargo.toml | 17 -- crates/rust-project-goals-cli-llm/src/main.rs | 37 --- .../src/templates.rs | 120 --------- .../rust-project-goals-cli-llm/src/updates.rs | 252 ------------------ crates/rust-project-goals-llm/Cargo.toml | 9 - crates/rust-project-goals-llm/src/lib.rs | 29 -- 8 files changed, 1 insertion(+), 492 deletions(-) delete mode 100644 crates/rust-project-goals-cli-llm/Cargo.toml delete mode 100644 crates/rust-project-goals-cli-llm/src/main.rs delete mode 100644 crates/rust-project-goals-cli-llm/src/templates.rs delete mode 100644 crates/rust-project-goals-cli-llm/src/updates.rs delete mode 100644 crates/rust-project-goals-llm/Cargo.toml delete mode 100644 crates/rust-project-goals-llm/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index dbc249ea..126fe1ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,7 +285,6 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", - "serde", "wasm-bindgen", "windows-targets 0.52.6", ] @@ -2007,23 +2006,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "rust-project-goals-cli-llm" -version = "0.1.0" -dependencies = [ - "anyhow", - "chrono", - "clap", - "comrak", - "handlebars", - "progress_bar", - "rust-project-goals", - "rust-project-goals-json", - "rust-project-goals-llm", - "serde", - "serde_json", -] - [[package]] name = "rust-project-goals-json" version = "0.1.0" @@ -2031,15 +2013,6 @@ dependencies = [ "serde", ] -[[package]] -name = "rust-project-goals-llm" -version = "0.1.0" -dependencies = [ - "chrono", - "clap", - "serde", -] - [[package]] name = "rust_team_data" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 00fd7ab1..f289762d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] -members = ["crates/mdbook-goals", "crates/rust-project-goals", "crates/rust-project-goals-cli", "crates/rust-project-goals-cli-llm", "crates/rust-project-goals-json", "crates/rust-project-goals-llm"] +members = ["crates/mdbook-goals", "crates/rust-project-goals", "crates/rust-project-goals-cli", "crates/rust-project-goals-json"] resolver = "2" [profile.dev] diff --git a/crates/rust-project-goals-cli-llm/Cargo.toml b/crates/rust-project-goals-cli-llm/Cargo.toml deleted file mode 100644 index c4233ae2..00000000 --- a/crates/rust-project-goals-cli-llm/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "rust-project-goals-cli-llm" -version = "0.1.0" -edition = "2021" - -[dependencies] -anyhow = "1.0.94" -chrono = "0.4.39" -clap = { version = "4.5.23", features = ["derive"] } -rust-project-goals = { version = "0.1.0", path = "../rust-project-goals" } -rust-project-goals-llm = { version = "0.1.0", path = "../rust-project-goals-llm" } -handlebars = { version = "6.2.0", features = ["dir_source"] } -serde = "1.0.216" -serde_json = "1.0.133" -comrak = "0.31.0" -progress_bar = "1.0.6" -rust-project-goals-json = { version = "0.1.0", path = "../rust-project-goals-json" } diff --git a/crates/rust-project-goals-cli-llm/src/main.rs b/crates/rust-project-goals-cli-llm/src/main.rs deleted file mode 100644 index 6452704c..00000000 --- a/crates/rust-project-goals-cli-llm/src/main.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! Code to collect updates on tracking issues and generate blog posts. - -use clap::Parser; -use rust_project_goals::gh::issue_id::Repository; -use rust_project_goals_llm::UpdateArgs; - -mod templates; -mod updates; - -#[derive(clap::Parser, Debug)] -#[structopt(about = "Project goal preprocessor")] -struct Opt { - repository: Repository, - updates_json: String, -} - -fn main() -> anyhow::Result<()> { - let Opt { - repository, - updates_json, - } = Opt::parse(); - let UpdateArgs { - milestone, - vscode, - output_file, - start_date, - end_date, - } = &serde_json::from_str(&updates_json)?; - updates::generate_updates( - &repository, - milestone, - output_file.as_deref(), - start_date, - end_date, - *vscode, - ) -} diff --git a/crates/rust-project-goals-cli-llm/src/templates.rs b/crates/rust-project-goals-cli-llm/src/templates.rs deleted file mode 100644 index 6e8b8e15..00000000 --- a/crates/rust-project-goals-cli-llm/src/templates.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::path::{Path, PathBuf}; - -use handlebars::{DirectorySourceOptions, Handlebars}; -use rust_project_goals::gh::issues::ExistingGithubComment; -use serde::Serialize; - -use rust_project_goals_json::Progress; - -pub struct Templates<'h> { - reg: Handlebars<'h>, -} - -impl<'h> Templates<'h> { - pub fn new() -> anyhow::Result { - let templates = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../templates"); - Self::from_templates_dir(&templates) - } - - pub fn from_templates_dir(dir_path: impl AsRef) -> anyhow::Result { - let dir_path = dir_path.as_ref(); - let mut reg = Handlebars::new(); - - reg.set_strict_mode(true); - - reg.register_templates_directory(dir_path, DirectorySourceOptions::default())?; - assert!(reg.get_template("updates").is_some()); - - reg.register_helper("markdown_to_html", Box::new(markdown_to_html)); - reg.register_helper("is_complete", Box::new(is_complete)); - - Ok(Templates { reg }) - } -} - -handlebars::handlebars_helper!(markdown_to_html: |md: String| comrak::markdown_to_html(&md, &Default::default())); - -handlebars::handlebars_helper!(is_complete: |p: Progress| match p { - Progress::Binary { is_closed } => is_closed, - Progress::Tracked { completed, total } => completed == total, - Progress::Error { .. } => false, -}); - -/// The parameters expected by the `updates.md` template. -#[derive(Serialize, Debug)] -pub struct Updates { - pub milestone: String, - pub flagship_goals: Vec, - pub other_goals: Vec, - pub goal_count: usize, - pub flagship_goal_count: usize, -} - -impl Updates { - pub fn new( - milestone: String, - flagship_goals: Vec, - other_goals: Vec, - ) -> Self { - Updates { - milestone, - flagship_goal_count: flagship_goals.len(), - goal_count: flagship_goals.len() + other_goals.len(), - flagship_goals, - other_goals, - } - } - pub fn render(self) -> anyhow::Result { - let templates = Templates::new()?; - Ok(templates.reg.render("updates", &self)?) - } -} - -/// Part of the parameters expected by the `updates.md` template. -#[derive(Serialize, Debug)] -pub struct UpdatesGoal { - /// Title of the tracking issue - pub title: String, - - /// Tracking issue number on the project goals repository - pub issue_number: u64, - - /// Comma-separated list of assignees - pub issue_assignees: String, - - /// URL of the tracking issue - pub issue_url: String, - - /// True if the issue is closed. - pub is_closed: bool, - - /// True if there are "help wanted" comments OR the TL;DR includes a help wanted request. - pub has_help_wanted: bool, - - /// If there are comments that include ["help wanted"](`rust_project_goals::re::HELP_WANTED`) - /// comments, those comments are included here. - pub help_wanted: Vec, - - /// Markdown with update text (bullet list) - pub comments: Vec, - - /// The "
" summary, a prettified version of comments.len(). - pub details_summary: String, - - /// Progress towards the goal - pub progress: Progress, - - /// TL;DR comment (if any, empty string if none) - pub tldr: Option, - - /// Contents of a "Why this goal?" section in the tracking issue (empty string if not present) - pub why_this_goal: String, - - /// If this goal needs to be separated from its following sibling by an empty line. - pub needs_separator: bool, -} - -#[derive(Serialize, Debug)] -pub struct HelpWanted { - pub text: String, -} diff --git a/crates/rust-project-goals-cli-llm/src/updates.rs b/crates/rust-project-goals-cli-llm/src/updates.rs deleted file mode 100644 index c4ae147a..00000000 --- a/crates/rust-project-goals-cli-llm/src/updates.rs +++ /dev/null @@ -1,252 +0,0 @@ -use anyhow::Context; -use chrono::{Datelike, NaiveDate}; -use rust_project_goals::markwaydown; -use rust_project_goals::re::{HELP_WANTED, TLDR}; -use rust_project_goals::util::comma; -use rust_project_goals_json::GithubIssueState; -use std::io::Write; -use std::path::Path; -use std::process::{Command, Stdio}; - -use crate::templates::{self, HelpWanted, UpdatesGoal}; -use rust_project_goals::gh::issues::ExistingGithubIssue; -use rust_project_goals::gh::{ - issue_id::{IssueId, Repository}, - issues::{checkboxes, list_issues_in_milestone, ExistingGithubComment}, -}; - -pub(crate) fn generate_updates( - repository: &Repository, - milestone: &str, - output_file: Option<&Path>, - start_date: &Option, - end_date: &Option, - vscode: bool, -) -> anyhow::Result<()> { - if output_file.is_none() && !vscode { - anyhow::bail!("either `--output-file` or `--vscode` must be specified"); - } - - let issues = list_issues_in_milestone(repository, milestone)?; - - let filter = Filter { - start_date: match start_date { - Some(date) => date.clone(), - None => default_start_date(), - }, - end_date, - }; - - progress_bar::init_progress_bar(issues.len()); - progress_bar::set_progress_bar_action( - "Executing", - progress_bar::Color::Blue, - progress_bar::Style::Bold, - ); - - let flagship_goals = prepare_goals(repository, &issues, &filter, true)?; - let other_goals = prepare_goals(repository, &issues, &filter, false)?; - let updates = templates::Updates::new(milestone.to_string(), flagship_goals, other_goals); - - progress_bar::finalize_progress_bar(); - - // Render the output using handlebars and write it to the file. - let output = updates.render()?; - - if let Some(output_file) = output_file { - std::fs::write(&output_file, output) - .with_context(|| format!("failed to write to `{}`", output_file.display()))?; - } else if vscode { - let mut child = Command::new("code") - .arg("-") - .stdin(Stdio::piped()) - .spawn() - .with_context(|| "failed to spawn `code` process")?; - - if let Some(stdin) = child.stdin.as_mut() { - stdin - .write_all(output.as_bytes()) - .with_context(|| "failed to write to `code` stdin")?; - } - - child - .wait() - .with_context(|| "failed to wait on `code` process")?; - } else { - println!("{output}"); - } - - Ok(()) -} - -fn prepare_goals( - repository: &Repository, - issues: &[ExistingGithubIssue], - filter: &Filter<'_>, - flagship: bool, -) -> anyhow::Result> { - let mut result = vec![]; - // We process flagship and regular goals in two passes, and capture comments differently for flagship goals. - for issue in issues { - if flagship != issue.has_flagship_label() { - continue; - } - - let issue_id = IssueId { - repository: repository.clone(), - number: issue.number, - }; - - let title = &issue.title; - - progress_bar::print_progress_bar_info( - &format!("Issue #{number}", number = issue.number), - title, - progress_bar::Color::Green, - progress_bar::Style::Bold, - ); - - let progress = checkboxes(&issue); - - let mut comments = issue.comments.clone(); - comments.sort_by_key(|c| c.created_at.clone()); - comments.retain(|c| !c.is_automated_comment() && filter.matches(c)); - // Prettify the comments' timestamp after using it for sorting. - for comment in comments.iter_mut() { - comment.created_at = format!("{}", comment.created_at_date()); - } - - let tldr = tldr(&issue_id, &mut comments)?; - - let (has_help_wanted, help_wanted) = help_wanted(&issue_id, &tldr, &comments)?; - - let why_this_goal = why_this_goal(&issue_id, issue)?; - - let details_summary = match comments.len() { - 0 => String::from("No detailed updates available."), - 1 => String::from("1 detailed update available."), - len => format!("{len} detailed updates available."), - }; - result.push(UpdatesGoal { - title: title.clone(), - issue_number: issue.number, - issue_assignees: comma(&issue.assignees), - issue_url: issue_id.url(), - progress, - has_help_wanted, - help_wanted, - is_closed: issue.state == GithubIssueState::Closed, - details_summary, - comments, - tldr, - why_this_goal, - needs_separator: true, // updated after sorting - }); - - progress_bar::inc_progress_bar(); - } - - // Updates are in a random order, sort them. - result.sort_by_cached_key(|update| update.title.to_lowercase()); - - // Mark the last entry as not needing a separator from its following sibling, it has none. - if let Some(last) = result.last_mut() { - last.needs_separator = false; - } - - Ok(result) -} - -/// Search for a TL;DR comment. If one is found, remove it and return the text. -fn tldr( - _issue_id: &IssueId, - comments: &mut Vec, -) -> anyhow::Result> { - // `comments` are sorted by creation date in an ascending order, so we look for the most recent - // TL;DR comment from the end. - let Some(index) = comments.iter().rposition(|c| c.body.starts_with(TLDR)) else { - return Ok(None); - }; - - let comment = comments.remove(index); - Ok(Some(comment.body[TLDR.len()..].trim().to_string())) -} - -/// Search for comments that talk about help being wanted and extract that -fn help_wanted( - _issue_id: &IssueId, - tldr: &Option, - comments: &[ExistingGithubComment], -) -> anyhow::Result<(bool, Vec)> { - use std::fmt::Write; - - let mut help_wanted = vec![]; - - let tldr_has_help_wanted = tldr - .as_deref() - .unwrap_or("") - .lines() - .any(|line| HELP_WANTED.is_match(line)); - - for comment in comments { - let mut lines = comment.body.split('\n').peekable(); - - // Look for a line that says "Help wanted" at the front. - // Then extract the rest of that line along with subsequent lines until we find a blank line. - while lines.peek().is_some() { - while let Some(line) = lines.next() { - if let Some(c) = HELP_WANTED.captures(line) { - let text = c["text"].trim().to_string(); - if !text.is_empty() { - help_wanted.push(HelpWanted { text }); - break; - } - } - } - - while let Some(line) = lines.next() { - if line.trim().is_empty() { - break; - } else { - let last = help_wanted.len() - 1; - writeln!(&mut help_wanted[last].text, "{line}")?; - } - } - } - } - - Ok((tldr_has_help_wanted || !help_wanted.is_empty(), help_wanted)) -} - -fn why_this_goal(issue_id: &IssueId, issue: &ExistingGithubIssue) -> anyhow::Result { - let sections = markwaydown::parse_text(issue_id.url(), &issue.body)?; - for section in sections { - if section.title == "Why this goal?" { - return Ok(section.text.trim().to_string()); - } - } - return Ok("".to_string()); -} - -struct Filter<'f> { - start_date: NaiveDate, - end_date: &'f Option, -} - -impl Filter<'_> { - fn matches(&self, comment: &ExistingGithubComment) -> bool { - let date = comment.created_at_date(); - - date >= self.start_date - && match self.end_date { - Some(end_date) => date <= *end_date, - None => true, - } - } -} - -fn default_start_date() -> NaiveDate { - let date = chrono::Utc::now().date_naive(); - let start_of_month = NaiveDate::from_ymd_opt(date.year(), date.month(), 1).unwrap(); - start_of_month - chrono::Duration::days(7) -} diff --git a/crates/rust-project-goals-llm/Cargo.toml b/crates/rust-project-goals-llm/Cargo.toml deleted file mode 100644 index 04176bc7..00000000 --- a/crates/rust-project-goals-llm/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "rust-project-goals-llm" -version = "0.1.0" -edition = "2021" - -[dependencies] -chrono = { version = "0.4.39", features = ["serde"] } -clap = "4.5.23" -serde = "1.0.216" diff --git a/crates/rust-project-goals-llm/src/lib.rs b/crates/rust-project-goals-llm/src/lib.rs deleted file mode 100644 index 3de479a9..00000000 --- a/crates/rust-project-goals-llm/src/lib.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Library for generating updates -- just encodes the command-line arguments. -//! Most of the work is in the `main.rs` binary. - -use std::path::PathBuf; - -use serde::{Deserialize, Serialize}; - -/// Updates struct -#[derive(clap::Args, Debug, Serialize, Deserialize)] -pub struct UpdateArgs { - /// Milestone for which we generate tracking issue data (e.g., `2024h2`). - pub milestone: String, - - /// Open the generated summary in vscode. - #[arg(long)] - pub vscode: bool, - - /// If specified, write the output into the given file. - #[arg(long)] - pub output_file: Option, - - /// Start date for comments. - /// If not given, defaults to 1 week before the start of this month. - pub start_date: Option, - - /// End date for comments. - /// If not given, no end date. - pub end_date: Option, -} From 1d911510dced12d713cad48d789fa9ef1637072f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 14 Jun 2025 09:15:20 +0000 Subject: [PATCH 08/13] reformat workspace key --- Cargo.toml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f289762d..92b0cf6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,10 @@ - [workspace] -members = ["crates/mdbook-goals", "crates/rust-project-goals", "crates/rust-project-goals-cli", "crates/rust-project-goals-json"] +members = [ + "crates/mdbook-goals", + "crates/rust-project-goals", + "crates/rust-project-goals-cli", + "crates/rust-project-goals-json", +] resolver = "2" [profile.dev] From efa487fdb0018731845a9eba18d38aeb9ad72128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 14 Jun 2025 17:41:42 +0000 Subject: [PATCH 09/13] fix CI: tracking issue id validation The template asks people to leave this field blank, but CI validates it's present and valid. --- crates/rust-project-goals/src/goal.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rust-project-goals/src/goal.rs b/crates/rust-project-goals/src/goal.rs index e739d717..fa29dc42 100644 --- a/crates/rust-project-goals/src/goal.rs +++ b/crates/rust-project-goals/src/goal.rs @@ -414,8 +414,8 @@ fn extract_metadata(sections: &[Section]) -> anyhow::Result> { .iter() .find(|row| row[0] == TRACKING_ISSUE_ROW) { - Some(r) => Some(r[1].parse()?), - None => None, + Some(r) if !r[1].is_empty() => Some(r[1].parse()?), + _ => None, }; verify_row(&first_table.rows, "Teams", TEAMS_WITH_ASKS_STR)?; From b64c073a5a0b9a9f120005310297a25adfbbc9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sat, 14 Jun 2025 17:55:57 +0000 Subject: [PATCH 10/13] fix goal template magic values to show up on the web Some rows use html comments to mark special tokens to be replaced, but they only show up in the markdown source, not on the website. Some people are copying the template from the website, so the rows are missing from the PRs. We encode the values in the markdown, so they show up on the web, and add a note to make sure to use the unencoded values if copying from source. --- src/TEMPLATE.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/TEMPLATE.md b/src/TEMPLATE.md index c3833a25..e8242bdf 100644 --- a/src/TEMPLATE.md +++ b/src/TEMPLATE.md @@ -10,12 +10,17 @@ > > The **status** should be either **Proposed** (if you have owners) > or **Proposed, Invited** (if you do not yet). +> +> Note that the **Teams** and **Task owners** rows use special values that can look different if +> you're copying this template from the markdown source or the project goals website. In the +> markdown source, they are encoded to show up on the website, but make sure to use raw ``. | Metadata | | |:-----------------|----------------------------------------------------------------------------------| | Point of contact | *must be a single Github username like @ghost* | -| Teams | | -| Task owners | | +| Teams | <!-- TEAMS WITH ASKS --> | +| Task owners | <!-- TASK OWNERS --> | | Status | Proposed | | Tracking issue | *if this is a continuing goal, add the old tracking issue, else leave blank* | | Zulip channel | N/A (an existing stream can be re-used or new streams can be created on request) | From a9a198e15a487dbd3e9c6fa4b0845e5981d41ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sun, 15 Jun 2025 11:22:11 +0000 Subject: [PATCH 11/13] add milestone validation for rpg updates this will help people who pass the path to the ./src/ milestone. --- crates/rust-project-goals-cli/src/updates.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/rust-project-goals-cli/src/updates.rs b/crates/rust-project-goals-cli/src/updates.rs index 181c6f75..48e301fb 100644 --- a/crates/rust-project-goals-cli/src/updates.rs +++ b/crates/rust-project-goals-cli/src/updates.rs @@ -1,5 +1,6 @@ use anyhow::Context; use chrono::{Datelike, NaiveDate}; +use regex::Regex; use rust_project_goals::markwaydown; use rust_project_goals::re::{HELP_WANTED, TLDR}; use rust_project_goals::util::comma; @@ -28,6 +29,14 @@ pub(crate) fn generate_updates( anyhow::bail!("either `--output-file` or `--vscode` must be specified"); } + let milestone_re = Regex::new(r"^\d{4}[hH][12]$").unwrap(); + if !milestone_re.is_match(milestone) { + anyhow::bail!( + "the milestone `{}` does not follow the `$year$semester` format, where $semester is `h1` or `h2`", + milestone, + ); + } + let issues = list_issues_in_milestone(repository, milestone)?; let filter = Filter { From 30db7e390965f6a722d7d437c318747fc43220c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sun, 15 Jun 2025 11:53:11 +0000 Subject: [PATCH 12/13] make goal tracking issues mandatory for accepted goals --- crates/rust-project-goals/src/goal.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/rust-project-goals/src/goal.rs b/crates/rust-project-goals/src/goal.rs index fa29dc42..cf39bbdd 100644 --- a/crates/rust-project-goals/src/goal.rs +++ b/crates/rust-project-goals/src/goal.rs @@ -409,13 +409,28 @@ fn extract_metadata(sections: &[Section]) -> anyhow::Result> { let status = Status::try_from(status_row[1].as_str())?; - let issue = match first_table + let issue = if let Some(r) = first_table .rows .iter() .find(|row| row[0] == TRACKING_ISSUE_ROW) { - Some(r) if !r[1].is_empty() => Some(r[1].parse()?), - _ => None, + // Accepted goals must have a tracking issue. + let has_tracking_issue = !r[1].is_empty(); + if status.acceptance == AcceptanceStatus::Accepted { + anyhow::ensure!( + has_tracking_issue, + "accepted goals cannot have an empty tracking issue" + ); + } + + // For the others, it's of course optional. + if has_tracking_issue { + Some(r[1].parse()?) + } else { + None + } + } else { + None }; verify_row(&first_table.rows, "Teams", TEAMS_WITH_ASKS_STR)?; From a78de20602f71d4329fd33aba1f70e636c1ff599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Rakic?= Date: Sun, 15 Jun 2025 12:17:19 +0000 Subject: [PATCH 13/13] fix status for 2025h1 goals --- src/2025h1/GPU-Offload.md | 2 +- src/2025h1/Polonius.md | 2 +- src/2025h1/all-hands.md | 3 +-- src/2025h1/annotate-snippets.md | 2 +- src/2025h1/arm-sve-sme.md | 3 +-- src/2025h1/async.md | 2 +- src/2025h1/build-std.md | 3 +-- src/2025h1/cargo-plumbing.md | 3 +-- src/2025h1/cargo-script.md | 6 +++--- src/2025h1/cargo-semver-checks.md | 2 +- src/2025h1/compiletest-directive-rework.md | 3 +-- src/2025h1/const-trait.md | 2 +- src/2025h1/eii.md | 3 +-- src/2025h1/ergonomic-rc.md | 2 +- src/2025h1/formality.md | 2 +- src/2025h1/improve-rustc-codegen.md | 13 +------------ src/2025h1/libtest-json.md | 3 +-- src/2025h1/macro-improvements.md | 3 +-- src/2025h1/metrics-initiative.md | 3 +-- src/2025h1/min_generic_const_arguments.md | 2 +- src/2025h1/next-solver.md | 2 +- src/2025h1/null-enum-discriminant-debug-checks.md | 3 +-- src/2025h1/open-namespaces.md | 3 +-- src/2025h1/optimize-clippy-linting-2.md | 2 +- src/2025h1/parallel-front-end.md | 2 +- src/2025h1/perf-improvements.md | 3 +-- src/2025h1/pub-priv.md | 3 +-- src/2025h1/pubgrub-in-cargo.md | 2 +- src/2025h1/restrictions.md | 3 +-- src/2025h1/rfl.md | 2 +- src/2025h1/rust-vision-doc.md | 3 +-- src/2025h1/safe-linking.md | 2 +- src/2025h1/seamless-rust-cpp.md | 2 +- src/2025h1/simd-multiversioning.md | 3 +-- src/2025h1/spec-fls-publish.md | 3 +-- src/2025h1/stabilize-project-goal-program.md | 3 +-- src/2025h1/stable-mir.md | 3 +-- src/2025h1/std-contracts.md | 2 +- src/2025h1/unsafe-fields.md | 3 +-- src/2025h1/verification-and-mirroring.md | 3 +-- 40 files changed, 42 insertions(+), 74 deletions(-) diff --git a/src/2025h1/GPU-Offload.md b/src/2025h1/GPU-Offload.md index 01b01b39..92d5e3f8 100644 --- a/src/2025h1/GPU-Offload.md +++ b/src/2025h1/GPU-Offload.md @@ -5,7 +5,7 @@ | Point of contact | @ZuseZ4 | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#109] | | Other tracking issues | [rust-lang/rust#124509], [rust-lang/rust#124509] | | Zulip channel | [#wg-autodiff][channel] | diff --git a/src/2025h1/Polonius.md b/src/2025h1/Polonius.md index c70aef37..883ad70e 100644 --- a/src/2025h1/Polonius.md +++ b/src/2025h1/Polonius.md @@ -5,7 +5,7 @@ | Point of contact | @lqd | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#118] | | Zulip channel | [#t-types/polonius][channel] | diff --git a/src/2025h1/all-hands.md b/src/2025h1/all-hands.md index ed562d4a..2b5665aa 100644 --- a/src/2025h1/all-hands.md +++ b/src/2025h1/all-hands.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ---------------------- | | Point of contact | @m-ou-se | | Teams | | | Task owners | | -| Status | Proposed for flagship | +| Status | Flagship | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#263] | diff --git a/src/2025h1/annotate-snippets.md b/src/2025h1/annotate-snippets.md index 9eed01e3..9fcc3d59 100644 --- a/src/2025h1/annotate-snippets.md +++ b/src/2025h1/annotate-snippets.md @@ -5,7 +5,7 @@ | Point of contact | @Muscraft | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#123] | | Zulip channel | N/A | diff --git a/src/2025h1/arm-sve-sme.md b/src/2025h1/arm-sve-sme.md index 652d5ff9..46af0ea3 100644 --- a/src/2025h1/arm-sve-sme.md +++ b/src/2025h1/arm-sve-sme.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ----------------------------- | | Point of contact | @davidtwco | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#270] | diff --git a/src/2025h1/async.md b/src/2025h1/async.md index 388dbaef..37c182df 100644 --- a/src/2025h1/async.md +++ b/src/2025h1/async.md @@ -6,7 +6,7 @@ | Point of contact | @tmandry | | Teams | | | Task owners | | -| Status | Proposed for flagship | +| Status | Flagship | | Tracking issue | [rust-lang/rust-project-goals#105] | | Zulip channel | [#wg-async][channel] | diff --git a/src/2025h1/build-std.md b/src/2025h1/build-std.md index 10b9b4f5..2677ef17 100644 --- a/src/2025h1/build-std.md +++ b/src/2025h1/build-std.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ------------------ | | Point of contact | @davidtwco | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#274] | diff --git a/src/2025h1/cargo-plumbing.md b/src/2025h1/cargo-plumbing.md index f63d207e..51df2823 100644 --- a/src/2025h1/cargo-plumbing.md +++ b/src/2025h1/cargo-plumbing.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ------------------------- | | Point of contact | @epage | | Teams | | | Task owners | | -| Status | Proposed for mentorship | +| Status | Invited | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#264] | diff --git a/src/2025h1/cargo-script.md b/src/2025h1/cargo-script.md index ee226b58..3c3a57d6 100644 --- a/src/2025h1/cargo-script.md +++ b/src/2025h1/cargo-script.md @@ -2,11 +2,11 @@ | Metadata | | |:-----------------|----------------------------------------------------------------------------------| -| Point of contact | @epage | +| Point of contact | @epage | | Teams | | -| Task owners | | +| Task owners | | | Status | Accepted | -| Tracking issue | [rust-lang/rust-project-goals#119] | +| Tracking issue | [rust-lang/rust-project-goals#119] | | Zulip channel | N/A (an existing stream can be re-used or new streams can be created on request) | ## Summary diff --git a/src/2025h1/cargo-semver-checks.md b/src/2025h1/cargo-semver-checks.md index 8bd36c0d..5ef785c4 100644 --- a/src/2025h1/cargo-semver-checks.md +++ b/src/2025h1/cargo-semver-checks.md @@ -5,7 +5,7 @@ | Point of contact | @obi1kenobi | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#104] | | Zulip channel | N/A | diff --git a/src/2025h1/compiletest-directive-rework.md b/src/2025h1/compiletest-directive-rework.md index ab647ad8..70279b75 100644 --- a/src/2025h1/compiletest-directive-rework.md +++ b/src/2025h1/compiletest-directive-rework.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ------------------------- | | Point of contact | @jieyouxu | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#259] | diff --git a/src/2025h1/const-trait.md b/src/2025h1/const-trait.md index 012911e3..b30df262 100644 --- a/src/2025h1/const-trait.md +++ b/src/2025h1/const-trait.md @@ -5,7 +5,7 @@ | Point of contact | @oli-obk | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#106] | | Zulip channel | N/A | diff --git a/src/2025h1/eii.md b/src/2025h1/eii.md index 3657a213..cb310789 100644 --- a/src/2025h1/eii.md +++ b/src/2025h1/eii.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | -------------------- | | Point of contact | @m-ou-se | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#254] | diff --git a/src/2025h1/ergonomic-rc.md b/src/2025h1/ergonomic-rc.md index 7a91c0c0..40e4df60 100644 --- a/src/2025h1/ergonomic-rc.md +++ b/src/2025h1/ergonomic-rc.md @@ -5,7 +5,7 @@ | Point of contact | @spastorino | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#107] | | Zulip channel | N/A | diff --git a/src/2025h1/formality.md b/src/2025h1/formality.md index b4ba0684..2f056420 100644 --- a/src/2025h1/formality.md +++ b/src/2025h1/formality.md @@ -5,7 +5,7 @@ | Point of contact | @nikomatsakis | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#122] | | Zulip channel | [#t-types/formality][channel] | diff --git a/src/2025h1/improve-rustc-codegen.md b/src/2025h1/improve-rustc-codegen.md index 61bdd6eb..2f93fb21 100644 --- a/src/2025h1/improve-rustc-codegen.md +++ b/src/2025h1/improve-rustc-codegen.md @@ -1,22 +1,11 @@ # Improve state machine codegen - - | Metadata | | | :-- | :-- | -| :----------------- | ------------- | | Point of contact | @folkertdev | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#258] | diff --git a/src/2025h1/libtest-json.md b/src/2025h1/libtest-json.md index 3057b850..f9fa6f2e 100644 --- a/src/2025h1/libtest-json.md +++ b/src/2025h1/libtest-json.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ----------------------------- | | Point of contact | @epage | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#255] | diff --git a/src/2025h1/macro-improvements.md b/src/2025h1/macro-improvements.md index 4ec0531b..1ee4ff84 100644 --- a/src/2025h1/macro-improvements.md +++ b/src/2025h1/macro-improvements.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | -------------------------- | | Point of contact | @joshtriplett | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#252] | diff --git a/src/2025h1/metrics-initiative.md b/src/2025h1/metrics-initiative.md index a4b3f5a6..eb95e751 100644 --- a/src/2025h1/metrics-initiative.md +++ b/src/2025h1/metrics-initiative.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | -------------------------- | | Point of contact | @yaahc | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#260] | diff --git a/src/2025h1/min_generic_const_arguments.md b/src/2025h1/min_generic_const_arguments.md index 8ddf6ed4..6b6d73a1 100644 --- a/src/2025h1/min_generic_const_arguments.md +++ b/src/2025h1/min_generic_const_arguments.md @@ -5,7 +5,7 @@ | Point of contact | @BoxyUwU | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#100] | | Zulip channel | [#project-const-generics][channel] | diff --git a/src/2025h1/next-solver.md b/src/2025h1/next-solver.md index c063944c..aaa10223 100644 --- a/src/2025h1/next-solver.md +++ b/src/2025h1/next-solver.md @@ -5,7 +5,7 @@ | Point of contact | @lcnr | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#113] | | Zulip channel | [#t-types/trait-system-refactor][channel] | diff --git a/src/2025h1/null-enum-discriminant-debug-checks.md b/src/2025h1/null-enum-discriminant-debug-checks.md index 4fcb160b..9d5882a3 100644 --- a/src/2025h1/null-enum-discriminant-debug-checks.md +++ b/src/2025h1/null-enum-discriminant-debug-checks.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ---------------------- | | Point of contact | @1c3t3a | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#262] | diff --git a/src/2025h1/open-namespaces.md b/src/2025h1/open-namespaces.md index 47e32c47..f28b1251 100644 --- a/src/2025h1/open-namespaces.md +++ b/src/2025h1/open-namespaces.md @@ -3,11 +3,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ---------------------------------- | | Point of contact | @epage | | Teams | | | Task owners | | -| Status | Proposed for mentorship | +| Status | Invited | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#256] | diff --git a/src/2025h1/optimize-clippy-linting-2.md b/src/2025h1/optimize-clippy-linting-2.md index 9f346a82..bd69908e 100644 --- a/src/2025h1/optimize-clippy-linting-2.md +++ b/src/2025h1/optimize-clippy-linting-2.md @@ -6,7 +6,7 @@ | Point of contact | @blyxyas | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#114] | | Zulip channel | N/A | diff --git a/src/2025h1/parallel-front-end.md b/src/2025h1/parallel-front-end.md index 0a07f7e9..c9dfbe8e 100644 --- a/src/2025h1/parallel-front-end.md +++ b/src/2025h1/parallel-front-end.md @@ -5,7 +5,7 @@ | Point of contact | @SparrowLii | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#121] | | Zulip channel | [#t-compiler/wg-parallel-rustc][channel] | diff --git a/src/2025h1/perf-improvements.md b/src/2025h1/perf-improvements.md index f9c1e78f..64dbf97c 100644 --- a/src/2025h1/perf-improvements.md +++ b/src/2025h1/perf-improvements.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | --------------------- | | Point of contact | @davidtwco | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | [#project-goals/2025h1/rustc-perf-improvements][channel] | | Tracking issue | [rust-lang/rust-project-goals#275] | diff --git a/src/2025h1/pub-priv.md b/src/2025h1/pub-priv.md index 3f3be462..de858cc4 100644 --- a/src/2025h1/pub-priv.md +++ b/src/2025h1/pub-priv.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ------------------------- | | Point of contact | @epage | | Teams | | | Task owners | | -| Status | Proposed for mentorship | +| Status | Invited | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#272] | diff --git a/src/2025h1/pubgrub-in-cargo.md b/src/2025h1/pubgrub-in-cargo.md index 17215357..a2354547 100644 --- a/src/2025h1/pubgrub-in-cargo.md +++ b/src/2025h1/pubgrub-in-cargo.md @@ -5,7 +5,7 @@ | Point of contact | @eh2406 | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#110] | | Zulip channel | N/A | diff --git a/src/2025h1/restrictions.md b/src/2025h1/restrictions.md index f31ca1af..b3e66f51 100644 --- a/src/2025h1/restrictions.md +++ b/src/2025h1/restrictions.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | -------------------------- | | Point of contact | @jhpratt | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#257] | diff --git a/src/2025h1/rfl.md b/src/2025h1/rfl.md index dd156591..a7c80be5 100644 --- a/src/2025h1/rfl.md +++ b/src/2025h1/rfl.md @@ -6,7 +6,7 @@ | Point of contact | @nikomatsakis | | Teams | | | Task owners | | -| Status | Proposed for flagship | +| Status | Flagship | | Tracking issue | [rust-lang/rust-project-goals#116] | | Zulip channel | [#rust-for-linux][channel] | diff --git a/src/2025h1/rust-vision-doc.md b/src/2025h1/rust-vision-doc.md index 78e0e66b..c9f3f1dc 100644 --- a/src/2025h1/rust-vision-doc.md +++ b/src/2025h1/rust-vision-doc.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ---------------------- | | Point of contact | @nikomatsakis | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#269] | diff --git a/src/2025h1/safe-linking.md b/src/2025h1/safe-linking.md index 08891b5a..2b62c061 100644 --- a/src/2025h1/safe-linking.md +++ b/src/2025h1/safe-linking.md @@ -5,7 +5,7 @@ | Point of contact | @m-ou-se | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#267] | diff --git a/src/2025h1/seamless-rust-cpp.md b/src/2025h1/seamless-rust-cpp.md index 5841ad6f..e1b8241d 100644 --- a/src/2025h1/seamless-rust-cpp.md +++ b/src/2025h1/seamless-rust-cpp.md @@ -5,7 +5,7 @@ | Point of contact | @tmandry | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#253] | diff --git a/src/2025h1/simd-multiversioning.md b/src/2025h1/simd-multiversioning.md index 25a1cd9a..1ca0b932 100644 --- a/src/2025h1/simd-multiversioning.md +++ b/src/2025h1/simd-multiversioning.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | ----------------------------------- | | Point of contact | @veluca93 | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | [#project-portable-simd][channel] | | Tracking issue | [rust-lang/rust-project-goals#261] | diff --git a/src/2025h1/spec-fls-publish.md b/src/2025h1/spec-fls-publish.md index 023878fa..1fcd1c56 100644 --- a/src/2025h1/spec-fls-publish.md +++ b/src/2025h1/spec-fls-publish.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | -------------------------- | | Point of contact | @joelmarcey | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | [#t-spec][channel] | | Tracking issue | [rust-lang/rust-project-goals#265] | diff --git a/src/2025h1/stabilize-project-goal-program.md b/src/2025h1/stabilize-project-goal-program.md index 02a523c3..cfaf2666 100644 --- a/src/2025h1/stabilize-project-goal-program.md +++ b/src/2025h1/stabilize-project-goal-program.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | --------------------------- | | Point of contact | @nikomatsakis | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | [#project-goals][channel] | | Tracking issue | [rust-lang/rust-project-goals#268] | diff --git a/src/2025h1/stable-mir.md b/src/2025h1/stable-mir.md index 148cfdbe..a225f064 100644 --- a/src/2025h1/stable-mir.md +++ b/src/2025h1/stable-mir.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | -------------------------------- | | Point of contact | @celinval | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | [#project-stable-mir][channel] | | Tracking issue | [rust-lang/rust-project-goals#266] | diff --git a/src/2025h1/std-contracts.md b/src/2025h1/std-contracts.md index 0dba5484..16af9005 100644 --- a/src/2025h1/std-contracts.md +++ b/src/2025h1/std-contracts.md @@ -5,7 +5,7 @@ | Point of contact | @celinval | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Tracking issue | [rust-lang/rust-project-goals#126] | | Zulip channel | N/A | diff --git a/src/2025h1/unsafe-fields.md b/src/2025h1/unsafe-fields.md index 2fc1547b..9d07ff43 100644 --- a/src/2025h1/unsafe-fields.md +++ b/src/2025h1/unsafe-fields.md @@ -2,11 +2,10 @@ | Metadata | | | :-- | :-- | -| :----------------- | -------------------------- | | Point of contact | @jswrenn | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#273] | diff --git a/src/2025h1/verification-and-mirroring.md b/src/2025h1/verification-and-mirroring.md index 5d7f92ac..10a7441a 100644 --- a/src/2025h1/verification-and-mirroring.md +++ b/src/2025h1/verification-and-mirroring.md @@ -2,12 +2,11 @@ | Metadata | | | :-- | :-- | -| :----------------- | -------------------------- | | Short title | Crates.io mirroring | | Point of contact | @walterhpearce | | Teams | | | Task owners | | -| Status | Proposed | +| Status | Accepted | | Zulip channel | N/A | | Tracking issue | [rust-lang/rust-project-goals#271] |