target tables

This commit is contained in:
nora 2024-02-08 17:50:24 +01:00
parent 09ee1f7a33
commit 8590f9b343
3 changed files with 172 additions and 47 deletions

View file

@ -8,7 +8,7 @@ use std::{
}; };
use eyre::{bail, Context, OptionExt, Result}; use eyre::{bail, Context, OptionExt, Result};
use parse::{ParsedTargetInfoFile, Tier, TriStateBool}; use parse::{Footnote, ParsedTargetInfoFile, Tier, TriStateBool};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
/// Information about a target obtained from `target_info.toml``. /// Information about a target obtained from `target_info.toml``.
@ -26,12 +26,14 @@ struct TargetMetadata {
notes: String, notes: String,
std: TriStateBool, std: TriStateBool,
host: TriStateBool, host: TriStateBool,
footnotes: Vec<Footnote>,
} }
const SECTIONS: &[&str] = &[ const SECTIONS: &[&str] = &[
"Overview",
"Requirements", "Requirements",
"Testing", "Testing",
"Building", "Building the target",
"Cross compilation", "Cross compilation",
"Building Rust programs", "Building Rust programs",
]; ];
@ -59,12 +61,12 @@ fn main() -> Result<()> {
match std::fs::create_dir("targets/src") { match std::fs::create_dir("targets/src") {
Ok(()) => {} Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {} 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)) let mut info_patterns = parse::load_target_infos(Path::new(input_dir))
.unwrap() .wrap_err("failed loading target_info")?
.into_iter() .into_iter()
.map(|info| { .map(|info| {
let metadata_used = vec![false; info.metadata.len()]; 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(), notes: metadata_pattern.notes.clone(),
host: metadata_pattern.host.clone(), host: metadata_pattern.host.clone(),
std: metadata_pattern.std.clone(), std: metadata_pattern.std.clone(),
footnotes: metadata_pattern.footnotes.clone(),
}); });
} }
} }

View file

@ -40,6 +40,15 @@ pub struct ParsedTargetMetadata {
pub notes: String, pub notes: String,
pub std: TriStateBool, pub std: TriStateBool,
pub host: TriStateBool, pub host: TriStateBool,
#[serde(default)]
pub footnotes: Vec<Footnote>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Footnote {
pub name: String,
pub content: String,
} }
#[derive(Debug, PartialEq, Clone, Copy, Deserialize)] #[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
@ -84,25 +93,49 @@ fn parse_file(name: &str, content: &str) -> Result<ParsedTargetInfoFile> {
.nth(1) .nth(1)
.ok_or_eyre("missing frontmatter")?; .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>(frontmatter).wrap_err("invalid frontmatter")?; serde_yaml::from_str::<Frontmatter>(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 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() { for (idx, line) in body.lines().enumerate() {
if line.starts_with("#") { let number = frontmatter_line_count + idx + 1; // 1 because "line numbers" are off by 1
if let Some(header) = line.strip_prefix("## ") { 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) { if !crate::SECTIONS.contains(&header) {
bail!( 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 super::SECTIONS
); );
} }
sections.push((header.to_owned(), String::new())); sections.push((header.to_owned(), String::new()));
} else { } else {
bail!("the only allowed headings are `## `"); bail!("on line {number}, the only allowed headings are `## `: `{line}`");
} }
} else { } else {
match sections.last_mut() { match sections.last_mut() {

View file

@ -3,7 +3,7 @@ use std::{fs, path::Path};
use crate::{ use crate::{
is_in_rust_lang_rust, is_in_rust_lang_rust,
parse::{Tier, TriStateBool}, parse::{Footnote, Tier, TriStateBool},
RustcTargetInfo, TargetDocs, RustcTargetInfo, TargetDocs,
}; };
@ -147,45 +147,67 @@ pub fn render_static(src_output: &Path, targets: &[(TargetDocs, RustcTargetInfo)
.wrap_err("writing front page information about experiment")?; .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 = src_output.join("platform-support.md");
let platform_support_main_old = let platform_support_main_old =
fs::read_to_string(&platform_support_main).wrap_err("reading platform-support.md")?; 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... Ok(())
//let tier1host_table = render_table( }
// targets
// .into_iter() impl Footnote {
// .filter(|target| target.0.tier == Some(Tier::One)), fn reference(&self) -> String {
//)?; format!("[^{}]", self.name)
// 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 fn render_platform_support_tables(
// .into_iter() content: &str,
// .filter(|target| target.0.tier == Some(Tier::Two) && target.0.has_host_tools()), targets: &[(TargetDocs, RustcTargetInfo)],
//) ) -> Result<String> {
//.wrap_err("rendering tier 2 table")?; 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( let tier2_table = render_table(
targets targets
.into_iter() .into_iter()
.filter(|target| target.0.tier == Some(Tier::Two) && !target.0.has_host_tools()), .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 targets
.into_iter() .into_iter()
.filter(|target| target.0.tier == Some(Tier::Three)), .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) let content = replace_section(&content, "TIER2", &tier2_table)
.wrap_err("replacing platform support.md")?; .wrap_err("replacing platform support.md")?;
let content = replace_section(&content, "TIER3", &tier3_table) let content = replace_section(&content, "TIER3", &tier3_table)
.wrap_err("replacing platform support.md")?; .wrap_err("replacing platform support.md")?;
fs::write(platform_support_main, content).wrap_err("writing platform-support.md")?; Ok(content)
Ok(())
} }
fn render_table_tri_state_bool(bool: TriStateBool) -> &'static str { 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>( struct TierTable {
targets: impl IntoIterator<Item = &'a (TargetDocs, RustcTargetInfo)>, filter: fn(&TargetDocs) -> bool,
include_std: bool,
include_host: bool,
}
fn render_table_awesome<'a>(
targets: &[(TargetDocs, RustcTargetInfo)],
table: TierTable,
) -> Result<String> { ) -> Result<String> {
let mut rows = Vec::new(); 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 { for (target, _) in targets {
let meta = target.metadata.as_ref(); let meta = target.metadata.as_ref();
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::<Vec<_>>()
.join(" ");
notes = format!("{notes} {footnotes_str}");
}
let std = if table.include_std {
let std = meta let std = meta
.map(|meta| render_table_tri_state_bool(meta.std)) .map(|meta| render_table_tri_state_bool(meta.std))
.unwrap_or("?"); .unwrap_or("?");
format!(" | {std}")
} else {
String::new()
};
let host = if table.include_host {
let host = meta let host = meta
.map(|meta| render_table_tri_state_bool(meta.host)) .map(|meta| render_table_tri_state_bool(meta.host))
.unwrap_or("?"); .unwrap_or("?");
format!(" | {host}")
} else {
String::new()
};
let notes = meta.map(|meta| meta.notes.as_str()).unwrap_or("unknown");
rows.push(format!( rows.push(format!(
"[`{0}`](platform-support/targets/{0}.md) | {std} | {host} | {notes}", "[`{0}`](platform-support/targets/{0}.md){std}{host} | {notes}",
target.name 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>( fn render_table<'a>(
targets: impl IntoIterator<Item = &'a (TargetDocs, RustcTargetInfo)>, targets: impl IntoIterator<Item = &'a (TargetDocs, RustcTargetInfo)>,
include_std: bool,
include_host: bool,
) -> Result<String> { ) -> Result<String> {
let mut rows = Vec::new(); let mut rows = Vec::new();
for (target, _) in targets { for (target, _) in targets {
let meta = target.metadata.as_ref(); let meta = target.metadata.as_ref();
let notes = meta.map(|meta| meta.notes.as_str()).unwrap_or("unknown");
let std = if include_std {
let std = meta let std = meta
.map(|meta| render_table_tri_state_bool(meta.std)) .map(|meta| render_table_tri_state_bool(meta.std))
.unwrap_or("?"); .unwrap_or("?");
let notes = meta.map(|meta| meta.notes.as_str()).unwrap_or("unknown"); 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!( rows.push(format!(
"[`{0}`](platform-support/targets/{0}.md) | {std} | {notes}", "[`{0}`](platform-support/targets/{0}.md){std}{host} | {notes}",
target.name target.name
)); ));
} }