name = "path_normalizer" category = "environment" mode = "convert" description = """ Read file paths from stdin, one per line. Normalize each path: 1. Replace a leading "~" with the value of $HOME. 2. Remove trailing slashes (except for root "/"). 3. Collapse consecutive slashes into one. 4. Resolve "." components (remove them). 5. Resolve ".." components (go up one directory level). Output the cleaned path, one per line. Skip empty lines. """ bash_source = ''' #!/bin/bash while IFS= read -r line || [[ -n "$line" ]]; do [[ -z "$line" ]] && continue # Expand tilde path=$(echo "$line" | sed "s:^~:$HOME:") # Collapse multiple slashes path=$(echo "$path" | sed 's:/\\+:/:g') # Remove trailing slash (but keep root) path=$(echo "$path" | sed 's:/$::') [[ -z "$path" ]] && path="/" # Resolve . and .. components IFS='/' read -ra parts <<< "$path" result=() absolute="" if [[ "$path" == /* ]]; then absolute="/" fi for part in "${parts[@]}"; do if [[ "$part" == "." || "$part" == "" ]]; then continue elif [[ "$part" == ".." ]]; then if [[ ${#result[@]} -gt 0 && "${result[-1]}" != ".." ]]; then unset 'result[${#result[@]}-1]' elif [[ -z "$absolute" ]]; then result+=("..") fi else result+=("$part") fi done if [[ -n "$absolute" ]]; then final="/" IFS='/'; final+="${result[*]}" else IFS='/'; final="${result[*]}" fi [[ -z "$final" ]] && final="/" echo "$final" done ''' [[test_cases]] description = "Tilde expansion and trailing slashes" stdin = """~/Documents/ ~/projects/code /var/log/""" expected_stdout = """/Users/testuser/Documents /Users/testuser/projects/code /var/log""" env = { "HOME" = "/Users/testuser" } [[test_cases]] description = "Resolving . and .. components" stdin = """/usr/local/./bin /home/user/../shared/docs /a/b/c/../../d""" expected_stdout = """/usr/local/bin /home/shared/docs /a/d""" [[test_cases]] description = "Collapsing multiple slashes" stdin = """/usr//local///bin /var/log//syslog""" expected_stdout = """/usr/local/bin /var/log/syslog""" [[test_cases]] description = "Root and relative paths" stdin = """/ ./config/settings ../parent/child""" expected_stdout = """/ config/settings ../parent/child"""