Add ${var} delimiter syntax for path templates
Users can now append literal text to template variables using curly
braces: ${albumtype}s produces "Albums", "Singles", "EPs". Without
braces, $albumtypes was rejected as an unknown variable by validation.
Both syntaxes work: $albumtype (plain) and ${albumtype} (delimited).
Bracket vars are resolved first to prevent partial matching conflicts.
Validation updated for album, single, and playlist templates.
This commit is contained in:
parent
c3f88a713a
commit
93e036848b
3 changed files with 39 additions and 16 deletions
|
|
@ -18498,6 +18498,25 @@ def _apply_path_template(template: str, context: dict) -> str:
|
|||
album_artist_value = resolved
|
||||
except Exception:
|
||||
pass
|
||||
# Support ${var} delimited syntax (e.g. ${albumtype}s → Albums)
|
||||
# Must run before $var replacements to prevent partial matching
|
||||
_bracket_map = {
|
||||
'albumartist': album_artist_value,
|
||||
'albumtype': clean_context.get('albumtype', 'Album'),
|
||||
'playlist': clean_context.get('playlist_name', ''),
|
||||
'artistletter': (clean_context.get('artist', 'U') or 'U')[0].upper(),
|
||||
'artist': clean_context.get('artist', 'Unknown Artist'),
|
||||
'album': clean_context.get('album', 'Unknown Album'),
|
||||
'title': clean_context.get('title', 'Unknown Track'),
|
||||
'track': f"{clean_context.get('track_number', 1):02d}",
|
||||
'disc': str(clean_context.get('disc_number', 1)),
|
||||
'discnum': str(clean_context.get('disc_number', 1)),
|
||||
'year': str(clean_context.get('year', '')),
|
||||
'quality': clean_context.get('quality', ''),
|
||||
}
|
||||
for var_name, val in _bracket_map.items():
|
||||
result = result.replace('${' + var_name + '}', val)
|
||||
|
||||
result = result.replace('$albumartist', album_artist_value)
|
||||
result = result.replace('$albumtype', clean_context.get('albumtype', 'Album'))
|
||||
result = result.replace('$playlist', clean_context.get('playlist_name', ''))
|
||||
|
|
|
|||
|
|
@ -5068,7 +5068,7 @@
|
|||
<input type="text" id="template-album-path"
|
||||
placeholder="$albumartist/$albumartist - $album/$track - $title">
|
||||
<small class="settings-hint">Variables: $albumartist, $artist, $artistletter, $album, $albumtype,
|
||||
$title, $track, $disc (01), $discnum (1), $year, $quality (filename only)</small>
|
||||
$title, $track, $disc (01), $discnum (1), $year, $quality (filename only). Use ${var} to append text: ${albumtype}s → Albums</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
|||
|
|
@ -5703,17 +5703,19 @@ function validateFileOrganizationTemplates() {
|
|||
errors.push('Album template cannot have consecutive slashes //');
|
||||
}
|
||||
// Check for likely typos of valid variables (case-insensitive to catch $Album, $ARTIST, etc.)
|
||||
const albumVarPattern = /\$[a-zA-Z]+/g;
|
||||
const albumVarPattern = /\$\{([a-zA-Z]+)\}|\$([a-zA-Z]+)/g;
|
||||
const foundVars = albumPath.match(albumVarPattern) || [];
|
||||
foundVars.forEach(v => {
|
||||
const lowerVar = v.toLowerCase();
|
||||
// Normalize ${var} to $var for validation
|
||||
const normalized = v.startsWith('${') ? '$' + v.slice(2, -1) : v;
|
||||
const lowerVar = normalized.toLowerCase();
|
||||
// Check if lowercase version exists in valid vars
|
||||
const isValid = validVars.album.some(validVar => validVar.toLowerCase() === lowerVar);
|
||||
if (!isValid) {
|
||||
errors.push(`Invalid variable "${v}" in album template. Valid: ${validVars.album.join(', ')}`);
|
||||
} else if (v !== lowerVar && validVars.album.includes(lowerVar)) {
|
||||
errors.push(`Invalid variable "${normalized}" in album template. Valid: ${validVars.album.join(', ')}`);
|
||||
} else if (normalized !== lowerVar && validVars.album.includes(lowerVar)) {
|
||||
// Variable is valid but has wrong case
|
||||
errors.push(`Variable "${v}" should be lowercase: "${lowerVar}"`);
|
||||
errors.push(`Variable "${normalized}" should be lowercase: "${lowerVar}"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -5730,15 +5732,16 @@ function validateFileOrganizationTemplates() {
|
|||
if (singlePath.includes('//')) {
|
||||
errors.push('Single template cannot have consecutive slashes //');
|
||||
}
|
||||
const singleVarPattern = /\$[a-zA-Z]+/g;
|
||||
const singleVarPattern = /\$\{([a-zA-Z]+)\}|\$([a-zA-Z]+)/g;
|
||||
const foundVars = singlePath.match(singleVarPattern) || [];
|
||||
foundVars.forEach(v => {
|
||||
const lowerVar = v.toLowerCase();
|
||||
const normalized = v.startsWith('${') ? '$' + v.slice(2, -1) : v;
|
||||
const lowerVar = normalized.toLowerCase();
|
||||
const isValid = validVars.single.some(validVar => validVar.toLowerCase() === lowerVar);
|
||||
if (!isValid) {
|
||||
errors.push(`Invalid variable "${v}" in single template. Valid: ${validVars.single.join(', ')}`);
|
||||
} else if (v !== lowerVar && validVars.single.includes(lowerVar)) {
|
||||
errors.push(`Variable "${v}" should be lowercase: "${lowerVar}"`);
|
||||
errors.push(`Invalid variable "${normalized}" in single template. Valid: ${validVars.single.join(', ')}`);
|
||||
} else if (normalized !== lowerVar && validVars.single.includes(lowerVar)) {
|
||||
errors.push(`Variable "${normalized}" should be lowercase: "${lowerVar}"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -5757,15 +5760,16 @@ function validateFileOrganizationTemplates() {
|
|||
if (playlistPath.includes('//')) {
|
||||
errors.push('Playlist template cannot have consecutive slashes //');
|
||||
}
|
||||
const playlistVarPattern = /\$[a-zA-Z]+/g;
|
||||
const playlistVarPattern = /\$\{([a-zA-Z]+)\}|\$([a-zA-Z]+)/g;
|
||||
const foundVars = playlistPath.match(playlistVarPattern) || [];
|
||||
foundVars.forEach(v => {
|
||||
const lowerVar = v.toLowerCase();
|
||||
const normalized = v.startsWith('${') ? '$' + v.slice(2, -1) : v;
|
||||
const lowerVar = normalized.toLowerCase();
|
||||
const isValid = validVars.playlist.some(validVar => validVar.toLowerCase() === lowerVar);
|
||||
if (!isValid) {
|
||||
errors.push(`Invalid variable "${v}" in playlist template. Valid: ${validVars.playlist.join(', ')}`);
|
||||
} else if (v !== lowerVar && validVars.playlist.includes(lowerVar)) {
|
||||
errors.push(`Variable "${v}" should be lowercase: "${lowerVar}"`);
|
||||
errors.push(`Invalid variable "${normalized}" in playlist template. Valid: ${validVars.playlist.join(', ')}`);
|
||||
} else if (normalized !== lowerVar && validVars.playlist.includes(lowerVar)) {
|
||||
errors.push(`Variable "${normalized}" should be lowercase: "${lowerVar}"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue