Beispiel #1
0
    def save(self, ignore_readonly: bool = False) -> None:
        """Save the palette file into the specified location.

        If ignore_readonly is true, this will ignore the `readonly`
        property of the palette (allowing resaving those properties over old
        versions). Otherwise those palettes always create a new file.
        """
        LOGGER.info('Saving "{}"!', self.name)
        props = Property.root(
            Property('Name', self.name), Property('TransName',
                                                  self.trans_name),
            Property('Group', self.group),
            Property('ReadOnly', srctools.bool_as_int(self.readonly)),
            Property('UUID', self.uuid.hex),
            Property('Items', [
                Property(item_id, str(subitem))
                for item_id, subitem in self.pos
            ]))
        # If default, don't include in the palette file.
        # Remove the translated name, in case it's not going to write
        # properly to the file.
        if self.trans_name:
            props['Name'] = ''
        else:
            del props['TransName']

        if self.settings is not None:
            self.settings.name = 'Settings'
            props.append(self.settings.copy())

        # We need to write a new file, determine a valid path.
        # Use a hash to ensure it's a valid path (without '-' if negative)
        # If a conflict occurs, add ' ' and hash again to get a different
        # value.
        if self.filename is None or (self.readonly and not ignore_readonly):
            hash_src = self.name
            while True:
                hash_filename = str(abs(hash(hash_src))) + PAL_EXT
                if os.path.isfile(hash_filename):
                    # Add a random character to iterate the hash.
                    hash_src += chr(random.randrange(0x10ffff))
                else:
                    file = open(os.path.join(PAL_DIR, hash_filename),
                                'w',
                                encoding='utf8')
                    self.filename = os.path.join(PAL_DIR, hash_filename)
                    break
        else:
            file = open(os.path.join(PAL_DIR, self.filename),
                        'w',
                        encoding='utf8')
        with file:
            for line in props.export():
                file.write(line)
Beispiel #2
0
def get_curr_settings(*, is_palette: bool) -> Property:
    """Return a property tree defining the current options."""
    props = Property.root()

    for opt_id, opt_func in OPTION_SAVE.items():
        # Skip if it opts out of being on the palette.
        if is_palette and not getattr(opt_func, 'to_palette', True):
            continue
        opt_prop = opt_func()
        opt_prop.name = opt_id.title()
        props.append(opt_prop)

    return props
Beispiel #3
0
def apply_replacements(conf: Property, item_id: str) -> Property:
    """Apply a set of replacement values to a config file, returning a new copy.

    The replacements are found in a 'Replacements' block in the property.
    These replace %values% starting and ending with percents. A double-percent
    allows literal percents. Unassigned values are an error.
    """
    replace: dict[str, str] = {}
    new_conf = Property.root() if conf.is_root() else Property(
        conf.real_name, [])

    # Strip the replacement blocks from the config, and save the values.
    for prop in conf:
        if prop.name == 'replacements':
            for rep_prop in prop:
                replace[rep_prop.name.strip('%')] = rep_prop.value
        else:
            new_conf.append(prop)

    def rep_func(match: Match) -> str:
        """Does the replacement."""
        var = match.group(1)
        if not var:  # %% becomes %.
            return '%'
        try:
            return replace[var.casefold()]
        except KeyError:
            raise ValueError(
                f'Unresolved variable in "{item_id}": {var!r}\nValid vars: {replace}'
            )

    for prop in new_conf.iter_tree(blocks=True):
        prop.name = RE_PERCENT_VAR.sub(rep_func, prop.real_name)
        if not prop.has_children():
            prop.value = RE_PERCENT_VAR.sub(rep_func, prop.value)

    return new_conf
Beispiel #4
0
	def concat_inner() -> Property:
		"""Resolve then merge the configs."""
		prop = Property.root()
		prop.extend(a())
		prop.extend(b())
		return prop
Beispiel #5
0
"""Implements callables which lazily parses and combines config files."""
from __future__ import annotations
from typing import Callable, Pattern
import functools

from srctools import Property, logger, KeyValError
from app import DEV_MODE
import packages
import utils


LOGGER = logger.get_logger(__name__)
LazyConf = Callable[[], Property]
# Empty property.
BLANK: LazyConf = lambda: Property.root()


def raw_prop(block: Property, source: str= '') -> LazyConf:
	"""Make an existing property conform to the interface."""
	if block or block.name is not None:
		if source:
			def copier() -> Property:
				"""Copy the config, then apply the source."""
				copy = block.copy()
				packages.set_cond_source(copy, source)
				return copy
			return copier
		else:  # We can just use the bound method.
			return block.copy
	else:  # If empty, source is irrelevant, and we can use the constant.
		return BLANK