feature/library-patch-endpoint #94
@@ -80,16 +80,21 @@ impl Library {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a comma-separated excluded_dirs column into a Vec, dropping
|
/// Parse an excluded_dirs string into a Vec, dropping empty entries.
|
||||||
/// empty entries (mirrors `AppState::parse_excluded_dirs` for the env
|
/// NULL → empty Vec. Duplicates are preserved — `PathExcluder` accepts
|
||||||
/// var). NULL → empty Vec. Duplicates are preserved — `PathExcluder`
|
/// repeats, and the storage-side normaliser is where dedup happens.
|
||||||
/// accepts repeats, and the storage-side normaliser is where dedup
|
///
|
||||||
/// happens.
|
/// Accepts both `,` and newline (`\n` / `\r\n`) as separators so the
|
||||||
|
/// UI's textarea can submit one-entry-per-line input without forcing
|
||||||
|
/// the operator to remember commas. The DB stores the canonical
|
||||||
|
/// comma-joined form (see `normalize_excluded_dirs_input`); the
|
||||||
|
/// newline path matters mostly for the frontend submit, but mirroring
|
||||||
|
/// it here keeps the parse direction round-trip safe.
|
||||||
pub fn parse_excluded_dirs_column(raw: Option<&str>) -> Vec<String> {
|
pub fn parse_excluded_dirs_column(raw: Option<&str>) -> Vec<String> {
|
||||||
match raw {
|
match raw {
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
Some(s) => s
|
Some(s) => s
|
||||||
.split(',')
|
.split(|c: char| matches!(c, ',' | '\n' | '\r'))
|
||||||
.map(str::trim)
|
.map(str::trim)
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.map(String::from)
|
.map(String::from)
|
||||||
@@ -739,6 +744,40 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_excluded_dirs_column_splits_on_newlines_too() {
|
||||||
|
// Newline-separated input from a textarea submit. One-per-line
|
||||||
|
// is the recommended UX because "I forgot the comma" was a
|
||||||
|
// recurring footgun (.thumbnails .thumbnails2 silently
|
||||||
|
// becomes a single never-matching pattern).
|
||||||
|
assert_eq!(
|
||||||
|
parse_excluded_dirs_column(Some("@eaDir\n.thumbnails\n/private")),
|
||||||
|
vec![
|
||||||
|
"@eaDir".to_string(),
|
||||||
|
".thumbnails".to_string(),
|
||||||
|
"/private".to_string()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
// Windows line endings (CRLF) — the carriage return is its own
|
||||||
|
// separator so the trailing empty token between \r and \n gets
|
||||||
|
// trimmed + dropped.
|
||||||
|
assert_eq!(
|
||||||
|
parse_excluded_dirs_column(Some("a\r\nb\r\nc")),
|
||||||
|
vec!["a".to_string(), "b".to_string(), "c".to_string()]
|
||||||
|
);
|
||||||
|
// Mixed comma + newline — the user pastes from one source,
|
||||||
|
// adds a few entries inline. Both work, in any combination.
|
||||||
|
assert_eq!(
|
||||||
|
parse_excluded_dirs_column(Some("a, b\nc,d")),
|
||||||
|
vec![
|
||||||
|
"a".to_string(),
|
||||||
|
"b".to_string(),
|
||||||
|
"c".to_string(),
|
||||||
|
"d".to_string()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn effective_excluded_dirs_unions_global_and_per_library() {
|
fn effective_excluded_dirs_unions_global_and_per_library() {
|
||||||
let lib_no_extras = Library {
|
let lib_no_extras = Library {
|
||||||
|
|||||||
Reference in New Issue
Block a user