Skip to content

Instantly share code, notes, and snippets.

@svallory
Last active April 14, 2024 05:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save svallory/a06232775829045ef7c80323ff89403f to your computer and use it in GitHub Desktop.
Save svallory/a06232775829045ef7c80323ff89403f to your computer and use it in GitHub Desktop.
My utilities
import { expandToChalkString } from './template-strings.ts';
const exit = (error: string, code: number = -1) => {
console.info(eval(`expandToChalkString\`${error}\``));
process.exit(code);
}
import type { ForegroundColorName } from 'chalk';
const info = (msg: string, color: ForegroundColorName = 'yellow') => {
console.info(eval(`expandToChalkString\`{${color} ${msg}}\``));
}
// OR
const info = (staticParts: TemplateStringsArray, ...substitutions: unknown[]) => {
console.info(expandToChalkString(staticParts, ...substitutions));
}
/******************************************************************************
* Copyright 2021 TypeFox GmbH
* This program and the accompanying materials are made available under the
* terms of the MIT License, which is available in the project root.
******************************************************************************/
import { chalkTemplateStderr } from 'chalk-template';
export const NEWLINE_REGEXP = /\r?\n/gm;
export const EOL = (typeof process === 'undefined') ? '\n' : (process.platform === 'win32') ? '\r\n' : '\n';
export const SNLE = Object.freeze('__«SKIP^NEW^LINE^IF^EMPTY»__');
export const nonWhitespace = /\S|$/;
/**
* A tag function that automatically aligns embedded multiline strings.
* Multiple lines are joined with the platform-specific line separator.
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns an aligned string that consists of the given parts
*/
export function expandToString(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string {
return internalExpandToString(EOL, staticParts, ...substitutions);
}
/**
* The same as {@link expandToString} but with support for coloring
* via the `chalk-template` package.
*
* A tag function that automatically aligns embedded multiline strings.
* Multiple lines are joined with the platform-specific line separator.
*
*
*
* @param staticParts the static parts of a tagged template literal
* @param substitutions the variable parts of a tagged template literal
* @returns an aligned string that consists of the given parts
*/
export function expandToChalkString(staticParts: TemplateStringsArray, ...substitutions: unknown[]): string {
return expandToString`${chalkTemplateStderr(staticParts, ...substitutions)}`;
}
function internalExpandToString(lineSep: string, staticParts: TemplateStringsArray, ...substitutions: unknown[]): string {
let lines = substitutions
// align substitutions and fuse them with static parts
.reduce((acc: string, subst: unknown, i: number) => acc + (subst === undefined ? SNLE : align(String(subst), acc)) + (staticParts[i + 1] ?? ''), staticParts[0])
// converts text to lines
.split(NEWLINE_REGEXP)
.filter(l => l.trim() !== SNLE)
// whitespace-only lines are empty (preserving leading whitespace)
.map(l => l.replace(SNLE, '').trimEnd());
// in order to nicely handle single line templates with the leading and trailing termintators (``) on separate lines, like
// expandToString`foo
// `,
// expandToString`
// foo
// `,
// expandToString`
// foo`,
// the same way as true single line templates like
// expandToString`foo`
// ...
// ... drop initial linebreak if the first line is empty or contains white space only, ...
const containsLeadingLinebreak = lines.length > 1 && lines[0].trim().length === 0;
lines = containsLeadingLinebreak ? lines.slice(1) : lines;
// .. and drop the last linebreak if it's the last charactor or is followed by white space
const containsTrailingLinebreak = lines.length !== 0 && lines[lines.length-1].trimEnd().length === 0;
lines = containsTrailingLinebreak ? lines.slice(0, lines.length-1) : lines;
// finds the minimum indentation
const indent = findIndentation(lines);
return lines
// shifts lines to the left
.map(line => line.slice(indent).trimEnd())
// convert lines to string
.join(lineSep);
}
// add the alignment of the previous static part to all lines of the following substitution
function align(subst: string, acc: string): string {
const length = Math.max(0, acc.length - acc.lastIndexOf('\n') - 1);
const indent = ' '.repeat(length);
return subst.replace(NEWLINE_REGEXP, EOL + indent);
}
// finds the indentation of a text block represented by a sequence of lines
export function findIndentation(lines: string[]): number {
const indents = lines.filter(line => line.length > 0).map(line => line.search(nonWhitespace));
const min = indents.length === 0 ? 0 : Math.min(...indents); // min(...[]) = min() = Infinity
return Math.max(0, min);
}
export function normalizeEOL(input: string): string {
return input.replace(NEWLINE_REGEXP, EOL);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment