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
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Parsed {
    pub dir: String,
    pub root: String,
    pub base: String,
    pub name: String,
    pub ext: String,
}

impl Default for Parsed {
    fn default() -> Self {
        Self {
            dir: "".to_owned(),
            root: "".to_owned(),
            base: "".to_owned(),
            name: "".to_owned(),
            ext: "".to_owned(),
        }
    }
}

pub(crate) fn normalize_string(
    path: &str,
    allow_above_root: bool,
    separator: &char,
    is_path_separator: &dyn Fn(&char) -> bool,
) -> String {
    let path = path.chars().collect::<Vec<char>>();

    let mut res: Vec<char> = Vec::new();
    let mut last_segment_length = 0;
    let mut last_slash = -1;
    let mut dots = 0;
    let mut code = ' ';

    {
        let mut i = 0;
        let path_len = path.len();

        while i <= path_len {
            if i < path_len {
                code = *path.get(i).unwrap();
            } else if is_path_separator(&code) {
                break;
            } else {
                code = CHAR_FORWARD_SLASH
            }

            if is_path_separator(&code) {
                if last_slash == i as i32 - 1 || dots == 1 {
                    // noop
                } else if dots == 2 {
                    if res.len() < 2
                        || last_segment_length != 2
                        || res.get(res.len() - 1).unwrap() != &CHAR_DOT
                        || res.get(res.len() - 2).unwrap() != &CHAR_DOT
                    {
                        if res.len() > 2 {
                            let last_slash_index =
                                last_index_of(&res, separator).map_or(-1, |s| s as i32);
                            if last_slash_index == -1 {
                                res = vec![];
                                last_segment_length = 0
                            } else {
                                res = res[0..last_slash_index as usize].to_vec();
                                last_segment_length = res.len() as i32
                                    - 1
                                    - last_index_of(&res, separator).map_or(-1, |s| s as i32);
                            }
                            last_slash = i as i32;
                            dots = 0;

                            i += 1;
                            continue;
                        } else if res.len() != 0 {
                            res = vec![];
                            last_segment_length = 0;
                            last_slash = i as i32;
                            dots = 0;

                            i += 1;
                            continue;
                        }
                    }
                    if allow_above_root {
                        if res.len() > 0 {
                            res.push(*separator);
                        }
                        res.push('.');
                        res.push('.');
                        last_segment_length = 2;
                    }
                } else {
                    if res.len() > 0 {
                        res.push(*separator)
                    }
                    path[(last_slash + 1) as usize..i as usize]
                        .iter()
                        .for_each(|c| res.push(*c));
                    last_segment_length = i as i32 - last_slash - 1;
                }
                last_slash = i as i32;
                dots = 0;
            } else if code == CHAR_DOT && dots != -1 {
                dots += 1;
            } else {
                dots = -1;
            }

            i += 1;
        }
    }

    res.into_iter().collect()
}

fn last_index_of(vec: &Vec<char>, tar: &char) -> Option<usize> {
    vec.iter()
        .enumerate()
        .rev()
        .find_map(|(idx, c)| if c == tar { Some(idx) } else { None })
}

#[inline]
pub(crate) fn is_empty(s: &str) -> bool {
    s.len() == 0
}

pub(crate) fn format_inner(sep: &str, path_object: Parsed) -> String {
    let root = path_object.root.clone();
    let dir = if !is_empty(&path_object.dir) {
        path_object.dir
    } else {
        path_object.root
    };
    let base = if !is_empty(&path_object.base) {
        path_object.base
    } else {
        format!("{}{}", &path_object.name, &path_object.ext)
    };

    if is_empty(&dir) {
        return base;
    }

    if dir == root {
        format!("{}{}", dir, base)
    } else {
        format!("{}{}{}", dir, sep, base)
    }
}

#[inline]
pub(crate) fn is_posix_path_separator(code: &char) -> bool {
    code == &CHAR_FORWARD_SLASH
}

// Alphabet chars.
// const CHAR_UPPERCASE_A: char = 65; /* A */
// const CHAR_LOWERCASE_A: char = 97; /* a */
// const CHAR_UPPERCASE_Z: char = 90; /* Z */
// const CHAR_LOWERCASE_Z: char = 122; /* z */
// const CHAR_UPPERCASE_C: char = 67; /* C */
// const CHAR_LOWERCASE_B: char = 98; /* b */
// const CHAR_LOWERCASE_E: char = 101; /* e */
// const CHAR_LOWERCASE_N: char = 110; /* n */
// // Non-alphabetic chars.
pub(crate) const CHAR_DOT: char = '.'; /* . */
pub(crate) const CHAR_FORWARD_SLASH: char = '/'; /* / */
// const CHAR_BACKWARD_SLASH: char = 92; /* \ */
// const CHAR_VERTICAL_LINE: char = 124; /* | */
// const CHAR_COLON: char = 58; /* : */
// const CHAR_QUESTION_MARK: char = 63; /* ? */
// const CHAR_UNDERSCORE: char = 95; /* _ */
// const CHAR_LINE_FEED: char = 10; /* \n */
// const CHAR_CARRIAGE_RETURN: char = 13; /* \r */
// const CHAR_TAB: char = 9; /* \t */
// const CHAR_FORM_FEED: char = 12; /* \f */
// const CHAR_EXCLAMATION_MARK: char = 33; /* ! */
// const CHAR_HASH: char = 35; /* # */
// const CHAR_SPACE: char = 32; /*   */
// const CHAR_NO_BREAK_SPACE: char = 160; /* \u00A0 */
// const CHAR_ZERO_WIDTH_NOBREAK_SPACE: char = 65279; /* \uFEFF */
// const CHAR_LEFT_SQUARE_BRACKET: char = 91; /* [ */
// const CHAR_RIGHT_SQUARE_BRACKET: char = 93; /* ] */
// const CHAR_LEFT_ANGLE_BRACKET: char = 60; /* < */
// const CHAR_RIGHT_ANGLE_BRACKET: char = 62; /* > */
// const CHAR_LEFT_CURLY_BRACKET: char = 123; /* { */
// const CHAR_RIGHT_CURLY_BRACKET: char = 125; /* } */
// const CHAR_HYPHEN_MINUS: char = 45; /* - */
// const CHAR_PLUS: char = 43; /* + */
// const CHAR_DOUBLE_QUOTE: char = 34; /* " */
// const CHAR_SINGLE_QUOTE: char = 39; /* ' */
// const CHAR_PERCENT: char = 37; /* % */
// const CHAR_SEMICOLON: char = 59; /* ; */
// const CHAR_CIRCUMFLEX_ACCENT: char = 94; /* ^ */
// const CHAR_GRAVE_ACCENT: char = 96; /* ` */
// const CHAR_AT: char = 64; /* @ */
// const CHAR_AMPERSAND: char = 38; /* & */
// const CHAR_EQUAL: char = 61; /* = */
// // Digits
// const CHAR_0: char = 48; /* 0 */
// const CHAR_9: char = 57; /* 9 */
// const EOL: isWindows ? '\r\n' : '\n'