Skip to content

Commit

Permalink
Add support for writing SNBT with comma separators. Ignore comments i…
Browse files Browse the repository at this point in the history
…n SNBT.
  • Loading branch information
peunsu committed Jun 10, 2024
1 parent 413ecb0 commit 918981d
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 90 deletions.
57 changes: 49 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
The FTB snbt tag is a variant of the "vanilla" snbt tag. It has no commas at end of lines, different suffixes for numeric values, and no support for array data type.

This is the example of the FTB snbt tag:

```python
{
some_tag: "some_value"
Expand All @@ -22,46 +23,73 @@ This is the example of the FTB snbt tag:
**This library is only for the FTB snbt tag**. If you are finding the snbt library for the "vanilla" snbt tag, use [nbtlib](https://github.com/vberlier/nbtlib) by [vberlier](https://github.com/vberlier).

## Installation

The package can be installed with ``pip``.

```bash
$ pip install ftb-snbt-lib
```

## Getting Started

* Import the library.

```python
>>> import ftb_snbt_lib as slib
```

* ``load(fp)``: Load the ftb snbt tag from a file (``fp``).
```python
>>> some_snbt = slib.load(open("tests/some_file.snbt", "r", encoding="utf-8"))
```
* The type of returned value is ``Compound``, a dictionary-like object.<br>
* ``load(fp)``: Load the ftb snbt tag from a file (``fp``).<br>
The type of returned value is ``Compound``, a dictionary-like object.<br>
The ``Compound`` is containing values with **[tag data types](#data-types)** provided by this library.

```python
>>> some_snbt = slib.load(open("tests/some_file.snbt", "r", encoding="utf-8"))
>>> type(some_snbt)
<class 'ftb_snbt_lib.tag.Compound'>
>>> print(some_snbt)
Compound({'some_tag': String('some_value'), 'another_tag': Byte(1)})
```

* ``dump(tag, fp)``: Dump the ftb snbt tag to a file (``fp``).
* ``dump(tag, fp, comma_sep=False)``: Dump the ftb snbt tag to a file (``fp``).<br>
If you set ``comma_sep`` parameter to ``True``, the output snbt has comma separator ``,\n`` instead of non-comma separator ``\n``.

```python
>>> slib.dump(some_snbt, open("tests/some_file_copy.snbt", "w", encoding="utf-8"))
# File Output:
# {
# some_tag: "some_value"
# another_tag: 1b
# }
```

* ``loads(s)``: Load the ftb snbt tag from a string ``s``.
```python
>>> slib.dump(some_snbt, open("tests/some_file_copy.snbt", "w", encoding="utf-8"), comma_sep=True)
# File Output:
# {
# some_tag: "some_value",
# another_tag: 1b
# }
```

* ``loads(s)``: Load the ftb snbt tag from a string ``s``.<br>
The type of returned value is ``Compound``.

```python
>>> another_snbt = slib.loads('''
... {
... some_tag: "some_value"
... another_tag: 1b
... }
... ''')
>>> type(another_snbt)
<class 'ftb_snbt_lib.tag.Compound'>
>>> print(another_snbt)
Compound({'some_tag': String('some_value'), 'another_tag': Byte(1)})
```

* ``dumps(tag)``: Dump the ftb snbt tag to a string.
* ``dumps(tag, comma_sep=False)``: Dump the ftb snbt tag to a string.<br>
If you set ``comma_sep`` parameter to ``True``, the output snbt has comma separator ``,\n`` instead of non-comma separator ``\n``.

```python
>>> dumped_snbt = slib.dumps(another_snbt)
>>> print(dumped_snbt)
Expand All @@ -71,8 +99,18 @@ Compound({'some_tag': String('some_value'), 'another_tag': Byte(1)})
}
```

```python
>>> dumped_snbt = slib.dumps(another_snbt, comma_sep=True)
>>> print(dumped_snbt)
{
some_tag: "some_value",
another_tag: 1b
}
```

* Edit the snbt tag. As its type is ``Compound``, it can be edited like a dictionary.<br>
The inserted or replace values should be any of **[tag data types](#data-types)** provided by this library.

```python
>>> another_snbt["some_tag"] = slib.String("another_value")
```
Expand All @@ -84,12 +122,14 @@ For instance, ``List[Byte(1), Byte(2), Byte(3)]`` must contain **only** the ``By
For instance, ``IntArray`` with a length of 3 must contain **three** ``Integer`` type objects, so **adding new objects, removing existing objects, and replacing with other type objects are not allowed**.

* Save the edited snbt tag to a json file.

```python
>>> import json
>>> json.dump(another_snbt, open("tests/test.json", "w", encoding="utf-8"), indent=4, ensure_ascii=False)
```

## Data Types

| Type | Description | Format | Example |
| - | - | - | - |
| Byte | A signed 8-bit integer.<br>Range: ``-128`` ~ ``127`` | ``<number>b`` | ``12b``, ``-35b`` |
Expand All @@ -105,6 +145,7 @@ For instance, ``IntArray`` with a length of 3 must contain **three** ``Integer``
| Compound | An ordered list of attribute-value pairs.<br>Each tag can be of **any type**. | Named tags enclosed in curly braces and delimited by commas or **newline** characters (``\n``).<br>The key (tag name) can be unquoted if it contains only ``0-9``, ``A-Z``, ``a-z``, ``_``, ``-``, ``.``, and ``+``. Otherwise the key should be quoted, using the format of ``String`` type. | <pre>[<br> tag1: "string"<br> tag2: 12b<br> \"quoted:tag\": 3.5d<br> ...<br>]</pre> |

## References

* [PLY - Python Lex-Yacc](https://github.com/dabeaz/ply) by [David Beazley](https://www.dabeaz.com)
* [nbtlib](https://github.com/vberlier/nbtlib) by [vberlier](https://github.com/vberlier)
* [NBT format - Minecraft Wiki (fandom)](https://minecraft.fandom.com/wiki/NBT_format)
2 changes: 1 addition & 1 deletion ftb_snbt_lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
from .tag import *
from .write import *

__version__ = '0.3.1'
__version__ = '0.4.0'
__all__ = ['parse', 'tag', 'write']
1 change: 1 addition & 0 deletions ftb_snbt_lib/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
)

t_ignore = ' \t\r'
t_ignore_COMMENT = r'\#.*'

def t_BOOL(t):
r'true|false'
Expand Down
175 changes: 94 additions & 81 deletions ftb_snbt_lib/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,83 +8,16 @@
'"': '\\"'
}

def get_writer():
"""Get the FTB snbt writer object.
Example
-------
>>> import ftb_snbt_lib as slib
>>> writer = slib.get_writer()
>>> snbt = Compound({
... 'some_tag': String('some_value'),
... 'another_tag': Byte(1)
... })
>>> dumped_snbt = writer.write(snbt)
>>> print(dumped_snbt)
{
some_tag: "some_value"
another_tag: 1b
}
"""
return Writer()

def dumps(tag: Base) -> str:
"""Dump an FTB snbt tag to a string.
A tag can be a ``Compound``, ``List``, ``Numeric``, ``Bool``, or ``String`` object.
The returned string can be written to a file or sent over a network.
Args
----
tag (Base): The FTB snbt tag to dump.
Returns
-------
str: The dumped FTB snbt string.
Example
-------
>>> import ftb_snbt_lib as slib
>>> snbt = Compound({
... 'some_tag': String('some_value'),
... 'another_tag': Byte(1)
... })
>>> dumped_snbt = slib.dumps(snbt)
>>> print(dumped_snbt)
{
some_tag: "some_value"
another_tag: 1b
}
"""
return get_writer().write(tag) + "\n"

def dump(tag: Base, fp) -> None:
"""Dump an FTB snbt tag to a file.
Args
----
tag (Base): The FTB snbt tag to dump.
fp (io.TextIOWrapper): The file to write the FTB snbt tag to (opened in writing text mode with a UTF-8 encoding.)
Example
-------
>>> import ftb_snbt_lib as slib
>>> snbt = Compound({
... 'some_tag': String('some_value'),
... 'another_tag': Byte(1)
... })
>>> slib.dump(snbt, open('some_file.snbt', 'w', encoding='utf-8'))
"""
fp.write(dumps(tag))

class Writer:
def __init__(self):
def __init__(self, comma_sep: bool = False):
self.indentation = "\t"

self.indent = ""
self.prev_indent = ""

self.separator = "\n"
if comma_sep:
self.separator = "," + self.separator

@contextmanager
def depth(self):
if self.indentation is None:
Expand All @@ -106,9 +39,9 @@ def should_expand(self, tag: List | Array | Compound) -> bool:
)
)

def expand(self, fmt: str) -> tuple[str, str]:
def expand(self, separator: str, fmt: str) -> tuple[str, str]:
return (
f"\n{self.indent}",
f"{separator}{self.indent}",
fmt.replace("{}", f"\n{self.indent}{{}}\n{self.prev_indent}"),
)

Expand All @@ -129,7 +62,7 @@ def write_string(self, tag: String) -> str:
return f'"{tag}"'

def write_list(self, tag: List) -> str:
separator, fmt = "\n", "[{}]"
separator, fmt = self.separator, "[{}]"

if len(tag) == 0:
return fmt.format(" ")
Expand All @@ -138,12 +71,12 @@ def write_list(self, tag: List) -> str:

with self.depth():
if self.should_expand(tag):
separator, fmt = self.expand(fmt)
separator, fmt = self.expand(separator, fmt)

return fmt.format(separator.join(map(self.write, tag)))

def write_array(self, tag: Array) -> str:
separator, fmt = "\n", f"[{tag.array_prefix};{{}}]"
separator, fmt = self.separator, f"[{tag.array_prefix};{{}}]"

if len(tag) == 0:
return fmt.format(" ")
Expand All @@ -152,19 +85,19 @@ def write_array(self, tag: Array) -> str:

with self.depth():
if self.should_expand(tag):
separator, fmt = self.expand(fmt)
separator, fmt = self.expand(separator, fmt)

return fmt.format(separator.join(map(self.write, tag)))

def write_compound(self, tag: Compound) -> str:
separator, fmt = "\n", "{{{}}}"
separator, fmt = self.separator, "{{{}}}"

if len(tag) == 0:
return fmt.format(" ")

with self.depth():
if self.should_expand(tag):
separator, fmt = self.expand(fmt)
separator, fmt = self.expand(separator, fmt)

return fmt.format(
separator.join(
Expand All @@ -176,4 +109,84 @@ def write_compound(self, tag: Compound) -> str:
def stringify_compound_key(self, key: String | str) -> str:
if isinstance(key, String):
return self.write_string(key)
return key
return key

def get_writer(comma_sep: bool = False) -> Writer:
"""Get the FTB snbt writer object.
Args
----
comma_sep (bool): Whether to separate tags with commas.
Returns
-------
Writer: The FTB snbt writer object.
Example
-------
>>> import ftb_snbt_lib as slib
>>> writer = slib.get_writer()
>>> snbt = slib.Compound({
... 'some_tag': slib.String('some_value'),
... 'another_tag': slib.Byte(1)
... })
>>> dumped_snbt = writer.write(snbt)
>>> print(dumped_snbt)
{
some_tag: "some_value"
another_tag: 1b
}
"""
return Writer(comma_sep=comma_sep)

def dumps(tag: Base, comma_sep: bool = False) -> str:
"""Dump an FTB snbt tag to a string.
A tag can be a ``Compound``, ``List``, ``Numeric``, ``Bool``, or ``String`` object.
The returned string can be written to a file or sent over a network.
Args
----
tag (Base): The FTB snbt tag to dump.
comma_sep (bool): Whether to separate tags with commas.
Returns
-------
str: The dumped FTB snbt string.
Example
-------
>>> import ftb_snbt_lib as slib
>>> snbt = slib.Compound({
... 'some_tag': slib.String('some_value'),
... 'another_tag': slib.Byte(1)
... })
>>> dumped_snbt = slib.dumps(snbt)
>>> print(dumped_snbt)
{
some_tag: "some_value"
another_tag: 1b
}
"""
return get_writer(comma_sep=comma_sep).write(tag) + "\n"

def dump(tag: Base, fp, comma_sep: bool = False) -> None:
"""Dump an FTB snbt tag to a file.
Args
----
tag (Base): The FTB snbt tag to dump.
fp (io.TextIOWrapper): The file to write the FTB snbt tag to (opened in writing text mode with a UTF-8 encoding.)
comma_sep (bool): Whether to separate tags with commas.
Example
-------
>>> import ftb_snbt_lib as slib
>>> snbt = slib.Compound({
... 'some_tag': slib.String('some_value'),
... 'another_tag': slib.Byte(1)
... })
>>> slib.dump(snbt, open('some_file.snbt', 'w', encoding='utf-8'))
"""
fp.write(dumps(tag, comma_sep=comma_sep))

0 comments on commit 918981d

Please sign in to comment.