main
Download
1052 lines 28.9 KiBYAMLModified 2026-03-14 raw
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052
Text.as_c_string:
  short: convert to C-style string
  description: >
    Converts a `Text` value to a C-style string.
  return:
    type: 'CString'
    description: >
      A C-style string (`CString`) representing the text.
  args:
    text:
      type: 'Text'
      description: >
        The text to be converted to a C-style string.
  example: |
    assert "Hello".as_c_string() == CString("Hello")

Text.at:
  short: get a letter
  description: >
    Get the graphical cluster at a given index. This is similar to `str[i]` with
    ASCII text, but has more correct behavior for unicode text.
  return:
    type: 'Text'
    description: >
      A `Text` with the single graphical cluster at the given index.
  note: >
    Negative indices are counted from the back of the text, so `-1` means the
    last cluster, `-2` means the second-to-last, and so on.
  args:
    text:
      type: 'Text'
      description: >
        The text from which to get a cluster.
    index:
      type: 'Int'
      description: >
        The index of the graphical cluster (1-indexed).
  example: |
    assert "Amélie".at(3) == "é"

Text.by_line:
  short: iterate by line
  description: >
    Returns an iterator function that can be used to iterate over the lines in a
    text.
  return:
    type: 'func(->Text?)'
    description: >
      An iterator function that returns one line at a time, until it runs out and
      returns `none`.
  note: >
    This function ignores a trailing newline if there is one. If you don't want
    this behavior, use `text.by_split($/{1 nl}/)` instead.
  args:
    text:
      type: 'Text'
      description: >
        The text to be iterated over, line by line.
  example: |
    text := "
        line one
        line two
    "
    lines := [line for line in text.by_line()]
    assert lines == ["line one", "line two"]

Text.by_split:
  short: iterate by a spliting text
  description: >
    Returns an iterator function that can be used to iterate over text separated by
    a delimiter.
  return:
    type: 'func(->Text?)'
    description: >
      An iterator function that returns one chunk of text at a time, separated by the
      given delimiter, until it runs out and returns `none`.
  args:
    text:
      type: 'Text'
      description: >
        The text to be iterated over in delimited chunks.
    delimiter:
      type: 'Text'
      default: '""'
      description: >
        An exact delimiter to use for splitting the text.
  note: >
    To split based on a set of delimiters, use Text.by_split_any().

    If an empty text is given as the delimiter, then each split will be the
    graphical clusters of the text.
  example: |
    text := "one,two,three"
    chunks := [chunk for chunk in text.by_split(",")]
    assert chunks == ["one", "two", "three"]

Text.by_split_any:
  short: iterate by one of many splitting characters
  description: >
    Returns an iterator function that can be used to iterate over text separated by
    one or more characters (grapheme clusters) from a given text of delimiters.
  return:
    type: 'func(->Text?)'
    description: >
      An iterator function that returns one chunk of text at a time, separated by the
      given delimiter characters, until it runs out and returns `none`.
  args:
    text:
      type: 'Text'
      description: >
        The text to be iterated over in delimited chunks.
    delimiters:
      type: 'Text'
      default: '" $\t\r\n"'
      description: >
        Grapheme clusters to use for splitting the text.
  note: >
    Splitting will occur on every place where one or more of the grapheme
    clusters in `delimiters` occurs.

    To split based on an exact delimiter, use Text.by_split().
  example: |
    text := "one,two,;,three"
    chunks := [chunk for chunk in text.by_split_any(",;")]
    assert chunks == ["one", "two", "three"]

Text.utf8:
  short: get UTF8 bytes
  description: >
    Converts a `Text` value to a list of bytes representing a UTF8 encoding of
    the text.
  return:
    type: '[Byte]'
    description: >
      A list of bytes (`[Byte]`) representing the text in UTF8 encoding.
  args:
    text:
      type: 'Text'
      description: >
        The text to be converted to UTF8 bytes.
  example: |
    assert "Amélie".utf8() == [65, 109, 195, 169, 108, 105, 101]

Text.caseless_equals:
  short: case-insensitive comparison
  description: >
    Checks whether two texts are equal, ignoring the casing of the letters (i.e.
    case-insensitive comparison).
  return:
    type: 'Bool'
    description: >
      `yes` if `a` and `b` are equal to each other, ignoring casing, otherwise `no`.
  args:
    a:
      type: 'Text'
      description: >
        The first text to compare case-insensitively.
    b:
      type: 'Text'
      description: >
        The second text to compare case-insensitively.
    language:
      type: 'Text'
      default: '"C"'
      description: >
        The ISO 639 language code for which casing rules to use.
  example: |
    assert "A".caseless_equals("a") == yes

    # Turkish lowercase "I" is "ı" (dotless I), not "i"
    assert "I".caseless_equals("i", language="tr_TR") == no

Text.codepoint_names:
  short: get unicode codepoint names
  description: >
    Returns a list of the names of each codepoint in the text.
  return:
    type: '[Text]'
    description: >
      A list of codepoint names (`[Text]`).
  args:
    text:
      type: 'Text'
      description: >
        The text from which to extract codepoint names.
  example: |
    assert "Amélie".codepoint_names() == [
        "LATIN CAPITAL LETTER A",
        "LATIN SMALL LETTER M",
        "LATIN SMALL LETTER E WITH ACUTE",
        "LATIN SMALL LETTER L",
        "LATIN SMALL LETTER I",
        "LATIN SMALL LETTER E",
    ]

Text.ends_with:
  short: check suffix
  description: >
    Checks if the `Text` ends with a literal suffix text.
  return:
    type: 'Bool'
    description: >
      `yes` if the text has the target, `no` otherwise.
  args:
    text:
      type: 'Text'
      description: >
        The text to be searched.
    suffix:
      type: 'Text'
      description: >
        The literal suffix text to check for.
    remainder:
      type: '&Text?'
      default: 'none'
      description: >
        If non-none, this value will be set to the rest of the text up to the trailing suffix.
        If the suffix is not found, this value will be set to the original text.
  example: |
    assert "hello world".ends_with("world") == yes
    remainder : Text
    assert "hello world".ends_with("world", &remainder) == yes
    assert remainder == "hello "

Text.distance:
  short: distance between two texts
  description: >
    Get an approximate distance between two texts, such that when the distance
    is small, the texts are similar and when the distance is large, the texts
    are dissimilar.
  note: >
    The exact distance algorithm is not specified and may be subject to change
    over time.
  return:
    type: 'Num'
    description: >
      The distance between the two texts (larger means more dissimilar).
  args:
    a:
      type: 'Text'
      description: >
        The first text to compare.
    b:
      type: 'Text'
      description: >
        The second text to compare.
    language:
      type: 'Text'
      default: '"C"'
      description: >
        The ISO 639 language code for which character width to use.
  example: |
    assert "hello".distance("hello") == 0
    texts := &["goodbye", "hello", "hallo"]
    texts.sort(func(a,b:&Text) a.distance("hello") <> b.distance("hello"))
    assert texts == ["hello", "hallo", "goodbye"]

Text.find:
  short: find a substring
  description: >
    Find a substring within a text and return its index, if found.
  return:
    type: 'Int'
    description: >
      The index where the first occurrence of `target` appears, or `none` if it is not found.
  args:
    text:
      type: 'Text'
      description: >
        The text to be searched.
    target:
      type: 'Text'
      description: >
        The target text to find.
    start:
      type: 'Int'
      default: '1'
      description: >
        The index at which to begin searching.
  example: |
    assert "one two".find("one") == 1
    assert "one two".find("two") == 5
    assert "one two".find("three") == none
    assert "one two".find("o", start=2) == 7

Text.from:
  short: slice from a starting index
  description: >
    Get a slice of the text, starting at the given position.
  return:
    type: 'Text'
    description: >
      The text from the given grapheme cluster to the end of the text.
  note: >
    A negative index counts backwards from the end of the text, so `-1` refers
    to the last cluster, `-2` the second-to-last, etc. Slice ranges will be
    truncated to the length of the text.
  args:
    text:
      type: 'Text'
      description: >
        The text to be sliced.
    first:
      type: 'Int'
      description: >
        The index to begin the slice.
  example: |
    assert "hello".from(2) == "ello"
    assert "hello".from(-2) == "lo"

Text.from_utf8:
  short: convert UTF8 byte list to text
  description: >
    Returns text that has been constructed from the given UTF8 bytes.
  note: >
    The text will be normalized, so the resulting text's UTF8 bytes may not
    exactly match the input.
  return:
    type: '[Text]'
    description: >
      A new text based on the input UTF8 bytes after normalization has been applied.
  args:
    bytes:
      type: '[Byte]'
      description: >
        The UTF-8 bytes of the desired text.
  example: |
    assert Text.from_utf8([195, 133, 107, 101]) == "Åke"

Text.from_utf16:
  short: convert UTF16 list to text
  description: >
    Returns text that has been constructed from the given UTF16 sequence.
  note: >
    The text will be normalized, so the resulting text's UTF16 sequence may not
    exactly match the input.
  return:
    type: '[Text]'
    description: >
      A new text based on the input UTF16 sequence after normalization has been applied.
  args:
    bytes:
      type: '[Int16]'
      description: >
        The UTF-16 integers of the desired text.
  example: |
    assert Text.from_utf16([197, 107, 101]) == "Åke"
    assert Text.from_utf16([12371, 12435, 12395, 12385, 12399, 19990, 30028]) == "こんにちは世界"

Text.from_c_string:
  short: convert C-style string to text
  description: >
    Converts a C-style string to a `Text` value.
  return:
    type: 'Text'
    description: >
      A `Text` value representing the C-style string.
  args:
    str:
      type: 'CString'
      description: >
        The C-style string to be converted.
  example: |
    assert Text.from_c_string(CString("Hello")) == "Hello"

Text.from_codepoint_names:
  short: convert list of unicode codepoint names to text
  description: >
    Returns text that has the given codepoint names (according to the Unicode
    specification) as its codepoints.
  note: >
    The text will be normalized, so the resulting text's codepoints may not
    exactly match the input codepoints.
  return:
    type: '[Text]'
    description: >
      A new text with the specified codepoints after normalization has been applied.
      Any invalid names are ignored.
  args:
    codepoint_names:
      type: '[Text]'
      description: >
        The names of each codepoint in the desired text (case-insentive).
  example: |
    text := Text.from_codepoint_names([
        "LATIN CAPITAL LETTER A WITH RING ABOVE",
        "LATIN SMALL LETTER K",
        "LATIN SMALL LETTER E",
    ])
    assert text == "Åke"

Text.from_utf32:
  short: convert UTF32 codepoints to text
  description: >
    Returns text that has been constructed from the given UTF32 codepoints.
  note: >
    The text will be normalized, so the resulting text's codepoints may not
    exactly match the input codepoints.
  return:
    type: '[Text]'
    description: >
      A new text with the specified codepoints after normalization has been applied.
  args:
    codepoints:
      type: '[Int32]'
      description: >
        The UTF32 codepoints in the desired text.
  example: |
    assert Text.from_utf32([197, 107, 101]) == "Åke"

Text.has:
  short: check for substring
  description: >
    Checks if the `Text` contains some target text.
  return:
    type: 'Bool'
    description: >
      `yes` if the target text is found, `no` otherwise.
  args:
    text:
      type: 'Text'
      description: >
        The text to be searched.
    target:
      type: 'Text'
      description: >
        The text to search for.
  example: |
    assert "hello world".has("wo") == yes
    assert "hello world".has("xxx") == no

Text.join:
  short: concatenate with separator
  description: >
    Joins a list of text pieces with a specified glue.
  return:
    type: 'Text'
    description: >
      A single `Text` value with the pieces joined by the glue.
  args:
    glue:
      type: 'Text'
      description: >
        The text used to join the pieces.
    pieces:
      type: '[Text]'
      description: >
        The list of text pieces to be joined.
  example: |
    assert ", ".join(["one", "two", "three"]) == "one, two, three"

Text.matches_glob:
  short: check if text matches a glob
  description: >
    Return whether or not the text matches the given glob.
  return:
    type: 'Bool'
    description: >
      Whether or not the text matches the given glob.
  args:
    path:
      type: 'Text'
      description: >
        The text to check.
    glob:
      type: 'Text'
      description: >
        The glob pattern to check.
  example: |
    assert "hello world".matches_glob("h* *d")

Text.middle_pad:
  short: pad text, centered
  description: >
    Pad some text on the left and right side so it reaches a target width.
  return:
    type: 'Text'
    description: >
      Text with length at least `width`, with extra padding on the left and right as
      needed. If `pad` has length greater than 1, it may be partially repeated to
      reach the exact desired length.
  args:
    text:
      type: 'Text'
      description: >
        The text to pad.
    width:
      type: 'Int'
      description: >
        The target width.
    pad:
      type: 'Text'
      default: '" "'
      description: >
        The padding text.
    language:
      type: 'Text'
      default: '"C"'
      description: >
        The ISO 639 language code for which character width to use.
  example: |
    assert "x".middle_pad(6) == "  x   "
    assert "x".middle_pad(10, "ABC") == "ABCAxABCAB"

Text.left_pad:
  short: left-pad text
  description: >
    Pad some text on the left side so it reaches a target width.
  return:
    type: 'Text'
    description: >
      Text with length at least `width`, with extra padding on the left as needed. If
      `pad` has length greater than 1, it may be partially repeated to reach the
      exact desired length.
  args:
    text:
      type: 'Text'
      description: >
        The text to pad.
    width:
      type: 'Int'
      description: >
        The target width.
    pad:
      type: 'Text'
      default: '" "'
      description: >
        The padding text.
    language:
      type: 'Text'
      default: '"C"'
      description: >
        The ISO 639 language code for which character width to use.
  example: |
    assert "x".left_pad(5) == "    x"
    assert "x".left_pad(5, "ABC") == "ABCAx"

Text.lines:
  short: get list of lines
  description: >
    Splits the text into a list of lines of text, preserving blank lines,
    ignoring trailing newlines, and handling `\r\n` the same as `\n`.
  return:
    type: '[Text]'
    description: >
      A list of substrings resulting from the split.
  args:
    text:
      type: 'Text'
      description: >
        The text to be split into lines.
  example: |
    assert "one\ntwo\nthree".lines() == ["one", "two", "three"]
    assert "one\ntwo\nthree\n".lines() == ["one", "two", "three"]
    assert "one\ntwo\nthree\n\n".lines() == ["one", "two", "three", ""]
    assert "one\r\ntwo\r\nthree\r\n".lines() == ["one", "two", "three"]
    assert "".lines() == []

Text.lower:
  short: convert to lowercase
  description: >
    Converts all characters in the text to lowercase.
  return:
    type: 'Text'
    description: >
      The lowercase version of the text.
  args:
    text:
      type: 'Text'
      description: >
        The text to be converted to lowercase.
    language:
      type: 'Text'
      default: '"C"'
      description: >
        The ISO 639 language code for which casing rules to use.
  example: |
    assert "AMÉLIE".lower() == "amélie"
    assert "I".lower(language="tr_TR") == "ı"

Text.quoted:
  short: add quotation marks and escapes
  description: >
    Formats the text with quotation marks and escapes.
  return:
    type: 'Text'
    description: >
      The text formatted as a quoted text.
  args:
    text:
      type: 'Text'
      description: >
        The text to be quoted.
    color:
      type: 'Bool'
      default: 'no'
      description: >
        Whether to add color formatting.
    quotation_mark:
      type: 'Text'
      default: '`"`'
      description: >
        The quotation mark to use.
  example: |
    assert "one\ntwo".quoted() == "\"one\\ntwo\""

Text.repeat:
  short: repeat text
  description: >
    Repeat some text multiple times.
  return:
    type: 'Text'
    description: >
      The text repeated the given number of times.
  args:
    text:
      type: 'Text'
      description: >
        The text to repeat.
    count:
      type: 'Int'
      description: >
        The number of times to repeat it. (Negative numbers are equivalent to zero).
  example: |
    assert "Abc".repeat(3) == "AbcAbcAbc"

Text.replace:
  short: replace a substring
  description: >
    Replaces occurrences of a target text with a replacement text.
  return:
    type: 'Text'
    description: >
      The text with occurrences of the target replaced.
  args:
    text:
      type: 'Text'
      description: >
        The text in which to perform replacements.
    target:
      type: 'Text'
      description: >
        The target text to be replaced.
    replacement:
      type: 'Text'
      description: >
        The text to replace the target with.
  example: |
    assert "Hello world".replace("world", "there") == "Hello there"

Text.reversed:
  short: get a reversed copy
  description: >
    Return a text that has the grapheme clusters in reverse order.
  return:
    type: 'Text'
    description: >
      A reversed version of the text.
  args:
    text:
      type: 'Text'
      description: >
        The text to reverse.
  example: |
    assert "Abc".reversed() == "cbA"

Text.right_pad:
  short: right-pad text
  description: >
    Pad some text on the right side so it reaches a target width.
  return:
    type: 'Text'
    description: >
      Text with length at least `width`, with extra padding on the right as needed. If
      `pad` has length greater than 1, it may be partially repeated to reach the
      exact desired length.
  args:
    text:
      type: 'Text'
      description: >
        The text to pad.
    width:
      type: 'Int'
      description: >
        The target width.
    pad:
      type: 'Text'
      default: '" "'
      description: >
        The padding text.
    language:
      type: 'Text'
      default: '"C"'
      description: >
        The ISO 639 language code for which character width to use.
  example: |
    assert "x".right_pad(5) == "x    "
    assert "x".right_pad(5, "ABC") == "xABCA"

Text.slice:
  short: get a slice of a text
  description: >
    Get a slice of the text.
  return:
    type: 'Text'
    description: >
      The text that spans the given grapheme cluster indices.
  note: >
    A negative index counts backwards from the end of the text, so `-1` refers
    to the last cluster, `-2` the second-to-last, etc. Slice ranges will be
    truncated to the length of the text.
  args:
    text:
      type: 'Text'
      description: >
        The text to be sliced.
    from:
      type: 'Int'
      default: '1'
      description: >
        The index of the first grapheme cluster to include (1-indexed).
    to:
      type: 'Int'
      default: '-1'
      description: >
        The index of the last grapheme cluster to include (1-indexed).
  example: |
    assert "hello".slice(2, 3) == "el"
    assert "hello".slice(to=-2) == "hell"
    assert "hello".slice(from=2) == "ello"

Text.split:
  short: split a text by a delimiter
  description: >
    Splits the text into a list of substrings based on exact matches of a delimiter.
  return:
    type: '[Text]'
    description: >
      A list of subtexts resulting from the split.
  args:
    text:
      type: 'Text'
      description: >
        The text to be split.
    delimiter:
      type: 'Text'
      default: '""'
      description: >
        The delimiter used to split the text.
  note: >
    To split based on a set of delimiters, use Text.split_any().

    If an empty text is given as the delimiter, then each split will be the
    graphical clusters of the text.

  example: |
    assert "one,two,,three".split(",") == ["one", "two", "", "three"]
    assert "abc".split() == ["a", "b", "c"]

Text.split_any:
  short: split a text by multiple delimiters
  description: >
    Splits the text into a list of substrings at one or more occurrences of a set
    of delimiter characters (grapheme clusters).
  return:
    type: '[Text]'
    description: >
      A list of subtexts resulting from the split.
  args:
    text:
      type: 'Text'
      description: >
        The text to be split.
    delimiters:
      type: 'Text'
      default: '" $\t\r\n"'
      description: >
        A text containing delimiters to use for splitting the text.
  note: >
    Splitting will occur on every place where one or more of the grapheme
    clusters in `delimiters` occurs.

    To split based on an exact delimiter, use Text.split().
  example: |
    assert "one, two,,three".split_any(", ") == ["one", "two", "three"]

Text.starts_with:
  short: check prefix
  description: >
    Checks if the `Text` starts with a literal prefix text.
  return:
    type: 'Bool'
    description: >
      `yes` if the text has the given prefix, `no` otherwise.
  args:
    text:
      type: 'Text'
      description: >
        The text to be searched.
    prefix:
      type: 'Text'
      description: >
        The literal prefix text to check for.
    remainder:
      type: '&Text?'
      default: 'none'
      description: >
        If non-none, this value will be set to the rest of the text after the prefix.
        If the prefix is not found, this value will be set to the original text.
  example: |
    assert "hello world".starts_with("hello") == yes
    remainder : Text
    assert "hello world".starts_with("hello", &remainder) == yes
    assert remainder == " world"

Text.title:
  short: titlecase
  description: >
    Converts the text to title case (capitalizing the first letter of each word).
  return:
    type: 'Text'
    description: >
      The text in title case.
  args:
    text:
      type: 'Text'
      description: >
        The text to be converted to title case.
    language:
      type: 'Text'
      default: '"C"'
      description: >
        The ISO 639 language code for which casing rules to use.
  example: |
    assert "amélie".title() == "Amélie"

    # In Turkish, uppercase "i" is "İ"
    assert "i".title(language="tr_TR") == "İ"

Text.to:
  short: slice to an end index
  description: >
    Get a slice of the text, ending at the given position.
  return:
    type: 'Text'
    description: >
      The text up to and including the given grapheme cluster.
  note: >
    A negative index counts backwards from the end of the text, so `-1` refers
    to the last cluster, `-2` the second-to-last, etc. Slice ranges will be
    truncated to the length of the text.
  args:
    text:
      type: 'Text'
      description: >
        The text to be sliced.
    last:
      type: 'Int'
      description: >
        The index of the last grapheme cluster to include (1-indexed).
  example: |
    assert "goodbye".to(3) == "goo"
    assert "goodbye".to(-2) == "goodby"

Text.translate:
  short: perform multiple replacements
  description: >
    Takes a table mapping target texts to their replacements and performs all the
    replacements in the table on the whole text. At each position, the first
    matching replacement is applied and the matching moves on to *after* the
    replacement text, so replacement text is not recursively modified. See
    Text.replace() for more information about replacement behavior.
  return:
    type: 'Text'
    description: >
      The text with all occurrences of the targets replaced with their corresponding
      replacement text.
  args:
    text:
      type: 'Text'
      description: >
        The text to be translated.
    translations:
      type: '{Text:Text}'
      description: >
        A table mapping from target text to its replacement.
  example: |
    text := "A <tag> & an ampersand".translate({
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': "&quot",
        "'": "&#39;",
    })
    assert text == "A &lt;tag&gt; &amp; an ampersand"

Text.trim:
  short: trim characters
  description: >
    Trims the given characters (grapheme clusters) from the left and/or right side of the text.
  return:
    type: 'Text'
    description: >
      The text without the trim characters at either end.
  args:
    text:
      type: 'Text'
      description: >
        The text to be trimmed.
    to_trim:
      type: 'Text'
      default: '" $\t\r\n"'
      description: >
        The characters to remove from the left/right of the text.
    left:
      type: 'Bool'
      default: 'yes'
      description: >
        Whether or not to trim from the front of the text.
    right:
      type: 'Bool'
      default: 'yes'
      description: >
        Whether or not to trim from the back of the text.
  example: |
    assert "   x y z    \n".trim() == "x y z"
    assert "one,".trim(",") == "one"
    assert "   xyz   ".trim(right=no) == "xyz   "

Text.upper:
  short: uppercase
  description: >
    Converts all characters in the text to uppercase.
  return:
    type: 'Text'
    description: >
      The uppercase version of the text.
  args:
    text:
      type: 'Text'
      description: >
        The text to be converted to uppercase.
    language:
      type: 'Text'
      default: '"C"'
      description: >
        The ISO 639 language code for which casing rules to use.
  example: |
    assert "amélie".upper() == "AMÉLIE"

    # In Turkish, uppercase "i" is "İ"
    assert "i".upper(language="tr_TR") == "İ"

Text.utf16:
  short: get UTF16 codepoints
  description: >
    Returns a list of Unicode code points for UTF16 encoding of the text.
  return:
    type: '[Int16]'
    description: >
      A list of 16-bit integer Unicode code points (`[Int16]`).
  args:
    text:
      type: 'Text'
      description: >
        The text from which to extract Unicode code points.
  example: |
    assert "Åke".utf16() == [197, 107, 101]
    assert "こんにちは世界".utf16() == [12371, 12435, 12395, 12385, 12399, 19990, 30028]

Text.utf32:
  short: get UTF32 codepoints
  description: >
    Returns a list of Unicode code points for UTF32 encoding of the text.
  return:
    type: '[Int32]'
    description: >
      A list of 32-bit integer Unicode code points (`[Int32]`).
  args:
    text:
      type: 'Text'
      description: >
        The text from which to extract Unicode code points.
  example: |
    assert "Amélie".utf32() == [65, 109, 233, 108, 105, 101]

Text.width:
  short: get display width
  description: >
    Returns the display width of the text as seen in a terminal with appropriate
    font rendering. This is usually the same as the text's `.length`, but there are
    some characters like emojis that render wider than 1 cell.
  note: >
    This will not always be exactly accurate when your terminal's font
    rendering can't handle some unicode displaying correctly.
  return:
    type: 'Int'
    description: >
      An integer representing the display width of the text.
  args:
    text:
      type: 'Text'
      description: >
        The text whose length you want.
  example: |
    assert "Amélie".width() == 6
    assert "🤠".width() == 2

Text.without_prefix:
  short: remove prefix
  description: >
    Returns the text with a given prefix removed (if present).
  return:
    type: 'Text'
    description: >
      A text without the given prefix (if present) or the unmodified text if the
      prefix is not present.
  args:
    text:
      type: 'Text'
      description: >
        The text to remove the prefix from.
    prefix:
      type: 'Text'
      description: >
        The prefix to remove.
  example: |
    assert "foo:baz".without_prefix("foo:") == "baz"
    assert "qux".without_prefix("foo:") == "qux"

Text.without_suffix:
  short: remove suffix
  description: >
    Returns the text with a given suffix removed (if present).
  return:
    type: 'Text'
    description: >
      A text without the given suffix (if present) or the unmodified text if the
      suffix is not present.
  args:
    text:
      type: 'Text'
      description: >
        The text to remove the suffix from.
    suffix:
      type: 'Text'
      description: >
        The suffix to remove.
  example: |
    assert "baz.foo".without_suffix(".foo") == "baz"
    assert "qux".without_suffix(".foo") == "qux"