Exemple #1
0
 def packer(schema, msg):
     '''Pack a list of arguments into a single unit.'''
     unit = []
     for i, subunit_len in enumerate(schema, start=1):
         subbed_args = []
         try:
             arg = arguments.pop(0)
             # If the arguments are in expression mode...
             if arg.startswith("$"):
                 # Make a list of all the arguments for this "phase."
                 active_args = [arg[1:]] + arguments[:subunit_len - 1]
                 # Slicing doesn't raise errors, so we need to validate
                 # that all these arguments exist.
                 if len(active_args) != subunit_len:
                     raise IndexError
                 for arg in active_args:
                     # Handle $, \, and Catalysis variables.
                     tag = re.sub(r"\\\\|\\\$|\$", repl_custom, arg)
                     if tag.count("\n") % 2:
                         raise Invalid("$ syntax", arg)
                     subbed_args.append(tag)
                 # Prefix the argument list and add the subunit.
                 unit.append(["xpr"] + subbed_args)
                 del arguments[:subunit_len - 1]
             # If the arguments are not in expression mode...
             else:
                 # Handle $ and \ escapes, and append a subdescriptor.
                 # The function will modify this if necessary.
                 arg = re.sub(r"\\\\|\\\$", repl_default, arg)
                 unit.append(["val", arg])
         except IndexError:
             raise Invalid("schema fail", msg, i)
     return unit
def parse_file():
    '''Parses the selector file.'''
    lines = extract_data(file_name)
    try:
        if len(lines) != 10:
            raise Invalid("selector length")
    except Invalid:
        print(sys.exc_info()[1].message.format(
            "end of file", file_name))
        terminate()
    try:
        for i, string in enumerate([
                "USERNAME:"******"PASSWORD:"******"DIRECTORY:", "TRIAL_ID:",
                "MAX_ERRORS:"]):
            if string != lines[2*i]:
                raise Invalid("selector", string)
    except Invalid:
        print(sys.exc_info()[1].message.format(
            "line {}".format(2*i+1), file_name))
        terminate()
    try:
        max_err = int_at_least(lines[9], 1, "Maximum errors allowable")
    except Invalid:
        print(sys.exc_info()[1].message.format("line 10", file_name))
        terminate()
    return lines[5], {
        "username": lines[1], "password": lines[3],
        "trial_id": lines[7]
    }, max_err
 def set_suffixes(self, suffix_list):
     '''Convert a list of suffixes into suffix settings. These include
     absolute settings (pose_descriptor: new_suffix) and relative
     settings (new_suffix - so this must go after the last suffix.)'''
     # If there's no set prefix, you can't use a suffix.
     if not self.character_prefix:
         raise Invalid("suffix no prefix")
     suffix_list = suffix_list.split(", ")
     sprite_names = self.sprite_suffix.keys()
     index = 0
     for tag in suffix_list:
         # If it's specifying the sprite to suffix absolutely...
         if ": " in tag:
             sprite_long, sprite_short = tag.split(": ", 1)
             if sprite_long in sprite_names and (re.match(
                     r'\w+$', sprite_short)):
                 self.sprite_suffix[sprite_long] = sprite_short
                 index = list(sprite_names).index(sprite_long) + 1
             elif sprite_long in self.sprite_suffix:
                 raise Invalid("not word", "Suffix " + sprite_short)
             else:
                 raise Invalid("unk sprite", sprite_long)
         # Otherwise, the sprite is the next one in the list.
         else:
             if not re.match(r'\w+$', tag):
                 raise Invalid("not word", "Suffix")
             # Get the object to bind to...
             try:
                 sprite_long = list(sprite_names)[index]
             except IndexError:
                 raise Invalid("excess suffix", "Suffix " + tag)
             # ...then bind and update the index of the "next" attribute.
             self.sprite_suffix[sprite_long] = tag
             index += 1
     self.last_method = False
    def update_suffixes(self, validate_prefix=True):
        '''Update the list of suffixes, performing validation as needed.'''

        # Early-exit if there is no prefix. Otherwise, validate against
        # duplicates if needed. The prefix is now confirmed valid.
        if not self.character_prefix:
            return
        elif validate_prefix and self.character_prefix in self.suffix_dicts:
            raise Invalid("prefix dupl", self.character_prefix)

        # Combine our lists of suffixes, to validate suffix uniqueness.
        builtin_suffix_list = list(self.sprite_suffix.values())
        suffix_list = self.custom_suffix_list + builtin_suffix_list
        # Ensure suffix uniqueness.
        for suffix in suffix_list:
            if not suffix_list.count(suffix) == 1:
                raise Invalid("suffix dupl", suffix)

        # Suffixes for custom sprites go 1, 2, 3, 4...
        current_suffix_dict = {
            suffix: self.custom_suffix_list.index(suffix) + 1
            for suffix in self.custom_suffix_list
        }
        # ...but suffixes for built-ins go -1, -2, -3, -4
        current_suffix_dict.update({
            suffix: -builtin_suffix_list.index(suffix) - 1
            for suffix in builtin_suffix_list
        })

        # We can now add this to the dictionary of sprites.
        self.suffix_dicts[self.character_prefix] = {
            "id": self.data["id"],
            "suffix dict": current_suffix_dict,
            "short": self.data["short_name"]
        }
Exemple #5
0
    def command_process(self, line, macro=False):
        '''Split the line into a list of commands and arguments, and then run
        it. If the line ends with a colon, expect dialogue. Otherwise, expect a
        new command and do some data valdiation.'''
        def func(match):
            r'''Function that handles escaping of comma-space, curly brackets
            and newline. Syntactical "{" are matched with "$", while "}" are 
            removed. Due to expression syntax and the dialogue command, 
            \ escape may not be safe.'''
            match = match.group(1)
            # Match \\ so they don't interfere with matching.
            # It may not be safe to treat them as an escaped \ yet.
            return {
                ", ": "\n",
                r"\\": r"\\",
                "\\, ": ", ",
                "\\{": "{",
                "\\}": "}",
                "{": "$",
                "}": ""
            }[match]

        # Split at a comma-space and preserves escape characters,
        # but replaces non-escaped "{" and "}" with "$" and "" respectively.
        command_list = re.sub(r"(\\\\|\\, |, |\\{|\\}|{|})", func,
                              line).split("\n")
        # Seek a terminal colon, being wary of the possibility of escape.
        colon_match = re.search(r"(\\)*:$", command_list[-1])
        start_dialogue = False
        if colon_match:
            if colon_match.group(1) and len(colon_match.group(1)) % 2:
                # Colon escaped. Remove the escaping slash.
                command_list[-1] = command_list[-1][:-2] + command_list[-1][-1]
            else:
                # Colon not escaped. Remove it, and mark dialogue to start.
                start_dialogue = True
                command_list[-1] = command_list[-1][:-1]

        self.command(command_list)

        # If the command is run as part of a macro, it can't end a frame.
        if macro:
            return

        # Dialogue isn't set to start - we still expect commands.
        if not start_dialogue:
            return self.command_process
        # Dialogue is set to start - validate current places and positions.
        else:
            pos_list = [
                n["position"] for n in (self.executor.frame["characters"])
            ]
            # Check if positions are duplicated...
            # If no place is set, there is only one available position,
            # so a repeat position means multiple characters.'''
            if len(pos_list) > len(set(pos_list)):
                if self.executor.frame["place"]:
                    raise Invalid("mult char pos")
                raise Invalid("mult char no place")
            return self.dialogue
Exemple #6
0
 def set_suffix(self, suffix):
     '''Set the suffix for the custom sprite.'''
     if not re.match(r'\w+$', suffix):
         raise Invalid("not word", "Suffix")
     if not self.parent_object.character_prefix:
         raise Invalid("suffix no prefix")
     self.parent_object.custom_suffix_list.append(suffix)
     self.parent_object.update_suffixes(False)
Exemple #7
0
    def cleanup(self):
        '''Cleanup actions to be run when the parser ends. Run blank again
        to word wrap, run anchor replacements, and do some validation.
        Then, end tKinter.'''

        # Run end-frame processing.
        self.blank()

        def list_wrap(*args):
            '''Function to get the value from a series of trial keys.'''
            item = self.executor.trial
            for key in args:
                item = item[key]
            return item

        # Do all anchor replacement.
        for key, value in self.executor.anc_dict.iteritems():

            def func(match):
                '''Function that returns the entry in the proper value dict
                for this match. Used for strng replacement.'''
                try:
                    return str(value["value"][match.group(1)])
                except KeyError:
                    raise Invalid("anc unset", key, match.group(1))

            for dest in value["destination"]:
                # The array or dict containing the replacement target.
                nonscalar = list_wrap(*dest[:-1])
                # The current value of the replacement target.
                string = list_wrap(*dest)
                # xpr replacement targets are separate by newlines.
                if string.startswith("xpr="):
                    nonscalar[dest[-1]] = re.sub("\n(.*)\n", func, string)
                # val replacement targets are everything after the val.
                elif string.startswith("val="):
                    nonscalar[dest[-1]] = re.sub("(?<=val=)(.*)", func, string)
                # And other replacment targets are the full value.
                else:
                    try:
                        nonscalar[dest[-1]] = value["value"][string]
                    except KeyError:
                        raise Invalid("anc unset", key, string)

        # Validate all prior assertions that psyche-lock sequences exist.
        for dest in self.executor.check_locks:
            string = list_wrap(*dest)
            if string.startswith("val="):
                target = int(string[4:])
                if not self.executor.trial["scenes"][target]["dialogues"][0][
                        "locks"]:
                    raise Invalid("missing locks", target)

        # Ensure that we're not expecting another frame.
        if self.executor.location:
            raise Invalid("block open")
        if self.executor.frame["merged_to_next"]:
            raise Invalid("terminal merge")
 def post_command_cleanup(self):
     '''Perform validation after ending a frame, and clear action_mult.'''
     pos_list = [n["position"] for n in (self.executor.frame["characters"])]
     # Check if positions are duplicated...
     # If no place is set, there is only one available position,
     # so a repeat position means multiple characters.'''
     if len(pos_list) > len(set(pos_list)):
         if self.executor.frame["place"]:
             raise Invalid("mult char pos")
         raise Invalid("mult char no place")
     # Clear the "expecting this kind of action".
     self.executor.action_mult = {}
 def repl_func(match):
     '''Function to perform anchor replacement when the anchor is not
     known in advance.'''
     anchor_type = match.group(1).lower().strip()
     anchor = match.group(2)
     try:
         anc_dict = self.executor.anc_dict[anchor_type]["value"]
     except KeyError:
         raise Invalid("unk anc type", anchor_type)
     try:
         return str(anc_dict[anchor])
     except KeyError:
         raise Invalid("anc unset", anchor_type, anchor)
Exemple #10
0
    def validate_context(self, command):
        '''Validate context. For example, some functions should never be
        called manually, should never be called on merged frames, or should
        only be called in a scene. This function validates this.'''

        # Define variables to be used in validation.
        name = re.sub("_(.)", lambda match: match.group(1).upper(),
                      command.__name__)
        location = self.executor.location
        dict_fragment = validation_dict[location]
        merged = self.executor.frame["merged_to_next"] or (
            command == self.executor.merge)
        try:
            merge_lock = "not merged" in self.executor.frame[
                "action_parameters"]["context"]
        except (KeyError, TypeError):
            merge_lock = False
        merge_lock = merge_lock or hasattr(command, "merge_lock")

        # Validate the command can be called manually, isn't scene-only,
        # doesn't have a merge problem, and isn't overriding an acton.
        if hasattr(command, "no_manual"):
            raise Invalid("ban manual", name)
        if hasattr(command, "scene_only") and (
                not self.executor.location.startswith("sce")):
            raise Invalid("enforce scene", name)
        if hasattr(command, "action") and self.executor.frame["action_name"]:
            raise Invalid("one action", name)
        if merged and merge_lock:
            raise Invalid("ban on merge", name)

        # Validate the command is acceptable in this location.
        use_default = dict_fragment["use_default"]
        custom = dict_fragment["custom"]
        if use_default:
            default_if_applicable = not hasattr(command, "special")
            block_rule = "a generally usable command or "
        else:
            default_if_applicable = False
            block_rule = ""
        if not (default_if_applicable or name in custom):
            location_term = "after {}".format(location) if location else (
                "outside of special blocks")
            raise Invalid("bad context", location_term, block_rule,
                          ", ".join(custom))

        # Some commands have different permissible functions after makeFrame.
        # Change location, if necessary, to reflect this.
        if location in {"ceStatement", "sceMain"} and name == "makeFrame":
            self.executor.location += " and makeFrame"
Exemple #11
0
 def automate(self, line):
     '''Handle parsing while inside an automation block.'''
     if line == "}":
         return self.no_macro
     try:
         attr, value = re.split(": ?", line, maxsplit=1)
     except ValueError:
         raise Invalid("config colon")
     if attr not in self.automate_dict:
         raise Invalid("config attr", attr)
     if attr == "id":
         int_at_least(value, 0, "Trial id " + attr)
     self.automate_dict[attr] = value
     return self.automate
Exemple #12
0
    def no_object(self, line):
        '''Handles parsing while not inside an object.'''
        # Parse line as "word string(optional) {" else crash.
        try:
            object_type, name = re.match(r"(\w+)\s*(.+?)?\s*\{$",
                                         line).groups()
        except AttributeError:
            raise Invalid("expected_new_obj")
        # Assume it's calling an object. If that fails, move on.
        try:
            self.active_object = getattr(object_classes,
                                         object_type.capitalize())()
        except AttributeError:
            pass
        else:
            if name:
                # Validate the object names...
                if name in self.using_objects:
                    raise Invalid("ban duplicate", "Object", name)
                if name in object_classes.reserved_names:
                    raise Invalid("ban obj name", name)
                # Store the object name for later...
                self.using_objects[name] = self.active_object
                # And make the handle the name unless overriden...
                if object_type.capitalize() == "Profile":
                    self.active_object.redefine("long", name)
                    self.active_object.redefine("short", name)
                else:
                    self.active_object.redefine("name", name)
            return self.in_object

        # Parse line as "parent_name sub_type {" else crash.
        try:
            parent_name, sub_type = re.match(r"(.+?)\s+(\w+)\s*\{$",
                                             line).groups()
        except AttributeError:
            if object_type in self.subobject_dict:
                raise Invalid("no parent obj")
            else:
                raise Invalid("unk line")

        sub_type = sub_type.lower()

        # Find the object it's a subobject of.
        try:
            base_object = self.using_objects[parent_name]
        except KeyError:
            raise Invalid("parent obj dne", parent_name)
        # Find the kind of subobject it's supposed to be.
        try:
            subobject_class = self.subobject_dict[sub_type]
        except KeyError:
            raise Invalid("subobj dne", sub_type)
        # Validate the current object permits this subobject.
        if subobject_class in base_object.sub_set:
            self.active_object = subobject_class(base_object)
        else:
            raise Invalid("obj subobj forbid", sub_type, parent_name)
        return self.in_object
Exemple #13
0
 def colon_redef(self, name, base):
     '''Take the given base, transform it into a dictionary of values for a
     given typical JSON row, and use that for the applicable function.'''
     try:
         base = self.base_dict[base.lower()]
     except KeyError:
         raise Invalid("attr val", base)
     name = name.lower()
     if name in self.colon_dict:
         self.key_to_method[name](base[name], **self.colon_dict[name])
         # name in self.colon_dict => name in base & self.key_to_method
     elif name in self.key_to_method:
         raise Invalid("no default attr", name)
     else:
         raise Invalid("attr name", name)
Exemple #14
0
 def redefine(self, name, value):
     '''Call row function (specified by name), passing the value.'''
     try:
         func = self.key_to_method[name]
     except KeyError:
         raise Invalid("attr name", name)
     func(value)
 def set_character_prefix(self, prefix):
     '''Set a prefix for this sprite. Also perform validation.'''
     if not re.match(r'\w+$', prefix):
         raise Invalid("not word", "Prefix")
     if prefix in reserved_names_prefix:
         raise Invalid("keyword claim", "Prefix", prefix)
     self.character_prefix = prefix
     # If the base isn't null, find the matching character, and set
     # suffixes.
     if self.data["base"] != "Inconnu":
         for char in self.base_dict.values():
             if self.data["base"] == char["sprite"]:
                 self.sprite_suffix = deepcopy(char["suffix"])
     else:
         self.sprite_suffix = {}
     self.last_method = False
Exemple #16
0
 def func(match):
     '''Function that returns the entry in the proper value dict
     for this match. Used for strng replacement.'''
     try:
         return str(value["value"][match.group(1)])
     except KeyError:
         raise Invalid("anc unset", key, match.group(1))
Exemple #17
0
 def append(self, value):
     '''Run redefine on the last function, with the old value, a newline,
     and then the value to append. The line continuation function.'''
     try:
         self.redefine(self.last_method["name"],
                       self.last_method["value"] + "\n" + value)
     except TypeError:
         raise Invalid("no continuation")
Exemple #18
0
 def no_macro(self, line):
     '''Handle parsing while not inside a macro.'''
     if line == "CONFIG {":
         return self.config
     try:
         macro = re.match(r"(\w+)\s*\{$", line).group(1)
     except AttributeError:
         raise Invalid("unk line")
     if macro.lower() == "config":
         return self.config
     if macro in self.macro_dict:
         raise Invalid("ban duplicate", "Macro", macro)
     # You can't give a macro the same name as a frame action.
     if macro in reserved_names:
         raise Invalid("keyword claim", "Macro", macro)
     self.macro_dict[macro] = self.active_macro = []
     return self.in_macro
 def new_base(self, sprite, active=False):
     '''Set the base attribute, which controls the default icon and
     sprites.'''
     if active:
         self.data["base"] = sprite
     else:
         raise Invalid("attr name", "sprite")
     self.last_method = False
Exemple #20
0
 def config(self, line):
     '''Handle parsing while inside a configuration block.'''
     if line == "}":
         return self.no_macro
     try:
         attr, value = re.split(": ?", line, maxsplit=1)
     except ValueError:
         raise Invalid("config colon")
     if attr not in self.config_dict:
         raise Invalid("config attr", attr)
     # Now we actually set the configuration attribute.
     if attr in {"autopause", "autowrap", "autoquote"}:
         self.config_dict[attr] = not self.config_dict[attr]
     else:
         self.config_dict[attr] = int_at_least(
             value, 0, "Configuration attribute " + attr)
     return self.config
Exemple #21
0
 def base(self, base):
     '''Run colon_redef on all applicable attributes.'''
     try:
         base = self.base_dict[base.lower()]
     except KeyError:
         raise Invalid("attr val", base)
     for name in self.colon_dict:
         if name not in self.base_exclude:
             self.key_to_method[name](base[name], **self.colon_dict[name])
Exemple #22
0
 def config(self, line):
     '''Handle parsing while inside a configuration block.'''
     if line == "}":
         return self.no_macro
     try:
         attr, value = line.rsplit(":", maxsplit=1)
         value = value.strip()
         attr, value = attr.lower(), value.lower()
     except ValueError:
         raise Invalid("config colon")
     if attr not in self.config_dict:
         raise Invalid("config attr", attr)
     if attr in self.customization_dict:
         self.config_dict[attr] = key_or_value(
             value, self.customization_dict[attr], attr)
     elif attr == "autoescape":
         self.config_dict[attr].update(value.split())
     else:
         self.config_dict[attr] = int_at_least(
             value, 0, "Configuration attribute " + attr)
     return self.config
Exemple #23
0
 def repl(match):
     '''Handle all replacements necessary for expression mode and
     Catalysis variables.'''
     if match.group(0) in {"{", "}"}:
         raise Invalid("unescaped brace", arg)
     return {
         r"\\": "\\",
         r"\$": "$",
         "$": "\n",
         r"\}": "}",
         r"\{": "}",
         r"\:": ":",
         ":": "\r"
     }[match.group()]
Exemple #24
0
def parse_file():
    '''Parses the selector file.'''
    lines = extract_data(file_name)
    try:
        if len(lines) != 8:
            raise Invalid("selector length")
    except Invalid:
        print sys.exc_info()[1].message.format(
            "end of file", file_name)
        terminate()
    try:
        for i, string in enumerate([
                "USERNAME:"******"PASSWORD:"******"DIRECTORY:", "TRIAL_ID:"]):
            if string != lines[2*i]:
                raise Invalid("selector", string)
    except Invalid:
        print sys.exc_info()[1].message.format(
                "line {}".format(2*i+1), file_name)
        terminate()
    return lines[5], {
        "username": lines[1], "password": lines[3],
        "trial_id": lines[7]
    }
Exemple #25
0
    def replacer(arg):
        '''Handle $, \, and Catalysis variables.'''
        def repl(match):
            '''Handle all replacements necessary for expression mode and
            Catalysis variables.'''
            if match.group(0) in {"{", "}"}:
                raise Invalid("unescaped brace", arg)
            return {
                r"\\": "\\",
                r"\$": "$",
                "$": "\n",
                r"\}": "}",
                r"\{": "}",
                r"\:": ":",
                ":": "\r"
            }[match.group()]

        tag = re.sub(r"\\(\\|\$|\{|\}|:)|\{|\}|\$|:", repl, arg)
        if tag.count("\n") % 2:
            raise Invalid("$ syntax", arg)
        # Now, check whether the : pattern is right.
        if not re.search(r"^[^\r\n]*(\n[^\r\n]*\r?[^\r\n]*\n[^\r\n]*)*$", tag):
            raise Invalid(": syntax", arg)
        return tag
Exemple #26
0
 def in_object(self, line):
     '''Handle parsing for an object.'''
     # First, check if it's setting an attribute to an AAO built-in.
     try:
         attr, value = re.match(r"(\w+)::\s*(.*)$", line).groups()
     except AttributeError:
         pass
     else:
         self.active_object.colon_redef(attr, value)
         return self.in_object
     # Second, check if it's a standard command.
     # The below ? allows for blank attributes, to allow a blank line.
     try:
         attr, value = re.match(r"(\w+):\s*(.*)?$", line).groups("")
     except AttributeError:
         pass
     else:
         self.active_object.redefine(attr.lower(), value)
         return self.in_object
     # Third, check if it's closing the object.
     if line == "}":
         try:
             self.active_object.update_suffixes()
         except AttributeError:
             pass
         # Conceptualizes that there is no active_object anymore.
         self.active_object = False
         return self.no_object
     # Last, check for line continuation. If this fails, it's over.
     try:
         value = re.match(r":\s?(.*)?$", line).group(1)
         value = value if value else ""
     except AttributeError:
         raise Invalid("in obj")
     else:
         self.active_object.append(value)
         return self.in_object
Exemple #27
0
 def cleanup(self):
     '''Handle validation after parsing is completed.'''
     # If we're expecting something in particular, there's a problem.
     # All objects should be closed.
     if self.next_method != self.no_object:
         raise Invalid("unclosed", "Object")
Exemple #28
0
    def cleanup(self):
        '''Cleanup actions to be run when the parser ends. Run blank again
        to word wrap, run anchor replacements, and do some validation.'''

        # Run end-frame processing.
        self.blank()

        def list_wrap(*args):
            '''Function to get the value from a series of trial keys.'''
            item = self.executor.trial
            for key in args:
                item = item[key]
            return item

        def repl_func(match):
            '''Function to perform anchor replacement when the anchor is not
            known in advance.'''
            anchor_type = match.group(1).lower().strip()
            anchor = match.group(2)
            try:
                anc_dict = self.executor.anc_dict[anchor_type]["value"]
            except KeyError:
                raise Invalid("unk anc type", anchor_type)
            try:
                return str(anc_dict[anchor])
            except KeyError:
                raise Invalid("anc unset", anchor_type, anchor)

        def recurse_and_anc_replace(json_item):
            '''Function to recurse over an iterable and perform anchor
            replacement on all scalars.'''
            iterable = json_item.items() if isinstance(
                json_item, dict) else enumerate(json_item)
            # Key is only a real key if this is a dictionary. It's an
            # index if this is a list, but the point is the same.
            for key, value in iterable:
                if isinstance(value, (list, dict)):
                    recurse_and_anc_replace(value)
                else:
                    json_item[key] = re.sub(r"\n(.*)\r ?(.*)\n", repl_func,
                                            value)

        for frame in self.executor.trial["frames"][1:]:
            recurse_and_anc_replace(frame["action_parameters"])

        # Do all anchor replacement.
        for key, value in self.executor.anc_dict.items():

            def func(match):
                '''Function that returns the entry in the proper value dict
                for this match. Used for strng replacement.'''
                try:
                    return str(value["value"][match.group(1)])
                except KeyError:
                    raise Invalid("anc unset", key, match.group(1))

            for dest in value["destination"]:
                # The array or dict containing the replacement target.
                nonscalar = list_wrap(*dest[:-1])
                # The current value of the replacement target.
                string = list_wrap(*dest)
                # xpr replacement targets are separate by newlines.
                if string.startswith("xpr="):
                    nonscalar[dest[-1]] = re.sub("\n(.*)\n", func, string)
                # val replacement targets are everything after the val.
                elif string.startswith("val="):
                    nonscalar[dest[-1]] = re.sub("(?<=val=)(.*)", func, string)
                # And other replacment targets are the full value.
                else:
                    try:
                        nonscalar[dest[-1]] = value["value"][string]
                    except KeyError:
                        raise Invalid("anc unset", key, string)

        # Validate all prior assertions that psyche-lock sequences exist.
        for dest in self.executor.check_locks:
            string = list_wrap(*dest)
            if string.startswith("val="):
                target = int(string[4:])
                if not self.executor.trial["scenes"][target]["dialogues"][0][
                        "locks"]:
                    raise Invalid("missing locks", target)

        # Ensure that we're not expecting another frame.
        if self.executor.location:
            raise Invalid("block open")
        if self.executor.frame["merged_to_next"]:
            raise Invalid("terminal merge")
Exemple #29
0
    def word_wrap(self):
        '''The first part of this program turns our string into a series
        of (tag, length) tuples, where each tuple represents a different
        functional unit.

        WARNING! This is by far the most sensitive and complicated code in
        the program. Please do not make any changes without understanding
        the operation of the entire function.'''
        def symbol_length(symbol):
            if symbol not in self.glyph_dict:
                self.glyph_dict[symbol] = self.font.measure(symbol)
            return self.glyph_dict[symbol]

        def get_length(text):
            '''Function that returns length for a string. Used only for a word
            or sequence of spaces. Subpixel rendering causes inconsistent
            pixel counts across machines if more than one character is
            measured at a time.'''
            return sum(symbol_length(n) for n in text)

        def escape(match):
            '''Handles pausing and escape characters. group(1) is the optional
            escape character, and group 2 is the symbol.'''
            if match.group(1):
                return match.group(2)
            try:
                return match.group(2) + self.escape_dict[match.group(2)]
            except KeyError:
                # Ellipsis case.
                if match.group(2) == len(match.group(2)) * "." and len(
                        match.group(2)) > 1:
                    return match.group(2) + self.escape_dict.get("…", "")
                else:
                    return match.group(2)

        words = []
        other = []
        for text in self.line_queue:
            # Split between AAO open/close tags, newlines, and other spaces.
            fragment_list = re.split(r'(\[#[^\]]*\]|\[/#]|[ \t\f\v]+|\n)',
                                     text[:-1])
            # The words go in one dictionary...
            words += [(x, get_length(x)) for x in fragment_list[::2]]
            # ...and everything else goes in the other.
            # \n's length is undefined, but it doesn't make a difference.
            other += [(x, get_length(x)) if x[0].isspace() else (x, 0)
                      for (x) in fragment_list[1::2]]
            # words is one element longer than other. We'll add another
            # element to make them pair up, and to mark where the frame
            # breaks off.
            other.append(('BREAK', 0))

        # If autopause is set, add pauses where applicable.
        if self.config_dict["autopause"]:
            temp_words = []
            found_last = False
            # In old!Catalysis, mid-word punctuation would be paused.
            # Disable this if autopause is "on," enable it for "legacy."
            anchor = "$" if self.config_dict["autopause"] == "on" else ""
            # Loop over each word in the list, from last to first. This...
            for tag, length in reversed(words):
                # ...means the first word in our loop is the last word
                # and hence needs no pauses. found_last handles this.
                if found_last and tag.lower(
                ) not in self.config_dict["autoescape"]:
                    tag = re.sub(r'(\\)?(\.+|\\|,|-|\?|!|…|;|:)' + anchor,
                                 escape, tag)
                # If the line ended in "other," a new word could have been
                # created. Only mark found_last is we have a non-empty tag.
                elif tag:
                    found_last = True
                temp_words.append((tag, length))
            words = temp_words[::-1]

        # Combine the words and other dict again, purging any nulls.
        sequence = [
            item for pair in zip(words, other) for item in pair
            if (item != ('', 0))
        ]
        total = 0
        wrapped_sequence = []

        # If autowrapping is enabled, loop over each term in the sequence.
        if self.config_dict["autowrap"]:
            for tag, length in sequence:
                # If the width would exceed the maximum with the new tag, or
                # the new tag is a newline, time to wrap!
                if total + length > 244 or tag == "\n":
                    # Delete the last space, if there is one.
                    if wrapped_sequence and wrapped_sequence[-1][0].isspace():
                        del wrapped_sequence[-1]
                    # And now append a newline and reset the total.
                    wrapped_sequence.append(("\n", 0))
                    total = 0
                    # Is the tag a space or newline? If so, wrapped_sequence
                    # and tag are how we want them, so continue.
                    if tag.isspace():
                        continue
                # Add the current term to the list, and update the width.
                wrapped_sequence.append((tag, length))
                total += length
        else:
            wrapped_sequence = sequence

        frame_text = [""]
        # Segment the list based on frame breaks, ignoring the terminal break
        # so as not to create a frame with blank text.
        for (tag, length) in wrapped_sequence[:-1]:
            if tag == "BREAK" and length == 0:
                # Interpret this as a new frame.
                frame_text.append("")
            else:
                # Add the tag to the contents for the new frame.
                frame_text[-1] += tag
        target_index = self.executor.frame["id"]
        frames = self.executor.trial["frames"]

        # Check for four-liners...
        if self.config_dict["threelines"] and "".join(frame_text).count(
                "\n") >= 3:
            raise Invalid("mult line", "".join(frame_text))

        # And finally load each segment into frames, starting from the last.
        while frame_text:
            frames[target_index]["text_content"] += frame_text.pop()
            target_index -= 1
Exemple #30
0
 def base_dict(self):
     '''The dictionary of default values. Should be overriden by most
     properties.'''
     raise Invalid("defaults unsupported")