Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,21 @@ private static bool SegmentMatchesExpectedTitle(
return true;
}

// Tolerate a differing leading article ("The"/"A"/"An") on either side, so a
// folder named "Language of Emotions" still matches the expected title
// "The Language of Emotions" (and vice versa). This stays a full-title equality
// modulo the article -- it never widens into substring or author matching, so
// the same-author / different-book boundary guards remain intact.
var articleFreeSegment = StripLeadingArticle(normalizedSegment);
if (titleTokens.Any(token =>
string.Equals(
StripLeadingArticle(token),
articleFreeSegment,
StringComparison.Ordinal)))
{
return true;
}

var components = segment
.Split(
[" - ", " – ", " — "],
Expand All @@ -167,6 +182,21 @@ private static bool SegmentMatchesExpectedTitle(
return false;
}

private static readonly string[] LeadingArticlePrefixes = ["the ", "a ", "an "];

private static string StripLeadingArticle(string normalizedToken)
{
foreach (var prefix in LeadingArticlePrefixes)
{
if (normalizedToken.StartsWith(prefix, StringComparison.Ordinal))
{
return normalizedToken[prefix.Length..];
}
}

return normalizedToken;
}

private static string? TryFindIdentifierBoundary(
string candidate,
string canonicalRoot,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,61 @@ public void Discover_LinkedDirectoryInsideIdentifierBoundary_IsNotTraversed()
&& issue.Path == link);
}

[Fact]
public void FindMatchingAudioFiles_FolderDropsLeadingThe_StillMatches()
{
var requested = CreateAudioFile(
"Karla McLaren",
"Language of Emotions",
"Language of Emotions.m4b");
var audiobook = new AudiobookBuilder()
.WithTitle("The Language of Emotions")
.WithAuthor("Karla McLaren")
.Build();

var result = Discover(audiobook);

var found = Assert.Single(result);
Assert.Equal(requested, found);
}

[Fact]
public void FindMatchingAudioFiles_FolderAddsLeadingArticle_StillMatches()
{
var requested = CreateAudioFile(
"Gabor Mate",
"The Myth of Normal",
"The Myth of Normal.m4b");
var audiobook = new AudiobookBuilder()
.WithTitle("Myth of Normal")
.WithAuthor("Gabor Mate")
.Build();

var result = Discover(audiobook);

var found = Assert.Single(result);
Assert.Equal(requested, found);
}

[Fact]
public void FindMatchingAudioFiles_ArticleToleranceDoesNotCrossLinkSiblingBooks()
{
// Same author, two books that both start with "The". Article-insensitivity
// must still compare the full remaining title, so only the requested book
// is attributed -- it must not collapse "The Reckoning" onto "The Awakening".
var requested = CreateAudioFile("Shared Author", "The Reckoning", "The Reckoning.m4b");
_ = CreateAudioFile("Shared Author", "The Awakening", "The Awakening.m4b");
var audiobook = new AudiobookBuilder()
.WithTitle("The Reckoning")
.WithAuthor("Shared Author")
.Build();

var result = Discover(audiobook);

var found = Assert.Single(result);
Assert.Equal(requested, found);
}

private List<string> Discover(Audiobook audiobook) =>
DiscoverResult(audiobook).AttributedFiles.ToList();

Expand Down