All files to-filename.ts

100% Statements 39/39
100% Branches 7/7
100% Functions 1/1
100% Lines 39/39

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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 771x 1x 1x           1x                                                   1x 7x 7x 7x 7x 7x 7x 7x 7x 7x   7x 7x 7x 7x 7x 7x   7x 4x 4x 4x   7x 1x 1x   7x   7x 4x 4x   7x 1x 1x   7x 4x 4x 3x 3x  
import { clean } from './clean.ts';
import { collapseWhitespace } from './collapse-whitespace.ts';
import { empty } from './unicode.ts';
 
/**
 * Regular expression matching one or more invalid filename characters.
 * @internal
 */
const badChars = /[/\\:*?<>|.]+/gu;
 
/**
 * Options for the {@link toFilename} function
 * @group String
 * @category Operations
 */
export type FilenameOptions = {
  /** the file name will be truncated to this length */
  maxLength?: number;
  /** character to use to replace "bad" characters */
  replacement?: string;
  /** number of characters to preserve at the end of the filename when truncated (for disambiguation) */
  disambiguate?: number;
  /** string to separate the main section from the disambiguated section */
  separator?: string;
};
 
/**
 * Convert a string so that it can be used as a filename
 * @param input - The string to escape
 * @param options - see {@link FilenameOptions}
 * @returns the file name
 * @group String
 * @category Operations
 */
export function toFilename(
  input: string,
  { maxLength = 64, replacement = '-', disambiguate = 10, separator = '…' }: FilenameOptions = {},
): string {
  let argInput = input;
  let suffix = empty;
  const compress = new RegExp(
    `\\s*${RegExp.escape(replacement)}[\\s${RegExp.escape(replacement)}]*`,
    'ug',
  );
 
  argInput = clean(
    collapseWhitespace(
      argInput.normalize('NFC').replaceAll('"', "'").replaceAll(badChars, replacement),
    ).replaceAll(compress, replacement),
    replacement,
  );
 
  if (suffix.length === 0 && argInput.length > maxLength) {
    suffix = argInput.slice(-disambiguate);
    argInput = argInput.slice(0, Math.max(0, argInput.length - suffix.length));
  }
 
  if (suffix.length > maxLength) {
    suffix = suffix.slice(0, Math.max(0, maxLength));
  }
 
  const length = maxLength - suffix.length;
 
  if (argInput.length > length) {
    argInput = argInput.slice(0, Math.max(0, length));
  }
 
  if (argInput.length === 0) {
    argInput = replacement;
  }
 
  if (suffix.length > 0) {
    return argInput + separator + suffix;
  }
  return argInput;
}