import copy
import dataclasses
import typing
from . import _indentlib
from . import contributions as _contributions
from . import enums as _enums
from . import lyenv as _lyenv
from . import string as _string
def _format_scheme_value(value):
if value is True:
result = "##t"
elif value is False:
result = "##f"
elif isinstance(value, int | float):
result = str(value)
elif value is None:
result = "##f"
elif isinstance(value, _enums.Horizontal | _enums.Vertical):
result = rf"#{value.name.lower()}"
elif isinstance(value, tuple) and len(value) == 2:
result = f"#'({value[0]} . {value[1]})"
else:
assert isinstance(value, str), repr(value)
result = str(value)
return result
def _format_lilypond_attribute(attribute):
assert isinstance(attribute, str), repr(attribute)
attribute = attribute.replace("__", ".")
result = attribute.replace("_", "-")
return result
def _make_lilypond_tweak_string(attribute, value, *, directed=True, grob=None) -> str:
if grob is not None:
grob = _string.to_upper_camel_case(grob)
grob += "."
else:
grob = ""
attribute = _format_lilypond_attribute(attribute)
value = _format_scheme_value(value)
string = rf"\tweak {grob}{attribute} {value}"
if directed:
string = "- " + string
return string
[docs]@dataclasses.dataclass(order=True, slots=True, unsafe_hash=True)
class LilyPondOverride:
r"""
LilyPond grob override.
.. container:: example
>>> override = abjad.LilyPondOverride(
... lilypond_type="Staff",
... grob_name="TextSpanner",
... once=True,
... property_path=(
... "bound-details",
... "left",
... "text",
... ),
... value=r"\markup \bold { over pressure }",
... )
>>> print(override.override_string)
\once \override Staff.TextSpanner.bound-details.left.text = \markup \bold { over pressure }
>>> override = abjad.LilyPondOverride(
... lilypond_type="Staff",
... grob_name="TextSpanner",
... once=True,
... property_path="bound_details__left__text",
... value=r"\markup \bold { over pressure }",
... )
>>> print(override.override_string)
\once \override Staff.TextSpanner.bound-details.left.text = \markup \bold { over pressure }
"""
lilypond_type: str | None = None
grob_name: str = "NoteHead"
once: bool = False
is_revert: bool = False
property_path: str | typing.Sequence[str] = "color"
value: bool | int | float | str = "#red"
format_leaf_children: typing.ClassVar[bool] = False
[docs] def __post_init__(self):
if self.lilypond_type is not None:
assert isinstance(self.lilypond_type, str)
assert isinstance(self.grob_name, str)
assert isinstance(self.once, bool), repr(self.once)
assert isinstance(self.is_revert, bool)
if isinstance(self.property_path, str):
property_path_ = (self.property_path,)
else:
property_path_ = tuple(self.property_path)
assert all(isinstance(_, str) for _ in property_path_)
self.property_path = property_path_
prototype = (
bool | int | float | str | _enums.Horizontal | _enums.Vertical | tuple
)
assert isinstance(self.value, prototype), repr(self.value)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
if not self.once:
revert_format = "\n".join(self.revert_format_pieces)
contributions.grob_reverts.append(revert_format)
if not self.is_revert:
override_format = "\n".join(self.override_format_pieces)
contributions.grob_overrides.append(override_format)
return contributions
def _override_property_path_string(self):
parts = []
if self.lilypond_type is not None:
parts.append(self.lilypond_type)
parts.append(self.grob_name)
for part in self.property_path:
part = _format_lilypond_attribute(part)
parts.append(part)
path = ".".join(parts)
return path
def _revert_property_path_string(self):
parts = []
if self.lilypond_type is not None:
parts.append(self.lilypond_type)
parts.append(self.grob_name)
part = _format_lilypond_attribute(self.property_path[0])
parts.append(part)
path = ".".join(parts)
return path
@property
def override_format_pieces(self) -> tuple[str, ...]:
r"""
Gets LilyPond grob override \override format pieces.
.. container:: example
>>> override = abjad.LilyPondOverride(
... lilypond_type="Staff",
... grob_name="TextSpanner",
... once=True,
... property_path=(
... "bound-details",
... "left",
... "text",
... ),
... value=r"\markup \bold { over pressure }",
... )
>>> for line in override.override_format_pieces:
... line
...
'\\once \\override Staff.TextSpanner.bound-details.left.text = \\markup \\bold { over pressure }'
"""
result = []
if self.once:
result.append(r"\once")
result.append(r"\override")
result.append(self._override_property_path_string())
result.append("=")
string = _format_scheme_value(self.value)
value_pieces = string.split("\n")
result.append(value_pieces[0])
result[:] = [" ".join(result)]
result.extend(value_pieces[1:])
return tuple(result)
@property
def override_string(self) -> str:
r"""
Gets LilyPond grob override \override string.
.. container:: example
>>> override = abjad.LilyPondOverride(
... grob_name="Glissando",
... property_path="style",
... value="#'zigzag",
... )
>>> override.override_string
"\\override Glissando.style = #'zigzag"
"""
return "\n".join(self.override_format_pieces)
@property
def revert_format_pieces(self) -> tuple[str, ...]:
r"""
Gets LilyPond grob override \revert format pieces.
.. container:: example
>>> override = abjad.LilyPondOverride(
... grob_name="Glissando",
... property_path="style",
... value="#'zigzag",
... )
>>> override.revert_format_pieces
('\\revert Glissando.style',)
"""
result = rf"\revert {self._revert_property_path_string()}"
return (result,)
@property
def revert_string(self) -> str:
r"""
Gets LilyPond grob override \revert string.
.. container:: example
>>> override = abjad.LilyPondOverride(
... grob_name="Glissando",
... property_path="style",
... value="#'zigzag",
... )
>>> override.revert_string
'\\revert Glissando.style'
"""
return "\n".join(self.revert_format_pieces)
[docs] def tweak_string(self, directed=True, grob=False) -> str:
r"""
Gets LilyPond grob override \tweak string.
.. container:: example
>>> override = abjad.LilyPondOverride(
... grob_name="Glissando",
... property_path="style",
... value="#'zigzag",
... )
>>> override.tweak_string()
"- \\tweak style #'zigzag"
.. container:: example
>>> override = abjad.LilyPondOverride(
... grob_name="RehearsalMark",
... property_path="color",
... value="#red",
... )
>>> override.tweak_string(directed=False)
'\\tweak color #red'
"""
if directed:
result = [r"- \tweak"]
else:
result = [r"\tweak"]
if grob:
property_path = [self.grob_name] + list(self.property_path)
else:
property_path = list(self.property_path)
string = ".".join(property_path)
result.append(string)
string = _format_scheme_value(self.value)
result.append(string)
return " ".join(result)
[docs]@dataclasses.dataclass(order=True, slots=True, unsafe_hash=True)
class LilyPondSetting:
r"""
LilyPond context setting.
.. container:: example
>>> context_setting = abjad.LilyPondSetting(
... lilypond_type="Score",
... context_property="autoBeaming",
... value="##f",
... )
>>> print("\n".join(context_setting.format_pieces))
\set Score.autoBeaming = ##f
"""
lilypond_type: str | None = None
context_property: str = "autoBeaming"
is_unset: bool = False
value: typing.Any = False
format_leaf_children: typing.ClassVar[bool] = False
[docs] def __post_init__(self):
if self.lilypond_type is not None:
assert isinstance(self.lilypond_type, str)
assert isinstance(self.context_property, str)
assert isinstance(self.is_unset, bool)
assert isinstance(self.value, bool | int | float | str), repr(self.value)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = "\n".join(self.format_pieces)
contributions.context_settings.append(string)
return contributions
@property
def format_pieces(self) -> tuple[str, ...]:
r"""
Gets LilyPond context setting ``\set`` or ``\unset`` format pieces.
"""
result = []
if not self.is_unset:
result.append(r"\set")
else:
result.append(r"\unset")
if self.lilypond_type is not None:
string = f"{self.lilypond_type}.{self.context_property}"
result.append(string)
else:
result.append(self.context_property)
result.append("=")
string = _format_scheme_value(self.value)
value_pieces = string.split("\n")
result.append(value_pieces[0])
result[:] = [" ".join(result)]
result.extend(value_pieces[1:])
return tuple(result)
[docs]class Interface:
"""
Base class from which grob, setting and tweak interfaces inherit.
"""
[docs] def __eq__(self, argument) -> bool:
"""
Is true when ``argument`` is an interface with attribute pairs equal to those of
this interface.
.. container:: example
>>> note_1 = abjad.Note("c'4")
>>> abjad.setting(note_1).Voice.auto_beaming = False
>>> abjad.setting(note_1).Staff.tupletFullLength = True
>>> note_2 = abjad.Note("c'4")
>>> abjad.setting(note_2).Voice.auto_beaming = False
>>> abjad.setting(note_2).Staff.tupletFullLength = True
>>> note_3 = abjad.Note("c'4")
>>> abjad.setting(note_3).Voice.auto_beaming = True
>>> setting_1 = abjad.setting(note_1)
>>> setting_2 = abjad.setting(note_2)
>>> setting_3 = abjad.setting(note_3)
>>> setting_1 == setting_1
True
>>> setting_1 == setting_2
True
>>> setting_1 == setting_3
False
>>> setting_2 == setting_1
True
>>> setting_2 == setting_2
True
>>> setting_2 == setting_3
False
>>> setting_3 == setting_1
False
>>> setting_3 == setting_2
False
>>> setting_3 == setting_3
True
.. container:: example
>>> note_1 = abjad.Note("c'4")
>>> abjad.override(note_1).NoteHead.color = "#red"
>>> abjad.override(note_1).Stem.color = "#red"
>>> note_2 = abjad.Note("c'4")
>>> abjad.override(note_2).NoteHead.color = "#red"
>>> abjad.override(note_2).Stem.color = "#red"
>>> note_3 = abjad.Note("c'4")
>>> abjad.override(note_3).NoteHead.color = "#red"
>>> override_1 = abjad.override(note_1)
>>> override_2 = abjad.override(note_2)
>>> override_3 = abjad.override(note_3)
>>> override_1 == override_1
True
>>> override_1 == override_2
True
>>> override_1 == override_3
False
>>> override_2 == override_1
True
>>> override_2 == override_2
True
>>> override_2 == override_3
False
>>> override_3 == override_1
False
>>> override_3 == override_2
False
>>> override_3 == override_3
True
"""
if isinstance(argument, type(self)):
attribute_pairs_1 = self._get_attribute_pairs()
attribute_pairs_2 = argument._get_attribute_pairs()
return attribute_pairs_1 == attribute_pairs_2
return False
def __getstate__(self) -> dict:
"""
Gets object state.
"""
return copy.deepcopy(vars(self))
[docs] def __hash__(self) -> int:
"""
Hashes interface.
"""
return super().__hash__()
[docs] def __repr__(self) -> str:
"""
Gets repr.
"""
body_string = ""
pairs = self._get_attribute_pairs()
pairs = [str(_) for _ in pairs]
body_string = ", ".join(pairs)
return f"{type(self).__name__}({body_string})"
def __setstate__(self, state) -> None:
"""
Sets object state.
"""
for key, value in state.items():
self.__dict__[key] = value
def _get_attribute_pairs(self):
return list(sorted(vars(self).items()))
[docs]class OverrideInterface(Interface):
"""
Override interface.
.. container:: example
Override interfaces are created by the ``abjad.override()`` factory function:
>>> note = abjad.Note("c'4")
>>> abjad.override(note)
OverrideInterface()
"""
[docs] def __getattr__(self, name):
r"""
Gets Interface (or OverrideInterface) keyed to ``name``.
.. container:: example
Somewhat confusingly, getting a grob name returns an interface:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> abjad.override(staff[0]).NoteHead
Interface()
While getting a context name returns an override interface:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> abjad.override(staff[0]).Staff
OverrideInterface()
Which can then be deferenced to get an interface:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> abjad.override(staff[0]).Staff.NoteHead
Interface()
Note that the dot-chained user syntax is unproblematic. But the class of each
interface returned in the chain is likely to be surprising at first encounter.
"""
camel_name = _string.to_upper_camel_case(name)
if name.startswith("_"):
try:
return vars(self)[name]
except KeyError:
type_name = type(self).__name__
raise AttributeError("{type_name!r} object has no attribute: {name!r}.")
elif camel_name in _lyenv.contexts:
try:
return vars(self)["_" + name]
except KeyError:
context = OverrideInterface()
vars(self)["_" + name] = context
return context
elif camel_name in _lyenv.grob_interfaces:
try:
return vars(self)[name]
except KeyError:
vars(self)[name] = Interface()
return vars(self)[name]
else:
try:
return vars(self)[name]
except KeyError:
type_name = type(self).__name__
raise AttributeError(
f"{type_name!r} object has no attribute: {name!r}."
)
[docs] def __setattr__(self, attribute, value):
"""
Sets attribute ``attribute`` of override interface to ``value``.
"""
object.__setattr__(self, attribute, value)
def _get_attribute_tuples(self):
result = []
for name, value in vars(self).items():
if type(value) is Interface:
grob, grob_proxy = name, value
pairs = iter(vars(grob_proxy).items())
for attribute, value in pairs:
triple = (grob, attribute, value)
result.append(triple)
else:
context, context_proxy = name.strip("_"), value
for grob, grob_proxy in vars(context_proxy).items():
pairs = iter(vars(grob_proxy).items())
for attribute, value in pairs:
quadruple = (context, grob, attribute, value)
result.append(quadruple)
result.sort()
return tuple(result)
def _list_contributions(self, contribution_type, once=False):
assert contribution_type in ("override", "revert")
result = []
for attribute_tuple in self._get_attribute_tuples():
if len(attribute_tuple) == 3:
context = None
grob = attribute_tuple[0]
attribute = attribute_tuple[1]
value = attribute_tuple[2]
elif len(attribute_tuple) == 4:
context = attribute_tuple[0]
grob = attribute_tuple[1]
attribute = attribute_tuple[2]
value = attribute_tuple[3]
else:
raise ValueError(f"invalid attribute tuple: {attribute_tuple!r}.")
if contribution_type == "override":
override = LilyPondOverride(
lilypond_type=context,
grob_name=grob,
once=once,
is_revert=False,
property_path=attribute,
value=value,
)
override_string = override.override_string
result.append(override_string)
else:
override = LilyPondOverride(
lilypond_type=context,
grob_name=grob,
is_revert=True,
property_path=attribute,
)
revert_string = override.revert_string
result.append(revert_string)
result.sort()
return result
[docs]class SettingInterface(Interface):
"""
LilyPond setting interface.
.. container:: example
>>> note = abjad.Note("c'4")
>>> abjad.setting(note)
SettingInterface()
"""
[docs] def __getattr__(self, name: str):
r"""
Gets arbitrary object keyed to ``name``.
.. container:: example
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> string = r'\markup "Vn. I"'
>>> abjad.setting(staff).instrumentName = string
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
\with
{
instrumentName = \markup "Vn. I"
}
{
c'4
d'4
e'4
f'4
}
.. container:: example
Returns arbitrary object keyed to ``name``:
>>> abjad.setting(staff).instrumentName
'\\markup "Vn. I"'
"""
camel_name = _string.to_upper_camel_case(name)
if name.startswith("_"):
try:
return vars(self)[name]
except KeyError:
message = "{type(self).__name__!r} object has no attribute: {name!r}."
raise AttributeError(message)
elif camel_name in _lyenv.contexts:
try:
return vars(self)["_" + name]
except KeyError:
context = Interface()
vars(self)["_" + name] = context
return context
else:
try:
return vars(self)[name]
except KeyError:
message = "{type(self).__name__!r} object has no attribute: {name!r}."
raise AttributeError(message)
def _format_in_with_block(self) -> list[str]:
strings = []
for key, value in vars(self).items():
assert isinstance(key, str), repr(key)
name = key.split("_")
first = name[0:1]
rest = name[1:]
rest = [_.title() for _ in rest]
name = first + rest
string = "".join(name)
value = _format_scheme_value(value)
value_parts = value.split("\n")
result = rf"{string!s} = {value_parts[0]!s}"
pieces = [result]
for part in value_parts[1:]:
pieces.append(_indentlib.INDENT + part)
string = "\n".join(pieces)
strings.append(string)
return strings
def _format_inline(self, context=None) -> list[str]:
result = []
for name, value in vars(self).items():
# if we've found a leaf context namespace
if name.startswith("_"):
for x, y in vars(value).items():
if not x.startswith("_"):
string = self._format_inline_helper(x, y, name)
result.append(string)
# otherwise we've found a default leaf context setting
else:
# parse default context setting
string = self._format_inline_helper(name, value)
result.append(string)
return result
def _format_inline_helper(self, name, value, context=None):
name = name.split("_")
first = name[0:1]
rest = name[1:]
rest = [_.title() for _ in rest]
name = first + rest
name = "".join(name)
value = _format_scheme_value(value)
if context is not None:
context_string = context[1:]
context_string = context_string.split("_")
context_string = [_.title() for _ in context_string]
context_string = "".join(context_string)
context_string += "."
else:
context_string = ""
result = rf"\set {context_string}{name} = {value}"
return result
def _get_attribute_tuples(self):
result = []
for name, value in vars(self).items():
if type(value) is Interface:
prefixed_context_name = name
lilypond_type = prefixed_context_name.strip("_")
context_proxy = value
attribute_pairs = context_proxy._get_attribute_pairs()
for attribute_name, attribute_value in attribute_pairs:
triple = (lilypond_type, attribute_name, attribute_value)
result.append(triple)
else:
attribute_name, attribute_value = name, value
result.append((attribute_name, attribute_value))
result.sort()
return result
[docs]def override(argument):
r"""
Makes LilyPond grob override interface.
.. container:: example
Overrides staff symbol color:
>>> staff = abjad.Staff("c'4 e'4 d'4 f'4")
>>> abjad.override(staff).StaffSymbol.color = "#red"
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
\with
{
\override StaffSymbol.color = #red
}
{
c'4
e'4
d'4
f'4
}
.. container:: example
Specify grob context like this:
>>> staff = abjad.Staff("c'4 e'4 d'4 f'4")
>>> abjad.override(staff[0]).Staff.StaffSymbol.color = "#blue"
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\once \override Staff.StaffSymbol.color = #blue
c'4
e'4
d'4
f'4
}
.. container:: example
Returns LilyPond grob override interface.
>>> staff = abjad.Staff("c'4 e' d' f'")
>>> abjad.override(staff)
OverrideInterface()
"""
if getattr(argument, "_overrides", None) is None:
argument._overrides = OverrideInterface()
return argument._overrides
[docs]def setting(argument):
r"""
Makes LilyPond setting name interface.
.. container:: example
Sets instrument name:
>>> staff = abjad.Staff("c'4 e'4 d'4 f'4")
>>> string = r'\markup "Vn. I"'
>>> abjad.setting(staff).instrumentName = string
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
\with
{
instrumentName = \markup "Vn. I"
}
{
c'4
e'4
d'4
f'4
}
.. container:: example
Returns LilyPond setting name interface:
>>> abjad.setting(staff)
SettingInterface(('instrumentName', '\\markup "Vn. I"'))
"""
if getattr(argument, "_lilypond_setting_name_manager", None) is None:
argument._lilypond_setting_name_manager = SettingInterface()
return argument._lilypond_setting_name_manager