From 8590f9b343247b5e22f3a534435f7cbf43d02167 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:50:24 +0100 Subject: [PATCH] target tables --- src/main.rs | 11 ++-- src/parse.rs | 47 ++++++++++++--- src/render.rs | 161 +++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 172 insertions(+), 47 deletions(-) diff --git a/src/main.rs b/src/main.rs index d6ae724..a931dc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use std::{ }; use eyre::{bail, Context, OptionExt, Result}; -use parse::{ParsedTargetInfoFile, Tier, TriStateBool}; +use parse::{Footnote, ParsedTargetInfoFile, Tier, TriStateBool}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; /// Information about a target obtained from `target_info.toml``. @@ -26,12 +26,14 @@ struct TargetMetadata { notes: String, std: TriStateBool, host: TriStateBool, + footnotes: Vec, } const SECTIONS: &[&str] = &[ + "Overview", "Requirements", "Testing", - "Building", + "Building the target", "Cross compilation", "Building Rust programs", ]; @@ -59,12 +61,12 @@ fn main() -> Result<()> { match std::fs::create_dir("targets/src") { Ok(()) => {} Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {} - e @ _ => e.unwrap(), + e @ _ => e.wrap_err("failed creating src dir")?, } } let mut info_patterns = parse::load_target_infos(Path::new(input_dir)) - .unwrap() + .wrap_err("failed loading target_info")? .into_iter() .map(|info| { let metadata_used = vec![false; info.metadata.len()]; @@ -173,6 +175,7 @@ fn target_doc_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> Ta notes: metadata_pattern.notes.clone(), host: metadata_pattern.host.clone(), std: metadata_pattern.std.clone(), + footnotes: metadata_pattern.footnotes.clone(), }); } } diff --git a/src/parse.rs b/src/parse.rs index 50e61eb..e1be2bc 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -40,6 +40,15 @@ pub struct ParsedTargetMetadata { pub notes: String, pub std: TriStateBool, pub host: TriStateBool, + #[serde(default)] + pub footnotes: Vec, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Footnote { + pub name: String, + pub content: String, } #[derive(Debug, PartialEq, Clone, Copy, Deserialize)] @@ -84,25 +93,49 @@ fn parse_file(name: &str, content: &str) -> Result { .nth(1) .ok_or_eyre("missing frontmatter")?; - let frontmatter = + let frontmatter_line_count = frontmatter.lines().count() + 2; // 2 from --- + + let mut frontmatter = serde_yaml::from_str::(frontmatter).wrap_err("invalid frontmatter")?; + frontmatter.metadata.iter_mut().for_each(|meta| { + meta.footnotes.iter_mut().for_each(|footnote| { + footnote.content = footnote.content.replace("\r\n", " ").replace("\n", " ") + }) + }); + let frontmatter = frontmatter; + let body = frontmatter_splitter.next().ok_or_eyre("no body")?; - let mut sections = Vec::new(); + let mut sections = Vec::<(String, String)>::new(); + let mut in_codeblock = false; - for line in body.lines() { - if line.starts_with("#") { - if let Some(header) = line.strip_prefix("## ") { + for (idx, line) in body.lines().enumerate() { + let number = frontmatter_line_count + idx + 1; // 1 because "line numbers" are off by 1 + if line.starts_with("```") { + in_codeblock ^= true; // toggle + } else if line.starts_with("#") { + if in_codeblock { + match sections.last_mut() { + Some((_, content)) => { + content.push_str(line); + content.push('\n'); + } + None if line.trim().is_empty() => {} + None => { + bail!("line {number} with content not allowed before the first heading") + } + } + } else if let Some(header) = line.strip_prefix("## ") { if !crate::SECTIONS.contains(&header) { bail!( - "`{header}` is not an allowed section name, must be one of {:?}", + "on line {number}, `{header}` is not an allowed section name, must be one of {:?}", super::SECTIONS ); } sections.push((header.to_owned(), String::new())); } else { - bail!("the only allowed headings are `## `"); + bail!("on line {number}, the only allowed headings are `## `: `{line}`"); } } else { match sections.last_mut() { diff --git a/src/render.rs b/src/render.rs index c760a80..4f7d525 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,7 +3,7 @@ use std::{fs, path::Path}; use crate::{ is_in_rust_lang_rust, - parse::{Tier, TriStateBool}, + parse::{Footnote, Tier, TriStateBool}, RustcTargetInfo, TargetDocs, }; @@ -147,45 +147,67 @@ pub fn render_static(src_output: &Path, targets: &[(TargetDocs, RustcTargetInfo) .wrap_err("writing front page information about experiment")?; } - // TODO: Render the nice table showing off all targets and their tier. let platform_support_main = src_output.join("platform-support.md"); let platform_support_main_old = fs::read_to_string(&platform_support_main).wrap_err("reading platform-support.md")?; + let platform_support_main_new = + render_platform_support_tables(&platform_support_main_old, targets)?; + fs::write(platform_support_main, platform_support_main_new) + .wrap_err("writing platform-support.md")?; - // needs footnotes... - //let tier1host_table = render_table( - // targets - // .into_iter() - // .filter(|target| target.0.tier == Some(Tier::One)), - //)?; - // Tier 2 without host doesn't exist right now - // they all support std, obviously - //let tier2host_table = render_table_without_std this needs a better scheme??( - // targets - // .into_iter() - // .filter(|target| target.0.tier == Some(Tier::Two) && target.0.has_host_tools()), - //) - //.wrap_err("rendering tier 2 table")?; + Ok(()) +} + +impl Footnote { + fn reference(&self) -> String { + format!("[^{}]", self.name) + } +} + +fn render_platform_support_tables( + content: &str, + targets: &[(TargetDocs, RustcTargetInfo)], +) -> Result { + let tier1host_table = render_table_awesome( + targets, + TierTable { + filter: |target| target.tier == Some(Tier::One), + include_host: false, + include_std: false, + }, + )?; + let tier2host_table = render_table( + targets + .into_iter() + .filter(|target| target.0.tier == Some(Tier::Two) && target.0.has_host_tools()), + false, + false, + )?; let tier2_table = render_table( targets .into_iter() .filter(|target| target.0.tier == Some(Tier::Two) && !target.0.has_host_tools()), + true, + false, )?; - let tier3_table = render_table_with_host( + let tier3_table = render_table( targets .into_iter() .filter(|target| target.0.tier == Some(Tier::Three)), + true, + true, )?; - let content = platform_support_main_old; + let content = replace_section(&content, "TIER1HOST", &tier1host_table) + .wrap_err("replacing platform support.md")?; + let content = replace_section(&content, "TIER2HOST", &tier2host_table) + .wrap_err("replacing platform support.md")?; let content = replace_section(&content, "TIER2", &tier2_table) .wrap_err("replacing platform support.md")?; let content = replace_section(&content, "TIER3", &tier3_table) .wrap_err("replacing platform support.md")?; - fs::write(platform_support_main, content).wrap_err("writing platform-support.md")?; - - Ok(()) + Ok(content) } fn render_table_tri_state_bool(bool: TriStateBool) -> &'static str { @@ -196,43 +218,110 @@ fn render_table_tri_state_bool(bool: TriStateBool) -> &'static str { } } -fn render_table_with_host<'a>( - targets: impl IntoIterator, +struct TierTable { + filter: fn(&TargetDocs) -> bool, + include_std: bool, + include_host: bool, +} + +fn render_table_awesome<'a>( + targets: &[(TargetDocs, RustcTargetInfo)], + table: TierTable, ) -> Result { let mut rows = Vec::new(); + let mut all_footnotes = Vec::new(); + + let targets = targets + .into_iter() + .filter(|target| (table.filter)(&target.0)); for (target, _) in targets { let meta = target.metadata.as_ref(); - let std = meta - .map(|meta| render_table_tri_state_bool(meta.std)) - .unwrap_or("?"); - let host = meta - .map(|meta| render_table_tri_state_bool(meta.host)) - .unwrap_or("?"); - let notes = meta.map(|meta| meta.notes.as_str()).unwrap_or("unknown"); + let mut notes = meta + .map(|meta| meta.notes.as_str()) + .unwrap_or("unknown") + .to_owned(); + + if meta.map_or(false, |meta| !meta.footnotes.is_empty()) { + let footnotes = &meta.unwrap().footnotes; + all_footnotes.extend(footnotes); + let footnotes_str = footnotes + .iter() + .map(|footnote| footnote.reference()) + .collect::>() + .join(" "); + + notes = format!("{notes} {footnotes_str}"); + } + + let std = if table.include_std { + let std = meta + .map(|meta| render_table_tri_state_bool(meta.std)) + .unwrap_or("?"); + format!(" | {std}") + } else { + String::new() + }; + + let host = if table.include_host { + let host = meta + .map(|meta| render_table_tri_state_bool(meta.host)) + .unwrap_or("?"); + format!(" | {host}") + } else { + String::new() + }; + rows.push(format!( - "[`{0}`](platform-support/targets/{0}.md) | {std} | {host} | {notes}", + "[`{0}`](platform-support/targets/{0}.md){std}{host} | {notes}", target.name )); } - Ok(rows.join("\n")) + let mut result = rows.join("\n"); + + for footnote in all_footnotes { + result.push_str("\n\n"); + result.push_str(&footnote.reference()); + result.push_str(": "); + result.push_str(&footnote.content); + } + + Ok(result) } fn render_table<'a>( targets: impl IntoIterator, + include_std: bool, + include_host: bool, ) -> Result { let mut rows = Vec::new(); for (target, _) in targets { let meta = target.metadata.as_ref(); - let std = meta - .map(|meta| render_table_tri_state_bool(meta.std)) - .unwrap_or("?"); + let notes = meta.map(|meta| meta.notes.as_str()).unwrap_or("unknown"); + let std = if include_std { + let std = meta + .map(|meta| render_table_tri_state_bool(meta.std)) + .unwrap_or("?"); + format!(" | {std}") + } else { + String::new() + }; + + let host = if include_host { + let host = meta + .map(|meta| render_table_tri_state_bool(meta.host)) + .unwrap_or("?"); + format!(" | {host}") + } else { + String::new() + }; + rows.push(format!( - "[`{0}`](platform-support/targets/{0}.md) | {std} | {notes}", + "[`{0}`](platform-support/targets/{0}.md){std}{host} | {notes}", target.name )); }