use std::{
collections::HashMap,
io::{self, Read},
str::FromStr,
};
pub(crate) fn generate() -> Result<String, String> {
let readme = read_readme()
.map_err(|e| e.to_string())?
.parse::<ReadMeSections>()?;
let about = readme.get("About")?;
let template_support = readme.get("Template support")?;
let gds_template_support = readme.get("Supported grid definition templates")?;
let drs_template_support = readme.get("Supported data representation templates")?;
let examples = readme.get("Usage examples")?;
let manifest = read_manifest()
.map_err(|e| e.to_string())?
.parse::<Features>()?
.text();
Ok(format!(
"{about}
# Template support
{template_support}
## Supported grid definition templates
{gds_template_support}
## Supported data representation templates
{drs_template_support}
# Examples
{examples}
# Crate features
Following crate features are available in this crate. These descriptions are
extracted from the manifest of the crate.
{manifest}
"
))
}
pub(crate) fn read_manifest() -> io::Result<String> {
let mut f = std::fs::File::open("Cargo.toml")?;
let mut manifest = String::new();
f.read_to_string(&mut manifest)?;
println!("cargo:rerun-if-changed=Cargo.toml");
Ok(manifest)
}
pub(crate) struct Features(String);
impl Features {
pub(crate) fn text(self) -> String {
self.0
}
}
impl FromStr for Features {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let doc = s
.parse::<toml_edit::DocumentMut>()
.map_err(|e| e.to_string())?;
let features = doc["features"].to_string();
let new_doc = format!(
"```text
[features]
{features}
```"
);
Ok(Self(new_doc))
}
}
fn read_readme() -> io::Result<String> {
let mut f = std::fs::File::open("README.md")?;
let mut readme = String::new();
f.read_to_string(&mut readme)?;
println!("cargo:rerun-if-changed=README.md");
Ok(readme)
}
#[derive(Debug)]
struct ReadMeSections(HashMap<String, String>);
impl ReadMeSections {
fn get(&self, key: &str) -> Result<&String, String> {
self.0
.get(key)
.ok_or(format!(r#""{key}" section not found"#))
}
}
impl std::str::FromStr for ReadMeSections {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut lines = s.split_inclusive("\n");
let mut map = HashMap::<String, String>::new();
let mut start = 0;
let mut pos = 0;
let mut title: Option<&str> = None;
let mut within_code_block = false;
let push = |map: &mut HashMap<String, String>,
title: Option<&str>,
s: &str,
start: usize,
end: usize| {
let Some(title) = title else { return };
if end <= start {
return;
}
let content = s[start..end].to_owned();
map.insert(title.to_owned(), content);
};
loop {
let Some(line) = lines.next() else {
push(&mut map, title, s, start, pos);
break;
};
pos += line.len();
if line.starts_with("```") {
within_code_block = !within_code_block;
continue;
}
if within_code_block {
continue;
}
if line.starts_with("#") {
push(&mut map, title, s, start, pos - line.len());
let s = line.trim_start_matches('#').trim();
title = Some(s);
start = pos;
}
}
Ok(Self(map))
}
}