1const fn cq_byte(b: u8) -> i8 {
14 match b {
15 0x00..=0x06 => 1,
16 0x07 => b'a' as i8,
17 0x08 => b'b' as i8,
18 0x09 => b't' as i8,
19 0x0a => b'n' as i8,
20 0x0b => b'v' as i8,
21 0x0c => b'f' as i8,
22 0x0d => b'r' as i8,
23 0x0e..=0x1f => 1,
24 0x20 | 0x21 => -1,
25 0x22 => b'"' as i8,
26 0x23..=0x5b => -1,
27 0x5c => b'\\' as i8,
28 0x5d..=0x7e => -1,
29 0x7f => 1,
30 0x80..=0xff => 0,
31 }
32}
33
34const fn cq_lookup_table() -> [i8; 256] {
35 let mut t = [0i8; 256];
36 let mut i = 0usize;
37 while i < 256 {
38 t[i] = cq_byte(i as u8);
39 i += 1;
40 }
41 t
42}
43
44static CQ_LOOKUP: [i8; 256] = cq_lookup_table();
45
46#[inline]
47fn cq_must_quote(byte: u8, quote_fully: bool) -> bool {
48 i32::from(CQ_LOOKUP[byte as usize]) + i32::from(quote_fully) > 0
49}
50
51#[must_use]
57pub fn quote_c_style(path: &str, quote_fully: bool) -> String {
58 let bytes = path.as_bytes();
59 let mut any = false;
60 for &b in bytes {
61 if cq_must_quote(b, quote_fully) {
62 any = true;
63 break;
64 }
65 }
66 if !any {
67 return path.to_owned();
68 }
69
70 let mut out = String::with_capacity(path.len() + 2);
71 out.push('"');
72 let mut p = 0usize;
73 while p < bytes.len() {
74 let mut len = 0usize;
75 while p + len < bytes.len() && !cq_must_quote(bytes[p + len], quote_fully) {
76 len += 1;
77 }
78 out.push_str(path.get(p..p + len).unwrap_or(""));
79 p += len;
80 if p >= bytes.len() {
81 break;
82 }
83 let ch = bytes[p];
84 p += 1;
85 out.push('\\');
86 let cq = CQ_LOOKUP[ch as usize];
87 if cq >= b' ' as i8 {
88 out.push(cq as u8 as char);
89 } else {
90 out.push(char::from(((ch >> 6) & 3) + b'0'));
91 out.push(char::from(((ch >> 3) & 7) + b'0'));
92 out.push(char::from((ch & 7) + b'0'));
93 }
94 }
95 out.push('"');
96 out
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn ascii_safe_unchanged() {
105 assert_eq!(quote_c_style("Name", true), "Name");
106 assert_eq!(quote_c_style("With SP in it", true), "With SP in it");
107 }
108
109 #[test]
110 fn t3902_expect_quoted() {
111 assert_eq!(quote_c_style("Name and a\nLF", true), "\"Name and a\\nLF\"");
112 assert_eq!(
113 quote_c_style("Name and an\tHT", true),
114 "\"Name and an\\tHT\""
115 );
116 assert_eq!(quote_c_style("Name\"", true), "\"Name\\\"\"");
117 }
118
119 #[test]
120 fn t3902_expect_raw_mode() {
121 let s = "濱野\t純";
122 assert_eq!(quote_c_style(s, false), "\"濱野\\t純\"");
123 let s2 = "濱野 純";
124 assert_eq!(quote_c_style(s2, false), "濱野 純");
125 }
126}