]>
Commit | Line | Data |
---|---|---|
064997fb FG |
1 | use std::{fs, ops::Range, path::Path}; |
2 | ||
3 | pub(crate) fn in_place(api: &str, path: &Path) { | |
4 | let path = { | |
5 | let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); | |
6 | Path::new(&dir).join(path) | |
7 | }; | |
8 | ||
2b03887a | 9 | let mut text = fs::read_to_string(&path).unwrap_or_else(|_| panic!("failed to read {path:?}")); |
064997fb FG |
10 | |
11 | let (insert_to, indent) = locate(&text); | |
12 | ||
13 | let api: String = | |
14 | with_preamble(api) | |
15 | .lines() | |
16 | .map(|it| { | |
17 | if it.trim().is_empty() { | |
18 | "\n".to_string() | |
19 | } else { | |
20 | format!("{}{}\n", indent, it) | |
21 | } | |
22 | }) | |
23 | .collect(); | |
24 | text.replace_range(insert_to, &api); | |
25 | ||
26 | fs::write(&path, text.as_bytes()).unwrap(); | |
27 | } | |
28 | ||
29 | pub(crate) fn stdout(api: &str) { | |
30 | print!("{}", with_preamble(api)) | |
31 | } | |
32 | ||
33 | fn with_preamble(api: &str) -> String { | |
34 | format!( | |
35 | "\ | |
36 | // generated start | |
37 | // The following code is generated by `xflags` macro. | |
38 | // Run `env UPDATE_XFLAGS=1 cargo build` to regenerate. | |
39 | {} | |
40 | // generated end | |
41 | ", | |
42 | api.trim() | |
43 | ) | |
44 | } | |
45 | ||
46 | fn locate(text: &str) -> (Range<usize>, String) { | |
47 | if let Some(it) = locate_existing(text) { | |
48 | return it; | |
49 | } | |
50 | if let Some(it) = locate_new(text) { | |
51 | return it; | |
52 | } | |
53 | panic!("failed to update xflags in place") | |
54 | } | |
55 | ||
56 | fn locate_existing(text: &str) -> Option<(Range<usize>, String)> { | |
57 | let start_idx = text.find("// generated start")?; | |
58 | let start_idx = newline_before(text, start_idx); | |
59 | ||
60 | let end_idx = text.find("// generated end")?; | |
61 | let end_idx = newline_after(text, end_idx); | |
62 | ||
63 | let indent = indent_at(text, start_idx); | |
64 | ||
65 | Some((start_idx..end_idx, indent)) | |
66 | } | |
67 | ||
68 | fn newline_before(text: &str, start_idx: usize) -> usize { | |
69 | text[..start_idx].rfind('\n').map_or(start_idx, |it| it + 1) | |
70 | } | |
71 | ||
72 | fn newline_after(text: &str, start_idx: usize) -> usize { | |
73 | start_idx + text[start_idx..].find('\n').map_or(text[start_idx..].len(), |it| it + 1) | |
74 | } | |
75 | ||
76 | fn indent_at(text: &str, start_idx: usize) -> String { | |
77 | text[start_idx..].chars().take_while(|&it| it == ' ').collect() | |
78 | } | |
79 | ||
80 | fn locate_new(text: &str) -> Option<(Range<usize>, String)> { | |
81 | let mut idx = text.find("xflags!")?; | |
82 | let mut lvl = 0i32; | |
83 | for c in text[idx..].chars() { | |
84 | idx += c.len_utf8(); | |
85 | match c { | |
86 | '{' => lvl += 1, | |
87 | '}' if lvl == 1 => break, | |
88 | '}' => lvl -= 1, | |
89 | _ => (), | |
90 | } | |
91 | } | |
92 | let indent = indent_at(text, newline_before(text, idx)); | |
93 | if text[idx..].starts_with('\n') { | |
94 | idx += 1; | |
95 | } | |
96 | Some((idx..idx, indent)) | |
97 | } |