All files / Bandstand/src/lib chart-diff.ts

100% Statements 33/33
90% Branches 18/20
100% Functions 1/1
100% Lines 33/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 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 431x 1x                 1x 2x 2x 2x 2x 2x 2x 2x 2x 5x 5x 5x 5x 2x 1x 1x 6x 2x 2x 2x 2x   6x 1x 1x 1x 6x 1x 1x 1x 1x 1x  
import {diffLines} from 'diff'
import {chartToChordsOverWords, parseChordPro} from './chordpro'
 
export type DiffRow = {kind: 'same' | 'added' | 'removed'; text: string}
 
/**
 * Line diff between two chart versions, compared as chords-over-lyrics text
 * (what musicians read) rather than raw ChordPro. Long unchanged stretches are
 * folded to a few lines of context either side of each change.
 */
export function chartDiff(
  before: string,
  after: string,
  context = 2,
): DiffRow[] {
  const a = chartToChordsOverWords(parseChordPro(before))
  const b = chartToChordsOverWords(parseChordPro(after))
  const rows: DiffRow[] = []
  for (const part of diffLines(a ? a + '\n' : '', b ? b + '\n' : '')) {
    const kind = part.added ? 'added' : part.removed ? 'removed' : 'same'
    for (const text of part.value.replace(/\n$/, '').split('\n'))
      rows.push({kind, text})
  }
  if (!rows.some((r) => r.kind !== 'same')) return []
  const keep = rows.map(() => false)
  rows.forEach((r, i) => {
    if (r.kind === 'same') return
    for (
      let j = Math.max(0, i - context);
      j <= Math.min(rows.length - 1, i + context);
      j++
    )
      keep[j] = true
  })
  const out: DiffRow[] = []
  rows.forEach((r, i) => {
    if (keep[i]) out.push(r)
    else if (keep[i - 1] || (i === 0 && !keep[0]))
      out.push({kind: 'same', text: '…'})
  })
  return out
}