Functional Weave
Code in Rust

text.initials@1.0.0

impl/typescript.ts

2,535 bytes · the TypeScript implementation · view raw

/**
 * The Unicode White_Space property, spelled out so all three languages agree:
 * JavaScript's \s, Python's str.isspace and Rust's char::is_whitespace each
 * draw the line slightly differently.
 */
function isWhitespace(cp: number): boolean {
  return (
    (cp >= 0x09 && cp <= 0x0d) ||
    cp === 0x20 ||
    cp === 0x85 ||
    cp === 0xa0 ||
    cp === 0x1680 ||
    (cp >= 0x2000 && cp <= 0x200a) ||
    cp === 0x2028 ||
    cp === 0x2029 ||
    cp === 0x202f ||
    cp === 0x205f ||
    cp === 0x3000
  );
}

/** Skipped, not counted, in the middle of a name: "Ludwig van Beethoven" is "LB". */
const PARTICLES = new Set([
  "van", "von", "de", "da", "di", "del", "della", "der", "den", "des", "du",
  "la", "le", "ter", "ten", "dos", "das", "do", "bin", "ibn",
]);

/** ASCII punctuation and typographic quotes are skipped at the start of a part. */
function isSkippable(ch: string): boolean {
  const cp = ch.codePointAt(0) as number;
  if (cp < 0x80) {
    const alnum = (cp >= 0x30 && cp <= 0x39) || (cp >= 0x41 && cp <= 0x5a) || (cp >= 0x61 && cp <= 0x7a);
    return !alnum;
  }
  return cp === 0x2018 || cp === 0x2019 || cp === 0x201c || cp === 0x201d;
}

/** One-to-one case mappings only, so "ß" stays "ß" in all three languages. */
function upperOne(ch: string): string {
  const mapped = ch.toUpperCase();
  return Array.from(mapped).length === 1 ? mapped : ch;
}

function lowerOne(ch: string): string {
  const mapped = ch.toLowerCase();
  return Array.from(mapped).length === 1 ? mapped : ch;
}

/** Initials from a personal name: "Jean-Paul Sartre" is "JPS". */
export function initials(name: string): string {
  if (typeof name !== "string") {
    throw new TypeError(`initials needs a string, received ${name}`);
  }

  const words: string[][] = [];
  let current: string[] = [];
  for (const ch of name) {
    if (isWhitespace(ch.codePointAt(0) as number)) {
      if (current.length) words.push(current);
      current = [];
    } else {
      current.push(ch);
    }
  }
  if (current.length) words.push(current);

  let out = "";
  words.forEach((word, i) => {
    if (i > 0 && i < words.length - 1 && PARTICLES.has(word.map(lowerOne).join(""))) return;
    // Hyphens and full stops split a word into parts that each give a letter.
    let atPartStart = true;
    for (const ch of word) {
      if (ch === "-" || ch === ".") {
        atPartStart = true;
      } else if (atPartStart && !isSkippable(ch)) {
        out += upperOne(ch);
        atPartStart = false;
      }
    }
  });
  return out;
}