async def parse_template(pak_id: str, file: File) -> None: """Parse the specified template file, extracting its ID.""" path = f'{pak_id}:{file.path}' temp_id = await trio.to_thread.run_sync(parse_template_fast, file, path, cancellable=True) if not temp_id: LOGGER.warning('Fast-parse failure on {}!', path) with file.open_str() as f: props = await trio.to_thread.run_sync(Property.parse, f, cancellable=True) vmf = await trio.to_thread.run_sync(VMF.parse, props, cancellable=True) del props conf_ents = list(vmf.by_class['bee2_template_conf']) if len(conf_ents) > 1: raise KeyValError(f'Multiple configuration entities in template!', path, None) elif not conf_ents: raise KeyValError(f'No configration entity for template!', path, None) temp_id = conf_ents[0]['template_id'] if not temp_id: raise KeyValError('No template ID for template!', path, None) TEMPLATES[temp_id.casefold()] = PackagePath(pak_id, file.path)
def load_soundscript( self, file: File, *, always_include: bool=False, ) -> Iterable[str]: """Read in a soundscript and record which files use it. If always_include is True, it will be included in the manifests even if it isn't used. The sounds registered by this soundscript are returned. """ try: with file.sys, file.open_str() as f: props = Property.parse(f, file.path, allow_escapes=False) except FileNotFoundError: # It doesn't exist, complain and pretend it's empty. LOGGER.warning('Soundscript "{}" does not exist!', file.path) return () except KeyValError: LOGGER.warning('Soundscript "{}" could not be parsed:', exc_info=True) return () return self._parse_soundscript(props, file.path, always_include)
def _parse_file(self, filesys: FileSystem, file: File): """Parse one file (recursively if needed).""" if file in self._parse_list: return self._parse_list.append(file) with filesys, file.open_str() as f: tokeniser = Tokenizer( f, filename=file.path, error=FGDParseError, string_bracket=False, ) for token, token_value in tokeniser: # The only things at top-level would be bare strings, and empty lines. if token is Token.NEWLINE: continue if token is not Token.STRING: raise tokeniser.error(token) token_value = token_value.casefold() if token_value == '@include': include_file = tokeniser.expect(Token.STRING) if not include_file.endswith('.fgd'): include_file += '.fgd' try: include = filesys[include_file] except KeyError: raise FileNotFoundError(file) self._parse_file(filesys, include) elif token_value == '@mapsize': # Max/min map size definition mapsize_args = tokeniser.expect(Token.PAREN_ARGS) try: min_size, max_size = mapsize_args.split(',') self.map_size_min = int(min_size.strip()) self.map_size_max = int(max_size.strip()) except ValueError: raise tokeniser.error( 'Invalid @MapSize: ({})', mapsize_args, ) # Entity definition... elif token_value[:1] == '@': try: ent_type = EntityTypes(token_value[1:]) except ValueError: raise tokeniser.error( 'Invalid Entity type "{}"!', ent_type[1:], ) EntityDef.parse(self, tokeniser, ent_type) else: raise tokeniser.error('Bad keyword {!r}', token_value)
def load_soundscript( self, file: File, *, always_include: bool = False, ) -> Iterable[str]: """Read in a soundscript and record which files use it. If always_include is True, it will be included in the manifests even if it isn't used. The sounds registered by this soundscript are returned. """ with file.sys, file.open_str() as f: props = Property.parse(f, file.path) return self._parse_soundscript(props, file.path, always_include)
def load_soundscript( self, file: File, *, always_include: bool=False, ) -> Iterable[str]: """Read in a soundscript and record which files use it. If always_include is True, it will be included in the manifests even if it isn't used. The sounds registered by this soundscript are returned. """ with file.sys, file.open_str() as f: props = Property.parse(f, file.path) return self._parse_soundscript(props, file.path, always_include)
def parse_template_fast(file: File, path: str) -> str: """Since we only care about a single KV, fully parsing is a big waste of time. So first try naively parsing - if we don't find it, fall back to full parsing. """ in_entity = False nest_counter = 0 has_classname = False found_id = '' temp_id = '' with file.open_str() as f: iterator = enumerate(f, 1) for lnum, line in iterator: line = line.strip().casefold() if not in_entity: if line != 'entity': continue lnum, line = next(iterator, (None, '')) if line.strip() != '{': raise KeyValError('Expected brace in entity definition', path, lnum) in_entity = True has_classname = False temp_id = '' elif line == '{': nest_counter += 1 elif line == '}': if nest_counter == 0: in_entity = False if has_classname and found_id: raise KeyValError( 'Multiple configuration entities in template!', path, lnum) elif has_classname and temp_id: found_id = temp_id else: nest_counter -= 1 else: # Inside ent. if line == '"classname" "bee2_template_conf"': has_classname = True elif line.startswith('"template_id"'): temp_id = line[15:-1] return found_id
def parse_manifest(fsys: FileSystem, file: File=None) -> Dict[str, 'SurfaceProp']: """Load surfaceproperties from a manifest. "scripts/surfaceproperties_manifest.txt" will be used if a file is not specified. """ with fsys: if not file: file = fsys['scripts/surfaceproperties_manifest.txt'] with file.open_str() as f: manifest = Property.parse(f, file.path) surf = {} for prop in manifest.find_all('surfaceproperties_manifest', 'file'): surf = SurfaceProp.parse_file(fsys.read_prop(prop.value), surf) return surf