import dataclasses
import typing
from . import tag as _tag
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Tweak:
"""
Tweak.
"""
string: str
tag: _tag.Tag | None = None
[docs] def __post_init__(self):
assert isinstance(self.string, str), repr(self.string)
if self.tag is not None:
assert isinstance(self.tag, _tag.Tag), repr(self.tag)
self._parse()
def _list_contributions(self):
result = []
deactivate = False
strings = [self.string]
if self.tag is not None:
strings = _tag.double_tag(strings, self.tag, deactivate=deactivate)
result.extend(strings)
return result
def _parse(self):
parts = self.string.split()
post_event = False
if parts[0] == "-":
post_event = True
parts.pop(0)
assert parts[0] == r"\tweak", repr(self.string)
parts.pop(0)
attribute = parts[0]
parts.pop(0)
value = " ".join(parts)
return post_event, attribute, value
[docs] def attribute(self) -> str:
post_event, attribute, value = self._parse()
return attribute
[docs] def post_event(self) -> bool:
post_event, attribute, value = self._parse()
return post_event
[docs] def value(self) -> str:
post_event, attribute, value = self._parse()
return value
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Bundle:
r"""
Bundled indicator.
.. container:: example
Raises exception on duplicate attributes:
>>> abjad.Bundle(
... indicator=abjad.Articulation("."),
... tweaks=(
... abjad.Tweak(r"- \tweak color #blue"),
... abjad.Tweak(r"- \tweak color #red"),
... ),
... )
Traceback (most recent call last):
...
Exception: duplicate 'color' attribute.
"""
indicator: typing.Any
tweaks: tuple[Tweak, ...] = ()
[docs] def __post_init__(self):
assert not isinstance(self.indicator, Bundle), repr(self.indicator)
assert isinstance(self.tweaks, tuple), repr(self.tweaks)
assert all(isinstance(_, Tweak) for _ in self.tweaks)
attributes = [_.attribute() for _ in self.tweaks]
for attribute in attributes:
if 1 < attributes.count(attribute):
raise Exception(f"duplicate {attribute!r} attribute.")
def _get_contributions(self, *, component=None, wrapper=None):
try:
contributions = self.indicator._get_contributions(
component=component, wrapper=wrapper
)
except TypeError:
component = component or wrapper.component
contributions = self.indicator._get_contributions(component=component)
lists = contributions.get_contribution_lists()
if len(lists) == 2 and ["<>"] in lists:
lists.remove(["<>"])
if len(lists) == 2 and [r"\pitchedTrill"] in lists:
lists.remove([r"\pitchedTrill"])
assert len(lists) == 1, repr(lists)
list_ = lists[0]
strings = []
for tweak in sorted(self.tweaks):
strings.extend(tweak._list_contributions())
if 2 <= len(list_) and list_[-2] in ("^", "_", "-"):
list_[-2:-2] = strings
else:
list_[-1:-1] = strings
return contributions
[docs] def get_attribute(self, attribute: str) -> Tweak | None:
r"""
Gets tweak with ``attribute``.
.. container:: example
>>> markup = abjad.Markup(r"\markup Allegro")
>>> bundle = abjad.bundle(
... markup,
... r"- \tweak color #red",
... r"- \tweak font-size 3",
... )
>>> bundle.get_attribute("color")
Tweak(string='- \\tweak color #red', tag=None)
>>> bundle.get_attribute("style") is None
True
"""
tweaks = [_ for _ in self.tweaks if _.attribute() == attribute]
assert len(tweaks) in (0, 1)
if tweaks:
tweak = tweaks[0]
return tweak
return None
[docs] def remove(self, tweak: Tweak) -> "Bundle":
r"""
Removes ``tweak`` from bundle and returns new bundle.
.. container:: example
>>> markup = abjad.Markup(r"\markup Allegro")
>>> bundle_1 = abjad.bundle(markup, r"- \tweak color #red")
>>> tweak = bundle_1.get_attribute("color")
>>> bundle_2 = bundle_1.remove(tweak)
>>> bundle_2
Bundle(indicator=Markup(string='\\markup Allegro'), tweaks=())
>>> bundle_3 = abjad.bundle(bundle_2, r"- \tweak color #blue")
>>> bundle_3.tweaks
(Tweak(string='- \\tweak color #blue', tag=None),)
"""
assert tweak in self.tweaks, repr(tweak)
tweaks = list(self.tweaks)
tweaks.remove(tweak)
new_bundle = dataclasses.replace(self, tweaks=tuple(tweaks))
return new_bundle
[docs]def bundle(
indicator: typing.Any,
*tweaks: str | Tweak,
tag: _tag.Tag | None = None,
overwrite: bool = False,
) -> Bundle:
r"""
Bundles ``indicator`` with ``tweaks``.
Bundles indicator:
.. container:: example
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> bundle = abjad.bundle(
... abjad.Articulation("."),
... r"- \tweak color #red",
... )
>>> abjad.attach(bundle, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
- \tweak color #red
- \staccato
d'4
e'4
f'4
}
.. container:: example
Bundles existing bundle:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> bundle = abjad.bundle(abjad.Articulation("."), r"- \tweak color #red")
>>> bundle = abjad.bundle(bundle, r"- \tweak font-size 3")
>>> abjad.attach(bundle, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
- \tweak color #red
- \tweak font-size 3
- \staccato
d'4
e'4
f'4
}
.. container:: example
Raises exception on duplicate attribute:
>>> bundle = abjad.bundle(
... abjad.Articulation("."),
... r"- \tweak color #blue",
... r"- \tweak color #red",
... )
Traceback (most recent call last):
...
Exception: duplicate 'color' attribute:
Tweak(string='- \\tweak color #blue', tag=None)
Tweak(string='- \\tweak color #red', tag=None)
Unless ``overwrite=True``:
>>> bundle = abjad.bundle(
... abjad.Articulation("."),
... r"- \tweak color #blue",
... r"- \tweak color #red",
... overwrite=True,
... )
>>> for _ in bundle.tweaks: _
Tweak(string='- \\tweak color #red', tag=None)
.. container:: example
Also raises exception on duplicate attribute:
>>> bundle = abjad.bundle(
... abjad.Articulation("."),
... r"- \tweak color #blue",
... )
>>> bundle = abjad.bundle(
... bundle,
... r"- \tweak color #red",
... )
Traceback (most recent call last):
...
Exception: duplicate 'color' attribute:
OLD: Tweak(string='- \\tweak color #blue', tag=None)
NEW: Tweak(string='- \\tweak color #red', tag=None)
Unless ``overwrite=True``:
>>> bundle = abjad.bundle(
... abjad.Articulation("."),
... r"- \tweak color #blue",
... )
>>> bundle = abjad.bundle(
... bundle,
... r"- \tweak color #red",
... overwrite=True,
... )
>>> for _ in bundle.tweaks: _
Tweak(string='- \\tweak color #red', tag=None)
"""
input_tweaks: list[Tweak] = []
for item in tweaks:
if isinstance(item, Tweak):
tweak = item
else:
assert isinstance(item, str)
tweak = Tweak(item, tag=tag)
tweak_attribute = tweak.attribute()
for input_tweak in input_tweaks[:]:
if input_tweak.attribute() == tweak_attribute:
if overwrite is True:
input_tweaks.remove(input_tweak)
else:
message = f"duplicate {tweak_attribute!r} attribute:\n"
message += repr(input_tweak) + "\n"
message += repr(tweak)
raise Exception(message)
input_tweaks.append(tweak)
if isinstance(indicator, Bundle):
bundle_tweaks = list(indicator.tweaks)
for input_tweak in input_tweaks:
input_tweak_attribute = input_tweak.attribute()
for bundle_tweak in bundle_tweaks[:]:
if bundle_tweak.attribute() == input_tweak_attribute:
if overwrite is True:
bundle_tweaks.remove(bundle_tweak)
else:
message = f"duplicate {input_tweak.attribute()!r} attribute:\n"
message += f"OLD: {bundle_tweak!r}\n"
message += f"NEW: {input_tweak!r}"
raise Exception(message)
bundle_tweaks.append(input_tweak)
indicator = indicator.indicator
input_tweaks = bundle_tweaks
input_tweaks.sort()
return Bundle(indicator, tweaks=tuple(input_tweaks))
[docs]def tweak(
indicator: typing.Any,
*tweaks: str | Tweak,
overwrite: bool = False,
tag: _tag.Tag | None = None,
) -> None:
"""
Appends ``tweaks`` to ``indicator``.
"""
from . import score as _score
prototype = (
_score.NoteHead,
_score.Tuplet,
)
assert isinstance(indicator, prototype), repr(indicator)
try:
tweaks_ = list(indicator.tweaks)
except AttributeError:
raise Exception(indicator)
if tag is not None:
assert isinstance(tag, _tag.Tag), repr(tag)
for item in tweaks:
duplicate = False
if isinstance(item, Tweak):
tweak = item
if tag is not None:
tweak = Tweak(tweak.string, tag=tag)
else:
assert isinstance(item, str), repr(item)
if getattr(indicator, "post_event", False) and not item.startswith("-"):
name = type(indicator).__name__
message = (
f"Must prefix {name} (LilyPond 'post-event') tweak with hyphen."
)
raise Exception(message)
if item.startswith("-") and not getattr(indicator, "post_event", False):
name = type(indicator).__name__
message = f"Must not prefix {name} tweak with hyphen."
raise Exception(message)
tweak = Tweak(item, tag=tag)
for existing_tweak in tweaks_[:]:
if existing_tweak == tweak:
duplicate = True
continue
if existing_tweak.attribute() == tweak.attribute():
if overwrite is True:
tweaks_.remove(existing_tweak)
else:
message = "conflicting tweaks:\n"
message += f" {existing_tweak.string}\n"
message += f" {tweak.string}"
raise Exception(message)
if not duplicate:
tweaks_.append(tweak)
tweaks_.sort()
try:
indicator.tweaks = tuple(tweaks_)
except dataclasses.FrozenInstanceError:
raise Exception(indicator, tweaks_)