import std/[logging, options, os, strutils] import zero_functional import ./ast type FormatContext = ref object chart: ChordChart currentKey: ChordChartPitch sourceKey: ChordChartPitch currentSection: ChordChartNode numberChart: bool transposeSteps: int const DEFAULT_STYLESHEET* = """ """ func scaleDegree(root: ChordChartPitch, p: ChordChartPitch): string = let distance = p - root case distance: of 0: "1" of 1: "♭2" of 2: "2" of 3: "♭3" of 4: "3" of 5: "4" of 6: "♭5" of 7: "5" of 8: "♭6" of 9: "6" of 10: "♭7" of 11: "7" else: raise newException(Exception, "Impossible") func format(ctx: FormatContext, chord: ChordChartChord, useNumber = false): string = ##if not useNumber and chord.original.isSome: return chord.original.get if useNumber: result = "" & ctx.currentKey.scaleDegree(chord.rootPitch) & "" if chord.flavor.isSome: result &= "" & chord.flavor.get & "" if chord.bassPitch.isSome: result &= "/" & ctx.currentKey.scaleDegree(chord.bassPitch.get) & "" else: result = "" & renderPitchInKey(chord.rootPitch, ctx.currentKey) & "" if chord.flavor.isSome: result &= "" & chord.flavor.get & "" if chord.bassPitch.isSome: result &= "/" & renderPitchInKey(chord.bassPitch.get, ctx.currentKey) & "" proc transpose(ctx: FormatContext, chord: ChordChartChord): ChordChartChord = result = chord let distance = ctx.currentKey - ctx.sourceKey if distance != 0: result = ChordChartChord( original: none[string](), rootPitch: chord.rootPitch + distance, flavor: if chord.flavor.isSome: some(chord.flavor.get) else: none[string](), bassPitch: if chord.bassPitch.isSome: some(chord.bassPitch.get + distance) else: none[ChordChartPitch]()) func makeSongOrder(songOrder, indent: string): string = result = indent & "
\p" & indent & "

Song Order

\p" & indent & " \p" & indent & "
\p" proc toHtml(ctx: var FormatContext, node: ChordChartNode, indent: string): string = result = "" case node.kind of ccnkSection: ctx.currentSection = node result &= indent & "
\p" & indent & " " & "

" & node.sectionName if ctx.currentSection.remainingSectionLine.isSome: result &= "" & ctx.currentSection.remainingSectionLine.get & "" result &= "

\p" var contents = newSeq[string]() for contentNode in node.sectionContents: contents.add(ctx.toHtml(contentNode, indent & " ")) result &= contents.join("\p") result &= indent & "
" ctx.currentSection = EMPTY_CHORD_CHART_NODE of ccnkLine: result &= indent & "
\p" for linePart in node.line: result &= ctx.toHtml(linePart, indent & " ") result &= indent & "
" of ccnkWord: result &= "" if node.chord.isSome: result &= "" & ctx.format(ctx.transpose(node.chord.get), ctx.numberChart) & "" result &= "" if node.word.isSome: result &= node.word.get else: result &= " " result &= "" result &= "" of ccnkNote: result &= indent & "
" & node.note & "
" of ccnkColBreak: result &= "
" of ccnkPageBreak: result &= "
" of ccnkTransposeKey: ctx.currentKey = ctx.currentKey + node.transposeSteps let headingVal = indent & "

Key Change: " & $ctx.currentKey & "

" if ctx.currentSection.kind == ccnkNone: result &= headingVal else: result &= "
" & headingVal & "
" of ccnkRedefineKey: let oldKey = ctx.currentKey ctx.sourceKey = node.newKey + ctx.transposeSteps ctx.currentKey = ctx.sourceKey if oldKey != ctx.currentKey: let headingVal = indent & "

Key Change: " & $ctx.currentKey & "

" if ctx.currentSection.kind == ccnkNone: result &= headingVal else: result &= "
" & headingVal & "
" of ccnkNone: discard proc toHtml*( cc: ChordChart, transpose = 0, numberChart = false, stylesheets = @[DEFAULT_STYLESHEET], scripts: seq[string] = @[]): string = var ctx = FormatContext( chart: cc, currentKey: cc.metadata.key.rootPitch + transpose, sourceKey: cc.metadata.key.rootPitch, currentSection: EMPTY_CHORD_CHART_NODE, numberChart: numberChart, transposeSteps: transpose) debug "Formatting:\p\tsource key: " & $ctx.sourceKey & "\p\ttransposing: " & $transpose & "\p\ttarget key: " & $ctx.currentKey result = """ """ & cc.metadata.title & "\p" for ss in stylesheets: if ss.startsWith("" else: warn "cannot read stylesheet file '" & ss & "'" for sc in scripts: if sc.startsWith("" else: warn "cannot read script file '" & sc & "'" result &= " \p " var indent = " " # Title result &= indent & "

" & cc.metadata.title & "

\p" var metadataPieces = @["Key: " & ctx.format(ctx.transpose(cc.metadata.key))] if cc.metadata.contains("time signature"): metadataPieces.add(cc.metadata["time signature"]) if cc.metadata.contains("bpm"): metadataPieces.add(cc.metadata["bpm"] & "bpm") if cc.metadata.contains("tempo"): metadataPieces.add(cc.metadata["tempo"]) result &= indent & "

" & metadataPieces.join(" | ") & "

\p" result &= "
" result &= join(cc.nodes --> map(ctx.toHtml(it, indent & " ")), "\p") if cc.metadata.contains("song order"): result &= makeSongOrder(cc.metadata["song order"], indent) result &= "
" result &= " \p"