#!/usr/bin/env python from itertools import batched from random import choice from re import compile, sub, split from evennia.utils import logger, dedent def paragraph(text): """ Removes initial spaces as well as final newlines. Blank lines, however, are preserved. """ # Shame we can chain this or _thread_, but: return sub(r";;", "\n\n", sub(r"\n", " ", sub(r"\n\n+", ";;", dedent(text)))).strip() def squish(text): "Remove series of spaces from the text." return sub('[ \t]+', ' ', text).strip() def _routput_choose(text): """ Pick a choice when text is: one ^ two ^ three Done by splitting the text, and calling random.choice(). """ choices = split(r" *\^ *", text) return choice(choices) def _routput_empty(entry): """ Return True if entry is empty, e.g. None or blank. False otherwise. Note: Spaces are ignored. """ if entry: if isinstance(entry, str) and entry.strip() == '': return True if isinstance(entry, (list, tuple)) and len(entry) == 0: return True return False return True def _routput_pair(no_choice, choices = None): """ Return a list based on the ^-separated options in 'choices'. Give a pair of split items, the 'no_choice' is the first section (before the << ... >>>), and the 'choices' is the section between the '<< ... >>' delimiter. """ # While unlikely, the text before << and the options between the # << ...>> text may be blank: if _routput_empty(no_choice) and _routput_empty(choices): return '' # If we start a string with <<...>>, then thte 'no_choice' option # is blank, so we only return a choice: if _routput_empty(no_choice): return _routput_choose(choices) # With text before, but not inside the <<...>> section, we just # return the first part: if _routput_empty(choices): return no_choice return no_choice + ' ' + _routput_choose(choices) def routput(text, *substitutions): """ Return string with internal word choices replaced randomly. For instance, the string: 'This feels <<^ very ^ quite>> <>.' Could return any of the following strings: 'This feels quite nice.' 'This feels cozy.' 'This feels very comfortable.' """ if text: # section is a list of some phrase followed by another phrase # that contains ^-separate choices: sections = split(r" *<< *(.*?) *>> *", text) # Parts are the first section followed by the _rendered_ # choices, so this^that is either 'this' or 'that': parts = [_routput_pair(*pair) for pair in batched(sections, 2)] # The parts may have empty strings, so we filter those out, # leaving on the phrases to join: phrases = [phrase for phrase in parts if not _routput_empty(phrase)] proposal = ' '.join(phrases).format(*substitutions) # If a choice is at the end of a sentence, and we could have # "no choice", as in: # "He searches <>." # We don't want a version that looks like: # "He searches # ." # with a space before the punctuation: # # Also, we should remove all double spaces that may show: return squish(sub(r"\s+([!?.,])", "\\1", proposal)) def choices(text, *substitutions): """ Returns a section of 'text' based on separating semicolons. Note that text can already be separated as a list or tuple. """ if isinstance(text, str): text = split(r"\s*;;\s*", text) if text: return routput(choice(text), *substitutions) def split_party_msg(viewer, msg, *substitutions): """ Send a message to 'viewer' as well as all people in the area. Note that 'msg' could have choices separated by ;; As well as random words, separated by << one ^ two ^ three >> As well as one word for 'viewer' and other for rest, as in <( You ^ {0} )> <( give ^ gives )> the ball. """ text = choices(msg, viewer.name.title()) pattern = compile(r"\<\( *(.*?) *\^ *(.*?) *\)\>") # First a message for the view: viewer.msg( pattern.sub("\\1", text) ) # Then the message for the rest of the area: viewer.location.msg_contents( pattern.sub("\\2", text), exclude=viewer) # def searsonal(text, **kwargs): # season = kwargs['season'] or kwargs['location'].get_season() # time_of_day = kwargs['time_of_day'] or kwargs['location'].get_time_of_day()