tidy/
fluent_alphabetical.rs
1use std::collections::HashMap;
4use std::fs::OpenOptions;
5use std::io::Write;
6use std::path::Path;
7
8use regex::Regex;
9
10use crate::walk::{filter_dirs, walk};
11
12fn message() -> &'static Regex {
13 static_regex!(r#"(?m)^([a-zA-Z0-9_]+)\s*=\s*"#)
14}
15
16fn filter_fluent(path: &Path) -> bool {
17 if let Some(ext) = path.extension() { ext.to_str() != Some("ftl") } else { true }
18}
19
20fn check_alphabetic(
21 filename: &str,
22 fluent: &str,
23 bad: &mut bool,
24 all_defined_msgs: &mut HashMap<String, String>,
25) {
26 let mut matches = message().captures_iter(fluent).peekable();
27 while let Some(m) = matches.next() {
28 let name = m.get(1).unwrap();
29 if let Some(defined_filename) = all_defined_msgs.get(name.as_str()) {
30 tidy_error!(
31 bad,
32 "{filename}: message `{}` is already defined in {}",
33 name.as_str(),
34 defined_filename,
35 );
36 }
37
38 all_defined_msgs.insert(name.as_str().to_owned(), filename.to_owned());
39
40 if let Some(next) = matches.peek() {
41 let next = next.get(1).unwrap();
42 if name.as_str() > next.as_str() {
43 tidy_error!(
44 bad,
45 "{filename}: message `{}` appears before `{}`, but is alphabetically later than it
46run `./x.py test tidy --bless` to sort the file correctly",
47 name.as_str(),
48 next.as_str()
49 );
50 }
51 } else {
52 break;
53 }
54 }
55}
56
57fn sort_messages(
58 filename: &str,
59 fluent: &str,
60 bad: &mut bool,
61 all_defined_msgs: &mut HashMap<String, String>,
62) -> String {
63 let mut chunks = vec![];
64 let mut cur = String::new();
65 for line in fluent.lines() {
66 if let Some(name) = message().find(line) {
67 if let Some(defined_filename) = all_defined_msgs.get(name.as_str()) {
68 tidy_error!(
69 bad,
70 "{filename}: message `{}` is already defined in {}",
71 name.as_str(),
72 defined_filename,
73 );
74 }
75
76 all_defined_msgs.insert(name.as_str().to_owned(), filename.to_owned());
77 chunks.push(std::mem::take(&mut cur));
78 }
79
80 cur += line;
81 cur.push('\n');
82 }
83 chunks.push(cur);
84 chunks.sort();
85 let mut out = chunks.join("");
86 out = out.trim().to_string();
87 out.push('\n');
88 out
89}
90
91pub fn check(path: &Path, bless: bool, bad: &mut bool) {
92 let mut all_defined_msgs = HashMap::new();
93 walk(
94 path,
95 |path, is_dir| filter_dirs(path) || (!is_dir && filter_fluent(path)),
96 &mut |ent, contents| {
97 if bless {
98 let sorted = sort_messages(
99 ent.path().to_str().unwrap(),
100 contents,
101 bad,
102 &mut all_defined_msgs,
103 );
104 if sorted != contents {
105 let mut f =
106 OpenOptions::new().write(true).truncate(true).open(ent.path()).unwrap();
107 f.write(sorted.as_bytes()).unwrap();
108 }
109 } else {
110 check_alphabetic(
111 ent.path().to_str().unwrap(),
112 contents,
113 bad,
114 &mut all_defined_msgs,
115 );
116 }
117 },
118 );
119
120 crate::fluent_used::check(path, all_defined_msgs, bad);
121}