Source code for abjad.string

import textwrap

import roman

from . import enums as _enums


[docs]def capitalize_start(string: str) -> str: """ Capitalizes start of string. Capitalizes only ``string[0]``; leaves noninitial characters unchanged: .. container:: example >>> abjad.string.capitalize_start("violin I") 'Violin I' Built-in ``str.capitalize()`` forces noninitial characters to lowercase: >>> "violin I".capitalize() 'Violin i' """ if not string: return string return string[0].upper() + string[1:]
[docs]def delimit_words(string: str, separate_caps: bool = False) -> list[str]: """ Delimits words in string. .. container:: example >>> abjad.string.delimit_words('scale degrees 4 and 5.') ['scale', 'degrees', '4', 'and', '5'] >>> abjad.string.delimit_words('scale degrees 4and5.') ['scale', 'degrees', '4', 'and', '5'] >>> abjad.string.delimit_words('scaleDegrees4and5.') ['scale', 'Degrees', '4', 'and', '5'] >>> abjad.string.delimit_words('ScaleDegrees4and 5.') ['Scale', 'Degrees', '4', 'and', '5'] >>> abjad.string.delimit_words('scale-degrees-4-and-5.') ['scale', 'degrees', '4', 'and', '5'] >>> abjad.string.delimit_words('SCALE_DEGREES_4_AND_5.') ['SCALE', 'DEGREES', '4', 'AND', '5'] .. container:: example >>> abjad.string.delimit_words('one < two') ['one', '<', 'two'] >>> abjad.string.delimit_words('one! two!') ['one', '!', 'two', '!'] .. container:: example Separates capital letters when keyword is true: >>> abjad.string.delimit_words('MRM') ['MRM'] >>> abjad.string.delimit_words("MRM", separate_caps=True) ['M', 'R', 'M'] >>> abjad.string.delimit_words('MRhM') ['MRh', 'M'] >>> abjad.string.delimit_words("MRhM", separate_caps=True) ['M', 'Rh', 'M'] """ wordlike_characters = ("<", ">", "!") words = [] current_word = "" for character in string: if ( not character.isalpha() and not character.isdigit() and character not in wordlike_characters ): if current_word: words.append(current_word) current_word = "" elif not current_word: current_word = current_word + character elif character.isupper(): if current_word[-1].isupper() and not separate_caps: current_word = current_word + character else: words.append(current_word) current_word = character elif character.islower(): if current_word[-1].isalpha(): current_word = current_word + character else: words.append(current_word) current_word = character elif character.isdigit(): if current_word[-1].isdigit(): current_word = current_word + character else: words.append(current_word) current_word = character elif character in wordlike_characters: if current_word[-1] in wordlike_characters: current_word = current_word + character else: words.append(current_word) current_word = character if current_word: words.append(current_word) return words
[docs]def from_roman(string: str) -> int: """ Changes string from Roman numeral to digits. .. container:: example >>> abjad.string.from_roman('IX') 9 >>> abjad.string.from_roman('ix') 9 Raises Roman numeral error when string is not Roman numeral: >>> abjad.string.from_roman('Allegro') Traceback (most recent call last): ... roman.InvalidRomanNumeralError: Invalid Roman numeral: Allegro """ if is_roman(string): return roman.fromRoman(string.upper()) number = roman.fromRoman(string) return number
[docs]def is_lilypond_identifier(string: str) -> bool: """ Is true when string starts with a letter and either 1. contains only letters and underscores thereafter, or 2. contains only letters, numbers and dots thereafter. .. container:: example >>> abjad.string.is_lilypond_identifier("ViolinOne") True >>> abjad.string.is_lilypond_identifier("Violin_One") True >>> abjad.string.is_lilypond_identifier("Violin One") False .. container:: example >>> abjad.string.is_lilypond_identifier("ViolinI") True >>> abjad.string.is_lilypond_identifier("Violin_I") True >>> abjad.string.is_lilypond_identifier("Violin I") False .. container:: example >>> abjad.string.is_lilypond_identifier("Violin1") False >>> abjad.string.is_lilypond_identifier("Violin_1") False >>> abjad.string.is_lilypond_identifier("Violin 1") False .. container:: example >>> abjad.string.is_lilypond_identifier("Violin.1") True >>> abjad.string.is_lilypond_identifier("Violin.1.1") False >>> abjad.string.is_lilypond_identifier("Violin.1_1") False >>> abjad.string.is_lilypond_identifier("Violin_1.1") False .. container:: example >>> abjad.string.is_lilypond_identifier("Violin.1.MusicVoice.1") True >>> abjad.string.is_lilypond_identifier("Violin.1.1.MusicVoice") False >>> abjad.string.is_lilypond_identifier("Violin.1_Music_Voice") False >>> abjad.string.is_lilypond_identifier("Violin.Music_Voice_1") False >>> abjad.string.is_lilypond_identifier("Violin.1_Music_Voice") False """ if not string: return False if string[0] in "_.": return False if string[0].isdigit(): return False for character in string: if not (character.isalnum() or character == "_" or character == "."): return False words = string.split(".") if words[0].isdigit(): return False previous_word_ends_in_digit = False for word in words: if previous_word_ends_in_digit and word[0].isdigit(): return False if any(_.isdigit() for _ in word): if any(not _.isdigit() for _ in word): return False if word[-1].isdigit(): previous_word_ends_in_digit = True else: previous_word_ends_in_digit = False return True
[docs]def is_roman(string: str) -> bool: """ Is true when string is Roman numeral. .. container:: example >>> abjad.string.is_roman('I') True >>> abjad.string.is_roman('II') True >>> abjad.string.is_roman('X') True >>> abjad.string.is_roman('XI') True >>> abjad.string.is_roman('C') True >>> abjad.string.is_roman('CI') True >>> abjad.string.is_roman('i') True >>> abjad.string.is_roman('F') False """ try: roman.fromRoman(string.upper()) return True except roman.InvalidRomanNumeralError: return False
[docs]def is_shout_case(string: str) -> bool: """ Is true when string and is shoutcase. .. container:: example >>> abjad.string.is_shout_case('FOO_BAR') True >>> abjad.string.is_shout_case('FooBar') False """ return string == to_shout_case(string)
[docs]def normalize(argument: str, indent: int | str | None = None) -> str: """ Normalizes string. .. container:: example >>> string = r''' ... foo ... bar ... ''' >>> print(string) <BLANKLINE> foo bar <BLANKLINE> >>> print(abjad.string.normalize(string)) foo bar >>> print(abjad.string.normalize(string, indent=4)) foo bar >>> print(abjad.string.normalize(string, indent='* ')) * foo * bar """ string = argument.replace("\t", " ") lines = string.split("\n") while lines and (not lines[0] or lines[0].isspace()): lines.pop(0) while lines and (not lines[-1] or lines[-1].isspace()): lines.pop() for i, line in enumerate(lines): lines[i] = line.rstrip() string = "\n".join(lines) string = textwrap.dedent(string) if indent: if isinstance(indent, str): indent_string = indent else: assert isinstance(indent, int) indent_string = abs(int(indent)) * " " lines = string.split("\n") for i, line in enumerate(lines): if line: lines[i] = f"{indent_string}{line}" string = "\n".join(lines) return string
[docs]def pluralize(string: str, count: int | None = None) -> str: """ Pluralizes English string. Changes terminal ``-y`` to ``-ies``: .. container:: example >>> abjad.string.pluralize('catenary') 'catenaries' Adds ``-es`` to terminal ``-s``, ``-sh``, ``-x`` and ``-z``: >>> abjad.string.pluralize('brush') 'brushes' Adds ``-s`` to all other strings: >>> abjad.string.pluralize('shape') 'shapes' .. container:: example Does not pluralize when ``count`` is 1: >>> abjad.string.pluralize('shape', count=1) 'shape' """ if count == 1: return string elif string.endswith("y"): result = string[:-1] + "ies" elif string.endswith(("s", "sh", "x", "z")): result = string + "es" else: result = string + "s" return result
[docs]def sort_roman(strings: list[str]) -> list[str]: """ Sorts strings containing Roman numerals. .. container:: example >>> strings = ['TromboneII', 'TromboneIII', 'TromboneI'] >>> abjad.string.sort_roman(strings) ['TromboneI', 'TromboneII', 'TromboneIII'] >>> strings = ['ViolinXI', 'ViolinX', 'ViolinIX'] >>> abjad.string.sort_roman(strings) ['ViolinIX', 'ViolinX', 'ViolinXI'] """ lists = [] for string in strings: list_ = [] for word in delimit_words(string): try: number = roman.fromRoman(word) list_.append(number) except roman.InvalidRomanNumeralError: list_.append(word) lists.append(list_) pairs = list(zip(strings, lists)) pairs.sort(key=lambda pair: pair[1]) strings_ = [pair[0] for pair in pairs] return strings_
[docs]def strip_roman(string: str) -> str: """ Strips roman numerals from right of string. .. container:: example >>> abjad.string.strip_roman('Trombone') 'Trombone' >>> abjad.string.strip_roman('TromboneI') 'Trombone' >>> abjad.string.strip_roman('TromboneII') 'Trombone' >>> abjad.string.strip_roman('TromboneIII') 'Trombone' >>> abjad.string.strip_roman('TromboneIV') 'Trombone' """ words = delimit_words(string) try: roman.fromRoman(words[-1]) words = words[:-1] except roman.InvalidRomanNumeralError: pass return "".join(words)
[docs]def to_shout_case(string: str) -> str: """ Changes string to shout case. .. container:: example >>> abjad.string.to_shout_case('scale degrees 4 and 5') 'SCALE_DEGREES_4_AND_5' >>> abjad.string.to_shout_case('scale_degrees_4_and_5') 'SCALE_DEGREES_4_AND_5' >>> abjad.string.to_shout_case('scale-degrees-4-and-5') 'SCALE_DEGREES_4_AND_5' >>> abjad.string.to_shout_case('ScaleDegrees4And5') 'SCALE_DEGREES_4_AND_5' """ words = delimit_words(string) words_ = [_.upper() for _ in words] string = "_".join(words_) return string
[docs]def to_tridirectional_lilypond_symbol( argument: int | str | None, ) -> str | None: """ Changes ``argument`` to tridirectional LilyPond symbol. .. container:: example >>> abjad.string.to_tridirectional_lilypond_symbol(abjad.UP) '^' >>> abjad.string.to_tridirectional_lilypond_symbol(abjad.DOWN) '_' >>> abjad.string.to_tridirectional_lilypond_symbol(1) '^' >>> abjad.string.to_tridirectional_lilypond_symbol(0) '-' >>> abjad.string.to_tridirectional_lilypond_symbol(-1) '_' >>> abjad.string.to_tridirectional_lilypond_symbol('^') '^' >>> abjad.string.to_tridirectional_lilypond_symbol('-') '-' >>> abjad.string.to_tridirectional_lilypond_symbol('_') '_' Returns none when ``argument`` is none: >>> abjad.string.to_tridirectional_lilypond_symbol(None) is None True """ if argument is None: return None elif argument in (_enums.UP, 1, "^"): return "^" elif argument in (_enums.DOWN, -1, "_"): return "_" elif argument in (_enums.CENTER, 0, "-"): return "-" else: raise ValueError(repr(argument))
[docs]def to_tridirectional_ordinal_constant( argument: int | str | None, ) -> _enums.Vertical | None: """ Changes ``argument`` to tridirectional ordinal constant. .. container:: example >>> abjad.string.to_tridirectional_ordinal_constant('^') <Vertical.UP: 1> >>> abjad.string.to_tridirectional_ordinal_constant('_') <Vertical.DOWN: -1> >>> abjad.string.to_tridirectional_ordinal_constant(1) <Vertical.UP: 1> >>> abjad.string.to_tridirectional_ordinal_constant(-1) <Vertical.DOWN: -1> >>> abjad.string.to_tridirectional_ordinal_constant(abjad.UP) <Vertical.UP: 1> >>> abjad.string.to_tridirectional_ordinal_constant(abjad.DOWN) <Vertical.DOWN: -1> >>> abjad.string.to_tridirectional_ordinal_constant(abjad.CENTER) <Vertical.CENTER: 0> >>> abjad.string.to_tridirectional_ordinal_constant(None) is None True """ if argument is None: return None elif argument in ("^", _enums.UP, 1): return _enums.UP elif argument in ("_", _enums.DOWN, -1): return _enums.DOWN elif argument in ("-", _enums.CENTER, 0): return _enums.CENTER else: raise ValueError(repr(argument))
[docs]def to_upper_camel_case(string: str) -> str: """ Changes string to upper camel case. .. container:: example >>> abjad.string.to_upper_camel_case('scale degrees 4 and 5') 'ScaleDegrees4And5' >>> abjad.string.to_upper_camel_case('scale_degrees_4_and_5') 'ScaleDegrees4And5' >>> abjad.string.to_upper_camel_case('scale-degrees-4-and-5') 'ScaleDegrees4And5' >>> abjad.string.to_upper_camel_case('ScaleDegrees4And5') 'ScaleDegrees4And5' """ words = delimit_words(string) words_ = [_.capitalize() for _ in words] string = "".join(words_) return string