PK!<nbtlib/__init__.pyfrom .tag import * from .nbt import * from .schema import * from .literal.parser import * from .literal.serializer import * __version__ = '1.4.2' PK!2>>nbtlib/__main__.pyfrom .cli import main if __name__ == '__main__': main() PK!8T nbtlib/cli.pyfrom argparse import ArgumentParser, ArgumentTypeError from nbtlib import nbt, parse_nbt, serialize_tag, InvalidLiteral from nbtlib.tag import Compound # Validation helper def compound_literal(literal): try: nbt_data = parse_nbt(literal) except InvalidLiteral as exc: raise ArgumentTypeError(exc) from exc else: if not isinstance(nbt_data, Compound): raise ArgumentTypeError('The root nbt tag must be a compound tag') return nbt_data # Create the argument parser parser = ArgumentParser(prog='nbt', description='Perform basic operations on nbt files.') group = parser.add_mutually_exclusive_group(required=True) group.add_argument('-r', action='store_true', help='read nbt data from a file') group.add_argument('-w', metavar='', type=compound_literal, help='write nbt to a file') group.add_argument('-m', metavar='', type=compound_literal, help='merge nbt into an nbt file') parser.add_argument('--plain', action='store_true', help='don\'t use gzip compression') parser.add_argument('--little', action='store_true', help='use little-endian format') parser.add_argument('--compact', action='store_true', help='output compact snbt') parser.add_argument('--pretty', action='store_true', help='output indented snbt') parser.add_argument('file', metavar='', help='the target file') # Define command-line interface def main(): args = parser.parse_args() gzipped, byteorder = not args.plain, 'little' if args.little else 'big' try: if args.r: read(args.file, gzipped, byteorder, args.compact, args.pretty) elif args.w: write(args.w, args.file, gzipped, byteorder) elif args.m: merge(args.m, args.file, gzipped, byteorder) except IOError as exc: parser.exit(1, str(exc) + '\n') def read(filename, gzipped, byteorder, compact, pretty): nbt_file = nbt.load(filename, gzipped=gzipped, byteorder=byteorder) print(serialize_tag(nbt_file, indent=4 if pretty else None, compact=compact)) def write(nbt_data, filename, gzipped, byteorder): nbt.File(nbt_data).save(filename, gzipped=gzipped, byteorder=byteorder) def merge(nbt_data, filename, gzipped, byteorder): nbt_file = nbt.load(filename, gzipped=gzipped, byteorder=byteorder) nbt_file.merge(nbt_data) nbt_file.save() PK!nbtlib/literal/__init__.pyPK!%m@@nbtlib/literal/parser.py"""This module exposes utilities for parsing snbt. Exported functions: parse_nbt -- Helper function that parses nbt literals tokenize -- Generator that lazily yields tokens from a string Exported classes: Parser -- Class that can parse nbt tags from a literal token stream Exported exceptions: InvalidLiteral -- Raised when parsing invalid nbt literals """ __all__ = ['parse_nbt', 'InvalidLiteral', 'tokenize', 'Parser'] import re from collections import namedtuple from ..tag import (Byte, Short, Int, Long, Float, Double, ByteArray, String, List, Compound, IntArray, LongArray, OutOfRange, IncompatibleItemType) from .serializer import ESCAPE_SEQUENCES # Token definition ESCAPE_REGEX = re.compile(r'\\.') TOKENS = { 'QUOTED_STRING': fr'"(?:{ESCAPE_REGEX.pattern}|[^\\])*?"', 'NUMBER': r'[+-]?(?:[0-9]*?\.[0-9]+|[0-9]+\.[0-9]*?|[0-9]+)[bslfdBSLFD]?(?![a-zA-Z0-9._+-])', 'STRING': r'[a-zA-Z0-9._+-]+', 'COMPOUND': r'\{', 'CLOSE_COMPOUND': r'\}', 'BYTE_ARRAY': r'\[B;', 'INT_ARRAY': r'\[I;', 'LONG_ARRAY': r'\[L;', 'LIST': r'\[', 'CLOSE_BRACKET': r'\]', 'COLON': r':', 'COMMA': r',', 'INVALID': r'.+?', } # Build the regex TOKENS_REGEX = re.compile( '|'.join(fr'\s*(?P<{key}>{value})\s*' for key, value in TOKENS.items()) ) # Associate number suffixes to tag types NUMBER_SUFFIXES = {'b': Byte, 's': Short, 'l': Long, 'f': Float, 'd': Double} # Custom errors class InvalidLiteral(ValueError): """Exception raised when parsing invalid nbt literals. The exception must be instantiated with two parameters. The first one needs to be a tuple representing the location of the error in the nbt string (start_index, end_index). The second argument is the actual error message. """ def __str__(self): return f'{self.args[1]} at position {self.args[0][0]}' # User-friendly helper def parse_nbt(literal): """Parse a literal nbt string and return the resulting tag.""" parser = Parser(tokenize(literal)) tag = parser.parse() cursor = parser.token_span[1] leftover = literal[cursor:] if leftover.strip(): parser.token_span = cursor, cursor + len(leftover) raise parser.error(f'Expected end of string but got {leftover!r}') return tag # Implement tokenization Token = namedtuple('Token', ['type', 'value', 'span']) def tokenize(string): """Match and yield all the tokens of the input string.""" for match in TOKENS_REGEX.finditer(string): yield Token(match.lastgroup, match.group().strip(), match.span()) # Implement parser class Parser: """Nbt literal parser. The parser needs to be instantiated with a token stream as argument. Using the `parse` method will return the corresponding nbt tag. The parser will raise an InvalidLiteral exception if it encounters an invalid nbt literal while parsing. """ def __init__(self, token_stream): self.token_stream = iter(token_stream) self.current_token = None self.token_span = (0, 0) self.next() def error(self, message): """Create an InvalidLiteral using the current token position.""" return InvalidLiteral(self.token_span, message) def next(self): """Move to the next token in the token stream.""" self.current_token = next(self.token_stream, None) if self.current_token is None: self.token_span = self.token_span[1], self.token_span[1] raise self.error('Unexpected end of input') self.token_span = self.current_token.span return self def parse(self): """Parse and return an nbt literal from the token stream.""" token_type = self.current_token.type.lower() handler = getattr(self, f'parse_{token_type}', None) if handler is None: raise self.error(f'Invalid literal {self.current_token.value!r}') return handler() def parse_quoted_string(self): """Parse a quoted string from the token stream.""" return String(self.unquote_string(self.current_token.value)) def parse_number(self): """Parse a number from the token stream.""" value = self.current_token.value suffix = value[-1].lower() try: if suffix in NUMBER_SUFFIXES: return NUMBER_SUFFIXES[suffix](value[:-1]) return Double(value) if '.' in value else Int(value) except OutOfRange: return String(value) def parse_string(self): """Parse a regular unquoted string from the token stream.""" return String(self.current_token.value) def collect_tokens_until(self, token_type): """Yield the item tokens in a comma-separated tag collection.""" self.next() if self.current_token.type == token_type: return while True: yield self.current_token self.next() if self.current_token.type == token_type: return if self.current_token.type != 'COMMA': raise self.error(f'Expected comma but got ' f'{self.current_token.value!r}') self.next() def parse_compound(self): """Parse a compound from the token stream.""" compound_tag = Compound() for token in self.collect_tokens_until('CLOSE_COMPOUND'): item_key = token.value if token.type not in ('NUMBER', 'STRING', 'QUOTED_STRING'): raise self.error(f'Expected compound key but got {item_key!r}') if token.type == 'QUOTED_STRING': item_key = self.unquote_string(item_key) if self.next().current_token.type != 'COLON': raise self.error(f'Expected colon but got ' f'{self.current_token.value!r}') self.next() compound_tag[item_key] = self.parse() return compound_tag def array_items(self, number_type, *, number_suffix=''): """Parse and yield array items from the token stream.""" for token in self.collect_tokens_until('CLOSE_BRACKET'): is_number = token.type == 'NUMBER' value = token.value.lower() if not (is_number and value.endswith(number_suffix)): raise self.error(f'Invalid {number_type} array element ' f'{token.value!r}') yield int(value.replace(number_suffix, '')) def parse_byte_array(self): """Parse a byte array from the token stream.""" return ByteArray(list(self.array_items('byte', number_suffix='b'))) def parse_int_array(self): """Parse an int array from the token stream.""" return IntArray(list(self.array_items('int'))) def parse_long_array(self): """Parse a long array from the token stream.""" return LongArray(list(self.array_items('long', number_suffix='l'))) def parse_list(self): """Parse a list from the token stream.""" try: return List([self.parse() for _ in self.collect_tokens_until('CLOSE_BRACKET')]) except IncompatibleItemType as exc: raise self.error(f'Item {str(exc.item)!r} is not a ' f'{exc.subtype.__name__} tag') from None def parse_invalid(self): """Parse an invalid token from the token stream.""" raise self.error(f'Invalid token {self.current_token.value!r}') def unquote_string(self, string): """Return the unquoted value of a quoted string.""" value = string[1:-1] for seq in ESCAPE_REGEX.findall(value): if seq not in ESCAPE_SEQUENCES: raise self.error(f'Invalid escape sequence "{seq}"') for seq, sub in ESCAPE_SEQUENCES.items(): value = value.replace(seq, sub) return value PK!pf22nbtlib/literal/serializer.py"""This module exposes utilities for serializing nbt tags to snbt. Exported functions: serialize_tag -- Helper function that serializes nbt tags Exported classes: Serializer -- Class that can turn nbt tags into their literal representation Exported objects: ESCAPE_SEQUENCES -- Maps escape sequences to their substitution ESCAPE_SUBS -- Maps substitutions to their escape sequence """ __all__ = ['serialize_tag', 'ESCAPE_SEQUENCES', 'ESCAPE_SUBS', 'Serializer'] import re from contextlib import contextmanager # Escape nbt strings ESCAPE_SEQUENCES = { r'\"': '"', r'\\': '\\', } ESCAPE_SUBS = dict(reversed(tuple(map(reversed, ESCAPE_SEQUENCES.items())))) def escape_string(string): """Return the escaped literal representation of an nbt string.""" for match, seq in ESCAPE_SUBS.items(): string = string.replace(match, seq) return f'"{string}"' # Detect if a compound key can be represented unquoted UNQUOTED_COMPOUND_KEY = re.compile(r'^[a-zA-Z0-9._+-]+$') def stringify_compound_key(key): """Escape the compound key if it can't be represented unquoted.""" if UNQUOTED_COMPOUND_KEY.match(key): return key return escape_string(key) # User-friendly helper def serialize_tag(tag, *, indent=None, compact=False): """Serialize an nbt tag to its literal representation.""" serializer = Serializer(indent=indent, compact=compact) return serializer.serialize(tag) # Implement serializer class Serializer: """Nbt tag serializer.""" def __init__(self, *, indent=None, compact=False): self.indentation = indent * ' ' if isinstance(indent, int) else indent self.comma = ',' if compact else ', ' self.colon = ':' if compact else ': ' self.semicolon = ';' if compact else '; ' self.indent = '' self.previous_indent = '' @contextmanager def depth(self): """Increase the level of indentation by one.""" if self.indentation is None: yield else: previous = self.previous_indent self.previous_indent = self.indent self.indent += self.indentation yield self.indent = self.previous_indent self.previous_indent = previous def should_expand(self, tag): """Return whether the specified tag should be expanded.""" return self.indentation is not None and tag and ( not self.previous_indent or ( tag.serializer == 'list' and tag.subtype.serializer in ('array', 'list', 'compound') ) or ( tag.serializer == 'compound' ) ) def expand(self, separator, fmt): """Return the expanded version of the separator and format string.""" return ( f'{separator}\n{self.indent}', fmt.replace('{}', f'\n{self.indent}{{}}\n{self.previous_indent}') ) def serialize(self, tag): """Return the literal representation of a tag.""" handler = getattr(self, f'serialize_{tag.serializer}', None) if handler is None: raise TypeError(f'Can\'t serialize {type(tag)!r} instance') return handler(tag) def serialize_numeric(self, tag): """Return the literal representation of a numeric tag.""" str_func = int.__str__ if isinstance(tag, int) else float.__str__ return str_func(tag) + tag.suffix def serialize_array(self, tag): """Return the literal representation of an array tag.""" elements = self.comma.join(f'{el}{tag.item_suffix}' for el in tag) return f'[{tag.array_prefix}{self.semicolon}{elements}]' def serialize_string(self, tag): """Return the literal representation of a string tag.""" return escape_string(tag) def serialize_list(self, tag): """Return the literal representation of a list tag.""" separator, fmt = self.comma, '[{}]' with self.depth(): if self.should_expand(tag): separator, fmt = self.expand(separator, fmt) return fmt.format(separator.join(map(self.serialize, tag))) def serialize_compound(self, tag): """Return the literal representation of a compound tag.""" separator, fmt = self.comma, '{{{}}}' with self.depth(): if self.should_expand(tag): separator, fmt = self.expand(separator, fmt) return fmt.format(separator.join( f'{stringify_compound_key(key)}{self.colon}{self.serialize(value)}' for key, value in tag.items() )) PK!5 nbtlib/nbt.py"""This module contains utilities for loading and creating nbt files. Exported items: load -- Helper function to load nbt files File -- Class that represents an nbt file, inherits from `Compound` """ __all__ = ['load', 'File'] import gzip from .tag import Compound def load(filename, *, gzipped=None, byteorder='big'): """Load the nbt file at the specified location. By default, the function will figure out by itself if the file is gzipped before loading it. You can pass a boolean to the `gzipped` keyword only argument to specify explicitly whether the file is compressed or not. You can also use the `byteorder` keyword only argument to specify whether the file is little-endian or big-endian. """ if gzipped is not None: return File.load(filename, gzipped, byteorder) # if we don't know we read the magic number with open(filename, 'rb') as buff: magic_number = buff.read(2) buff.seek(0) if magic_number == b'\x1f\x8b': buff = gzip.GzipFile(fileobj=buff) return File.from_buffer(buff, byteorder) class File(Compound): """Class representing a compound nbt file. The class inherits from `Compound`, so all of the dict operations inherited by `Compound` are also available on `File` instances. The `load` class method can be use to load files from disk. If you need to create the file from a file-like object you can use the inherited `parse` method. Getting the root tag of the file can be done with the `root` property. You can use the `save` method to save modifications. Using the `File` instance as a context manager will automatically save modifications when the `__exit__` method is called. Attributes: filename -- The name of the file gzipped -- Boolean indicating if the file is gzipped byteorder -- The byte order (either 'big' or 'little') """ # We remove the inherited end tag as the end of nbt files is # specified by the end of the file buffer end_tag = b'' def __init__(self, *args, gzipped=False, byteorder='big'): super().__init__(*args) self.filename = None self.gzipped = gzipped self.byteorder = byteorder @property def root_name(self): """The name of the root nbt tag.""" return next(iter(self), None) @root_name.setter def root_name(self, value): self[value] = self.pop(self.root_name) @property def root(self): """The root nbt tag of the file.""" return self[self.root_name] @root.setter def root(self, value): self[self.root_name] = value @classmethod def from_buffer(cls, buff, byteorder='big'): """Load nbt file from a file-like object. The `buff` argument can be either a standard `io.BufferedReader` for uncompressed nbt or a `gzip.GzipFile` for gzipped nbt data. """ self = cls.parse(buff, byteorder) self.filename = getattr(buff, 'name', self.filename) self.gzipped = isinstance(buff, gzip.GzipFile) self.byteorder = byteorder return self @classmethod def load(cls, filename, gzipped, byteorder='big'): """Read, parse and return the file at the specified location. The `gzipped` argument is used to indicate if the specified file is gzipped. The `byteorder` argument lets you specify whether the file is big-endian or little-endian. """ open_file = gzip.open if gzipped else open with open_file(filename, 'rb') as buff: return cls.from_buffer(buff, byteorder) def save(self, filename=None, *, gzipped=None, byteorder=None): """Write the file at the specified location. The `gzipped` keyword only argument indicates if the file should be gzipped. The `byteorder` keyword only argument lets you specify whether the file should be big-endian or little-endian. If the method is called without any argument, it will default to the instance attributes and use the file's `filename`, `gzipped` and `byteorder` attributes. Calling the method without a `filename` will raise a `ValueError` if the `filename` of the file is `None`. """ if gzipped is None: gzipped = self.gzipped if filename is None: filename = self.filename if filename is None: raise ValueError('No filename specified') open_file = gzip.open if gzipped else open with open_file(filename, 'wb') as buff: self.write(buff, byteorder or self.byteorder) def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.save() def __repr__(self): return f'<{self.__class__.__name__} {self.root_name!r}: {self.root!r}>' PK!Q+l nbtlib/schema.py"""This module defines tools for creating tag schemas. Exported items: schema -- Helper function to define compound schemas CompoundSchema -- `Compound` subclass that enforces a tag schema """ __all__ = ['schema', 'CompoundSchema'] from itertools import chain from .tag import Compound, CastError def schema(name, dct, *, strict=False): """Create a compound tag schema. This function is a short convenience function that makes it easy to subclass the base `CompoundSchema` class. The `name` argument is the name of the class and `dct` should be a dictionnary containing the actual schema. The schema should map keys to tag types or other compound schemas. If the `strict` keyword only argument is set to True, interacting with keys that are not defined in the schema will raise a `TypeError`. """ return type(name, (CompoundSchema,), {'__slots__': (), 'schema': dct, 'strict': strict}) class CompoundSchema(Compound): """Class that extends the base `Compound` tag by enforcing a schema. Defining a custom schema is really useful if you're dealing with recurring data structures. Subclassing the `CompoundSchema` class with your own schema will save you some typing by casting all the keys defined in the schema to the appropriate tag type. The class inherits from `Compound` and will cast values to the predefined tag types for all of the inherited mutating operations. Class attributes: schema -- Dictionnary mapping keys to tag types or other schemas strict -- Boolean enabling strict schema validation """ __slots__ = () schema = {} strict = False def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for key, value in self.items(): correct_value = self.cast_item(key, value) if correct_value is not value: super().__setitem__(key, correct_value) def __setitem__(self, key, value): super().__setitem__(key, self.cast_item(key, value)) def update(self, mapping, **kwargs): pairs = chain(mapping.items(), kwargs.items()) super().update( (key, self.cast_item(key, value)) for key, value in pairs ) @classmethod def cast_item(cls, key, value): """Cast schema item to the appropriate tag type.""" schema_type = cls.schema.get(key, None) if schema_type is None: if cls.strict: raise TypeError(f'Invalid key {key!r}') elif not isinstance(value, schema_type): try: return schema_type(value) except CastError: raise except Exception as exc: raise CastError(value, schema_type) from exc return value PK!8L<0B0B nbtlib/tag.py"""This module contains nbt tag definitions. All the tag classes defined here can be used to instantiate nbt tags. They also all have a `parse` classmethod that reads nbt data from a file-like object and returns a tag instance. Tag instances can write their binary representation to file-like objects using the `write` method. Each tag inherits from the equivalent python data type. This means that all the operations that are commonly used on the base types are available on nbt tags. Exported classes: End -- Represents the end of a compound tag Byte -- Represents a byte tag, inherits from `int` Short -- Represents a short tag, inherits from `int` Int -- Represents an int tag, inherits from `int` Long -- Represents a long tag, inherits from `int` Float -- Represents a float tag, inherits from `float` Double -- Represents a double tag, inherits from `float` ByteArray -- Represents a byte array tag, inherits from `ndarray` String -- Represents a string tag, inherits from `str` List -- Represents a generic list tag, inherits from `list` Compound -- Represents a compound tag, inherits from `dict` IntArray -- Represents an int array tag, inherits from `ndarray` LongArray -- Represents a long array tag, inherits from `ndarray` Exported exceptions: EndEndInstantiation -- Raised when instantiating an End tag OutOfRange -- Raised when the value of a numerical tag is out of range IncompatibleItemType -- Raised when the type of a list item is incompatible CastError -- Raised when casting a value to a tag fails """ __all__ = ['End', 'Byte', 'Short', 'Int', 'Long', 'Float', 'Double', 'ByteArray', 'String', 'List', 'Compound', 'IntArray', 'LongArray', 'EndInstantiation', 'OutOfRange', 'IncompatibleItemType', 'CastError'] from struct import Struct, error as StructError import numpy as np from .literal.serializer import serialize_tag # Struct formats used to pack and unpack numeric values def get_format(fmt, string): """Return a dictionnary containing a format for each byte order.""" return {'big': fmt('>' + string), 'little': fmt('<' + string)} BYTE = get_format(Struct, 'b') SHORT = get_format(Struct, 'h') USHORT = get_format(Struct, 'H') INT = get_format(Struct, 'i') LONG = get_format(Struct, 'q') FLOAT = get_format(Struct, 'f') DOUBLE = get_format(Struct, 'd') # Custom errors class EndInstantiation(TypeError): """Raised when trying to instantiate an `End` tag.""" def __init__(self): super().__init__('End tags can\'t be instantiated') class OutOfRange(ValueError): """Raised when a numeric value is out of range.""" def __init__(self, value): super().__init__(f'{value!r} is out of range') class IncompatibleItemType(TypeError): """Raised when a list item is incompatible with the subtype of the list.""" def __init__(self, item, subtype): super().__init__(f'{item!r} should be a {subtype.__name__} tag') self.item = item self.subtype = subtype class CastError(ValueError): """Raised when an object couldn't be casted to the appropriate tag type.""" def __init__(self, obj, tag_type): super().__init__(f'Couldn\'t cast {obj!r} to {tag_type.__name__}') self.obj = obj self.tag_type = tag_type # Read/write helpers for numeric and string values def read_numeric(fmt, buff, byteorder='big'): """Read a numeric value from a file-like object.""" try: fmt = fmt[byteorder] return fmt.unpack(buff.read(fmt.size))[0] except StructError: return 0 except KeyError as exc: raise ValueError('Invalid byte order') from exc def write_numeric(fmt, value, buff, byteorder='big'): """Write a numeric value to a file-like object.""" try: buff.write(fmt[byteorder].pack(value)) except KeyError as exc: raise ValueError('Invalid byte order') from exc def read_string(buff, byteorder='big'): """Read a string from a file-like object.""" length = read_numeric(USHORT, buff, byteorder) return buff.read(length).decode('utf-8') def write_string(value, buff, byteorder='big'): """Write a string to a file-like object.""" data = value.encode('utf-8') write_numeric(USHORT, len(data), buff, byteorder) buff.write(data) # Tag definitions class Base: """Base class inherited by all nbt tags. This class is not meant to be instantiated. Derived classes that define a tag id are required to override the `parse` classmethod and the `write` method. Class attributes: all_tags -- Dictionnary mapping tag ids to child classes """ __slots__ = () all_tags = {} tag_id = None serializer = None def __init_subclass__(cls): # Add class to the `all_tags` dictionnary if it has a tag id if cls.tag_id is not None and cls.tag_id not in cls.all_tags: cls.all_tags[cls.tag_id] = cls @classmethod def get_tag(cls, tag_id): """Return the class corresponding to the given tag id.""" return cls.all_tags[tag_id] @classmethod def parse(cls, buff, byteorder='big'): """Parse data from a file-like object and return a tag instance.""" def write(self, buff, byteorder='big'): """Write the binary representation of the tag to a file-like object.""" def __repr__(self): if self.tag_id is not None: return f'{self.__class__.__name__}({super().__repr__()})' return super().__repr__() def __str__(self): try: return serialize_tag(self) except TypeError: return super().__str__() class End(Base): """Nbt tag used to mark the end of a compound tag.""" __slots__ = () tag_id = 0 def __new__(cls, *args, **kwargs): raise EndInstantiation() class Numeric(Base): """Intermediate class that represents a numeric nbt tag. This class is not meant to be instantiated. It inherits from the `Base` class and defines an additional class attribute `fmt`. Derived classes must assign this attribute to the struct format corresponding to the tag type. They must also inherit from a builtin numeric type (`int` or `float`). The class overrides `parse` and `write` and uses the `fmt` attribute to pack and unpack the tag value. Class attributes: fmt -- The struct format used to pack and unpack the tag value """ __slots__ = () serializer = 'numeric' fmt = None suffix = '' range = None def __init_subclass__(cls): super().__init_subclass__() if issubclass(cls, int): limit = 2 ** (8 * cls.fmt['big'].size - 1) cls.range = range(-limit, limit) def __new__(cls, *args, **kwargs): self = super().__new__(cls, *args, **kwargs) if cls.range is not None and int(self) not in cls.range: raise OutOfRange(self) return self @classmethod def parse(cls, buff, byteorder='big'): return cls(read_numeric(cls.fmt, buff, byteorder)) def write(self, buff, byteorder='big'): write_numeric(self.fmt, self, buff, byteorder) class Byte(Numeric, int): """Nbt tag representing a signed byte.""" __slots__ = () tag_id = 1 fmt = BYTE suffix = 'b' class Short(Numeric, int): """Nbt tag representing a signed 16 bit integer.""" __slots__ = () tag_id = 2 fmt = SHORT suffix = 's' class Int(Numeric, int): """Nbt tag representing a signed 32 bit integer.""" __slots__ = () tag_id = 3 fmt = INT class Long(Numeric, int): """Nbt tag representing a signed 64 bit integer.""" __slots__ = () tag_id = 4 fmt = LONG suffix = 'L' class Float(Numeric, float): """Nbt tag representing a single-precision floating point number.""" __slots__ = () tag_id = 5 fmt = FLOAT suffix = 'f' class Double(Numeric, float): """Nbt tag representing a double-precision floating point number.""" __slots__ = () tag_id = 6 fmt = DOUBLE suffix = 'd' class Array(Base, np.ndarray): """Intermediate class that represents an array nbt tag. This class is not meant to be instantiated. It inherits from the `Base` class and the numpy `ndarray` type. Class attributes: item_type -- The numpy array data type array_prefix -- The literal array prefix item_suffix -- The literal item suffix """ __slots__ = () serializer = 'array' item_type = None array_prefix = None item_suffix = '' def __new__(cls, value=None, *, length=0, byteorder='big'): item_type = cls.item_type[byteorder] if value is None: return np.zeros((length,), item_type).view(cls) return np.asarray(value, item_type).view(cls) @classmethod def parse(cls, buff, byteorder='big'): item_type = cls.item_type[byteorder] data = buff.read(read_numeric(INT, buff, byteorder) * item_type.itemsize) return cls(np.frombuffer(data, item_type), byteorder=byteorder) def write(self, buff, byteorder='big'): write_numeric(INT, len(self), buff, byteorder) array = self if self.item_type[byteorder] is self.dtype else self.byteswap() buff.write(array.tobytes()) def __bool__(self): return all(self) def __repr__(self): return f'{self.__class__.__name__}([{", ".join(map(str, self))}])' class ByteArray(Array): """Nbt tag representing an array of signed bytes.""" __slots__ = () tag_id = 7 item_type = get_format(np.dtype, 'b') array_prefix = 'B' item_suffix = 'B' class String(Base, str): """Nbt tag representing a string.""" __slots__ = () tag_id = 8 serializer = 'string' @classmethod def parse(cls, buff, byteorder='big'): return cls(read_string(buff, byteorder)) def write(self, buff, byteorder='big'): write_string(self, buff, byteorder) class ListMeta(type): """Allows class indexing to create and return subclasses on the fly. This metaclass is used by the List tag class definition. It allows the class to create and return subclasses of itself when it is indexed with a tag type. If a subclass of the specified type has already been created, the existing subclass will be returned. """ def __init__(cls, name, bases, dct): super().__init__(name, bases, dct) cls.variants = {} def __getitem__(cls, item): if item is End: return cls try: return List.variants[item] except KeyError: variant = type(f'{List.__name__}[{item.__name__}]', (List,), {'__slots__': (), 'subtype': item}) List.variants[item] = variant return variant class List(Base, list, metaclass=ListMeta): """Nbt tag representing a list of other nbt tags. The list can only hold a single type of tag. To enforce this constraint, the class must be subclassed and define an appropriate subtype. The `ListMeta` metaclass is used to seamlessly implement this operation. This means that accessing List[TagName] will return a subclass of List with the subtype TagName. On top of that, List inherits from Base and the python builtin list type. This means that all the usual list operations are supported on list tag instances. Mutating operations have been overwritten to include an isinstance() check. For instance, when calling the `append` method, the appended item will be wrapped by the defined subtype if isinstance(item, TagName) returns False. Class attributes: subtype -- The nbt tag that will be used to wrap list items """ __slots__ = () tag_id = 9 serializer = 'list' subtype = End def __new__(cls, iterable=()): if cls.subtype is End: subtype = End for item in iterable: item_type = type(item) if issubclass(item_type, Base): subtype = item_type if cls.is_concrete_subtype(subtype): break cls = cls[subtype] return super().__new__(cls, iterable) def __init__(self, iterable=()): super().__init__(map(self.cast_item, iterable)) @staticmethod def is_concrete_subtype(subtype): """Check if a subtype is concrete or could be casted to something else.""" while issubclass(subtype, List): subtype = subtype.subtype return subtype is not End @classmethod def parse(cls, buff, byteorder='big'): tag = cls.get_tag(read_numeric(BYTE, buff, byteorder)) length = read_numeric(INT, buff, byteorder) return cls[tag](tag.parse(buff, byteorder) for _ in range(length)) def write(self, buff, byteorder='big'): write_numeric(BYTE, self.subtype.tag_id, buff, byteorder) write_numeric(INT, len(self), buff, byteorder) for elem in self: elem.write(buff, byteorder) def __setitem__(self, key, value): super().__setitem__(key, self.cast_item(value)) def append(self, value): super().append(self.cast_item(value)) def extend(self, iterable): super().extend(map(self.cast_item, iterable)) def insert(self, index, value): super().insert(index, self.cast_item(value)) @classmethod def cast_item(cls, item): """Cast list item to the appropriate tag type.""" if not isinstance(item, cls.subtype): incompatible = isinstance(item, Base) and not any( issubclass(cls.subtype, tag_type) and isinstance(item, tag_type) for tag_type in cls.all_tags.values() ) if incompatible: raise IncompatibleItemType(item, cls.subtype) try: return cls.subtype(item) except EndInstantiation: raise ValueError('List tags without any subtype must either ' 'be empty or instantiated with elements from ' 'which a subtype can be inferred') from None except (IncompatibleItemType, CastError): raise except Exception as exc: raise CastError(item, cls.subtype) from exc return item class Compound(Base, dict): """Nbt tag that represents a mapping of strings to other nbt tags. The Compound class inherits both from Base and the python builtin dict type. This means that all the operations that are usually available on python dictionaries are supported. Class attributes: end_tag -- Bytes used to mark the end of the compound """ __slots__ = () tag_id = 10 serializer = 'compound' end_tag = b'\x00' @classmethod def parse(cls, buff, byteorder='big'): self = cls() tag_id = read_numeric(BYTE, buff, byteorder) while tag_id != 0: name = read_string(buff, byteorder) self[name] = cls.get_tag(tag_id).parse(buff, byteorder) tag_id = read_numeric(BYTE, buff, byteorder) return self def write(self, buff, byteorder='big'): for name, tag in self.items(): write_numeric(BYTE, tag.tag_id, buff, byteorder) write_string(name, buff, byteorder) tag.write(buff, byteorder) buff.write(self.end_tag) def merge(self, other): """Recursively merge tags from another compound.""" for key, value in other.items(): if key in self and (isinstance(self[key], Compound) and isinstance(value, dict)): self[key].merge(value) else: self[key] = value class IntArray(Array): """Nbt tag representing an array of signed integers.""" __slots__ = () tag_id = 11 item_type = get_format(np.dtype, 'i4') array_prefix = 'I' class LongArray(Array): """Nbt tag representing an array of signed longs.""" __slots__ = () tag_id = 12 item_type = get_format(np.dtype, 'i8') array_prefix = 'L' item_suffix = 'L' PK!HNG'''nbtlib-1.4.2.dist-info/entry_points.txtN+I/N.,()K*$L<..PK!11nbtlib-1.4.2.dist-info/LICENSEMIT License Copyright (c) 2019 Valentin Berlier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. PK!HڽTUnbtlib-1.4.2.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!H{f nbtlib-1.4.2.dist-info/METADATAYs D I^\X4\FA|]yU3cY՘MY'W|-):=ѓ(_Db&oz1cOˤ΁#7KכJ-M/TDɊ֚<) 'o T0⽙wWi+N g4f~V,e֗ðQ|+$VvE9͹6BpV|ݼ>eF,ձ,USvo=,.zZ9Դ&a>>LlG]cD!oGƐ< KLTAk2ff )s>Y0QJsR;!3 I뀫R ,S˃B &L;/Nf=yqN UBىt0u .N;Р t 7&0,NvlQj-%כ/ A(Zg\˄Uex C+VаRXb;1{^* IBTܐ\6oɂӷ2+SӴLNeQŲ,aRH6yJs^J. $5X=K ?3,d"o%HuNF3 3 71ؿR8-FCKHr@e4/'@8KMm}⑷D/≛ɜp`l1k61G+Y7\Svrwϓc-`}=N&кθb'H'H;2оs--#qI=>/bv!s Ysݞ¸unך1d)J2iB`#: dSr(\T#^O<I>1'9ya i4) ~n_H>.h %2%~o%~bGT)MfÏwLQBL%-ÃʎG$%!&KM; \7nQyzs'j?SW=,3@;g ѯ=#[?#8񘝌ˏݩSuڮ6Q,;.sG"cG`p_jFVUjRlv;hr{~/qy7->"%.qt^Nt8IFW҉≸֣m!ޜDQNUdH\7Z7]-7jk742*Fb 'Mv =8I"O\cK_QOLo2Q笵$T[frTq8=N'pӢMl~ˌi[+R?`n5!imzX~lCrO"$\Jp=d͒.‘Z:~jgIaO$r8}/$^W5M D:0`%|z=4Zd\lόBg]{/Wzm孢4n{#'>w(|s_lj׹'..\h $Hn⟶=jɷ{-K> % 0ũ%CqѵK΢YѮpL'cD}D@=C{ mD 9DVqr͟vO?݆^xש;@(W$ҒZBwn+-KiuJg[w?}m1ݣ/?~ooohK>2{h #Rh%<`m(i+m:r=F2WQa\=>DR+V]AF ۖg5RҎ7;Lf>\.ĝI:3'ߔ3Ɏ J u(r]^)A^5c_Xjl2Cn|+,6e&R&J"j.9t{ Pх e pm[~J&֍}`SQg)ڻoq{hwSe>{AwݪM{IŠ,8 J>1mV>Q-CrsޥyxQLV!} S9p.e~υ-u )ۆ%EFEOZ%9Z*QbmE Nhz 5:@6ftE_pܦ tRyAI l~vc(]z5آp72-9RJ<..U\΂.{1m'RAWEl<ڎ.qףM3{_Ό4]yA"2щۧS 7JQl{,!P?SqhHx;Oeg CsH^)AV|Rat fMκ:FLTZw*~xfգ[:bh>,=Hg ".a7E7ʞ0MOn둹8j6<aq"I3Kq?>5P=tzg}x=צP#$5"=:9g@hq|杋@ۃ(Qw/9lBS9v=TZŞv'8pAS^-֟DƸ:z+)cUC̽ VRR{tvL2#r ڻJ4!"uvUPK!<nbtlib/__init__.pyPK!2>>nbtlib/__main__.pyPK!8T 8nbtlib/cli.pyPK!L nbtlib/literal/__init__.pyPK!%m@@ nbtlib/literal/parser.pyPK!pf22*nbtlib/literal/serializer.pyPK!5 f=nbtlib/nbt.pyPK!Q+l aQnbtlib/schema.pyPK!8L<0B0B ]nbtlib/tag.pyPK!HNG'''wnbtlib-1.4.2.dist-info/entry_points.txtPK!11nbtlib-1.4.2.dist-info/LICENSEPK!HڽTUPnbtlib-1.4.2.dist-info/WHEELPK!H{f ޤnbtlib-1.4.2.dist-info/METADATAPK!HhQ%nbtlib-1.4.2.dist-info/RECORDPKL