Skip to content

Commit

Permalink
Expand {chorus} directives (#802)
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnversluis authored Jan 5, 2023
1 parent 52871d1 commit 03714d8
Show file tree
Hide file tree
Showing 14 changed files with 675 additions and 21 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,9 @@ See https://www.chordpro.org/chordpro/directives-key/</p></dd>
<dd><p>Chordfont directive. See https://www.chordpro.org/chordpro/directives-props_title_legacy/</p></dd>
<dt><a href="#TITLESIZE">TITLESIZE</a> : <code>string</code></dt>
<dd><p>Chordfont directive. See https://www.chordpro.org/chordpro/directives-props_title_legacy/</p></dd>
<dt><a href="#TITLECOLOUR">TITLECOLOUR</a> : <code>string</code></dt>
<dd><p>Chorus directive. Support repeating an earlier defined section.
See https://www.chordpro.org/chordpro/directives-env_chorus/</p></dd>
<dt><a href="#defaultCss">defaultCss</a> ⇒ <code>string</code></dt>
<dd><p>Generates basic CSS, scoped within the provided selector, to use with output generated by [HtmlTableFormatter](#HtmlTableFormatter)</p></dd>
<dt><a href="#defaultCss">defaultCss</a> ⇒ <code>string</code></dt>
Expand Down Expand Up @@ -783,6 +786,7 @@ If not, it returns [INDETERMINATE](#INDETERMINATE)</p>
* [.bodyLines](#Song+bodyLines)[<code>Array.&lt;Line&gt;</code>](#Line)
* [.bodyParagraphs](#Song+bodyParagraphs)[<code>Array.&lt;Paragraph&gt;</code>](#Paragraph)
* [.paragraphs](#Song+paragraphs) : [<code>Array.&lt;Paragraph&gt;</code>](#Paragraph)
* [.expandedBodyParagraphs](#Song+expandedBodyParagraphs) : [<code>Array.&lt;Paragraph&gt;</code>](#Paragraph)
* ~~[.metaData](#Song+metaData)~~
* [.clone()](#Song+clone)[<code>Song</code>](#Song)
* [.setKey(key)](#Song+setKey)[<code>Song</code>](#Song)
Expand Down Expand Up @@ -826,6 +830,12 @@ if you want to skip the &quot;header lines&quot;: the lines that only contain me
### song.paragraphs : [<code>Array.&lt;Paragraph&gt;</code>](#Paragraph)
<p>The [Paragraph](#Paragraph) items of which the song consists</p>

**Kind**: instance property of [<code>Song</code>](#Song)
<a name="Song+expandedBodyParagraphs"></a>

### song.expandedBodyParagraphs : [<code>Array.&lt;Paragraph&gt;</code>](#Paragraph)
<p>The body paragraphs of the song, with any <code>{chorus}</code> tag expanded into the targetted chorus</p>

**Kind**: instance property of [<code>Song</code>](#Song)
<a name="Song+metaData"></a>

Expand Down Expand Up @@ -1116,6 +1126,7 @@ https://chordpro.org/chordpro/directives-env_bridge/, https://chordpro.org/chord
| [configuration.metadata] | <code>object</code> | <code>{}</code> | |
| [configuration.metadata.separator] | <code>string</code> | <code>&quot;\&quot;, \&quot;&quot;</code> | <p>The separator to be used when rendering a metadata value that has multiple values. See: https://bit.ly/2SC9c2u</p> |
| [configuration.key] | [<code>Key</code>](#Key) \| <code>string</code> | <code></code> | <p>The key to use for rendering. The chord sheet will be transposed from the song's original key (as indicated by the <code>{key}</code> directive) to the specified key. Note that transposing will only work if the original song key is set.</p> |
| [configuration.expandChorusDirective] | <code>boolean</code> | <code>false</code> | <p>Whether or not to expand <code>{chorus}</code> directives by rendering the last defined chorus inline after the directive.</p> |

<a name="HtmlDivFormatter"></a>

Expand Down Expand Up @@ -1891,6 +1902,13 @@ See https://www.chordpro.org/chordpro/directives-key/</p>
## TITLESIZE : <code>string</code>
<p>Chordfont directive. See https://www.chordpro.org/chordpro/directives-props_title_legacy/</p>

**Kind**: global variable
<a name="TITLECOLOUR"></a>

## TITLECOLOUR : <code>string</code>
<p>Chorus directive. Support repeating an earlier defined section.
See https://www.chordpro.org/chordpro/directives-env_chorus/</p>

**Kind**: global variable
<a name="defaultCss"></a>

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"build": "yarn build:code-generate && yarn build:sources && yarn build:browserify",
"jest": "jest",
"test": "yarn lint && yarn jest",
"test:debug": "open -a \"Brave Browser\" chrome://inspect && node --nolazy --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --colors --verbose",
"jest:debug": "open -a \"Brave Browser\" chrome://inspect && node --nolazy --inspect-brk ./node_modules/jest/bin/jest.js --runInBand --colors --verbose",
"eslint": "node_modules/.bin/eslint --ext .ts .",
"lint": "yarn build:code-generate && yarn eslint",
"lint:fix": "yarn build:code-generate && node_modules/.bin/eslint --fix --ext .ts .",
Expand Down
10 changes: 8 additions & 2 deletions src/chord_sheet/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { CHORUS, NONE, VERSE } from '../constants';
import Item from './item';
import Font from './font';

type MapItemFunc = (_item: Item) => Item;
type MapItemFunc = (_item: Item) => Item | null;

export type LineType = 'verse' | 'chorus' | 'none';

Expand All @@ -32,6 +32,8 @@ class Line {

transposeKey: string | null = null;

lineNumber: number | null = null;

/**
* The text font that applies to this line. Is derived from the directives:
* `textfont`, `textsize` and `textcolour`
Expand Down Expand Up @@ -61,6 +63,10 @@ class Line {
return this.items.length === 0;
}

isNotEmpty(): boolean {
return !this.isEmpty();
}

/**
* Adds an item ({@link ChordLyricsPair} or {@link Tag}) to the line
* @param {ChordLyricsPair|Tag} item The item to be added
Expand Down Expand Up @@ -101,7 +107,7 @@ class Line {
const clonedItem = item.clone();
return func ? func(clonedItem) : clonedItem;
})
.filter((item) => item);
.filter((item) => item !== null) as Item[];

clonedLine.type = this.type;
return clonedLine;
Expand Down
74 changes: 70 additions & 4 deletions src/chord_sheet/song.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,17 @@ import {
} from '../constants';

import Tag, {
CAPO,
END_OF_CHORUS,
END_OF_TAB,
END_OF_VERSE,
KEY,
NEW_KEY,
START_OF_CHORUS,
START_OF_TAB,
START_OF_VERSE,
TRANSPOSE,
NEW_KEY,
CAPO,
KEY,
CHORUS as CHORUS_TAG,
} from './tag';

interface MapItemsCallback {
Expand Down Expand Up @@ -146,18 +147,83 @@ class Song extends MetadataAccessors {
this.setCurrentProperties(this.sectionType);
this.currentLine.transposeKey = this.transposeKey ?? this.currentKey;
this.currentLine.key = this.currentKey || this.metadata.getSingle(KEY);
this.currentLine.lineNumber = this.lines.length - 1;
return this.currentLine;
}

private expandLine(line: Line): Line[] {
const expandedLines = line.items.flatMap((item: Item) => {
if (item instanceof Tag && item.name === CHORUS_TAG) {
return this.getLastChorusBefore(line.lineNumber);
}

return [];
});

return [line, ...expandedLines];
}

private getLastChorusBefore(lineNumber: number | null): Line[] {
const lines: Line[] = [];

if (!lineNumber) {
return lines;
}

for (let i = lineNumber - 1; i >= 0; i -= 1) {
const line = this.lines[i];

if (line.type === CHORUS) {
const filteredLine = this.filterChorusStartEndDirectives(line);

if (!(line.isNotEmpty() && filteredLine.isEmpty())) {
lines.unshift(line);
}
} else if (lines.length > 0) {
break;
}
}

return lines;
}

private filterChorusStartEndDirectives(line: Line) {
return line.mapItems((item: Item) => {
if (item instanceof Tag) {
if (item.name === START_OF_CHORUS || item.name === END_OF_CHORUS) {
return null;
}
}

return item;
});
}

/**
* The {@link Paragraph} items of which the song consists
* @member {Paragraph[]}
*/
get paragraphs(): Paragraph[] {
return this.linesToParagraphs(this.lines);
}

/**
* The body paragraphs of the song, with any `{chorus}` tag expanded into the targetted chorus
* @type {Paragraph[]}
*/
get expandedBodyParagraphs(): Paragraph[] {
return this.selectRenderableItems(
this.linesToParagraphs(
this.lines.flatMap((line: Line) => this.expandLine(line)),
),
) as Paragraph[];
}

linesToParagraphs(lines: Line[]) {
let currentParagraph = new Paragraph();
const paragraphs = [currentParagraph];

this.lines.forEach((line) => {
lines.forEach((line) => {
if (line.isEmpty()) {
currentParagraph = new Paragraph();
paragraphs.push(currentParagraph);
Expand Down
8 changes: 8 additions & 0 deletions src/chord_sheet/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,13 @@ export const TITLESIZE = 'titlesize';
*/
export const TITLECOLOUR = 'titlecolour';

/**
* Chorus directive. Support repeating an earlier defined section.
* See https://www.chordpro.org/chordpro/directives-env_chorus/
* @type {string}
*/
export const CHORUS = 'chorus';

const TITLE_SHORT = 't';
const SUBTITLE_SHORT = 'st';
const COMMENT_SHORT = 'c';
Expand Down Expand Up @@ -261,6 +268,7 @@ const DIRECTIVES_WITH_RENDERABLE_LABEL = [
START_OF_CHORUS,
START_OF_BRIDGE,
START_OF_TAB,
CHORUS,
];

const ALIASES: Record<string, string> = {
Expand Down
13 changes: 7 additions & 6 deletions src/formatter/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ export type ConfigurationProperties = Record<string, any> & {
separator: string,
},
key?: Key | string | null,
expandChorusDirective?: boolean,
}

export const defaultConfiguration: ConfigurationProperties = {
evaluate: false,
metadata: { separator: ',' },
key: null,
expandChorusDirective: false,
};

class Configuration {
Expand All @@ -26,13 +28,12 @@ class Configuration {

configuration: Record<string, any>;

constructor(configuration: ConfigurationProperties = defaultConfiguration) {
if ('evaluate' in configuration) {
this.evaluate = !!configuration.evaluate;
} else {
this.evaluate = !!defaultConfiguration.evaluate;
}
expandChorusDirective: boolean;

constructor(configuration: ConfigurationProperties = defaultConfiguration) {
const mergedConfig: ConfigurationProperties = { ...defaultConfiguration, ...configuration };
this.evaluate = !!mergedConfig.evaluate;
this.expandChorusDirective = !!mergedConfig.expandChorusDirective;
this.metadata = new MetadataConfiguration(configuration.metadata);
this.key = configuration.key ? Key.wrap(configuration.key) : null;
this.configuration = configuration;
Expand Down
2 changes: 2 additions & 0 deletions src/formatter/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class Formatter {
* from the song's original key (as indicated by the `{key}` directive) to the specified key.
* Note that transposing will only work
* if the original song key is set.
* @param {boolean} [configuration.expandChorusDirective=false] Whether or not to expand `{chorus}` directives
* by rendering the last defined chorus inline after the directive.
*/
constructor(configuration: ConfigurationProperties | null = null) {
this.configuration = new Configuration(configuration || {});
Expand Down
12 changes: 11 additions & 1 deletion src/formatter/html_formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import Formatter from './formatter';
import Configuration from './configuration/configuration';
import Song from '../chord_sheet/song';
import { breakingChange, scopeCss } from '../utilities';
import Paragraph from '../chord_sheet/paragraph';

export type HtmlTemplateArgs = {
configuration: Configuration;
song: Song;
renderBlankLines?: boolean;
bodyParagraphs: Paragraph[],
};

export type Template = (_args: HtmlTemplateArgs) => string;
Expand All @@ -22,7 +24,15 @@ abstract class HtmlFormatter extends Formatter {
* @returns {string} The HTML string
*/
format(song: Song): string {
return this.template({ song, configuration: this.configuration });
const { bodyParagraphs, expandedBodyParagraphs } = song;

return this.template(
{
song,
configuration: this.configuration,
bodyParagraphs: this.configuration.expandChorusDirective ? expandedBodyParagraphs : bodyParagraphs,
},
);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/formatter/templates/html_div_formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ export default (
song: {
title,
subtitle,
bodyParagraphs,
metadata,
},
bodyParagraphs,
}: HtmlTemplateArgs,
): string => stripHTML(`
${ when(title, () => `<h1>${ title }</h1>`) }
Expand Down
2 changes: 1 addition & 1 deletion src/formatter/templates/html_table_formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ export default (
song: {
title,
subtitle,
bodyParagraphs,
bodyLines,
metadata,
},
bodyParagraphs,
}: HtmlTemplateArgs,
): string => stripHTML(`
${ when(title, () => `<h1>${ title}</h1>`) }
Expand Down
5 changes: 3 additions & 2 deletions src/formatter/text_formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ class TextFormatter extends Formatter {
}

formatParagraphs(): string {
const { bodyParagraphs, metadata } = this.song;
const { bodyParagraphs, expandedBodyParagraphs, metadata } = this.song;
const { expandChorusDirective } = this.configuration;

return bodyParagraphs
return (expandChorusDirective ? expandedBodyParagraphs : bodyParagraphs)
.map((paragraph: Paragraph) => this.formatParagraph(paragraph, metadata))
.join('\n\n');
}
Expand Down
Loading

0 comments on commit 03714d8

Please sign in to comment.