lyrics-structure 1.0.6 → 1.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/CHANGELOG.md +43 -0
- package/README.md +54 -84
- package/index.ts +2 -175
- package/jest.config.js +14 -0
- package/lyrics.test.ts +66 -0
- package/lyrics.ts +81 -0
- package/package.json +16 -10
- package/slide.test.ts +67 -0
- package/slide.ts +83 -0
- package/tsconfig.json +113 -113
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -129
- package/dist/test.d.ts +0 -1
- package/dist/test.js +0 -155
- package/test.js +0 -209
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.2.0] - 2024-03-XX
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Simplified the lyrics format to use clear section markers
|
|
7
|
+
- Updated documentation with clearer examples
|
|
8
|
+
- Improved type definitions for better TypeScript support
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Support for section indications in parentheses
|
|
12
|
+
- Better handling of repeated sections
|
|
13
|
+
- More intuitive content structure
|
|
14
|
+
|
|
15
|
+
### Removed
|
|
16
|
+
- Slide-based content splitting
|
|
17
|
+
- Special command tags
|
|
18
|
+
- Complex formatting options
|
|
19
|
+
|
|
20
|
+
## [1.1.0] - 2024-03-XX
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- Initial release
|
|
24
|
+
- `getParts` function for basic content extraction
|
|
25
|
+
- `getSlideParts` function for slide-based content splitting
|
|
26
|
+
- Support for special command tags
|
|
27
|
+
- Comprehensive test suite
|
|
28
|
+
- TypeScript types and documentation
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
- Bracketed section parsing
|
|
32
|
+
- Special command tag handling
|
|
33
|
+
- Slide splitting based on content length
|
|
34
|
+
- Empty line separation
|
|
35
|
+
- Long line handling (>40 characters)
|
|
36
|
+
- Whitespace preservation
|
|
37
|
+
- Unicode support in tags
|
|
38
|
+
|
|
39
|
+
### Test Coverage
|
|
40
|
+
- Basic functionality tests
|
|
41
|
+
- Slide splitting tests
|
|
42
|
+
- Special command tests
|
|
43
|
+
- Edge case handling
|
package/README.md
CHANGED
|
@@ -1,84 +1,54 @@
|
|
|
1
|
-
# Lyrics
|
|
2
|
-
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
[/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
I find my courage, I find my place
|
|
56
|
-
[/verse2]
|
|
57
|
-
|
|
58
|
-
[chorus]`;
|
|
59
|
-
|
|
60
|
-
// Get the individual parts (verses and chorus)
|
|
61
|
-
const parts = getParts(lyrics);
|
|
62
|
-
console.log(parts);
|
|
63
|
-
// Output will be an array with the content of verse1, chorus, verse2, and chorus again
|
|
64
|
-
// Note that [chorus] is referenced twice but only defined once
|
|
65
|
-
|
|
66
|
-
// Get slide-friendly sections
|
|
67
|
-
const slides = getSlideParts(lyrics, 4);
|
|
68
|
-
console.log(slides);
|
|
69
|
-
// Output will break the content into slide-sized chunks with at most 4 lines each
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Implementation
|
|
73
|
-
|
|
74
|
-
This library is used in the Stage app ([stage.loha.dev](https://stage.loha.dev)) for lyrics display and presentation.
|
|
75
|
-
|
|
76
|
-
## Installation
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
npm install lyrics-structure
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## License
|
|
83
|
-
|
|
84
|
-
ISC
|
|
1
|
+
# Lyrics Parser
|
|
2
|
+
|
|
3
|
+
A TypeScript library for parsing lyrics with bracketed sections and special commands. Supports splitting text into structured parts with names and indications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install lyrics-parser
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { getLyricsParts } from 'lyrics-parser';
|
|
15
|
+
|
|
16
|
+
const lyrics = `[verse 1] (first time)
|
|
17
|
+
This is the first verse content
|
|
18
|
+
[/verse 1]
|
|
19
|
+
|
|
20
|
+
[chorus]
|
|
21
|
+
This is the chorus content
|
|
22
|
+
[/chorus]
|
|
23
|
+
|
|
24
|
+
[verse 2]
|
|
25
|
+
This is the second verse content
|
|
26
|
+
[/verse 2]`;
|
|
27
|
+
|
|
28
|
+
const parts = getLyricsParts(lyrics);
|
|
29
|
+
// Result: Array of LyricPart objects with name, repetition, indication, and content
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- Extracts content from bracketed sections
|
|
35
|
+
- Handles section names and indications
|
|
36
|
+
- Supports repeated sections
|
|
37
|
+
- Preserves non-bracketed content
|
|
38
|
+
- TypeScript support
|
|
39
|
+
|
|
40
|
+
## Example Format
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
[partname] (indication)
|
|
44
|
+
content
|
|
45
|
+
[/partname]
|
|
46
|
+
|
|
47
|
+
[another part]
|
|
48
|
+
more content
|
|
49
|
+
[/another part]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT
|
package/index.ts
CHANGED
|
@@ -1,175 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
* Does not consider line length or formatting.
|
|
4
|
-
*
|
|
5
|
-
* @param text - The input text to be split into parts
|
|
6
|
-
* @returns An array of content strings
|
|
7
|
-
*/
|
|
8
|
-
export const getParts = (text?: string): string[] => {
|
|
9
|
-
if (!text) return [];
|
|
10
|
-
|
|
11
|
-
// Process parts in brackets and create a map
|
|
12
|
-
const partsMap = new Map<string, string>();
|
|
13
|
-
|
|
14
|
-
// First pass: extract all content with closing tags
|
|
15
|
-
const cleanedText = text.replace(
|
|
16
|
-
/\[(.*?)\]([\s\S]*?)\[\/\1\]/g,
|
|
17
|
-
(match, key, content) => {
|
|
18
|
-
if (!partsMap.has(key)) {
|
|
19
|
-
partsMap.set(key, content.trim());
|
|
20
|
-
}
|
|
21
|
-
return `[${key}]`;
|
|
22
|
-
},
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
// Second pass: handle validation and solo tags that should reuse content
|
|
26
|
-
const processedText = cleanedText.replace(
|
|
27
|
-
/\[([^\]]+)\](?!\s*\[\/)(?!\s*\])/g, // Match tags that don't have a closing tag after them
|
|
28
|
-
(match, key) => {
|
|
29
|
-
if (partsMap.has(key)) {
|
|
30
|
-
return match; // Keep the reference if we already have content for this key
|
|
31
|
-
} else {
|
|
32
|
-
console.warn(`Warning: Tag [${key}] has no content and was not previously defined`);
|
|
33
|
-
return ''; // Remove invalid tags
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
const result: string[] = [];
|
|
39
|
-
const parts = processedText
|
|
40
|
-
.trim()
|
|
41
|
-
.split(/\[([^\]]+)\]/)
|
|
42
|
-
.filter(Boolean);
|
|
43
|
-
|
|
44
|
-
parts.forEach((part) => {
|
|
45
|
-
if (partsMap.has(part)) {
|
|
46
|
-
// Add the stored part content
|
|
47
|
-
result.push(partsMap.get(part)!);
|
|
48
|
-
} else if (part.trim()) {
|
|
49
|
-
// Add non-bracketed content
|
|
50
|
-
result.push(part.trim());
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
return result;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Splits text into natural sections based on text structure.
|
|
59
|
-
* Works with plain text without requiring markdown or special formatting.
|
|
60
|
-
* Empty lines are treated as natural separators between parts.
|
|
61
|
-
*
|
|
62
|
-
* @param text - The input text to be split into sections
|
|
63
|
-
* @param maxLinesPerSlide - Maximum number of lines to include in a single slide (default: 6)
|
|
64
|
-
* @returns An array of content sections
|
|
65
|
-
*/
|
|
66
|
-
export const getSlideParts = (text?: string, maxLinesPerSlide: number = 6): string[] => {
|
|
67
|
-
if (!text) return [];
|
|
68
|
-
|
|
69
|
-
// Process parts in brackets and create a map for the entire text first
|
|
70
|
-
const partsMap = new Map<string, string>();
|
|
71
|
-
|
|
72
|
-
// Extract all content with closing tags
|
|
73
|
-
text.replace(
|
|
74
|
-
/\[(.*?)\]([\s\S]*?)\[\/\1\]/g,
|
|
75
|
-
(match, key, content) => {
|
|
76
|
-
if (!partsMap.has(key)) {
|
|
77
|
-
partsMap.set(key, content.trim());
|
|
78
|
-
}
|
|
79
|
-
return match; // Return original match to not modify the text yet
|
|
80
|
-
},
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
// Now split by empty lines to respect user-defined separations
|
|
84
|
-
const paragraphBlocks = text.split(/\n\s*\n/)
|
|
85
|
-
.filter(block => block.trim().length > 0)
|
|
86
|
-
.map(block => block.trim());
|
|
87
|
-
|
|
88
|
-
const result: string[] = [];
|
|
89
|
-
|
|
90
|
-
// Process each paragraph block separately
|
|
91
|
-
paragraphBlocks.forEach(block => {
|
|
92
|
-
// Check if this block is a solo tag reference
|
|
93
|
-
const soloTagMatch = block.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
94
|
-
if (soloTagMatch && partsMap.has(soloTagMatch[1])) {
|
|
95
|
-
// This is a solo tag reference, process the referenced content
|
|
96
|
-
const referencedContent = partsMap.get(soloTagMatch[1])!;
|
|
97
|
-
processContent(referencedContent, result, maxLinesPerSlide);
|
|
98
|
-
} else {
|
|
99
|
-
// Process bracketed content within this regular block
|
|
100
|
-
const blockParts = getParts(block);
|
|
101
|
-
blockParts.forEach(part => {
|
|
102
|
-
processContent(part, result, maxLinesPerSlide);
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
return result;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Helper function to process content and add it to the result array
|
|
112
|
-
*/
|
|
113
|
-
function processContent(content: string, result: string[], maxLinesPerSlide: number): void {
|
|
114
|
-
// Check if content has line breaks that should be respected
|
|
115
|
-
const lines = content.split('\n').filter(line => line.trim().length > 0);
|
|
116
|
-
|
|
117
|
-
// If we have multiple lines and more than maxLinesPerSlide, create slides based on line count
|
|
118
|
-
if (lines.length > 1) {
|
|
119
|
-
if (lines.length > maxLinesPerSlide) {
|
|
120
|
-
// Break into multiple slides based on maxLinesPerSlide
|
|
121
|
-
let currentSlide: string[] = [];
|
|
122
|
-
|
|
123
|
-
lines.forEach(line => {
|
|
124
|
-
if (currentSlide.length >= maxLinesPerSlide) {
|
|
125
|
-
result.push(currentSlide.join('\n'));
|
|
126
|
-
currentSlide = [];
|
|
127
|
-
}
|
|
128
|
-
currentSlide.push(line);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Add the final slide if it exists
|
|
132
|
-
if (currentSlide.length > 0) {
|
|
133
|
-
result.push(currentSlide.join('\n'));
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
// Just use the whole content as a single slide
|
|
137
|
-
result.push(content);
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
// For single large paragraphs (no line breaks), try to find natural sentence groups
|
|
141
|
-
const sentences = content.split(/(?<=[.!?])\s+/)
|
|
142
|
-
.filter(s => s.trim().length > 0);
|
|
143
|
-
|
|
144
|
-
// Group sentences into reasonable chunks
|
|
145
|
-
const sentenceGroups: string[] = [];
|
|
146
|
-
let currentGroup: string[] = [];
|
|
147
|
-
let currentLength = 0;
|
|
148
|
-
|
|
149
|
-
sentences.forEach(sentence => {
|
|
150
|
-
// Natural breakpoint based on content and length
|
|
151
|
-
// Group 3-5 sentences together or until we reach ~300 chars
|
|
152
|
-
if (currentGroup.length >= 4 || currentLength > 250) {
|
|
153
|
-
sentenceGroups.push(currentGroup.join(' '));
|
|
154
|
-
currentGroup = [];
|
|
155
|
-
currentLength = 0;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
currentGroup.push(sentence);
|
|
159
|
-
currentLength += sentence.length;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
// Add the last group if it exists
|
|
163
|
-
if (currentGroup.length > 0) {
|
|
164
|
-
sentenceGroups.push(currentGroup.join(' '));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// If we created multiple groups, use them
|
|
168
|
-
if (sentenceGroups.length > 1) {
|
|
169
|
-
result.push(...sentenceGroups);
|
|
170
|
-
} else {
|
|
171
|
-
// Otherwise just use the whole part
|
|
172
|
-
result.push(content);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
1
|
+
export * from './slide.js';
|
|
2
|
+
export * from './lyrics.js';
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
|
2
|
+
export default {
|
|
3
|
+
preset: 'ts-jest',
|
|
4
|
+
testEnvironment: 'node',
|
|
5
|
+
extensionsToTreatAsEsm: ['.ts'],
|
|
6
|
+
moduleNameMapper: {
|
|
7
|
+
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
8
|
+
},
|
|
9
|
+
transform: {
|
|
10
|
+
'^.+\\.tsx?$': ['ts-jest', {
|
|
11
|
+
useESM: true,
|
|
12
|
+
}],
|
|
13
|
+
},
|
|
14
|
+
};
|
package/lyrics.test.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
describe('getLyricsParts', () => {
|
|
24
|
+
it('should correctly parse lyrics into parts', () => {
|
|
25
|
+
const result = getLyricsParts(testLyrics);
|
|
26
|
+
|
|
27
|
+
expect(result).toEqual([
|
|
28
|
+
{
|
|
29
|
+
name: 'partname 1',
|
|
30
|
+
repetition: false,
|
|
31
|
+
indication: 'indication 1',
|
|
32
|
+
content: 'content 1'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'partname 1',
|
|
36
|
+
repetition: true,
|
|
37
|
+
indication: 'indication 2',
|
|
38
|
+
content: 'content 1'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'partname 2',
|
|
42
|
+
repetition: false,
|
|
43
|
+
indication: null,
|
|
44
|
+
content: 'content 2'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'interlude 1',
|
|
48
|
+
repetition: false,
|
|
49
|
+
indication: null,
|
|
50
|
+
content: undefined
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'partname 3',
|
|
54
|
+
repetition: false,
|
|
55
|
+
indication: null,
|
|
56
|
+
content: undefined
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: undefined,
|
|
60
|
+
repetition: false,
|
|
61
|
+
indication: null,
|
|
62
|
+
content: 'content without partname container'
|
|
63
|
+
}
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
});
|
package/lyrics.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
|
|
46
|
+
for (let i = 0; i < lines.length; i++) {
|
|
47
|
+
const line = lines[i].trim();
|
|
48
|
+
|
|
49
|
+
// Check for part start
|
|
50
|
+
const partStartMatch = line.match(/^\[([^\]]+)\](?:\s*\(([^)]+)\))?$/);
|
|
51
|
+
if (partStartMatch) {
|
|
52
|
+
const partName = partStartMatch[1];
|
|
53
|
+
const indication = partStartMatch[2] || null;
|
|
54
|
+
|
|
55
|
+
// Check if this part has been seen before
|
|
56
|
+
const partCount = (seenParts.get(partName) || 0) + 1;
|
|
57
|
+
seenParts.set(partName, partCount);
|
|
58
|
+
|
|
59
|
+
currentPart = {
|
|
60
|
+
name: partName,
|
|
61
|
+
repetition: partCount > 1,
|
|
62
|
+
indication,
|
|
63
|
+
content: partContentMap.get(partName)
|
|
64
|
+
};
|
|
65
|
+
parts.push(currentPart);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Handle content without a part container
|
|
70
|
+
if (line && !line.startsWith('[') && !line.startsWith('[/')) {
|
|
71
|
+
parts.push({
|
|
72
|
+
name: undefined,
|
|
73
|
+
repetition: false,
|
|
74
|
+
indication: null,
|
|
75
|
+
content: line
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return parts;
|
|
81
|
+
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lyrics-structure",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Parser for lyrics with structured sections, names, and indications",
|
|
4
5
|
"main": "dist/index.js",
|
|
5
6
|
"types": "dist/index.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "jest",
|
|
10
|
+
"test:watch": "jest --watch"
|
|
9
11
|
},
|
|
10
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"lyrics",
|
|
14
|
+
"parser",
|
|
15
|
+
"slides",
|
|
16
|
+
"sections"
|
|
17
|
+
],
|
|
11
18
|
"author": "",
|
|
12
|
-
"license": "
|
|
13
|
-
"type": "module",
|
|
14
|
-
"description": "",
|
|
19
|
+
"license": "MIT",
|
|
15
20
|
"devDependencies": {
|
|
16
|
-
"@types/
|
|
17
|
-
"
|
|
18
|
-
"
|
|
21
|
+
"@types/jest": "^29.0.0",
|
|
22
|
+
"jest": "^29.0.0",
|
|
23
|
+
"ts-jest": "^29.0.0",
|
|
24
|
+
"typescript": "^5.0.0"
|
|
19
25
|
}
|
|
20
26
|
}
|
package/slide.test.ts
ADDED
|
@@ -0,0 +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
|
+
|
|
67
|
+
});
|