blob: 83a404c93917e2c93e556274df9d3a6e1990b193 [file] [log] [blame]
use std::{fs, ops::Range, path::Path};
pub(crate) fn in_place(api: &str, path: &Path) {
let path = {
let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
Path::new(&dir).join(path)
};
let mut text = fs::read_to_string(&path).unwrap_or_else(|_| panic!("failed to read {path:?}"));
let (insert_to, indent) = locate(&text);
let api: String =
with_preamble(api)
.lines()
.map(|it| {
if it.trim().is_empty() {
"\n".to_string()
} else {
format!("{}{}\n", indent, it)
}
})
.collect();
text.replace_range(insert_to, &api);
fs::write(&path, text.as_bytes()).unwrap();
}
pub(crate) fn stdout(api: &str) {
print!("{}", with_preamble(api))
}
fn with_preamble(api: &str) -> String {
format!(
"\
// generated start
// The following code is generated by `xflags` macro.
// Run `env UPDATE_XFLAGS=1 cargo build` to regenerate.
{}
// generated end
",
api.trim()
)
}
fn locate(text: &str) -> (Range<usize>, String) {
if let Some(it) = locate_existing(text) {
return it;
}
if let Some(it) = locate_new(text) {
return it;
}
panic!("failed to update xflags in place")
}
fn locate_existing(text: &str) -> Option<(Range<usize>, String)> {
let start_idx = text.find("// generated start")?;
let start_idx = newline_before(text, start_idx);
let end_idx = text.find("// generated end")?;
let end_idx = newline_after(text, end_idx);
let indent = indent_at(text, start_idx);
Some((start_idx..end_idx, indent))
}
fn newline_before(text: &str, start_idx: usize) -> usize {
text[..start_idx].rfind('\n').map_or(start_idx, |it| it + 1)
}
fn newline_after(text: &str, start_idx: usize) -> usize {
start_idx + text[start_idx..].find('\n').map_or(text[start_idx..].len(), |it| it + 1)
}
fn indent_at(text: &str, start_idx: usize) -> String {
text[start_idx..].chars().take_while(|&it| it == ' ').collect()
}
fn locate_new(text: &str) -> Option<(Range<usize>, String)> {
let mut idx = text.find("xflags!")?;
let mut lvl = 0i32;
for c in text[idx..].chars() {
idx += c.len_utf8();
match c {
'{' => lvl += 1,
'}' if lvl == 1 => break,
'}' => lvl -= 1,
_ => (),
}
}
let indent = indent_at(text, newline_before(text, idx));
if text[idx..].starts_with('\n') {
idx += 1;
}
Some((idx..idx, indent))
}