-
-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
325 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ lib | |
node_modules | ||
src/formatter/templates/*.js | ||
src/parser/chord_pro_peg_parser.js | ||
src/parser/on_song_grammar.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
// https://www.onsongapp.com/docs/features/formats/onsong/ | ||
|
||
ChordSheet | ||
= metadata:Metadata __ sections:Section* { | ||
return { type: "chordsheet", metadata, sections } | ||
} | ||
|
||
// https://www.onsongapp.com/docs/features/formats/onsong/metadata/ | ||
Metadata | ||
// First line is implicitly title | ||
= first:(!Metatag value:MetaValue EOL { | ||
return { type: "metatag", name: "title", value } | ||
})? | ||
// Second line is implicitly artist | ||
second:(!Metatag value:MetaValue EOL { | ||
return { type: "metatag", name: "artist", value } | ||
})? | ||
rest:(@Metatag __)* | ||
{ | ||
return [ first, second, ...rest ].filter(Boolean) | ||
} | ||
|
||
Metatag "metatag" | ||
= name:MetaName _ ":" _ value:MetaValue EOL { | ||
return { type: "metatag", name: name.toLowerCase().trim(), value } | ||
} | ||
|
||
MetaName | ||
= $[^\r\n:]+ | ||
|
||
MetaValue | ||
= $[^\r\n]+ | ||
|
||
// https://www.onsongapp.com/docs/features/formats/onsong/section/ | ||
Section | ||
// Section with explcit name and maybe a body | ||
= name:SectionName __ items:SectionBody* { return { type: 'section', name, items } } | ||
// Section without explicit name | ||
/ items:SectionBody+ { return { type: 'section', name: null, items } } | ||
|
||
SectionName | ||
= name:MetaName _ ":" EOL { | ||
return name.trim() | ||
} | ||
|
||
SectionBody | ||
= @Stanza __ | ||
|
||
Stanza | ||
= lines:Line+ { | ||
return { type: 'stanza', lines } | ||
} | ||
|
||
Line | ||
= parts:(ChordLyricsPair / JustChords / JustLyrics)+ EOL { | ||
return {type: 'line', parts } | ||
} | ||
|
||
JustChords | ||
= chords:Chord { | ||
return { type: "ChordLyricsPair", chords, lyrics: "" } | ||
} | ||
|
||
JustLyrics | ||
= lyrics:Lyrics { | ||
return { type: "ChordLyricsPair", lyrics, chords: "" } | ||
} | ||
|
||
ChordLyricsPair | ||
= chords:Chord lyrics:Lyrics { | ||
return { type: "ChordLyricsPair", chords: chords || '', lyrics } | ||
} | ||
|
||
Chord "chord" | ||
= !Escape "[" chords:$(ChordChar*) "]" { | ||
return chords; | ||
} | ||
|
||
ChordChar | ||
= [^\]] | ||
|
||
Lyrics "lyrics" | ||
= $(Char+) | ||
|
||
// MusicalInstruction // e.g. (Repeat 8x) | ||
// = "(" [^)]+ ")" | ||
|
||
Char | ||
= [^\|\[\]\\#\r\n] | ||
|
||
Escape | ||
= "\\" | ||
|
||
Space | ||
= "\t" / " " | ||
|
||
NewLine "new line" | ||
= "\r\n" / "\r" / "\n" | ||
|
||
EOL "end of line" // Strict linebreak or end of file | ||
= _ (NewLine / EOF) | ||
|
||
EOF | ||
= !. | ||
|
||
_ // Insignificant space | ||
= Space* | ||
|
||
__ // Insignificant whitespace | ||
= (Space / NewLine)* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
import * as peggy from 'peggy'; | ||
import '../matchers'; | ||
import { readFileSync } from 'fs'; | ||
|
||
describe('OnSongGrammar', () => { | ||
const examples = { | ||
Metadata: { | ||
// First and second lines are assumed to be title / artist | ||
'Song Title\nArtist Name': [ | ||
{ type: 'metatag', name: 'title', value: 'Song Title' }, | ||
{ type: 'metatag', name: 'artist', value: 'Artist Name' }, | ||
], | ||
'Song Title\nArtist:Artist Name': [ | ||
{ type: 'metatag', name: 'title', value: 'Song Title' }, | ||
{ type: 'metatag', name: 'artist', value: 'Artist Name' }, | ||
], | ||
'Song Title\nTime:3/4': [ | ||
{ type: 'metatag', name: 'title', value: 'Song Title' }, | ||
{ type: 'metatag', name: 'time', value: '3/4' }, | ||
], | ||
'title: Song Title\nArtist: Artist Name': [ | ||
{ type: 'metatag', name: 'title', value: 'Song Title' }, | ||
{ type: 'metatag', name: 'artist', value: 'Artist Name' }, | ||
], | ||
// "before the first blank line or until no more metatags are encountered" | ||
'A: 1\n\nB:2\n\n': [ | ||
{ type: 'metatag', name: 'a', value: '1' }, | ||
{ type: 'metatag', name: 'b', value: '2' }, | ||
], | ||
// TODO: | ||
// "{title: ChordPro}": [ | ||
// { type: 'metatag', name: 'title', value: 'ChordPro' }, | ||
// ] | ||
// "Notes:" : { // Known metatag without a value | ||
// { type: 'metatag', name: 'Notes', value: '' }, | ||
// } | ||
'Unknown Tag:': Error, // Unknown metatag without a value | ||
}, | ||
|
||
// Inline Tags - https://www.onsongapp.com/docs/features/formats/onsong/metadata/?#inline-tags | ||
|
||
SectionName: { | ||
'Chorus:': 'Chorus', | ||
'Verse 1:\n': 'Verse 1', | ||
'Intro :': 'Intro', | ||
'Intro: ': 'Intro', | ||
}, | ||
|
||
Section: { | ||
'Chorus:\nThis is a stanza\n\nThis is another stanza': { | ||
type: 'section', | ||
name: 'Chorus', | ||
items: [ | ||
{ | ||
type: 'stanza', | ||
lines: [ | ||
{ type: 'line', parts: [{ type: 'ChordLyricsPair', chords: '', lyrics: 'This is a stanza' }] }, | ||
], | ||
}, | ||
{ | ||
type: 'stanza', | ||
lines: [ | ||
{ type: 'line', parts: [{ type: 'ChordLyricsPair', chords: '', lyrics: 'This is another stanza' }] }, | ||
], | ||
}, | ||
], | ||
}, | ||
|
||
'Intro:\n\n': { | ||
type: 'section', | ||
name: 'Intro', | ||
items: [], | ||
}, | ||
|
||
'Intro:\n\n\n[G]': { | ||
type: 'section', | ||
name: 'Intro', | ||
items: [ | ||
{ | ||
type: 'stanza', | ||
lines: [{ type: 'line', parts: [{ type: 'ChordLyricsPair', chords: 'G', lyrics: '' }] }], | ||
}, | ||
], | ||
}, | ||
|
||
'Chord and lyrics': { | ||
type: 'section', | ||
name: null, | ||
items: [ | ||
{ | ||
type: 'stanza', | ||
lines: [ | ||
{ type: 'line', parts: [{ type: 'ChordLyricsPair', chords: '', lyrics: 'Chord and lyrics' }] }, | ||
], | ||
}, | ||
], | ||
}, | ||
}, | ||
|
||
Line: { | ||
'This [D]is a s[G]ong,': { | ||
type: 'line', | ||
parts: [ | ||
{ type: 'ChordLyricsPair', chords: '', lyrics: 'This ' }, | ||
{ type: 'ChordLyricsPair', chords: 'D', lyrics: 'is a s' }, | ||
{ type: 'ChordLyricsPair', chords: 'G', lyrics: 'ong,' }, | ||
], | ||
}, | ||
'Ends with a chord [D]': { | ||
type: 'line', | ||
parts: [ | ||
{ type: 'ChordLyricsPair', chords: '', lyrics: 'Ends with a chord ' }, | ||
{ type: 'ChordLyricsPair', chords: 'D', lyrics: '' }, | ||
], | ||
}, | ||
'[D]Starts with a chord': { | ||
type: 'line', | ||
parts: [ | ||
{ type: 'ChordLyricsPair', chords: 'D', lyrics: 'Starts with a chord' }, | ||
], | ||
}, | ||
'Just lyrics': { | ||
type: 'line', | ||
parts: [ | ||
{ type: 'ChordLyricsPair', chords: '', lyrics: 'Just lyrics' }, | ||
], | ||
}, | ||
|
||
'[G]': { | ||
type: 'line', | ||
parts: [{ type: 'ChordLyricsPair', chords: 'G', lyrics: '' }], | ||
}, | ||
}, | ||
|
||
Chord: { | ||
'[G]': 'G', | ||
'[D/F#]': 'D/F#', | ||
'[Bsus2]': 'Bsus2', | ||
'\\[notachord]': Error, | ||
}, | ||
|
||
ChordSheet: { | ||
'Title\n\nChord and lyrics': { | ||
type: 'chordsheet', | ||
metadata: [{ type: 'metatag', name: 'title', value: 'Title' }], | ||
sections: [ | ||
{ | ||
type: 'section', | ||
items: [ | ||
{ | ||
lines: [ | ||
{ parts: [{ chords: '', lyrics: 'Chord and lyrics', type: 'ChordLyricsPair' }], type: 'line' }, | ||
], | ||
type: 'stanza', | ||
}, | ||
], | ||
name: null, | ||
}, | ||
], | ||
}, | ||
'Title\n\nIntro:\n': { | ||
type: 'chordsheet', | ||
metadata: [{ type: 'metatag', name: 'title', value: 'Title' }], | ||
sections: [ | ||
{ | ||
type: 'section', | ||
name: 'Intro', | ||
items: [], | ||
}, | ||
], | ||
}, | ||
'Tempo: 73\nUnknown(s): Value:with@various:characters1-5\n\nChorus:': { | ||
type: 'chordsheet', | ||
metadata: [ | ||
{ name: 'tempo', type: 'metatag', value: '73' }, | ||
{ name: 'unknown(s)', type: 'metatag', value: 'Value:with@various:characters1-5' }, | ||
], | ||
sections: [ | ||
{ items: [], name: 'Chorus', type: 'section' }, | ||
], | ||
}, | ||
}, | ||
}; | ||
|
||
const grammar = readFileSync('src/parser/on_song_grammar.pegjs', { encoding: 'utf-8' }); | ||
const { parse } = peggy.generate(grammar, { | ||
// Allow starting with these in tests | ||
allowedStartRules: Object.keys(examples), | ||
}); | ||
|
||
Object.entries(examples).forEach(([startRule, ruleExamples]) => { | ||
describe(startRule, () => { | ||
Object.entries(ruleExamples).forEach(([input, expected]) => { | ||
test(JSON.stringify(input), () => { | ||
try { | ||
const actual = parse(input, { startRule }); | ||
expect(actual).toEqual(expected); | ||
} catch (e) { | ||
if (expected !== Error) { | ||
throw e; | ||
} | ||
} | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters