diff --git a/nirman/parser.py b/nirman/parser.py index 8cb2af7..f29f6e6 100644 --- a/nirman/parser.py +++ b/nirman/parser.py @@ -1,6 +1,18 @@ import re from typing import List, Tuple +# A set for fast, case-insensitive lookup of Windows reserved names. +# We also include '.' and '..' which are special on all platforms. +RESERVED_NAMES = { + "con", "prn", "aux", "nul", + "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9", + "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", + ".", ".." +} + +# Regex for invalid characters, EXCLUDING slashes which are handled separately. +INVALID_CHARS_REGEX = r'[<>:"|?*]' + def parse_markdown_tree(lines: List[str]) -> List[Tuple[int, str, bool]]: """ Parse a markdown tree structure into a list of tuples representing the file/folder hierarchy. @@ -38,10 +50,29 @@ def parse_markdown_tree(lines: List[str]) -> List[Tuple[int, str, bool]]: depth = 0 else: depth = (len(prefix) // 4) + 1 + + # 1. First, determine if it's a directory from the raw name. + is_directory = name.endswith(('/', '\\')) + + # 2. Then, create the clean_name by stripping the slashes. + clean_name = name.strip('\\/') + + # 3. Now, sanitize the clean_name for invalid characters. + clean_name = re.sub(INVALID_CHARS_REGEX, '_', clean_name) + + # 4. Finally, sanitize for reserved system names. + base_name = clean_name.split('.')[0] + if base_name.lower() in RESERVED_NAMES: + clean_name = "_" + clean_name is_directory = name.endswith(('/', '\\')) clean_name = name.strip('\\/') + # Get the base name before any extension for the check. + base_name = clean_name.split('.')[0] + if base_name.lower() in RESERVED_NAMES: + clean_name = "_" + clean_name + if clean_name == '.' and depth == 0: is_directory = True