pyrecli 0.2.1__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyrecli-0.2.1/README.md → pyrecli-0.3.1/PKG-INFO +49 -2
- pyrecli-0.2.1/PKG-INFO → pyrecli-0.3.1/README.md +25 -20
- pyrecli-0.3.1/pyproject.toml +34 -0
- pyrecli-0.3.1/pyrecli/command/cctoken.py +10 -0
- {pyrecli-0.2.1 → pyrecli-0.3.1}/pyrecli/command/docs.py +5 -13
- pyrecli-0.3.1/pyrecli/command/grabinv.py +44 -0
- {pyrecli-0.2.1 → pyrecli-0.3.1}/pyrecli/command/rename.py +13 -13
- pyrecli-0.3.1/pyrecli/command/scan.py +14 -0
- {pyrecli-0.2.1 → pyrecli-0.3.1}/pyrecli/command/script.py +8 -20
- pyrecli-0.3.1/pyrecli/command/send.py +16 -0
- pyrecli-0.3.1/pyrecli/command/slice.py +15 -0
- {pyrecli-0.2.1 → pyrecli-0.3.1}/pyrecli/pyrecli.py +59 -47
- pyrecli-0.3.1/pyrecli/util.py +132 -0
- pyrecli-0.2.1/pyproject.toml +0 -23
- pyrecli-0.2.1/pyrecli/command/grabinv.py +0 -49
- pyrecli-0.2.1/pyrecli/command/scan.py +0 -19
- pyrecli-0.2.1/pyrecli/command/send.py +0 -26
- pyrecli-0.2.1/pyrecli/command/slice.py +0 -21
- pyrecli-0.2.1/pyrecli/util.py +0 -76
- {pyrecli-0.2.1 → pyrecli-0.3.1}/LICENSE +0 -0
- {pyrecli-0.2.1 → pyrecli-0.3.1}/pyrecli/__init__.py +0 -0
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyrecli
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: Command line utilities for DiamondFire templates
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: diamondfire,minecraft,template,cli,tools
|
|
8
|
+
Author: Amp
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: dfpyre (>=0.9.6)
|
|
18
|
+
Requires-Dist: pytest (>=9.0.2) ; extra == "dev"
|
|
19
|
+
Requires-Dist: rapidnbt (>=1.3.5)
|
|
20
|
+
Requires-Dist: twine (>=6.2.0) ; extra == "dev"
|
|
21
|
+
Project-URL: Repository, https://github.com/Amp63/pyrecli
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
1
24
|
# pyrecli
|
|
2
25
|
|
|
3
26
|
Command line utilities for DiamondFire templates
|
|
@@ -19,6 +42,7 @@ pip install pyrecli
|
|
|
19
42
|
- `grabinv`: Save all templates in your Minecraft inventory to a file (requires [CodeClient](github.com/DFOnline/CodeClient))
|
|
20
43
|
- `docs`: Generate markdown documentation from template data
|
|
21
44
|
- `slice`: Slice a template into multiple smaller templates
|
|
45
|
+
- `cctoken`: Get a reusable CodeClient authentication token
|
|
22
46
|
|
|
23
47
|
|
|
24
48
|
## What is this useful for?
|
|
@@ -49,8 +73,7 @@ pyrecli scan templates.dfts
|
|
|
49
73
|
|
|
50
74
|
**[Requires CodeClient]**
|
|
51
75
|
|
|
52
|
-
Sends all templates in a file back to
|
|
53
|
-
Essentially the reverse of `scan`.
|
|
76
|
+
Sends all templates in a file back to your inventory.
|
|
54
77
|
|
|
55
78
|
Example:
|
|
56
79
|
```sh
|
|
@@ -131,3 +154,27 @@ Example:
|
|
|
131
154
|
# Slices the first template in `templates.dfts` with a target length of 50 and stores them in `sliced_templates.dfts`
|
|
132
155
|
pyrecli slice templates.dfts sliced_templates.dfts 50
|
|
133
156
|
```
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
### CCToken
|
|
160
|
+
|
|
161
|
+
Returns a CodeClient authentication token that can be used in commands that require CodeClient authorization.
|
|
162
|
+
This is useful for reducing the amount of times you need to run `/auth`.
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
```sh
|
|
166
|
+
# Get a token with the read_plot and inventory scopes
|
|
167
|
+
pyrecli cctoken mytoken.txt "read_plot inventory"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
### Command Chaining
|
|
172
|
+
|
|
173
|
+
You can combine the pipe operator (`|`) with hyphen (`-`) file paths to chain multiple commands together.
|
|
174
|
+
|
|
175
|
+
Example:
|
|
176
|
+
```sh
|
|
177
|
+
# Scans the plot, renames a variable, then sends renamed templates back to DiamondFire
|
|
178
|
+
pyrecli scan - | pyrecli rename - foo bar | pyrecli send -
|
|
179
|
+
|
|
180
|
+
```
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: pyrecli
|
|
3
|
-
Version: 0.2.1
|
|
4
|
-
Summary: Command line utilities for DiamondFire templates
|
|
5
|
-
Home-page: https://github.com/Amp63/pyrecli
|
|
6
|
-
License: MIT
|
|
7
|
-
Keywords: diamondfire,minecraft,template,cli
|
|
8
|
-
Author: Amp
|
|
9
|
-
Requires-Python: >=3.10,<3.13
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Requires-Dist: dfpyre (>=0.9.3,<0.10.0)
|
|
16
|
-
Project-URL: Repository, https://github.com/Amp63/pyrecli
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
|
|
19
1
|
# pyrecli
|
|
20
2
|
|
|
21
3
|
Command line utilities for DiamondFire templates
|
|
@@ -37,6 +19,7 @@ pip install pyrecli
|
|
|
37
19
|
- `grabinv`: Save all templates in your Minecraft inventory to a file (requires [CodeClient](github.com/DFOnline/CodeClient))
|
|
38
20
|
- `docs`: Generate markdown documentation from template data
|
|
39
21
|
- `slice`: Slice a template into multiple smaller templates
|
|
22
|
+
- `cctoken`: Get a reusable CodeClient authentication token
|
|
40
23
|
|
|
41
24
|
|
|
42
25
|
## What is this useful for?
|
|
@@ -67,8 +50,7 @@ pyrecli scan templates.dfts
|
|
|
67
50
|
|
|
68
51
|
**[Requires CodeClient]**
|
|
69
52
|
|
|
70
|
-
Sends all templates in a file back to
|
|
71
|
-
Essentially the reverse of `scan`.
|
|
53
|
+
Sends all templates in a file back to your inventory.
|
|
72
54
|
|
|
73
55
|
Example:
|
|
74
56
|
```sh
|
|
@@ -150,3 +132,26 @@ Example:
|
|
|
150
132
|
pyrecli slice templates.dfts sliced_templates.dfts 50
|
|
151
133
|
```
|
|
152
134
|
|
|
135
|
+
|
|
136
|
+
### CCToken
|
|
137
|
+
|
|
138
|
+
Returns a CodeClient authentication token that can be used in commands that require CodeClient authorization.
|
|
139
|
+
This is useful for reducing the amount of times you need to run `/auth`.
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
```sh
|
|
143
|
+
# Get a token with the read_plot and inventory scopes
|
|
144
|
+
pyrecli cctoken mytoken.txt "read_plot inventory"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
### Command Chaining
|
|
149
|
+
|
|
150
|
+
You can combine the pipe operator (`|`) with hyphen (`-`) file paths to chain multiple commands together.
|
|
151
|
+
|
|
152
|
+
Example:
|
|
153
|
+
```sh
|
|
154
|
+
# Scans the plot, renames a variable, then sends renamed templates back to DiamondFire
|
|
155
|
+
pyrecli scan - | pyrecli rename - foo bar | pyrecli send -
|
|
156
|
+
|
|
157
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyrecli"
|
|
3
|
+
version = "0.3.1"
|
|
4
|
+
description = "Command line utilities for DiamondFire templates"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Amp"}
|
|
7
|
+
]
|
|
8
|
+
license = "MIT"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
keywords = ["diamondfire", "minecraft", "template", "cli", "tools"]
|
|
11
|
+
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
dependencies = [
|
|
14
|
+
"dfpyre>=0.9.6",
|
|
15
|
+
"rapidnbt>=1.3.5"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
dev = [
|
|
20
|
+
"pytest>=9.0.2",
|
|
21
|
+
"twine>=6.2.0"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Repository = "https://github.com/Amp63/pyrecli"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
[tool.poetry.scripts]
|
|
29
|
+
pyrecli = "pyrecli.pyrecli:main"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
[build-system]
|
|
33
|
+
requires = ["poetry-core"]
|
|
34
|
+
build-backend = "poetry.core.masonry.api"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from pyrecli.util import connect_to_codeclient, write_output_file
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def cctoken_command(output_path: str, scopes: str):
|
|
5
|
+
ws = connect_to_codeclient(scopes)
|
|
6
|
+
|
|
7
|
+
ws.send('token')
|
|
8
|
+
token = ws.recv().replace('token ', '')
|
|
9
|
+
|
|
10
|
+
write_output_file(output_path, token)
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from typing import Literal, TypedDict
|
|
2
|
-
from result import Result, Err
|
|
3
2
|
from dfpyre import DFTemplate, Item, Parameter
|
|
4
3
|
from mcitemlib.itemlib import MCItemlibException
|
|
5
4
|
from pyrecli.util import read_input_file, write_output_file, parse_templates_from_string
|
|
@@ -24,15 +23,9 @@ class TemplateDocData(TypedDict):
|
|
|
24
23
|
doc_lines: list[str]
|
|
25
24
|
|
|
26
25
|
|
|
27
|
-
def docs_command(input_path: str, output_path: str, title: str, include_hidden: bool, omit_toc: bool)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return Err(input_result.err_value)
|
|
31
|
-
|
|
32
|
-
templates_result = parse_templates_from_string(input_result.ok_value)
|
|
33
|
-
if templates_result.is_err():
|
|
34
|
-
return Err(templates_result.err_value)
|
|
35
|
-
templates = templates_result.ok_value
|
|
26
|
+
def docs_command(input_path: str, output_path: str, title: str, include_hidden: bool, omit_toc: bool):
|
|
27
|
+
templates_string = read_input_file(input_path)
|
|
28
|
+
templates = parse_templates_from_string(templates_string)
|
|
36
29
|
|
|
37
30
|
def get_function_name(template: DFTemplate) -> str:
|
|
38
31
|
first_block = template.codeblocks[0]
|
|
@@ -72,7 +65,7 @@ def docs_command(input_path: str, output_path: str, title: str, include_hidden:
|
|
|
72
65
|
lore_text = [escape_md(l.to_string()) for l in first_arg.get_lore()]
|
|
73
66
|
if lore_text:
|
|
74
67
|
template_doc_lines.extend(lore_text)
|
|
75
|
-
except
|
|
68
|
+
except:
|
|
76
69
|
# There are so many things that can go wrong here due to various legacy
|
|
77
70
|
# item formats and weird MC string edge cases, so we can just skip
|
|
78
71
|
# if there's a problem.
|
|
@@ -120,5 +113,4 @@ def docs_command(input_path: str, output_path: str, title: str, include_hidden:
|
|
|
120
113
|
output_lines.extend(doc_data['doc_lines'])
|
|
121
114
|
output_lines.append('')
|
|
122
115
|
|
|
123
|
-
|
|
124
|
-
return write_result
|
|
116
|
+
write_output_file(output_path, '\n'.join(output_lines))
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from rapidnbt import nbtio, CompoundTagVariant
|
|
3
|
+
from pyrecli.util import write_output_file, connect_to_codeclient, print_status
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def grabinv_command(output_path: str, token: str|None=None):
|
|
7
|
+
ws = connect_to_codeclient('inventory', token)
|
|
8
|
+
|
|
9
|
+
ws.send('inv')
|
|
10
|
+
inventory = ws.recv()
|
|
11
|
+
ws.close()
|
|
12
|
+
|
|
13
|
+
inventory = f'{{inventory:{inventory}}}'
|
|
14
|
+
inventory_nbt = nbtio.loads_snbt(inventory)
|
|
15
|
+
|
|
16
|
+
template_codes: list[str] = []
|
|
17
|
+
for tag in inventory_nbt['inventory']:
|
|
18
|
+
tag: CompoundTagVariant
|
|
19
|
+
components = tag['components']
|
|
20
|
+
if components.is_null():
|
|
21
|
+
continue
|
|
22
|
+
|
|
23
|
+
custom_data = components['minecraft:custom_data']
|
|
24
|
+
if custom_data.is_null():
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
pbv_tag = custom_data['PublicBukkitValues']
|
|
28
|
+
if pbv_tag.is_null():
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
code_template_data = pbv_tag['hypercube:codetemplatedata']
|
|
32
|
+
if code_template_data.is_null():
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
code_template_json = json.loads(code_template_data.get_string())
|
|
36
|
+
|
|
37
|
+
template_code = code_template_json.get('code')
|
|
38
|
+
if template_code:
|
|
39
|
+
template_codes.append(template_code)
|
|
40
|
+
|
|
41
|
+
if not template_codes:
|
|
42
|
+
print_status('Could not find any templates in the inventory.')
|
|
43
|
+
|
|
44
|
+
write_output_file(output_path, '\n'.join(template_codes))
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from typing import Literal
|
|
2
2
|
import re
|
|
3
|
-
from
|
|
4
|
-
from dfpyre import Variable, Number, String, Text
|
|
3
|
+
from dfpyre import Variable, Number, String, Text, Parameter
|
|
5
4
|
from pyrecli.util import read_input_file, write_output_file, parse_templates_from_string
|
|
6
5
|
|
|
7
6
|
|
|
@@ -22,33 +21,34 @@ def rename_var_in_text_code(s: str, var_to_rename: str, new_var_name: str):
|
|
|
22
21
|
|
|
23
22
|
def rename_command(input_path: str, output_path: str|None,
|
|
24
23
|
var_to_rename: str, new_var_name: str,
|
|
25
|
-
var_to_rename_scope: Literal['game', 'saved', 'local', 'line']|None)
|
|
24
|
+
var_to_rename_scope: Literal['game', 'saved', 'local', 'line']|None):
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return Err(input_result.err_value)
|
|
30
|
-
|
|
31
|
-
templates_result = parse_templates_from_string(input_result.ok_value)
|
|
32
|
-
if templates_result.is_err():
|
|
33
|
-
return Err(templates_result.err_value)
|
|
34
|
-
templates = templates_result.ok_value
|
|
26
|
+
templates_string = read_input_file(input_path)
|
|
27
|
+
templates = parse_templates_from_string(templates_string)
|
|
35
28
|
|
|
36
29
|
for template in templates:
|
|
37
30
|
for codeblock in template.codeblocks:
|
|
38
31
|
for argument in codeblock.args:
|
|
32
|
+
# Try to rename variable
|
|
39
33
|
if isinstance(argument, Variable):
|
|
40
34
|
if argument.name == var_to_rename and (var_to_rename_scope is None or argument.scope == var_to_rename_scope):
|
|
41
35
|
argument.name = new_var_name
|
|
42
36
|
argument.name = rename_var_in_text_code(argument.name, var_to_rename, new_var_name)
|
|
43
37
|
|
|
38
|
+
# Try to rename parameter
|
|
39
|
+
elif isinstance(argument, Parameter) and var_to_rename_scope == 'line':
|
|
40
|
+
if argument.name == var_to_rename:
|
|
41
|
+
argument.name = new_var_name
|
|
42
|
+
|
|
43
|
+
# Check for occurrences of the variable in text codes
|
|
44
44
|
elif isinstance(argument, (Number, String, Text)) and isinstance(argument.value, str):
|
|
45
45
|
argument.value = rename_var_in_text_code(argument.value, var_to_rename, new_var_name)
|
|
46
46
|
|
|
47
|
+
# Check for text codes in function calls
|
|
47
48
|
if codeblock.type in {'call_func', 'start_process'}:
|
|
48
49
|
new_data = rename_var_in_text_code(codeblock.data.get('data'), var_to_rename, new_var_name)
|
|
49
50
|
codeblock.data['data'] = new_data
|
|
50
51
|
|
|
51
52
|
new_file_content = '\n'.join(t.build() for t in templates)
|
|
52
53
|
write_path = output_path if output_path else input_path
|
|
53
|
-
|
|
54
|
-
return write_result
|
|
54
|
+
write_output_file(write_path, new_file_content)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from pyrecli.util import write_output_file, connect_to_codeclient, print_status
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def scan_command(output_path: str, token: str|None=None):
|
|
5
|
+
ws = connect_to_codeclient('read_plot', token)
|
|
6
|
+
|
|
7
|
+
print_status('Scanning plot...')
|
|
8
|
+
ws.send('scan')
|
|
9
|
+
|
|
10
|
+
scan_results = ws.recv()
|
|
11
|
+
print_status('Done.')
|
|
12
|
+
ws.close()
|
|
13
|
+
|
|
14
|
+
write_output_file(output_path, scan_results)
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from result import Result, Ok, Err
|
|
3
2
|
from dfpyre import DFTemplate
|
|
4
3
|
from pyrecli.util import read_input_file, write_output_file, parse_templates_from_string
|
|
5
4
|
|
|
6
5
|
|
|
7
|
-
def write_to_directory(dir_name: str, templates: list[DFTemplate], flags: dict[str, int|bool])
|
|
6
|
+
def write_to_directory(dir_name: str, templates: list[DFTemplate], flags: dict[str, int|bool]):
|
|
8
7
|
if not os.path.isdir(dir_name):
|
|
9
8
|
os.mkdir(dir_name)
|
|
10
9
|
|
|
11
10
|
for template in templates:
|
|
12
11
|
script_path = f'{dir_name}/{template._get_template_name()}.py'
|
|
13
12
|
script_string = template.generate_script(**flags)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
f.write(script_string)
|
|
17
|
-
except OSError as e:
|
|
18
|
-
return Err(str(e))
|
|
19
|
-
|
|
20
|
-
return Ok(None)
|
|
13
|
+
with open(script_path, 'w') as f:
|
|
14
|
+
f.write(script_string)
|
|
21
15
|
|
|
22
16
|
|
|
23
|
-
def write_to_single_file(file_path: str, templates: list[DFTemplate], flags: dict[str, int|bool])
|
|
17
|
+
def write_to_single_file(file_path: str, templates: list[DFTemplate], flags: dict[str, int|bool]):
|
|
24
18
|
file_content = []
|
|
25
19
|
for i, template in enumerate(templates):
|
|
26
20
|
if i == 0:
|
|
@@ -29,18 +23,12 @@ def write_to_single_file(file_path: str, templates: list[DFTemplate], flags: dic
|
|
|
29
23
|
template_script = template.generate_script(include_import=False, assign_variable=True, **flags)
|
|
30
24
|
file_content.append(template_script)
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
write_output_file(file_path, '\n\n'.join(file_content))
|
|
34
27
|
|
|
35
|
-
def script_command(input_path: str, output_path: str, one_file: bool, flags: dict[str, int|bool]) -> Result[None, str]:
|
|
36
|
-
input_result = read_input_file(input_path)
|
|
37
|
-
if input_result.is_err():
|
|
38
|
-
return Err(input_result.err_value)
|
|
39
28
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
templates = templates_result.ok_value
|
|
29
|
+
def script_command(input_path: str, output_path: str, one_file: bool, flags: dict[str, int|bool]):
|
|
30
|
+
templates_string = read_input_file(input_path)
|
|
31
|
+
templates = parse_templates_from_string(templates_string)
|
|
44
32
|
|
|
45
33
|
if one_file or output_path == '-':
|
|
46
34
|
return write_to_single_file(output_path, templates, flags)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from pyrecli.util import connect_to_codeclient, read_input_file, parse_templates_from_string, print_status
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def send_command(input_path: str):
|
|
5
|
+
templates_string = read_input_file(input_path)
|
|
6
|
+
templates = parse_templates_from_string(templates_string)
|
|
7
|
+
|
|
8
|
+
ws = connect_to_codeclient()
|
|
9
|
+
|
|
10
|
+
for template in templates:
|
|
11
|
+
item = template.generate_template_item()
|
|
12
|
+
ws.send(f'give {item.get_snbt()}')
|
|
13
|
+
|
|
14
|
+
ws.close()
|
|
15
|
+
|
|
16
|
+
print_status(f'Sent {len(templates)} template{"s" if len(templates) != 1 else ''} successfully.')
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from pyrecli.util import read_input_file, write_output_file, parse_templates_from_string, NoTemplatesError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def slice_command(input_path: str, output_path: str, target_length: int):
|
|
5
|
+
templates_string = read_input_file(input_path)
|
|
6
|
+
templates = parse_templates_from_string(templates_string)
|
|
7
|
+
|
|
8
|
+
if not templates:
|
|
9
|
+
raise NoTemplatesError(f'Could not find any templates in {input_path}')
|
|
10
|
+
|
|
11
|
+
first_template = templates[0]
|
|
12
|
+
sliced_templates = first_template.slice(target_length)
|
|
13
|
+
built_templates = [t.build() for t in sliced_templates]
|
|
14
|
+
|
|
15
|
+
write_output_file(output_path, '\n'.join(built_templates))
|
|
@@ -9,6 +9,8 @@ from pyrecli.command.rename import rename_command
|
|
|
9
9
|
from pyrecli.command.grabinv import grabinv_command
|
|
10
10
|
from pyrecli.command.docs import docs_command
|
|
11
11
|
from pyrecli.command.slice import slice_command
|
|
12
|
+
from pyrecli.command.cctoken import cctoken_command
|
|
13
|
+
from pyrecli.util import print_status
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
def rename_target_scope(value):
|
|
@@ -18,7 +20,6 @@ def rename_target_scope(value):
|
|
|
18
20
|
return value
|
|
19
21
|
|
|
20
22
|
|
|
21
|
-
|
|
22
23
|
def slice_target_length(value):
|
|
23
24
|
MINIMUM_LENGTH = 5
|
|
24
25
|
ivalue = int(value)
|
|
@@ -27,13 +28,14 @@ def slice_target_length(value):
|
|
|
27
28
|
return ivalue
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
def main():
|
|
31
|
+
def main() -> int:
|
|
31
32
|
parser = argparse.ArgumentParser(prog='pyrecli', description='Command line utilities for DiamondFire templates')
|
|
32
33
|
parser.add_argument('--version', '-v', action='version', version=f'pyrecli {importlib.metadata.version('pyrecli')}')
|
|
33
34
|
subparsers = parser.add_subparsers(dest='command', help='Available commands:', required=True, metavar='<command>')
|
|
34
35
|
|
|
35
36
|
parser_scan = subparsers.add_parser('scan', help='Scan the current plot templates with CodeClient')
|
|
36
37
|
parser_scan.add_argument('output_path', help='The file to output template data to', type=str)
|
|
38
|
+
parser_scan.add_argument('--token', '-t', help='The CodeClient authentication token to use', type=str, default=None)
|
|
37
39
|
|
|
38
40
|
parser_send = subparsers.add_parser('send', help='Send templates to DiamondFire with CodeClient')
|
|
39
41
|
parser_send.add_argument('input_path', help='The file containing template data', type=str)
|
|
@@ -48,20 +50,21 @@ def main():
|
|
|
48
50
|
parser_script.add_argument('--preserve_slots', '-s', help='Save the positions of items within chests', action='store_true')
|
|
49
51
|
parser_script.add_argument('--build_and_send', '-b', help='Add `.build_and_send()` to the end of the generated template(s)', action='store_true')
|
|
50
52
|
|
|
51
|
-
parser_rename = subparsers.add_parser('rename', help='Rename a variable')
|
|
53
|
+
parser_rename = subparsers.add_parser('rename', help='Rename all occurrences of a variable')
|
|
52
54
|
parser_rename.add_argument('input_path', help='The file containing template data', type=str)
|
|
53
55
|
parser_rename.add_argument('var_to_rename', help='The variable to rename', type=str)
|
|
54
56
|
parser_rename.add_argument('new_var_name', help='The new name for the variable', type=str)
|
|
55
|
-
parser_rename.add_argument('--
|
|
57
|
+
parser_rename.add_argument('--var_scope', '-s', help='The scope to match', type=rename_target_scope, default=None)
|
|
56
58
|
parser_rename.add_argument('--output_path', '-o', help='The file to output to', type=str, default=None)
|
|
57
59
|
|
|
58
60
|
parser_grabinv = subparsers.add_parser('grabinv', help='Save all templates in the inventory to a file with CodeClient')
|
|
59
61
|
parser_grabinv.add_argument('output_path', help='The file to output template data to', type=str)
|
|
62
|
+
parser_grabinv.add_argument('--token', '-t', help='The CodeClient authentication token to use', type=str, default=None)
|
|
60
63
|
|
|
61
64
|
parser_docs = subparsers.add_parser('docs', help='Generate markdown documentation from template data')
|
|
62
65
|
parser_docs.add_argument('input_path', help='The file containing template data', type=str)
|
|
63
66
|
parser_docs.add_argument('output_path', help='The file to output to', type=str)
|
|
64
|
-
parser_docs.add_argument('title', help='The title for the docs', type=str)
|
|
67
|
+
parser_docs.add_argument('--title', '-t', help='The title for the docs', type=str, default='Template Docs')
|
|
65
68
|
parser_docs.add_argument('--include_hidden', '-ih', help='Include hidden functions and processes', action='store_true')
|
|
66
69
|
parser_docs.add_argument('--notoc', help='Omit the table of contents', action='store_true')
|
|
67
70
|
|
|
@@ -70,51 +73,60 @@ def main():
|
|
|
70
73
|
parser_slice.add_argument('output_path', help='The file to output template data to', type=str)
|
|
71
74
|
parser_slice.add_argument('target_length', help='The maximum length of each sliced template', type=slice_target_length)
|
|
72
75
|
|
|
76
|
+
parser_cctoken = subparsers.add_parser('cctoken', help='Request a CodeClient token with the specified scopes')
|
|
77
|
+
parser_cctoken.add_argument('output_path', help='The file to output the token to', type=str)
|
|
78
|
+
parser_cctoken.add_argument('scopes', help='The scopes to request', type=str)
|
|
79
|
+
|
|
80
|
+
|
|
73
81
|
parsed_args = parser.parse_args()
|
|
74
82
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
83
|
+
try:
|
|
84
|
+
match parsed_args.command:
|
|
85
|
+
case 'scan':
|
|
86
|
+
scan_command(parsed_args.output_path, parsed_args.token)
|
|
87
|
+
|
|
88
|
+
case 'send':
|
|
89
|
+
send_command(parsed_args.input_path)
|
|
90
|
+
|
|
91
|
+
case 'script':
|
|
92
|
+
scriptgen_flags = {
|
|
93
|
+
'indent_size': parsed_args.indent_size,
|
|
94
|
+
'literal_shorthand': parsed_args.literal_shorthand,
|
|
95
|
+
'var_shorthand': parsed_args.var_shorthand,
|
|
96
|
+
'preserve_slots': parsed_args.preserve_slots,
|
|
97
|
+
'build_and_send': parsed_args.build_and_send
|
|
98
|
+
}
|
|
99
|
+
script_command(parsed_args.input_path, parsed_args.output_path, parsed_args.onefile, scriptgen_flags)
|
|
100
|
+
|
|
101
|
+
case 'rename':
|
|
102
|
+
rename_command(
|
|
103
|
+
parsed_args.input_path, parsed_args.output_path,
|
|
104
|
+
parsed_args.var_to_rename, parsed_args.new_var_name, parsed_args.var_scope
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
case 'grabinv':
|
|
108
|
+
grabinv_command(parsed_args.output_path, parsed_args.token)
|
|
109
|
+
|
|
110
|
+
case 'docs':
|
|
111
|
+
docs_command(
|
|
112
|
+
parsed_args.input_path, parsed_args.output_path,
|
|
113
|
+
parsed_args.title, parsed_args.include_hidden, parsed_args.notoc
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
case 'slice':
|
|
117
|
+
slice_command(
|
|
118
|
+
parsed_args.input_path, parsed_args.output_path,
|
|
119
|
+
parsed_args.target_length
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
case 'cctoken':
|
|
123
|
+
cctoken_command(parsed_args.output_path, parsed_args.scopes)
|
|
112
124
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
except Exception as e:
|
|
126
|
+
print_status(e)
|
|
127
|
+
return 1
|
|
116
128
|
|
|
117
|
-
|
|
129
|
+
return 0
|
|
118
130
|
|
|
119
131
|
if __name__ == '__main__':
|
|
120
|
-
main()
|
|
132
|
+
sys.exit(main())
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
import websocket
|
|
5
|
+
from dfpyre import DFTemplate
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
CODECLIENT_URL = 'ws://localhost:31375'
|
|
9
|
+
|
|
10
|
+
BASE64_REGEX = re.compile(r'^[A-Za-z0-9+/]+={0,2}$')
|
|
11
|
+
|
|
12
|
+
class TemplateParsingError(Exception):
|
|
13
|
+
"""Exception class for DFTemplate parsing errors"""
|
|
14
|
+
|
|
15
|
+
class NoTemplatesError(Exception):
|
|
16
|
+
"""Exception class for empty template list"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def print_status(*args, **kwargs):
|
|
20
|
+
"""Prints a message to stderr"""
|
|
21
|
+
print(*args, **kwargs, file=sys.stderr)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def connect_to_codeclient(scopes: str|None=None, token: str|None=None) -> websocket.WebSocket:
|
|
25
|
+
"""
|
|
26
|
+
Tries to connect to the CodeClient websocket server with the specified scopes.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
scopes: The scopes to request
|
|
30
|
+
token: The CodeClient authentication token to use
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
The connected websocket
|
|
34
|
+
|
|
35
|
+
Raises:
|
|
36
|
+
ConnectionRefusedError: If connection to the server could not be established
|
|
37
|
+
PermissionError: If scope authentication fails
|
|
38
|
+
"""
|
|
39
|
+
ws = websocket.WebSocket()
|
|
40
|
+
ws.connect(CODECLIENT_URL)
|
|
41
|
+
|
|
42
|
+
print_status('Connected to CodeClient.')
|
|
43
|
+
|
|
44
|
+
if token:
|
|
45
|
+
ws.send(f'token {token}')
|
|
46
|
+
auth_message = ws.recv()
|
|
47
|
+
elif scopes:
|
|
48
|
+
print_status('Please run /auth in game.')
|
|
49
|
+
ws.send(f'scopes {scopes}')
|
|
50
|
+
auth_message = ws.recv()
|
|
51
|
+
|
|
52
|
+
if (token or scopes):
|
|
53
|
+
if auth_message != 'auth':
|
|
54
|
+
raise PermissionError('Failed to authenticate.')
|
|
55
|
+
else:
|
|
56
|
+
print_status('Authentication successful.')
|
|
57
|
+
|
|
58
|
+
return ws
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def parse_templates_from_string(templates: str) -> list[DFTemplate]:
|
|
62
|
+
"""
|
|
63
|
+
Parses a newline-delimited string of template codes into a list of templates.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
templates: The string of templates to parse
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
The list of parsed templates
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If a template code is not a valid base64 string
|
|
73
|
+
TemplateParsingError: If a template failed to parse
|
|
74
|
+
"""
|
|
75
|
+
template_codes = templates.split('\n')
|
|
76
|
+
|
|
77
|
+
for i, template_code in enumerate(template_codes):
|
|
78
|
+
if not BASE64_REGEX.match(template_code):
|
|
79
|
+
raise ValueError(f'Template code at line {i+1} is not a base64 string.')
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
return [DFTemplate.from_code(c) for c in template_codes]
|
|
83
|
+
except Exception as e:
|
|
84
|
+
raise TemplateParsingError(f'Error while parsing template: {e}') from None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def read_input_file(path: str) -> str:
|
|
88
|
+
"""
|
|
89
|
+
Returns the string content of the file at a specified path.
|
|
90
|
+
If the path is a hyphen ('-'), then input will be read from stdin.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
path: The file path to read
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
The file content or input from stdin
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
FileNotFoundError: If the path is not a file or the file doesn't exist
|
|
100
|
+
OSError: If the file was unable to be opened or read
|
|
101
|
+
"""
|
|
102
|
+
if path == '-':
|
|
103
|
+
try:
|
|
104
|
+
input_string = sys.stdin.read()
|
|
105
|
+
except EOFError:
|
|
106
|
+
pass
|
|
107
|
+
return input_string.strip()
|
|
108
|
+
|
|
109
|
+
if not os.path.isfile(path):
|
|
110
|
+
raise FileNotFoundError(f'"{path}" is not a file.')
|
|
111
|
+
|
|
112
|
+
with open(path, 'r') as f:
|
|
113
|
+
return f.read()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def write_output_file(path: str, content: str):
|
|
117
|
+
"""
|
|
118
|
+
Writes string content to a specified file.
|
|
119
|
+
If the file path is a hyphen ('-') then the content will be printed to stdout.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
path: The file path to write to
|
|
123
|
+
content: The string content to write
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
OSError: If the file was unable to be accessed
|
|
127
|
+
"""
|
|
128
|
+
if path == '-':
|
|
129
|
+
print(content, end='')
|
|
130
|
+
else:
|
|
131
|
+
with open(path, 'w') as f:
|
|
132
|
+
f.write(content)
|
pyrecli-0.2.1/pyproject.toml
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "pyrecli"
|
|
3
|
-
version = "0.2.1"
|
|
4
|
-
description = "Command line utilities for DiamondFire templates"
|
|
5
|
-
authors = ["Amp"]
|
|
6
|
-
readme = "README.md"
|
|
7
|
-
license = "MIT"
|
|
8
|
-
repository = "https://github.com/Amp63/pyrecli"
|
|
9
|
-
keywords = ["diamondfire", "minecraft", "template", "cli"]
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
[tool.poetry.dependencies]
|
|
13
|
-
python = ">=3.10,<3.13"
|
|
14
|
-
dfpyre = "^0.9.3"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
[tool.poetry.scripts]
|
|
18
|
-
pyrecli = "pyrecli.pyrecli:main"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
[build-system]
|
|
22
|
-
requires = ["poetry-core"]
|
|
23
|
-
build-backend = "poetry.core.masonry.api"
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from result import Result, Ok, Err
|
|
3
|
-
import amulet_nbt
|
|
4
|
-
from amulet_nbt import CompoundTag, StringTag
|
|
5
|
-
from pyrecli.util import write_output_file, connect_to_codeclient
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def grabinv_command(output_path: str) -> Result[None, str]:
|
|
9
|
-
ws_result = connect_to_codeclient('inventory')
|
|
10
|
-
if ws_result.is_err():
|
|
11
|
-
return Err(ws_result.err_value)
|
|
12
|
-
ws = ws_result.ok_value
|
|
13
|
-
|
|
14
|
-
ws.send('inv')
|
|
15
|
-
inventory = ws.recv()
|
|
16
|
-
inventory_nbt = amulet_nbt.from_snbt(inventory)
|
|
17
|
-
|
|
18
|
-
template_codes: list[str] = []
|
|
19
|
-
for tag in inventory_nbt:
|
|
20
|
-
components: CompoundTag = tag.get('components')
|
|
21
|
-
if components is None:
|
|
22
|
-
continue
|
|
23
|
-
|
|
24
|
-
custom_data: CompoundTag = components.get('minecraft:custom_data')
|
|
25
|
-
if custom_data is None:
|
|
26
|
-
continue
|
|
27
|
-
|
|
28
|
-
pbv_tag: CompoundTag = custom_data.get('PublicBukkitValues')
|
|
29
|
-
if pbv_tag is None:
|
|
30
|
-
continue
|
|
31
|
-
|
|
32
|
-
code_template_data: StringTag = pbv_tag.get('hypercube:codetemplatedata')
|
|
33
|
-
if code_template_data is None:
|
|
34
|
-
continue
|
|
35
|
-
|
|
36
|
-
code_template_json = json.loads(str(code_template_data))
|
|
37
|
-
|
|
38
|
-
template_code = code_template_json.get('code')
|
|
39
|
-
if template_code:
|
|
40
|
-
template_codes.append(template_code)
|
|
41
|
-
|
|
42
|
-
if not template_codes:
|
|
43
|
-
return Err('Could not find any templates in the inventory.')
|
|
44
|
-
|
|
45
|
-
output_result = write_output_file(output_path, '\n'.join(template_codes))
|
|
46
|
-
if output_result.is_err():
|
|
47
|
-
return output_result
|
|
48
|
-
|
|
49
|
-
return Ok(None)
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from result import Result, Err
|
|
2
|
-
from pyrecli.util import write_output_file, connect_to_codeclient
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def scan_command(output_path: str) -> Result[None, str]:
|
|
6
|
-
ws_result = connect_to_codeclient('read_plot')
|
|
7
|
-
if ws_result.is_err():
|
|
8
|
-
return Err(ws_result.err_value)
|
|
9
|
-
ws = ws_result.ok_value
|
|
10
|
-
|
|
11
|
-
print('Scanning plot...')
|
|
12
|
-
ws.send('scan')
|
|
13
|
-
|
|
14
|
-
scan_results = ws.recv()
|
|
15
|
-
print('Done.')
|
|
16
|
-
ws.close()
|
|
17
|
-
|
|
18
|
-
write_result = write_output_file(output_path, scan_results)
|
|
19
|
-
return write_result
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from result import Result, Ok, Err
|
|
2
|
-
from pyrecli.util import connect_to_codeclient, read_input_file, parse_templates_from_string
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def send_command(input_path: str) -> Result[None, str]:
|
|
6
|
-
input_result = read_input_file(input_path)
|
|
7
|
-
if input_result.is_err():
|
|
8
|
-
return Err(input_result.err_value)
|
|
9
|
-
|
|
10
|
-
templates_result = parse_templates_from_string(input_result.ok_value)
|
|
11
|
-
if templates_result.is_err():
|
|
12
|
-
return Err(templates_result.err_value)
|
|
13
|
-
templates = templates_result.ok_value
|
|
14
|
-
|
|
15
|
-
ws_result = connect_to_codeclient()
|
|
16
|
-
if ws_result.is_err():
|
|
17
|
-
return Err(ws_result.err_value)
|
|
18
|
-
ws = ws_result.ok_value
|
|
19
|
-
|
|
20
|
-
for template in templates:
|
|
21
|
-
item = template.generate_template_item()
|
|
22
|
-
ws.send(f'give {item.get_snbt()}')
|
|
23
|
-
|
|
24
|
-
ws.close()
|
|
25
|
-
print(f'Sent {len(templates)} template{"s" if len(templates) != 1 else ''} successfully.')
|
|
26
|
-
return Ok(None)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from result import Result, Err
|
|
2
|
-
from pyrecli.util import read_input_file, write_output_file, parse_templates_from_string
|
|
3
|
-
|
|
4
|
-
def slice_command(input_path: str, output_path: str, target_length: int) -> Result[None, str]:
|
|
5
|
-
input_result = read_input_file(input_path)
|
|
6
|
-
if input_result.is_err():
|
|
7
|
-
return Err(input_result.err_value)
|
|
8
|
-
|
|
9
|
-
templates_result = parse_templates_from_string(input_result.ok_value)
|
|
10
|
-
if templates_result.is_err():
|
|
11
|
-
return Err(templates_result.err_value)
|
|
12
|
-
templates = templates_result.ok_value
|
|
13
|
-
|
|
14
|
-
if not templates:
|
|
15
|
-
return Err(f'Could not find any templates in {input_path}')
|
|
16
|
-
|
|
17
|
-
first_template = templates[0]
|
|
18
|
-
sliced_templates = first_template.slice(target_length)
|
|
19
|
-
built_templates = [t.build() for t in sliced_templates]
|
|
20
|
-
|
|
21
|
-
return write_output_file(output_path, '\n'.join(built_templates))
|
pyrecli-0.2.1/pyrecli/util.py
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import re
|
|
3
|
-
import sys
|
|
4
|
-
from result import Result, Ok, Err
|
|
5
|
-
import websocket
|
|
6
|
-
from dfpyre import DFTemplate
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
CODECLIENT_URL = 'ws://localhost:31375'
|
|
10
|
-
|
|
11
|
-
BASE64_REGEX = re.compile(r'^[A-Za-z0-9+/]+={0,2}$')
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def connect_to_codeclient(scopes: str|None=None) -> Result[websocket.WebSocket, str]:
|
|
15
|
-
ws = websocket.WebSocket()
|
|
16
|
-
try:
|
|
17
|
-
ws.connect(CODECLIENT_URL)
|
|
18
|
-
except ConnectionRefusedError:
|
|
19
|
-
return Err('Failed to connect to CodeClient.')
|
|
20
|
-
|
|
21
|
-
print('Connected to CodeClient.')
|
|
22
|
-
|
|
23
|
-
if scopes:
|
|
24
|
-
print('Please run /auth in game.')
|
|
25
|
-
ws.send(f'scopes {scopes}')
|
|
26
|
-
auth_message = ws.recv()
|
|
27
|
-
|
|
28
|
-
if auth_message != 'auth':
|
|
29
|
-
return Err('Failed to authenticate.')
|
|
30
|
-
print('Authentication received.')
|
|
31
|
-
|
|
32
|
-
return Ok(ws)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def parse_templates_from_string(templates: str) -> Result[list[DFTemplate], str]:
|
|
36
|
-
template_codes = templates.split('\n')
|
|
37
|
-
|
|
38
|
-
for i, template_code in enumerate(template_codes):
|
|
39
|
-
if not BASE64_REGEX.match(template_code):
|
|
40
|
-
return Err(f'Template code at line {i+1} is not a base64 string.')
|
|
41
|
-
|
|
42
|
-
try:
|
|
43
|
-
return Ok([DFTemplate.from_code(c) for c in template_codes])
|
|
44
|
-
except Exception as e:
|
|
45
|
-
return Err(str(e))
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def read_input_file(path: str) -> Result[str, str]:
|
|
49
|
-
if path == '-':
|
|
50
|
-
try:
|
|
51
|
-
input_string = sys.stdin.read()
|
|
52
|
-
except EOFError:
|
|
53
|
-
pass
|
|
54
|
-
return Ok(input_string.strip())
|
|
55
|
-
|
|
56
|
-
if not os.path.isfile(path):
|
|
57
|
-
return Err(f'"{path}" is not a file.')
|
|
58
|
-
|
|
59
|
-
try:
|
|
60
|
-
with open(path, 'r') as f:
|
|
61
|
-
return Ok(f.read())
|
|
62
|
-
except OSError as e:
|
|
63
|
-
return Err(str(e))
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def write_output_file(path: str, content: str) -> Result[None, str]:
|
|
67
|
-
if path == '-':
|
|
68
|
-
print(content, end='')
|
|
69
|
-
else:
|
|
70
|
-
try:
|
|
71
|
-
with open(path, 'w') as f:
|
|
72
|
-
f.write(content)
|
|
73
|
-
except OSError as e:
|
|
74
|
-
return Err(str(e))
|
|
75
|
-
|
|
76
|
-
return Ok(None)
|
|
File without changes
|
|
File without changes
|