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, 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, 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, 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)) }