1pub mod locator {
2 #[allow(dead_code)]
3 #[derive(Debug, Clone)]
4 pub struct Locator {
5 original_lines: Vec<String>,
6 line_offsets: Vec<u32>,
7 }
8
9 type Location = (u32, u32);
10
11 impl Locator {
12 pub fn new(original: &str) -> Self {
13 let original_lines = original
14 .split('\n')
15 .map(|line| line.to_owned())
16 .collect::<Vec<String>>();
17
18 let mut line_offsets: Vec<u32> = vec![];
19
20 let mut pos_in_original = 0;
21 for line in original_lines.iter() {
22 line_offsets.push(pos_in_original);
23 pos_in_original += line.len() as u32 + 1;
24 }
25
26 Locator {
27 original_lines,
28 line_offsets,
29 }
30 }
31
32 pub fn locate(&self, index: u32) -> Location {
33 let mut i = 0;
34 let mut j = self.line_offsets.len();
35
36 while i < j {
37 let m = (i + j) >> 1;
38 if index < self.line_offsets[m] {
39 j = m;
40 } else {
41 i = m + 1;
42 }
43 }
44 let line = (i - 1) as u32;
45 let column = index - self.line_offsets[line as usize];
46
47 (line, column)
48 }
49 }
50
51 #[cfg(test)]
52 mod tests {
53 use super::Locator;
54
55 #[test]
56 fn test() {
57 let locator = Locator::new("magic\nstring\nrs");
58
59 assert_eq!(locator.original_lines[0], "magic");
60 assert_eq!(locator.original_lines[1], "string");
61 assert_eq!(locator.original_lines[2], "rs");
62
63 assert_eq!(locator.line_offsets[0], 0);
64 assert_eq!(locator.line_offsets[1], 6);
65 assert_eq!(locator.line_offsets[2], 13);
66
67 assert_eq!(locator.locate(2), (0, 2));
68 assert_eq!(locator.locate(8), (1, 2));
69 assert_eq!(locator.locate(14), (2, 1));
70 }
71 }
72}
73
74pub mod trim {
75 use regex::Regex;
76
77 use crate::Result;
78
79 pub fn trim_start_regexp<'a>(s: &'a str, reg_pat: &'a str) -> Result<&'a str> {
80 if s.is_empty() {
81 return Ok(s);
82 }
83
84 let matcher = Regex::new(reg_pat)?;
85 let chars = s.chars().collect::<Vec<_>>();
86
87 let mut pos = 0;
88
89 while pos < s.len() {
90 let c = chars.get(pos).unwrap();
91 if !matcher.is_match(c.to_string().as_str()) {
92 break;
93 }
94 pos += 1;
95 }
96
97 Ok(&s[pos..])
98 }
99
100 pub fn trim_end_regexp<'a>(s: &'a str, reg_pat: &'a str) -> Result<&'a str> {
101 if s.is_empty() {
102 return Ok(s);
103 }
104
105 let matcher = Regex::new(reg_pat)?;
106 let chars = s.chars().collect::<Vec<_>>();
107
108 let mut pos = (s.len() - 1) as i32;
109
110 while pos >= 0 {
111 let c = chars.get(pos as usize).unwrap();
112 if !matcher.is_match(c.to_string().as_str()) {
113 break;
114 }
115 pos -= 1;
116 }
117
118 Ok(&s[..(pos + 1) as usize])
119 }
120
121 #[test]
122 fn should_trim_start() -> Result {
123 assert_eq!(trim_start_regexp(" abc ", "\\s")?, "abc ");
124 assert_eq!(trim_start_regexp("\t\t\tabc\t\t", "\\t")?, "abc\t\t");
125 assert_eq!(trim_start_regexp("\n\nabc\t\t", "\n")?, "abc\t\t");
126 assert_eq!(trim_start_regexp("\n\n\n", "\n")?, "");
127
128 Ok(())
129 }
130
131 #[test]
132 fn should_trim_end() -> Result {
133 assert_eq!(trim_end_regexp(" abc ", "\\s")?, " abc");
134 assert_eq!(trim_end_regexp("\t\t\tabc\t\t", "\\t")?, "\t\t\tabc");
135 assert_eq!(trim_end_regexp("\t\tabc\n\n", "\n")?, "\t\tabc");
136 assert_eq!(trim_end_regexp("\n\n\n", "\n")?, "");
137
138 Ok(())
139 }
140
141 #[test]
142 fn should_not_trim_unrelated_contents() -> Result {
143 assert_eq!(trim_start_regexp("\\s\\sabc", "\\s")?, "\\s\\sabc");
144 assert_eq!(trim_end_regexp("abc\\t\\t", "\\t")?, "abc\\t\\t");
145
146 Ok(())
147 }
148}
149
150use crate::{Error, MagicStringErrorType, Result};
151
152pub fn normalize_index(s: &str, index: i64) -> Result<usize> {
153 let len = s.len() as i64;
154
155 let index = if index < 0 { index + len } else { index };
156
157 if index < 0 || index > len {
158 return Err(Error::new_with_reason(
159 MagicStringErrorType::MagicStringOutOfRangeError,
160 "index out of range",
161 ));
162 }
163
164 Ok(index as usize)
165}