Source code for turberfield.dialogue.performer
#!/usr/bin/env python3
# encoding: UTF-8
# This file is part of turberfield.
#
# Turberfield is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Turberfield is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with turberfield. If not, see <http://www.gnu.org/licenses/>.
from collections import defaultdict
import itertools
import re
from turberfield.dialogue.model import Model
from turberfield.dialogue.model import SceneScript
from turberfield.dialogue.types import Stateful
[docs]class Performer:
@staticmethod
def next(folders, ensemble, strict=True, roles=1):
for folder in folders:
scripts = SceneScript.scripts(**folder._asdict())
interludes = folder.interludes or itertools.repeat(None)
for index, script, interlude in zip(itertools.count(), scripts, interludes):
with script as dialogue:
selection = dialogue.select(ensemble, roles=roles)
if selection and all(selection.values()):
return (folder, index, script, selection, interlude)
elif not strict and any(selection.values()):
return (folder, index, script, selection, interlude)
else:
return None
def react(self, obj):
if self.condition is False:
return obj
if isinstance(obj, Model.Property):
if obj.object is not None:
setattr(obj.object, obj.attr, obj.val)
elif isinstance(obj, Model.Memory):
if obj.subject and obj.object is None and obj.state is not None:
obj.subject.state = obj.state
return obj
@staticmethod
def allows(item: Model.Condition):
if item.format == "state" and isinstance(item.object, Stateful):
rhs = item.value
if item.regex:
lhs = item.object.state
else:
lhs = item.object.get_state(type(item.value))
else:
fmt = "".join(("{0.", item.format, "}"))
try:
lhs = fmt.format(item.object)
except (AttributeError, IndexError, KeyError, ValueError):
return False
else:
rhs = str(item.value)
if item.regex:
return item.regex.match(str(lhs))
else:
return lhs == rhs
@property
def stopped(self):
"""Is `True` when none of the folders can be cast, `False` otherwise."""
return not bool(self.next(self.folders, self.ensemble))
[docs] def __init__(self, folders, ensemble):
"""An object which can select actors for a scene and run a performance.
:param folders: A sequence of
:py:class:`~turberfield.dialogue.model.SceneScript.Folder` objects.
:param ensemble: A sequence of Python objects.
"""
self.folders = folders
self.ensemble = ensemble
self.metadata = defaultdict(list)
self.shots = []
self.script = None
self.selection = None
self.condition = None
[docs] def run(self, react=True, strict=True, roles=1):
"""Select a cast and perform the next scene.
:param bool react: If `True`, then Property directives are executed
at the point they are encountered. Pass `False` to skip them
so they can be enacted later on.
:param bool strict: Only fully-cast scripts to be performed.
:param int roles: Maximum number of roles permitted each character.
This method is a generator. It yields events from the performance.
If a :py:class:`~turberfield.dialogue.model.Model.Condition` is
encountered, it is evaluated. No events are generated while the most recent
condition is False.
A new :py:class:`~turberfield.dialogue.model.Model.Shot` resets the
current condition.
"""
try:
folder, index, self.script, self.selection, interlude = self.next(
self.folders, self.ensemble,
strict=strict, roles=roles
)
except TypeError:
raise GeneratorExit
with self.script as dialogue:
model = dialogue.cast(self.selection).run()
for shot, item in model:
if self.condition is not False:
yield shot
yield item
if not self.shots or self.shots[-1][:2] != shot[:2]:
self.shots.append(shot._replace(items=self.script.fP))
self.condition = None
if isinstance(item, Model.Condition):
self.condition = self.allows(item)
if react:
self.react(item)
for key, value in model.metadata:
if value not in self.metadata[key]:
self.metadata[key].append(value)