mirror of
https://github.com/Noratrieb/target-tier-docs-experiment.git
synced 2026-01-15 00:45:09 +01:00
rewrite it all
This commit is contained in:
parent
761818fda2
commit
b90a97face
6 changed files with 321 additions and 129 deletions
125
src/main.rs
125
src/main.rs
|
|
@ -1,23 +1,30 @@
|
|||
mod parse;
|
||||
|
||||
use std::{
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use parse::ParsedTargetInfoFile;
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
|
||||
/// Information about a target obtained from `target_info.toml``.
|
||||
struct TargetDocs {
|
||||
name: String,
|
||||
maintainers: Vec<String>,
|
||||
requirements: Option<String>,
|
||||
testing: Option<String>,
|
||||
building_the_target: Option<String>,
|
||||
cross_compilation: Option<String>,
|
||||
building_rust_programs: Option<String>,
|
||||
tier: u8,
|
||||
sections: Vec<(String, String)>,
|
||||
tier: String,
|
||||
}
|
||||
|
||||
const SECTIONS: &[&str] = &[
|
||||
"Requirements",
|
||||
"Testing",
|
||||
"Building",
|
||||
"Cross compilation",
|
||||
"Building Rust programs",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
let rustc =
|
||||
PathBuf::from(std::env::var("RUSTC").expect("must pass RUSTC env var pointing to rustc"));
|
||||
|
|
@ -31,7 +38,11 @@ fn main() {
|
|||
e @ _ => e.unwrap(),
|
||||
}
|
||||
|
||||
let mut info_patterns = load_target_info_patterns();
|
||||
let mut info_patterns = parse::load_target_infos(Path::new("target_info"))
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|info| TargetPatternEntry { info, used: false })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
eprintln!("Collecting rustc information");
|
||||
let rustc_infos = targets
|
||||
|
|
@ -92,19 +103,18 @@ fn render_target_md(target: &TargetDocs, rustc_info: &RustcTargetInfo) -> String
|
|||
|
||||
doc.push_str(&maintainers_str);
|
||||
|
||||
let mut section = |value: &Option<String>, name| {
|
||||
for section_name in SECTIONS {
|
||||
let value = target
|
||||
.sections
|
||||
.iter()
|
||||
.find(|(name, _)| name == section_name);
|
||||
|
||||
let section_str = match value {
|
||||
Some(value) => format!("## {name}\n{value}\n"),
|
||||
None => format!("## {name}\nUnknown.\n"),
|
||||
Some((name, value)) => format!("## {name}\n{value}\n"),
|
||||
None => format!("## {section_name}\nUnknown.\n"),
|
||||
};
|
||||
doc.push_str(§ion_str)
|
||||
};
|
||||
|
||||
section(&target.requirements, "Requirements");
|
||||
section(&target.testing, "Testing");
|
||||
section(&target.building_the_target, "Building");
|
||||
section(&target.cross_compilation, "Cross Compilation");
|
||||
section(&target.building_rust_programs, "Building Rust Programs");
|
||||
}
|
||||
|
||||
let cfg_text = rustc_info
|
||||
.target_cfgs
|
||||
|
|
@ -114,7 +124,8 @@ fn render_target_md(target: &TargetDocs, rustc_info: &RustcTargetInfo) -> String
|
|||
.join("\n");
|
||||
let cfg_text =
|
||||
format!("This target defines the following target-specific cfg values:\n{cfg_text}\n");
|
||||
section(&Some(cfg_text), "cfg");
|
||||
|
||||
doc.push_str(&format!("## cfg\n{cfg_text}\n"));
|
||||
|
||||
doc
|
||||
}
|
||||
|
|
@ -158,51 +169,16 @@ But as you might notice, all targets are actually present with a stub :3.
|
|||
// TODO: Render the nice table showing off all targets and their tier.
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct TargetInfoTable {
|
||||
target: Vec<TargetInfoPattern>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct TargetInfoPattern {
|
||||
pattern: String,
|
||||
#[serde(default)]
|
||||
maintainers: Vec<String>,
|
||||
tier: Option<u8>,
|
||||
requirements: Option<String>,
|
||||
testing: Option<String>,
|
||||
building_the_target: Option<String>,
|
||||
cross_compilation: Option<String>,
|
||||
building_rust_programs: Option<String>,
|
||||
}
|
||||
|
||||
struct TargetPatternEntry {
|
||||
info: TargetInfoPattern,
|
||||
info: ParsedTargetInfoFile,
|
||||
used: bool,
|
||||
}
|
||||
|
||||
fn load_target_info_patterns() -> Vec<TargetPatternEntry> {
|
||||
let file = include_str!("../target_info.toml");
|
||||
let table = toml::from_str::<TargetInfoTable>(file).unwrap();
|
||||
|
||||
table
|
||||
.target
|
||||
.into_iter()
|
||||
.map(|info| TargetPatternEntry { info, used: false })
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Gets the target information from `target_info.toml` by applying all patterns that match.
|
||||
fn target_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> TargetDocs {
|
||||
let mut tier = None;
|
||||
let mut maintainers = Vec::new();
|
||||
let mut requirements = None;
|
||||
let mut testing = None;
|
||||
let mut building_the_target = None;
|
||||
let mut cross_compilation = None;
|
||||
let mut building_rust_programs = None;
|
||||
let mut sections = Vec::new();
|
||||
|
||||
for target_pattern in info_patterns {
|
||||
if glob_match::glob_match(&target_pattern.info.pattern, target) {
|
||||
|
|
@ -211,41 +187,28 @@ fn target_info(info_patterns: &mut [TargetPatternEntry], target: &str) -> Target
|
|||
|
||||
maintainers.extend_from_slice(&target_pattern.maintainers);
|
||||
|
||||
fn set_once<T: Clone>(
|
||||
target: &str,
|
||||
pattern_value: &Option<T>,
|
||||
to_insert: &mut Option<T>,
|
||||
name: &str,
|
||||
) {
|
||||
if let Some(pattern_value) = pattern_value {
|
||||
if to_insert.is_some() {
|
||||
panic!("target {target} inherits a {name} from multiple patterns, create a more specific pattern and add it there");
|
||||
}
|
||||
*to_insert = Some(pattern_value.clone());
|
||||
if let Some(pattern_value) = &target_pattern.tier {
|
||||
if tier.is_some() {
|
||||
panic!("target {target} inherits a tier from multiple patterns, create a more specific pattern and add it there");
|
||||
}
|
||||
tier = Some(pattern_value.clone());
|
||||
}
|
||||
|
||||
for (section_name, content) in &target_pattern.sections {
|
||||
if sections.iter().any(|(name, _)| name == section_name) {
|
||||
panic!("target {target} inherits the section {section_name} from multiple patterns, create a more specific pattern and add it there");
|
||||
}
|
||||
sections.push((section_name.clone(), content.clone()));
|
||||
}
|
||||
#[rustfmt::skip]
|
||||
{
|
||||
set_once(target, &target_pattern.tier, &mut tier, "tier");
|
||||
set_once(target, &target_pattern.requirements, &mut requirements, "requirements");
|
||||
set_once(target, &target_pattern.testing, &mut testing, "testing");
|
||||
set_once(target, &target_pattern.building_the_target, &mut building_the_target, "building_the_target");
|
||||
set_once(target, &target_pattern.cross_compilation, &mut cross_compilation, "cross_compilation");
|
||||
set_once(target, &target_pattern.building_rust_programs, &mut building_rust_programs, "building_rust_programs");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
TargetDocs {
|
||||
name: target.to_owned(),
|
||||
maintainers,
|
||||
requirements,
|
||||
testing,
|
||||
building_the_target,
|
||||
cross_compilation,
|
||||
building_rust_programs,
|
||||
// tier: tier.expect(&format!("no tier found for target {target}")),
|
||||
tier: tier.unwrap_or(0),
|
||||
tier: tier.unwrap_or("UNKNOWN".to_owned()),
|
||||
sections,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
179
src/parse.rs
Normal file
179
src/parse.rs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
//! Suboptimal half-markdown parser that's just good-enough for this.
|
||||
|
||||
use eyre::{bail, OptionExt, Result, WrapErr};
|
||||
use std::{fs::DirEntry, path::Path};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ParsedTargetInfoFile {
|
||||
pub pattern: String,
|
||||
pub tier: Option<String>,
|
||||
pub maintainers: Vec<String>,
|
||||
pub sections: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Frontmatter {
|
||||
tier: Option<String>,
|
||||
#[serde(default)]
|
||||
maintainers: Vec<String>,
|
||||
}
|
||||
|
||||
pub fn load_target_infos(directory: &Path) -> Result<Vec<ParsedTargetInfoFile>> {
|
||||
let dir = std::fs::read_dir(directory).unwrap();
|
||||
let mut infos = Vec::new();
|
||||
|
||||
for entry in dir {
|
||||
let entry = entry?;
|
||||
infos.push(
|
||||
load_single_target_info(&entry)
|
||||
.wrap_err_with(|| format!("loading {}", entry.path().display()))?,
|
||||
)
|
||||
}
|
||||
|
||||
Ok(infos)
|
||||
}
|
||||
|
||||
fn load_single_target_info(entry: &DirEntry) -> Result<ParsedTargetInfoFile> {
|
||||
let pattern = entry.file_name();
|
||||
let name = pattern
|
||||
.to_str()
|
||||
.ok_or_eyre("file name is invalid utf8")?
|
||||
.strip_suffix(".md")
|
||||
.ok_or_eyre("target_info files must end with .md")?;
|
||||
let content: String = std::fs::read_to_string(entry.path()).wrap_err("reading content")?;
|
||||
|
||||
parse_file(name, &content)
|
||||
}
|
||||
|
||||
fn parse_file(name: &str, content: &str) -> Result<ParsedTargetInfoFile> {
|
||||
let mut frontmatter_splitter = content.split("---\n");
|
||||
|
||||
let frontmatter = frontmatter_splitter
|
||||
.nth(1)
|
||||
.ok_or_eyre("missing frontmatter")?;
|
||||
|
||||
let frontmatter =
|
||||
serde_yaml::from_str::<Frontmatter>(frontmatter).wrap_err("invalid frontmatter")?;
|
||||
|
||||
let body = frontmatter_splitter.next().ok_or_eyre("no body")?;
|
||||
|
||||
let mut sections = Vec::new();
|
||||
|
||||
for line in body.lines() {
|
||||
if line.starts_with("#") {
|
||||
if let Some(header) = line.strip_prefix("## ") {
|
||||
if !crate::SECTIONS.contains(&header) {
|
||||
bail!(
|
||||
"`{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 `## `");
|
||||
}
|
||||
} else {
|
||||
match sections.last_mut() {
|
||||
Some((_, content)) => {
|
||||
content.push_str(line);
|
||||
content.push('\n');
|
||||
}
|
||||
None if line.trim().is_empty() => {}
|
||||
None => bail!("line with content not allowed before the first heading"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sections
|
||||
.iter_mut()
|
||||
.for_each(|section| section.1 = section.1.trim().to_owned());
|
||||
|
||||
Ok(ParsedTargetInfoFile {
|
||||
pattern: name.to_owned(),
|
||||
maintainers: frontmatter.maintainers,
|
||||
tier: frontmatter.tier,
|
||||
sections,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn no_frontmatter() {
|
||||
let name = "archlinux-unknown-linux-gnu.md"; // arch linux is an arch, right?
|
||||
let content = "";
|
||||
assert!(super::parse_file(name, content).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_section() {
|
||||
let name = "6502-nintendo-nes.md";
|
||||
let content = "
|
||||
---
|
||||
---
|
||||
|
||||
## Not A Real Section
|
||||
";
|
||||
|
||||
assert!(super::parse_file(name, content).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_header() {
|
||||
let name = "x86_64-known-linux-gnu.md";
|
||||
let content = "
|
||||
---
|
||||
---
|
||||
|
||||
# x86_64-known-linux-gnu
|
||||
";
|
||||
|
||||
assert!(super::parse_file(name, content).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_correctly() {
|
||||
let name = "cat-unknown-linux-gnu.md";
|
||||
let content = r#"
|
||||
---
|
||||
tier: "1" # first-class cats
|
||||
maintainers: ["who maintains the cat?"]
|
||||
---
|
||||
## Requirements
|
||||
|
||||
This target mostly just meows and doesn't do much.
|
||||
|
||||
## Testing
|
||||
|
||||
You can pet the cat and it might respond positively.
|
||||
|
||||
## Cross compilation
|
||||
|
||||
If you're on a dog system, there might be conflicts with the cat, be careful.
|
||||
But it should be possible.
|
||||
"#;
|
||||
|
||||
let info = super::parse_file(name, content).unwrap();
|
||||
|
||||
assert_eq!(info.maintainers, vec!["who maintains the cat?"]);
|
||||
assert_eq!(info.pattern, name);
|
||||
assert_eq!(info.tier, Some("1".to_owned()));
|
||||
assert_eq!(
|
||||
info.sections,
|
||||
vec![
|
||||
(
|
||||
"Requirements".to_owned(),
|
||||
"This target mostly just meows and doesn't do much.".to_owned(),
|
||||
),
|
||||
(
|
||||
"Testing".to_owned(),
|
||||
"You can pet the cat and it might respond positively.".to_owned(),
|
||||
),
|
||||
(
|
||||
"Cross compilation".to_owned(),
|
||||
"If you're on a dog system, there might be conflicts with the cat, be careful.\nBut it should be possible.".to_owned(),
|
||||
),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue