1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
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))
}
|