lyrics-structure 1.2.2 → 1.2.3

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/lyrics.test.ts CHANGED
@@ -1,76 +1,76 @@
1
- import { getLyricsParts } from './lyrics';
2
-
3
- const testLyrics = `[partname 1] (indication 1)
4
-
5
- content 1
6
-
7
- [/partname 1]
8
-
9
- [partname 1] (indication 2)
10
-
11
- [partname 2]
12
-
13
- content 2
14
-
15
- [/partname 2]
16
-
17
- [interlude 1]
18
-
19
- [partname 3]
20
-
21
- content without partname container
22
-
23
- content standalone 1
24
- content standalone 2
25
- `;
26
-
27
- describe('getLyricsParts', () => {
28
- it('should correctly parse lyrics into parts', () => {
29
- const result = getLyricsParts(testLyrics);
30
-
31
- expect(result).toEqual([
32
- {
33
- name: 'partname 1',
34
- repetition: false,
35
- indication: 'indication 1',
36
- content: 'content 1',
37
- },
38
- {
39
- name: 'partname 1',
40
- repetition: true,
41
- indication: 'indication 2',
42
- content: 'content 1',
43
- },
44
- {
45
- name: 'partname 2',
46
- repetition: false,
47
- indication: null,
48
- content: 'content 2',
49
- },
50
- {
51
- name: 'interlude 1',
52
- repetition: false,
53
- indication: null,
54
- content: undefined,
55
- },
56
- {
57
- name: 'partname 3',
58
- repetition: false,
59
- indication: null,
60
- content: undefined,
61
- },
62
- {
63
- name: undefined,
64
- repetition: false,
65
- indication: null,
66
- content: 'content without partname container',
67
- },
68
- {
69
- name: undefined,
70
- repetition: false,
71
- indication: null,
72
- content: 'content standalone 1\ncontent standalone 2',
73
- },
74
- ]);
75
- });
76
- });
1
+ import { getLyricsParts } from './lyrics';
2
+
3
+ const testLyrics = `[partname 1] (indication 1)
4
+
5
+ content 1
6
+
7
+ [/partname 1]
8
+
9
+ [partname 1] (indication 2)
10
+
11
+ [partname 2]
12
+
13
+ content 2
14
+
15
+ [/partname 2]
16
+
17
+ [interlude 1]
18
+
19
+ [partname 3]
20
+
21
+ content without partname container
22
+
23
+ content standalone 1
24
+ content standalone 2
25
+ `;
26
+
27
+ describe('getLyricsParts', () => {
28
+ it('should correctly parse lyrics into parts', () => {
29
+ const result = getLyricsParts(testLyrics);
30
+
31
+ expect(result).toEqual([
32
+ {
33
+ name: 'partname 1',
34
+ repetition: false,
35
+ indication: 'indication 1',
36
+ content: 'content 1',
37
+ },
38
+ {
39
+ name: 'partname 1',
40
+ repetition: true,
41
+ indication: 'indication 2',
42
+ content: 'content 1',
43
+ },
44
+ {
45
+ name: 'partname 2',
46
+ repetition: false,
47
+ indication: null,
48
+ content: 'content 2',
49
+ },
50
+ {
51
+ name: 'interlude 1',
52
+ repetition: false,
53
+ indication: null,
54
+ content: undefined,
55
+ },
56
+ {
57
+ name: 'partname 3',
58
+ repetition: false,
59
+ indication: null,
60
+ content: undefined,
61
+ },
62
+ {
63
+ name: undefined,
64
+ repetition: false,
65
+ indication: null,
66
+ content: 'content without partname container',
67
+ },
68
+ {
69
+ name: undefined,
70
+ repetition: false,
71
+ indication: null,
72
+ content: 'content standalone 1\ncontent standalone 2',
73
+ },
74
+ ]);
75
+ });
76
+ });
package/lyrics.ts CHANGED
@@ -1,107 +1,107 @@
1
- export type LyricPart = {
2
- name: string | undefined;
3
- repetition: boolean;
4
- indication: string | null;
5
- content: string | undefined;
6
- };
7
- /**
8
- * Splits lyrics into structured parts, handling named sections, repetitions, and indications.
9
- * Works with lyrics formatted using square brackets for section names and optional parentheses for indications.
10
- *
11
- * Example input:
12
- * ```
13
- * [verse 1] (first time)
14
- * Lyrics content here
15
- * [/verse 1]
16
- *
17
- * [chorus]
18
- * Chorus lyrics
19
- * [/chorus]
20
- * ```
21
- *
22
- * @param lyrics - The input lyrics text to be parsed into parts
23
- * @returns Array of LyricPart objects containing structured lyrics data
24
- */
25
-
26
- export function getLyricsParts(lyrics: string): LyricPart[] {
27
- const parts: LyricPart[] = [];
28
- const seenParts = new Map<string, number>();
29
- const partContentMap = new Map<string, string>();
30
-
31
- // First pass: extract all content with closing tags
32
- const cleanedText = lyrics.replace(
33
- /\[([^\]]+)\](?:\s*\(([^)]+)\))?\s*([\s\S]*?)\[\/\1\]/g,
34
- (match, key: string, indication: string | undefined, content: string) => {
35
- if (!partContentMap.has(key)) {
36
- partContentMap.set(key, content.trim());
37
- }
38
- return `[${key}]${indication ? ` (${indication})` : ''}`;
39
- }
40
- );
41
-
42
- // Split the lyrics into lines and process them
43
- const lines = cleanedText.split('\n');
44
- let currentPart: LyricPart | null = null;
45
- let currentUnnamedContent: string[] = [];
46
-
47
- for (let i = 0; i < lines.length; i++) {
48
- const line = lines[i].trim();
49
-
50
- // Check for part start
51
- const partStartMatch = line.match(/^\[([^\]]+)\](?:\s*\(([^)]+)\))?$/);
52
- if (partStartMatch) {
53
- // If we have accumulated unnamed content, add it as a part
54
- if (currentUnnamedContent.length > 0) {
55
- parts.push({
56
- name: undefined,
57
- repetition: false,
58
- indication: null,
59
- content: currentUnnamedContent.join('\n'),
60
- });
61
- currentUnnamedContent = [];
62
- }
63
-
64
- const partName = partStartMatch[1];
65
- const indication = partStartMatch[2] || null;
66
-
67
- // Check if this part has been seen before
68
- const partCount = (seenParts.get(partName) || 0) + 1;
69
- seenParts.set(partName, partCount);
70
-
71
- currentPart = {
72
- name: partName,
73
- repetition: partCount > 1,
74
- indication,
75
- content: partContentMap.get(partName),
76
- };
77
- parts.push(currentPart);
78
- continue;
79
- }
80
-
81
- // Handle content without a part container
82
- if (line && !line.startsWith('[') && !line.startsWith('[/')) {
83
- currentUnnamedContent.push(line);
84
- } else if (currentUnnamedContent.length > 0) {
85
- // If we hit a part marker or empty line, add the accumulated content
86
- parts.push({
87
- name: undefined,
88
- repetition: false,
89
- indication: null,
90
- content: currentUnnamedContent.join('\n'),
91
- });
92
- currentUnnamedContent = [];
93
- }
94
- }
95
-
96
- // Add any remaining unnamed content
97
- if (currentUnnamedContent.length > 0) {
98
- parts.push({
99
- name: undefined,
100
- repetition: false,
101
- indication: null,
102
- content: currentUnnamedContent.join('\n'),
103
- });
104
- }
105
-
106
- return parts;
107
- }
1
+ export type LyricPart = {
2
+ name: string | undefined;
3
+ repetition: boolean;
4
+ indication: string | null;
5
+ content: string | undefined;
6
+ };
7
+ /**
8
+ * Splits lyrics into structured parts, handling named sections, repetitions, and indications.
9
+ * Works with lyrics formatted using square brackets for section names and optional parentheses for indications.
10
+ *
11
+ * Example input:
12
+ * ```
13
+ * [verse 1] (first time)
14
+ * Lyrics content here
15
+ * [/verse 1]
16
+ *
17
+ * [chorus]
18
+ * Chorus lyrics
19
+ * [/chorus]
20
+ * ```
21
+ *
22
+ * @param lyrics - The input lyrics text to be parsed into parts
23
+ * @returns Array of LyricPart objects containing structured lyrics data
24
+ */
25
+
26
+ export function getLyricsParts(lyrics: string): LyricPart[] {
27
+ const parts: LyricPart[] = [];
28
+ const seenParts = new Map<string, number>();
29
+ const partContentMap = new Map<string, string>();
30
+
31
+ // First pass: extract all content with closing tags
32
+ const cleanedText = lyrics.replace(
33
+ /\[([^\]]+)\](?:\s*\(([^)]+)\))?\s*([\s\S]*?)\[\/\1\]/g,
34
+ (match, key: string, indication: string | undefined, content: string) => {
35
+ if (!partContentMap.has(key)) {
36
+ partContentMap.set(key, content.trim());
37
+ }
38
+ return `[${key}]${indication ? ` (${indication})` : ''}`;
39
+ }
40
+ );
41
+
42
+ // Split the lyrics into lines and process them
43
+ const lines = cleanedText.split('\n');
44
+ let currentPart: LyricPart | null = null;
45
+ let currentUnnamedContent: string[] = [];
46
+
47
+ for (let i = 0; i < lines.length; i++) {
48
+ const line = lines[i].trim();
49
+
50
+ // Check for part start
51
+ const partStartMatch = line.match(/^\[([^\]]+)\](?:\s*\(([^)]+)\))?$/);
52
+ if (partStartMatch) {
53
+ // If we have accumulated unnamed content, add it as a part
54
+ if (currentUnnamedContent.length > 0) {
55
+ parts.push({
56
+ name: undefined,
57
+ repetition: false,
58
+ indication: null,
59
+ content: currentUnnamedContent.join('\n'),
60
+ });
61
+ currentUnnamedContent = [];
62
+ }
63
+
64
+ const partName = partStartMatch[1];
65
+ const indication = partStartMatch[2] || null;
66
+
67
+ // Check if this part has been seen before
68
+ const partCount = (seenParts.get(partName) || 0) + 1;
69
+ seenParts.set(partName, partCount);
70
+
71
+ currentPart = {
72
+ name: partName,
73
+ repetition: partCount > 1,
74
+ indication,
75
+ content: partContentMap.get(partName),
76
+ };
77
+ parts.push(currentPart);
78
+ continue;
79
+ }
80
+
81
+ // Handle content without a part container
82
+ if (line && !line.startsWith('[') && !line.startsWith('[/')) {
83
+ currentUnnamedContent.push(line);
84
+ } else if (currentUnnamedContent.length > 0) {
85
+ // If we hit a part marker or empty line, add the accumulated content
86
+ parts.push({
87
+ name: undefined,
88
+ repetition: false,
89
+ indication: null,
90
+ content: currentUnnamedContent.join('\n'),
91
+ });
92
+ currentUnnamedContent = [];
93
+ }
94
+ }
95
+
96
+ // Add any remaining unnamed content
97
+ if (currentUnnamedContent.length > 0) {
98
+ parts.push({
99
+ name: undefined,
100
+ repetition: false,
101
+ indication: null,
102
+ content: currentUnnamedContent.join('\n'),
103
+ });
104
+ }
105
+
106
+ return parts;
107
+ }
package/package.json CHANGED
@@ -1,28 +1,28 @@
1
- {
2
- "name": "lyrics-structure",
3
- "version": "1.2.2",
4
- "description": "Parser for lyrics with structured sections, names, and indications",
5
- "main": "dist/index.js",
6
- "types": "dist/index.d.ts",
7
- "scripts": {
8
- "build": "tsc",
9
- "test": "jest",
10
- "test:watch": "jest --watch",
11
- "format": "prettier --write \"**/*.{ts,js,json,md}\""
12
- },
13
- "keywords": [
14
- "lyrics",
15
- "parser",
16
- "slides",
17
- "sections"
18
- ],
19
- "author": "",
20
- "license": "MIT",
21
- "devDependencies": {
22
- "@types/jest": "^29.0.0",
23
- "jest": "^29.0.0",
24
- "prettier": "^3.5.3",
25
- "ts-jest": "^29.0.0",
26
- "typescript": "^5.0.0"
27
- }
28
- }
1
+ {
2
+ "name": "lyrics-structure",
3
+ "version": "1.2.3",
4
+ "description": "Parser for lyrics with structured sections, names, and indications",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "test": "jest",
10
+ "test:watch": "jest --watch",
11
+ "format": "prettier --write \"**/*.{ts,js,json,md}\""
12
+ },
13
+ "keywords": [
14
+ "lyrics",
15
+ "parser",
16
+ "slides",
17
+ "sections"
18
+ ],
19
+ "author": "",
20
+ "license": "MIT",
21
+ "devDependencies": {
22
+ "@types/jest": "^29.0.0",
23
+ "jest": "^29.0.0",
24
+ "prettier": "^3.5.3",
25
+ "ts-jest": "^29.0.0",
26
+ "typescript": "^5.0.0"
27
+ }
28
+ }
package/slide.test.ts CHANGED
@@ -1,67 +1,67 @@
1
- import { getSlideParts } from './slide.js';
2
-
3
- describe('Comprehensive Tests', () => {
4
- test('handles all cases in one comprehensive input', () => {
5
- const comprehensiveInput = `
6
- [verse]
7
- First line of verse
8
- Second line of verse
9
- Third line of verse
10
- Fourth line of verse
11
- Fifth line of verse
12
- [/verse]
13
- [verse]
14
- (inside parentheses)
15
- Regular text line 1
16
- Regular text line 2
17
-
18
- [chorus]
19
- First chorus line
20
- Second chorus line
21
- [/chorus]
22
-
23
- [verse 1]
24
- This is a very long line that should be considered too long for the slide
25
- This is another very long line that should also be considered too long
26
- Short line 1
27
- Short line 2
28
- [/verse 1]
29
-
30
- Regular line 1
31
- Regular line 2
32
-
33
- More content
34
-
35
- This is a very long line that exceeds forty characters for testing purposes
36
- This is another very long line that also exceeds the forty character limit
37
- Another very long line that should be considered too long for the slide
38
- And yet another very long line that should be split into a new section
39
- Regular line
40
-
41
- [verse 2]
42
- First line with spaces
43
- Second line with spaces
44
- [/verse 2]
45
-
46
- [chorus]`;
47
-
48
- expect(getSlideParts(comprehensiveInput)).toEqual([
49
- 'First line of verse\nSecond line of verse\nThird line of verse\nFourth line of verse',
50
- 'Fifth line of verse',
51
- 'First line of verse\nSecond line of verse\nThird line of verse\nFourth line of verse',
52
- 'Fifth line of verse',
53
- 'Regular text line 1\nRegular text line 2',
54
- 'First chorus line\nSecond chorus line',
55
- 'This is a very long line that should be considered too long for the slide\nThis is another very long line that should also be considered too long',
56
- 'Short line 1\nShort line 2',
57
- 'Regular line 1\nRegular line 2',
58
- 'More content',
59
- 'This is a very long line that exceeds forty characters for testing purposes\nThis is another very long line that also exceeds the forty character limit',
60
- 'Another very long line that should be considered too long for the slide\nAnd yet another very long line that should be split into a new section',
61
- 'Regular line',
62
- 'First line with spaces\nSecond line with spaces',
63
- 'First chorus line\nSecond chorus line'
64
- ]);
65
- });
66
-
1
+ import { getSlideParts } from './slide.js';
2
+
3
+ describe('Comprehensive Tests', () => {
4
+ test('handles all cases in one comprehensive input', () => {
5
+ const comprehensiveInput = `
6
+ [verse]
7
+ First line of verse
8
+ Second line of verse
9
+ Third line of verse
10
+ Fourth line of verse
11
+ Fifth line of verse
12
+ [/verse]
13
+ [verse]
14
+ (inside parentheses)
15
+ Regular text line 1
16
+ Regular text line 2
17
+
18
+ [chorus]
19
+ First chorus line
20
+ Second chorus line
21
+ [/chorus]
22
+
23
+ [verse 1]
24
+ This is a very long line that should be considered too long for the slide
25
+ This is another very long line that should also be considered too long
26
+ Short line 1
27
+ Short line 2
28
+ [/verse 1]
29
+
30
+ Regular line 1
31
+ Regular line 2
32
+
33
+ More content
34
+
35
+ This is a very long line that exceeds forty characters for testing purposes
36
+ This is another very long line that also exceeds the forty character limit
37
+ Another very long line that should be considered too long for the slide
38
+ And yet another very long line that should be split into a new section
39
+ Regular line
40
+
41
+ [verse 2]
42
+ First line with spaces
43
+ Second line with spaces
44
+ [/verse 2]
45
+
46
+ [chorus]`;
47
+
48
+ expect(getSlideParts(comprehensiveInput)).toEqual([
49
+ 'First line of verse\nSecond line of verse\nThird line of verse\nFourth line of verse',
50
+ 'Fifth line of verse',
51
+ 'First line of verse\nSecond line of verse\nThird line of verse\nFourth line of verse',
52
+ 'Fifth line of verse',
53
+ 'Regular text line 1\nRegular text line 2',
54
+ 'First chorus line\nSecond chorus line',
55
+ 'This is a very long line that should be considered too long for the slide\nThis is another very long line that should also be considered too long',
56
+ 'Short line 1\nShort line 2',
57
+ 'Regular line 1\nRegular line 2',
58
+ 'More content',
59
+ 'This is a very long line that exceeds forty characters for testing purposes\nThis is another very long line that also exceeds the forty character limit',
60
+ 'Another very long line that should be considered too long for the slide\nAnd yet another very long line that should be split into a new section',
61
+ 'Regular line',
62
+ 'First line with spaces\nSecond line with spaces',
63
+ 'First chorus line\nSecond chorus line'
64
+ ]);
65
+ });
66
+
67
67
  });