mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-14 05:06:16 -08:00
fix(portals): a failed pin read must not export as "no pins"
Three findings, all in code from this session. Backup called the lenient `listForPlaylist`, which turns a failed read into `[]`. Since `e0ebbeaf` made restore treat `sourcePins` as authoritative — clearing the playlist's pins before applying it — an export whose read failed produced a file that looks complete and wipes every pin on restore. Losing them is bad; losing them through the one feature meant to protect them is worse. Backup now uses a strict listing that throws, so the export fails instead. The diacritic map stopped at U+024F, which is tidy and leaves Vietnamese out: `ố` is U+1ED1, the normalizer folds it to `o`, and the scan filtered those rows out before confirmation. Latin Extended Additional is included now; the filter decides what belongs, so the range only has to be wide. And two panels spelling one language differently (`eng` vs `en`, or `en-US`) raised a dub warning between identical tracks. Tags are canonicalized before comparison — 639-2 collapses to 639-1, both German forms meet at `de`, regions drop, and `und` becomes nothing. Anything that survives longer than three characters is not a language code, so the comparison is declined rather than guessed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
960a33940f
commit
76804ee1bc
10 files changed
+267
-38
No files matched your search
@@ -135,6 +135,19 @@ describeWithSqlite('caseInsensitiveGlobPattern against SQLite', () => {
|
||||
expect(sqliteGlob('Ca', pattern)).toBe(true);
|
||||
});
|
||||
|
||||
it('reaches beyond Latin Extended-B for the fold', () => {
|
||||
// "Bố" (U+1ED1) is Vietnamese and normalizes to "bo" like any other
|
||||
// accented o. A range that stops at Latin Extended-B looks tidy and
|
||||
// leaves a whole language filtered out before confirmation.
|
||||
const pattern = caseInsensitiveGlobPattern('bo', {
|
||||
foldDiacritics: true,
|
||||
}) as string;
|
||||
|
||||
expect(sqliteGlob('Bố', pattern)).toBe(true);
|
||||
expect(sqliteGlob('bồ', pattern)).toBe(true);
|
||||
expect(sqliteGlob('Bo', pattern)).toBe(true);
|
||||
});
|
||||
|
||||
it('does not fold diacritics unless asked', () => {
|
||||
const pattern = caseInsensitiveGlobPattern('ca') as string;
|
||||
|
||||
|
||||
@@ -25,23 +25,39 @@ const GLOB_METACHARACTERS = /[*?[\]^-]/;
|
||||
*/
|
||||
const ACCENTED_BY_BASE = ((): Map<string, string[]> => {
|
||||
const byBase = new Map<string, string[]>();
|
||||
// Latin-1 Supplement through Latin Extended-B, which is where the accented
|
||||
// forms of ASCII letters live.
|
||||
for (let codePoint = 0xc0; codePoint <= 0x24f; codePoint += 1) {
|
||||
const character = String.fromCodePoint(codePoint);
|
||||
const base = character.normalize('NFD').replace(/\p{M}/gu, '');
|
||||
if (base.length !== 1 || !/[a-z]/i.test(base)) {
|
||||
continue;
|
||||
// Latin-1 Supplement through Latin Extended-B, PLUS Latin Extended
|
||||
// Additional — which is where Vietnamese lives (ố is U+1ED1). Stopping at
|
||||
// Latin Extended-B looks tidy and silently leaves a whole language out,
|
||||
// while the normalizer folds it perfectly well. The filter below decides
|
||||
// what actually belongs, so a range only has to be wide enough.
|
||||
const ranges: ReadonlyArray<[number, number]> = [
|
||||
[0x00c0, 0x024f],
|
||||
[0x1e00, 0x1eff],
|
||||
];
|
||||
for (const [first, last] of ranges) {
|
||||
for (let codePoint = first; codePoint <= last; codePoint += 1) {
|
||||
addAccentedForm(byBase, String.fromCodePoint(codePoint));
|
||||
}
|
||||
|
||||
const key = base.toLowerCase();
|
||||
const forms = byBase.get(key) ?? [];
|
||||
forms.push(character);
|
||||
byBase.set(key, forms);
|
||||
}
|
||||
return byBase;
|
||||
})();
|
||||
|
||||
/** Files one character under the plain ASCII letter it decomposes to. */
|
||||
function addAccentedForm(
|
||||
byBase: Map<string, string[]>,
|
||||
character: string
|
||||
): void {
|
||||
const base = character.normalize('NFD').replace(/\p{M}/gu, '');
|
||||
if (base.length !== 1 || !/[a-z]/i.test(base)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = base.toLowerCase();
|
||||
const forms = byBase.get(key) ?? [];
|
||||
forms.push(character);
|
||||
byBase.set(key, forms);
|
||||
}
|
||||
|
||||
/**
|
||||
* A `*…*` containment pattern matching `token` in any case, or `null` when the
|
||||
* token cannot be expressed safely.
|
||||
|
||||
Reference in new issue
Block a user