trackparse 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  // src/data.generated.ts
4
- var SPEC_VERSION = "0.1.0";
4
+ var SPEC_VERSION = "0.2.0";
5
5
  var descriptorsData = {
6
6
  "descriptors": [
7
7
  "radio",
@@ -469,11 +469,11 @@ function isAllDigits(s) {
469
469
  for (let i = 0; i < s.length; i++) if (!isDigit(s[i])) return false;
470
470
  return true;
471
471
  }
472
- var ALPHABETIC = /^\p{Alphabetic}$/u;
472
+ var LETTER = /^\p{L}$/u;
473
473
  function isLetterAt(s, index) {
474
474
  const cp = s.codePointAt(index);
475
475
  if (cp === void 0) return false;
476
- return ALPHABETIC.test(String.fromCodePoint(cp));
476
+ return LETTER.test(String.fromCodePoint(cp));
477
477
  }
478
478
  function asciiLower(s) {
479
479
  let out = "";
@@ -705,8 +705,15 @@ function byPos(items) {
705
705
  }
706
706
 
707
707
  // src/tables.ts
708
+ function stringList(value) {
709
+ return Array.isArray(value) ? value.filter((s) => typeof s === "string") : [];
710
+ }
711
+ function stringEntries(value) {
712
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return [];
713
+ return Object.entries(value).filter((e) => typeof e[1] === "string");
714
+ }
708
715
  function lowerKeys(list) {
709
- return (list ?? []).map((s) => asciiLower(s).trim()).filter((s) => s.length > 0);
716
+ return stringList(list).map((s) => asciiLower(s).trim()).filter((s) => s.length > 0);
710
717
  }
711
718
  function buildTables(keywords) {
712
719
  const featMarkers = new PhraseTable(
@@ -722,7 +729,7 @@ function buildTables(keywords) {
722
729
  );
723
730
  const junkEntries = [
724
731
  ...Object.entries(junkData.phrases),
725
- ...Object.entries(keywords?.junk ?? {})
732
+ ...stringEntries(keywords?.junk)
726
733
  ];
727
734
  const genres = [...genresData.genres, ...lowerKeys(keywords?.genres)];
728
735
  const junkPhrases = new PhraseTable(junkEntries);
@@ -732,7 +739,7 @@ function buildTables(keywords) {
732
739
  ]);
733
740
  const headEntries = [
734
741
  ...Object.entries(versionKeywordsData.heads),
735
- ...Object.entries(keywords?.versionHeads ?? {})
742
+ ...stringEntries(keywords?.versionHeads)
736
743
  ];
737
744
  const heads = new PhraseTable(headEntries);
738
745
  const runInfo = /* @__PURE__ */ new Map();
@@ -966,7 +973,7 @@ function normalize(input) {
966
973
 
967
974
  // src/context.ts
968
975
  function makeCtx(t, options) {
969
- const known = (options?.knownArtists ?? []).filter((k) => typeof k === "string");
976
+ const known = stringList(options?.knownArtists);
970
977
  const knownKeys = new Set(known.map(dedupKey).filter((k) => k.length > 0));
971
978
  const knownMaxLen = known.reduce((m, k) => Math.max(m, k.length), 0);
972
979
  const splitAnd = options?.splitAnd ?? "auto";
@@ -2088,21 +2095,28 @@ function extractUnbracketedCredits(input, out, ctx) {
2088
2095
  return skel;
2089
2096
  }
2090
2097
  function stripTrailingJunk(input, out, ctx) {
2091
- let skel = trimSkel(input);
2098
+ const skel = trimSkel(input);
2099
+ const spans = wordSpans(skel.text);
2100
+ const words = spans.map((s) => s.text);
2101
+ let end = spans.length;
2092
2102
  for (; ; ) {
2093
- const hit = trailingJunkPhrase(skel.text, ctx);
2094
- if (!hit) return skel;
2095
- let before = trimSkel(sliceSkel(skel, 0, hit.start));
2096
- const lastChar = before.text[before.text.length - 1];
2097
- const lone = before.text.length === 1 || before.text[before.text.length - 2] === " ";
2098
- if ((lastChar === "-" || lastChar === "|") && lone) {
2099
- before = trimSkel(sliceSkel(before, 0, before.text.length - 1));
2100
- }
2101
- if (before.text.length === 0) return skel;
2102
- const raw = collapseSpaces(skel.text.slice(hit.start));
2103
- out.junk.push({ raw, kind: hit.kind, pos: skel.pos[hit.start] ?? 0 });
2104
- skel = before;
2103
+ const m = ctx.t.junkPhrases.matchEnding(words, end);
2104
+ if (!m) break;
2105
+ const first = end - m.length;
2106
+ let keep = first;
2107
+ if (keep > 0 && isLoneEdge(words[keep - 1])) keep--;
2108
+ if (keep === 0) break;
2109
+ const from = spans[first]?.start ?? 0;
2110
+ const to = spans[end - 1]?.end ?? skel.text.length;
2111
+ out.junk.push({
2112
+ raw: collapseSpaces(skel.text.slice(from, to)),
2113
+ kind: m.entry.value,
2114
+ pos: skel.pos[from] ?? 0
2115
+ });
2116
+ end = keep;
2105
2117
  }
2118
+ if (end === spans.length) return skel;
2119
+ return trimSkel(sliceSkel(skel, 0, spans[end - 1]?.end ?? 0));
2106
2120
  }
2107
2121
  function isLoneEdge(word) {
2108
2122
  return word === "-" || word === "|";
@@ -2242,16 +2256,21 @@ function createParser(base = {}) {
2242
2256
  };
2243
2257
  }
2244
2258
  function mergeKeywords(a, b) {
2245
- const x = a.keywords ?? {};
2246
- const y = b.keywords ?? {};
2259
+ const x = isRecord(a.keywords) ? a.keywords : {};
2260
+ const y = isRecord(b.keywords) ? b.keywords : {};
2261
+ const map = (k) => Object.fromEntries([...stringEntries(x[k]), ...stringEntries(y[k])]);
2262
+ const list = (k) => [...stringList(x[k]), ...stringList(y[k])];
2247
2263
  return {
2248
- versionHeads: { ...x.versionHeads, ...y.versionHeads },
2249
- descriptors: [...x.descriptors ?? [], ...y.descriptors ?? []],
2250
- genres: [...x.genres ?? [], ...y.genres ?? []],
2251
- junk: { ...x.junk, ...y.junk },
2252
- featMarkers: [...x.featMarkers ?? [], ...y.featMarkers ?? []]
2264
+ versionHeads: map("versionHeads"),
2265
+ descriptors: list("descriptors"),
2266
+ genres: list("genres"),
2267
+ junk: map("junk"),
2268
+ featMarkers: list("featMarkers")
2253
2269
  };
2254
2270
  }
2271
+ function isRecord(v) {
2272
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2273
+ }
2255
2274
  var defaultParser = createParser();
2256
2275
  function parse(input, options) {
2257
2276
  return defaultParser.parse(input, options);
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- declare const SPEC_VERSION = "0.1.0";
1
+ declare const SPEC_VERSION = "0.2.0";
2
2
 
3
3
  /** Public types. They mirror spec/schema/parsed-track.schema.json and options.schema.json. */
4
4
  type Mode = "clean" | "youtube" | "filename";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- declare const SPEC_VERSION = "0.1.0";
1
+ declare const SPEC_VERSION = "0.2.0";
2
2
 
3
3
  /** Public types. They mirror spec/schema/parsed-track.schema.json and options.schema.json. */
4
4
  type Mode = "clean" | "youtube" | "filename";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/data.generated.ts
2
- var SPEC_VERSION = "0.1.0";
2
+ var SPEC_VERSION = "0.2.0";
3
3
  var descriptorsData = {
4
4
  "descriptors": [
5
5
  "radio",
@@ -467,11 +467,11 @@ function isAllDigits(s) {
467
467
  for (let i = 0; i < s.length; i++) if (!isDigit(s[i])) return false;
468
468
  return true;
469
469
  }
470
- var ALPHABETIC = /^\p{Alphabetic}$/u;
470
+ var LETTER = /^\p{L}$/u;
471
471
  function isLetterAt(s, index) {
472
472
  const cp = s.codePointAt(index);
473
473
  if (cp === void 0) return false;
474
- return ALPHABETIC.test(String.fromCodePoint(cp));
474
+ return LETTER.test(String.fromCodePoint(cp));
475
475
  }
476
476
  function asciiLower(s) {
477
477
  let out = "";
@@ -703,8 +703,15 @@ function byPos(items) {
703
703
  }
704
704
 
705
705
  // src/tables.ts
706
+ function stringList(value) {
707
+ return Array.isArray(value) ? value.filter((s) => typeof s === "string") : [];
708
+ }
709
+ function stringEntries(value) {
710
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return [];
711
+ return Object.entries(value).filter((e) => typeof e[1] === "string");
712
+ }
706
713
  function lowerKeys(list) {
707
- return (list ?? []).map((s) => asciiLower(s).trim()).filter((s) => s.length > 0);
714
+ return stringList(list).map((s) => asciiLower(s).trim()).filter((s) => s.length > 0);
708
715
  }
709
716
  function buildTables(keywords) {
710
717
  const featMarkers = new PhraseTable(
@@ -720,7 +727,7 @@ function buildTables(keywords) {
720
727
  );
721
728
  const junkEntries = [
722
729
  ...Object.entries(junkData.phrases),
723
- ...Object.entries(keywords?.junk ?? {})
730
+ ...stringEntries(keywords?.junk)
724
731
  ];
725
732
  const genres = [...genresData.genres, ...lowerKeys(keywords?.genres)];
726
733
  const junkPhrases = new PhraseTable(junkEntries);
@@ -730,7 +737,7 @@ function buildTables(keywords) {
730
737
  ]);
731
738
  const headEntries = [
732
739
  ...Object.entries(versionKeywordsData.heads),
733
- ...Object.entries(keywords?.versionHeads ?? {})
740
+ ...stringEntries(keywords?.versionHeads)
734
741
  ];
735
742
  const heads = new PhraseTable(headEntries);
736
743
  const runInfo = /* @__PURE__ */ new Map();
@@ -964,7 +971,7 @@ function normalize(input) {
964
971
 
965
972
  // src/context.ts
966
973
  function makeCtx(t, options) {
967
- const known = (options?.knownArtists ?? []).filter((k) => typeof k === "string");
974
+ const known = stringList(options?.knownArtists);
968
975
  const knownKeys = new Set(known.map(dedupKey).filter((k) => k.length > 0));
969
976
  const knownMaxLen = known.reduce((m, k) => Math.max(m, k.length), 0);
970
977
  const splitAnd = options?.splitAnd ?? "auto";
@@ -2086,21 +2093,28 @@ function extractUnbracketedCredits(input, out, ctx) {
2086
2093
  return skel;
2087
2094
  }
2088
2095
  function stripTrailingJunk(input, out, ctx) {
2089
- let skel = trimSkel(input);
2096
+ const skel = trimSkel(input);
2097
+ const spans = wordSpans(skel.text);
2098
+ const words = spans.map((s) => s.text);
2099
+ let end = spans.length;
2090
2100
  for (; ; ) {
2091
- const hit = trailingJunkPhrase(skel.text, ctx);
2092
- if (!hit) return skel;
2093
- let before = trimSkel(sliceSkel(skel, 0, hit.start));
2094
- const lastChar = before.text[before.text.length - 1];
2095
- const lone = before.text.length === 1 || before.text[before.text.length - 2] === " ";
2096
- if ((lastChar === "-" || lastChar === "|") && lone) {
2097
- before = trimSkel(sliceSkel(before, 0, before.text.length - 1));
2098
- }
2099
- if (before.text.length === 0) return skel;
2100
- const raw = collapseSpaces(skel.text.slice(hit.start));
2101
- out.junk.push({ raw, kind: hit.kind, pos: skel.pos[hit.start] ?? 0 });
2102
- skel = before;
2101
+ const m = ctx.t.junkPhrases.matchEnding(words, end);
2102
+ if (!m) break;
2103
+ const first = end - m.length;
2104
+ let keep = first;
2105
+ if (keep > 0 && isLoneEdge(words[keep - 1])) keep--;
2106
+ if (keep === 0) break;
2107
+ const from = spans[first]?.start ?? 0;
2108
+ const to = spans[end - 1]?.end ?? skel.text.length;
2109
+ out.junk.push({
2110
+ raw: collapseSpaces(skel.text.slice(from, to)),
2111
+ kind: m.entry.value,
2112
+ pos: skel.pos[from] ?? 0
2113
+ });
2114
+ end = keep;
2103
2115
  }
2116
+ if (end === spans.length) return skel;
2117
+ return trimSkel(sliceSkel(skel, 0, spans[end - 1]?.end ?? 0));
2104
2118
  }
2105
2119
  function isLoneEdge(word) {
2106
2120
  return word === "-" || word === "|";
@@ -2240,16 +2254,21 @@ function createParser(base = {}) {
2240
2254
  };
2241
2255
  }
2242
2256
  function mergeKeywords(a, b) {
2243
- const x = a.keywords ?? {};
2244
- const y = b.keywords ?? {};
2257
+ const x = isRecord(a.keywords) ? a.keywords : {};
2258
+ const y = isRecord(b.keywords) ? b.keywords : {};
2259
+ const map = (k) => Object.fromEntries([...stringEntries(x[k]), ...stringEntries(y[k])]);
2260
+ const list = (k) => [...stringList(x[k]), ...stringList(y[k])];
2245
2261
  return {
2246
- versionHeads: { ...x.versionHeads, ...y.versionHeads },
2247
- descriptors: [...x.descriptors ?? [], ...y.descriptors ?? []],
2248
- genres: [...x.genres ?? [], ...y.genres ?? []],
2249
- junk: { ...x.junk, ...y.junk },
2250
- featMarkers: [...x.featMarkers ?? [], ...y.featMarkers ?? []]
2262
+ versionHeads: map("versionHeads"),
2263
+ descriptors: list("descriptors"),
2264
+ genres: list("genres"),
2265
+ junk: map("junk"),
2266
+ featMarkers: list("featMarkers")
2251
2267
  };
2252
2268
  }
2269
+ function isRecord(v) {
2270
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2271
+ }
2253
2272
  var defaultParser = createParser();
2254
2273
  function parse(input, options) {
2255
2274
  return defaultParser.parse(input, options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackparse",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Spec-driven parser for music track strings: artists, feat credits, remixes/versions, junk and more from single track strings (store titles, YouTube titles, filenames).",
5
5
  "license": "MIT",
6
6
  "author": "Maksim Maksimov",