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