Support one- and two-column layouts. Transposing keys doesn't start a new section.
This commit is contained in:
@ -77,7 +77,7 @@ iterator pairs*(ccmd: ChordChartMetadata): tuple[key, value: string] =
|
||||
|
||||
func kind*(ccn: ChordChartNode): ChordChartNodeKind = ccn.kind
|
||||
|
||||
func `$`(pitch: ChordChartPitch): string =
|
||||
func `$`*(pitch: ChordChartPitch): string =
|
||||
["A♭", "A", "B♭", "B", "C", "D♭", "D", "E♭", "E", "F", "G♭", "G"][ord(pitch)]
|
||||
|
||||
func renderPitchInKey*(
|
||||
@ -259,7 +259,7 @@ proc parsePitch*(ctx: ParserContext, keyValue: string): ChordChartPitch =
|
||||
# see regexr.com/70nv1
|
||||
let CHORD_REGEX =
|
||||
"([b#♭♮𝄫𝄪]?[A-G1-7][b#♭♮𝄫𝄪]?)" & # chord root
|
||||
"((min|maj|aug|dim|sus|6\\/9|[mM1-9#b♭♮𝄫𝄪Δ+oøoø][0-9]?|\\([1-9#b♭]+\\))*)" & # chord flavor/type
|
||||
"((min|maj|aug|dim|sus|6\\/9|[mM1-9#b♭♮𝄫𝄪Δ+oøoø°𝆩][0-9]?|\\([1-9#b♭]+\\))*)" & # chord flavor/type
|
||||
"(\\/([b#♭♮𝄫𝄪]?[A-G1-7][b#♭♮𝄫𝄪]?))?" # optional bass
|
||||
let CHORD_PAT = re(CHORD_REGEX)
|
||||
|
||||
@ -344,13 +344,46 @@ let PAGE_BREAK_PAT = re"\s*PAGE_BREAK\s*$"
|
||||
let TRANSPOSE_PAT = re"\s*TRANSPOSE KEY ([+-]\d+)\s*$"
|
||||
let REDEFINE_KEY_PAT = re"\s*REDEFINE KEY ([+-]\d+)\s*$"
|
||||
#let NOTE_PAT = re"^(.*)({{[^}]+}}|{[^}]+})(.*)$"
|
||||
let NOTE_START_PAT = re"{{"
|
||||
let NOTE_END_PAT = re"}}"
|
||||
let NOTE_START_PAT = re"\{\{"
|
||||
let NOTE_END_PAT = re"\}\}"
|
||||
let SPACE_PAT = re"\s"
|
||||
let CHORD_IN_LYRICS_PAT = re"(\w+)(\[.+)"
|
||||
let CHORD_AND_LYRICS_PAT = re"^\[([^\]]+)\]([^\s\[]+)(.*)$"
|
||||
let BRACED_CHORD_PAT = re"^\[([^\]]+)\]$"
|
||||
let NAKED_CHORDS_ONLY_PAT = re("^(" & CHORD_REGEX & "\\s*\\|*\\s*)+$")
|
||||
let NAKED_CHORDS_ONLY_PAT = re("^\\s*(" & CHORD_REGEX & "\\s*\\|*\\s*)+$")
|
||||
|
||||
proc readNote(ctx: var ParserContext, firstLine: string): tuple[pre, note, post: string] =
|
||||
let startLineNum = ctx.curLineNum
|
||||
|
||||
result = ("", "", "")
|
||||
|
||||
let startMatch = firstLine.find(NOTE_START_PAT)
|
||||
var endMatch = firstLine.find(NOTE_END_PAT)
|
||||
|
||||
if startMatch.isNone: return
|
||||
|
||||
if startMatch.get.matchBounds.a > 0:
|
||||
result.pre = firstLine[0..<startMatch.get.matchBounds.a]
|
||||
|
||||
if endMatch.isSome:
|
||||
result.note = firstLine[(startMatch.get.matchBounds.b + 1)..<endMatch.get.matchBounds.a]
|
||||
result.post = firstLine[(endMatch.get.matchBounds.b + 1)..^1]
|
||||
else:
|
||||
# if we don't find the end of the note, then we need to read more lines
|
||||
# until we do
|
||||
result.note = firstLine[(startMatch.get.matchBounds.b + 1)..^1]
|
||||
|
||||
while ctx.lines.len > ctx.curLineNum and ctx.hasNextLine:
|
||||
let line = ctx.nextLine
|
||||
endMatch = line.find(NOTE_END_PAT)
|
||||
if endMatch.isSome:
|
||||
result.note &= line[0..<endMatch.get.matchBounds.a]
|
||||
result.post = line[(endMatch.get.matchBounds.b + 1)..^1]
|
||||
return
|
||||
else: result.note &= line
|
||||
|
||||
raise ctx.makeError("a note section was started on line " &
|
||||
$startLineNum & " and never closed")
|
||||
|
||||
proc parseLineParts(ctx: ParserContext, parts: varargs[string]): seq[ChordChartNode] =
|
||||
result = @[]
|
||||
@ -394,10 +427,11 @@ proc parseLineParts(ctx: ParserContext, parts: varargs[string]): seq[ChordChartN
|
||||
chord: none[ChordChartChord](),
|
||||
word: some(p)))
|
||||
|
||||
proc parseLine(ctx: ParserContext, line: string): ChordChartNode =
|
||||
proc parseLine(lentCtx: var ParserContext, line: string): ChordChartNode =
|
||||
let ctx = lentCtx
|
||||
result = ChordChartNode(kind: ccnkLine)
|
||||
|
||||
let m = line.match(NAKED_CHORDS_ONLY_PAT)
|
||||
var m = line.match(NAKED_CHORDS_ONLY_PAT)
|
||||
if m.isSome:
|
||||
result.line = line.splitWhitespace --> map(
|
||||
ChordChartNode(
|
||||
@ -406,49 +440,28 @@ proc parseLine(ctx: ParserContext, line: string): ChordChartNode =
|
||||
spaceBefore: false, #FIXME
|
||||
chord: ctx.parseChord(it.strip),
|
||||
word: none[string]()))
|
||||
return
|
||||
|
||||
m = line.match(NOTE_START_PAT)
|
||||
if m.isSome:
|
||||
let (pre, note, post) = lentCtx.readNote(line)
|
||||
result.line = ctx.parseLineParts(pre) &
|
||||
@[ChordChartNode(
|
||||
kind: ccnkNote,
|
||||
inclInLyrics: true,
|
||||
note: note)] &
|
||||
ctx.parseLineParts(post)
|
||||
return
|
||||
|
||||
else: result.line = ctx.parseLineParts(line.splitWhitespace)
|
||||
|
||||
proc readNote(ctx: var ParserContext, endPat: Regex): string =
|
||||
let startLineNum = ctx.curLineNum
|
||||
result = ""
|
||||
|
||||
while ctx.lines.len > ctx.curLineNum and ctx.hasNextLine:
|
||||
let line = ctx.nextLine
|
||||
let m = line.find(endPat)
|
||||
if m.isSome:
|
||||
result &= line[0..<m.get.matchBounds.a]
|
||||
ctx.pushPartialsToParse(line[m.get.matchBounds.b..^1])
|
||||
break
|
||||
else: result &= line
|
||||
|
||||
if not ctx.hasNextLine:
|
||||
raise ctx.makeError("a note section was started on line " &
|
||||
$startLineNum & " and never closed")
|
||||
|
||||
proc parseBody(ctx: var ParserContext): seq[ChordChartNode] =
|
||||
result = @[]
|
||||
while ctx.hasNextLine:
|
||||
var line = ctx.nextLine
|
||||
|
||||
var m = line.find(NOTE_START_PAT)
|
||||
if m.isSome:
|
||||
if m.get.matchBounds.a > 0:
|
||||
# if this is not the first character of the line then let's split the
|
||||
# line and continue to parse
|
||||
ctx.pushPartialsToParse(
|
||||
line[0..<m.get.matchBounds.a],
|
||||
line[m.get.matchBounds.a..^1])
|
||||
continue
|
||||
|
||||
else:
|
||||
# if this is the first character of the line, then let's parse the note
|
||||
result.add(ChordChartNode(
|
||||
kind: ccnkNote,
|
||||
inclInLyrics: m.get.match.len < 2,
|
||||
note: ctx.readNote(NOTE_END_PAT)))
|
||||
|
||||
m = line.match(TRANSPOSE_PAT)
|
||||
var m = line.match(TRANSPOSE_PAT)
|
||||
if m.isSome:
|
||||
addToCurSection(ChordChartNode(
|
||||
kind: ccnkTransposeKey,
|
||||
@ -475,6 +488,7 @@ proc parseBody(ctx: var ParserContext): seq[ChordChartNode] =
|
||||
m = line.match(PAGE_BREAK_PAT)
|
||||
if m.isSome:
|
||||
result.add(ChordChartNode(kind: ccnkPageBreak))
|
||||
ctx.curSection = EMPTY_CHORD_CHART_NODE
|
||||
continue
|
||||
|
||||
m = line.match(SECTION_LINE_PAT)
|
||||
|
Reference in New Issue
Block a user