* Main parsing routine. Splits and validates a filename. * Supports strict delimiters (spaces around the dash) or loose delimiters.
(filename, options = {})
| 87 | * Supports strict delimiters (spaces around the dash) or loose delimiters. |
| 88 | */ |
| 89 | static parse(filename, options = {}) { |
| 90 | const { strictDelimiter = true } = options; |
| 91 | const cleaned = this.cleanString(filename); |
| 92 | const result = { |
| 93 | original: filename, |
| 94 | cleaned: cleaned, |
| 95 | approved: false, |
| 96 | artist: null, |
| 97 | title: null, |
| 98 | reasons: [], |
| 99 | checklist: { |
| 100 | separatorFound: false, |
| 101 | artistValid: false, |
| 102 | titleValid: false |
| 103 | } |
| 104 | }; |
| 105 | |
| 106 | if (!cleaned) { |
| 107 | result.reasons.push('Empty filename after cleaning'); |
| 108 | return result; |
| 109 | } |
| 110 | |
| 111 | // Standard separators: space-dash-space, en-dash, em-dash, colon, tilde |
| 112 | // e.g., "Artist - Title", "Artist: Title", "Artist ~ Title" |
| 113 | let separatorRegex = /\s+[-–—~:]\s+/; |
| 114 | if (!strictDelimiter) { |
| 115 | // In loose mode, support any dash bordered by word characters, or just colon/tilde |
| 116 | separatorRegex = /\s+[-–—~:]\s+|(?<=\w)-(?=\w)|[-–—~:]/; |
| 117 | } |
| 118 | |
| 119 | let parts = cleaned.split(separatorRegex).map(p => p.trim()).filter(Boolean); |
| 120 | |
| 121 | // If no standard separator is found but we split on raw dash/colon |
| 122 | if (parts.length === 1 && strictDelimiter) { |
| 123 | result.reasons.push('No standard space-separated delimiter (e.g. " - ") found'); |
| 124 | return result; |
| 125 | } |
| 126 | |
| 127 | result.checklist.separatorFound = true; |
| 128 | |
| 129 | // Check if we have standard "01 - Artist - Title" (3 parts) where first is index |
| 130 | if (parts.length >= 3) { |
| 131 | const firstPartCheck = this.validatePart(parts[0]); |
| 132 | if (!firstPartCheck.valid && (this.NUMERIC_ONLY_REGEX.test(parts[0]) || this.TRACK_INDEX_ONLY_REGEX.test(parts[0]))) { |
| 133 | // Discard the leading track number part and take the second and third parts! |
| 134 | parts = [parts[1], parts[2]]; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | if (parts.length < 2) { |
| 139 | result.reasons.push('Could not split filename into both artist and title candidates'); |
| 140 | return result; |
| 141 | } |
| 142 | |
| 143 | let artistCandidate = parts[0]; |
| 144 | let titleCandidate = parts[1]; |
| 145 | |
| 146 | // If the first part is purely numbers/indexes, it's just index and title (no artist!) |
no test coverage detected