pyrecli 0.1.3__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.1.3/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Amp63
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pyrecli-0.1.3/PKG-INFO ADDED
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.1
2
+ Name: pyrecli
3
+ Version: 0.1.3
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
+ Project-URL: Repository, https://github.com/Amp63/pyrecli
16
+ Description-Content-Type: text/markdown
17
+
18
+ # pyrecli
19
+
20
+ Command line utilities for DiamondFire templates
21
+
22
+ ## Installation
23
+
24
+ Run the following command in a terminal:
25
+
26
+ ```sh
27
+ pip install pyrecli
28
+ ```
29
+
30
+ ## Commands
31
+
32
+ - `scan`: Scan all templates on the plot and dump them to a text file (requires [CodeClient](github.com/DFOnline/CodeClient))
33
+ - `send`: Send template items to DiamondFire (requires [CodeClient](github.com/DFOnline/CodeClient))
34
+ - `rename`: Rename all occurences of a variable (including text codes)
35
+ - `script`: Generate python scripts from template data
36
+ - `grabinv`: Save all templates in your Minecraft inventory to a file (requires [CodeClient](github.com/DFOnline/CodeClient))
37
+ - `docs`: Generate markdown documentation from template data
38
+
39
+
40
+ ## What is this useful for?
41
+
42
+ - Backing up a plot
43
+ - Getting an accurate text representation of DF code
44
+ - Open sourcing
45
+ - Version control
46
+ - Large scale refactoring
47
+
48
+
49
+ ## Example Usage
50
+
51
+ These two commands will scan your plot, convert each template into a python script, then place the scripts into a directory called `myplot`.
52
+
53
+ ```sh
54
+ pyrecli scan templates.dfts
55
+ pyrecli script templates.dfts myplot
56
+ ```
57
+
58
+ If you prefer the templates to be outputted to a single file, use the `--onefile` flag:
59
+
60
+ ```sh
61
+ pyrecli scan templates.dfts
62
+ pyrecli script templates.dfts myplot.py --onefile
63
+ ```
64
+
65
+ For more information about generating scripts, run `pyrecli script -h`.
66
+
@@ -0,0 +1,48 @@
1
+ # pyrecli
2
+
3
+ Command line utilities for DiamondFire templates
4
+
5
+ ## Installation
6
+
7
+ Run the following command in a terminal:
8
+
9
+ ```sh
10
+ pip install pyrecli
11
+ ```
12
+
13
+ ## Commands
14
+
15
+ - `scan`: Scan all templates on the plot and dump them to a text file (requires [CodeClient](github.com/DFOnline/CodeClient))
16
+ - `send`: Send template items to DiamondFire (requires [CodeClient](github.com/DFOnline/CodeClient))
17
+ - `rename`: Rename all occurences of a variable (including text codes)
18
+ - `script`: Generate python scripts from template data
19
+ - `grabinv`: Save all templates in your Minecraft inventory to a file (requires [CodeClient](github.com/DFOnline/CodeClient))
20
+ - `docs`: Generate markdown documentation from template data
21
+
22
+
23
+ ## What is this useful for?
24
+
25
+ - Backing up a plot
26
+ - Getting an accurate text representation of DF code
27
+ - Open sourcing
28
+ - Version control
29
+ - Large scale refactoring
30
+
31
+
32
+ ## Example Usage
33
+
34
+ These two commands will scan your plot, convert each template into a python script, then place the scripts into a directory called `myplot`.
35
+
36
+ ```sh
37
+ pyrecli scan templates.dfts
38
+ pyrecli script templates.dfts myplot
39
+ ```
40
+
41
+ If you prefer the templates to be outputted to a single file, use the `--onefile` flag:
42
+
43
+ ```sh
44
+ pyrecli scan templates.dfts
45
+ pyrecli script templates.dfts myplot.py --onefile
46
+ ```
47
+
48
+ For more information about generating scripts, run `pyrecli script -h`.
@@ -0,0 +1,22 @@
1
+ [tool.poetry]
2
+ name = "pyrecli"
3
+ version = "0.1.3"
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
+
15
+
16
+ [tool.poetry.scripts]
17
+ pyrecli = "pyrecli.pyrecli:main"
18
+
19
+
20
+ [build-system]
21
+ requires = ["poetry-core"]
22
+ build-backend = "poetry.core.masonry.api"
File without changes
@@ -0,0 +1,124 @@
1
+ from typing import Literal, TypedDict
2
+ from result import Result, Err
3
+ from dfpyre import DFTemplate, Item, Parameter
4
+ from mcitemlib.itemlib import MCItemlibException
5
+ from pyrecli.util import read_input_file, write_output_file, parse_templates_from_string
6
+
7
+
8
+ STARTER_BLOCK_LOOKUP = {
9
+ 'func': 'Function',
10
+ 'process': 'Process'
11
+ }
12
+
13
+
14
+ def escape_md(s: str) -> str:
15
+ MD_CHARS = r'*`$#!&^~'
16
+ for char in MD_CHARS:
17
+ s = s.replace(char, rf'\{char}')
18
+ return s
19
+
20
+
21
+ class TemplateDocData(TypedDict):
22
+ template_type: Literal['Function', 'Process']
23
+ function_name: str
24
+ doc_lines: list[str]
25
+
26
+
27
+ def docs_command(input_path: str, output_path: str, title: str, include_hidden: bool, omit_toc: bool) -> Result[None, str]:
28
+ input_result = read_input_file(input_path)
29
+ if input_result.is_err():
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
36
+
37
+ def get_function_name(template: DFTemplate) -> str:
38
+ first_block = template.codeblocks[0]
39
+ function_name = first_block.data.get('data')
40
+ return escape_md(function_name)
41
+
42
+ block_type_order = list(STARTER_BLOCK_LOOKUP.keys())
43
+ templates = [t for t in templates if t.codeblocks[0].action_name == 'dynamic']
44
+ templates.sort(key=get_function_name)
45
+ templates.sort(key=lambda t: block_type_order.index(t.codeblocks[0].type))
46
+
47
+ output_lines: list[str] = [
48
+ f'# {title}',
49
+ ''
50
+ ]
51
+
52
+ template_docs: list[TemplateDocData] = []
53
+ for template in templates:
54
+ first_block = template.codeblocks[0]
55
+
56
+ # Skip if hidden
57
+ if first_block.tags.get('Is Hidden') == 'True' and not include_hidden:
58
+ continue
59
+
60
+ # Add function / process name
61
+ template_type = STARTER_BLOCK_LOOKUP[first_block.type]
62
+ template_name = get_function_name(template)
63
+ template_doc_lines: list[str] = []
64
+
65
+ template_doc_lines.append(f'## {template_type}: {template_name}')
66
+
67
+ # Parse description
68
+ if first_block.args:
69
+ first_arg = first_block.args[0]
70
+ if isinstance(first_arg, Item):
71
+ try:
72
+ lore_text = [escape_md(l.to_string()) for l in first_arg.get_lore()]
73
+ if lore_text:
74
+ template_doc_lines.extend(lore_text)
75
+ except (MCItemlibException, AttributeError):
76
+ # There are so many things that can go wrong here due to various legacy
77
+ # item formats and weird MC string edge cases, so we can just skip
78
+ # if there's a problem.
79
+ pass
80
+
81
+ # Parse parameters
82
+ parameter_lines: list[str] = []
83
+ for arg in first_block.args:
84
+ if isinstance(arg, Parameter):
85
+ optional_text = "*" if arg.optional else ""
86
+ default_value_text = f'= `{escape_md(arg.default_value.__repr__())}`' if arg.default_value else ''
87
+ parameter_lines.append(f'- *`{escape_md(arg.name)}{optional_text}`*: `{arg.param_type.get_string_value()}` {default_value_text}')
88
+ if arg.description:
89
+ parameter_lines.append(f' - {escape_md(arg.description)}')
90
+ if arg.note:
91
+ parameter_lines.append(f' - {escape_md(arg.note)}')
92
+ if parameter_lines:
93
+ template_doc_lines.append('')
94
+ template_doc_lines.append('### Parameters:')
95
+ template_doc_lines.extend(parameter_lines)
96
+
97
+ doc_data = TemplateDocData(template_type=template_type, function_name=template_name, doc_lines=template_doc_lines)
98
+ template_docs.append(doc_data)
99
+
100
+
101
+ # Add table of contents
102
+ def add_toc_group(doc_data_list: list[TemplateDocData], group_title: str):
103
+ if doc_data_list:
104
+ output_lines.append(f'### {group_title}')
105
+ for doc_data in doc_data_list:
106
+ link = f'#{doc_data["template_type"]}-{doc_data["function_name"]}'.lower().replace(' ', '-')
107
+ output_lines.append(f'- [{doc_data["function_name"]}]({link})')
108
+
109
+ if not omit_toc:
110
+ output_lines.append('## Contents')
111
+ function_templates = [d for d in template_docs if d['template_type'] == 'Function']
112
+ add_toc_group(function_templates, 'Functions')
113
+ output_lines.append('')
114
+ process_templates = [d for d in template_docs if d['template_type'] == 'Process']
115
+ add_toc_group(process_templates, 'Processes')
116
+ output_lines.append('\n')
117
+
118
+ # Add template docs to output lines
119
+ for doc_data in template_docs:
120
+ output_lines.extend(doc_data['doc_lines'])
121
+ output_lines.append('')
122
+
123
+ write_result = write_output_file(output_path, '\n'.join(output_lines))
124
+ return write_result
@@ -0,0 +1,49 @@
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)
@@ -0,0 +1,54 @@
1
+ from typing import Literal
2
+ import re
3
+ from result import Result, Err
4
+ from dfpyre import Variable, Number, String, Text
5
+ from pyrecli.util import read_input_file, write_output_file, parse_templates_from_string
6
+
7
+
8
+ TEXT_CODE_PATTERNS = [
9
+ re.compile(r"%var\(([a-zA-Z0-9!@#$%^&*~`\-_=+\\|;':\",.\/<>? ]+)\)"),
10
+ re.compile(r"%index\(([a-zA-Z0-9!@#$%^&*~`\-_=+\\|;':\",.\/<>? ]+),\d+\)"),
11
+ re.compile(r"%entry\(([a-zA-Z0-9!@#$%^&*~`\-_=+\\|;':\",.\/<>? ]+),[a-zA-Z0-9!@#$%^&*~`\-_=+\\|;':\",.\/<>? ]+\)")
12
+ ]
13
+
14
+
15
+ def rename_var_in_text_code(s: str, var_to_rename: str, new_var_name: str):
16
+ for pattern in TEXT_CODE_PATTERNS:
17
+ match = pattern.search(s)
18
+ if match and match.group(1) == var_to_rename:
19
+ s = s.replace(match.group(1), new_var_name)
20
+ return s
21
+
22
+
23
+ def rename_command(input_path: str, output_path: str|None,
24
+ var_to_rename: str, new_var_name: str,
25
+ var_to_rename_scope: Literal['game', 'saved', 'local', 'line']|None) -> Result[None, str]:
26
+
27
+ input_result = read_input_file(input_path)
28
+ if input_result.is_err():
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
35
+
36
+ for template in templates:
37
+ for codeblock in template.codeblocks:
38
+ for argument in codeblock.args:
39
+ if isinstance(argument, Variable):
40
+ if argument.name == var_to_rename and (var_to_rename_scope is None or argument.scope == var_to_rename_scope):
41
+ argument.name = new_var_name
42
+ argument.name = rename_var_in_text_code(argument.name, var_to_rename, new_var_name)
43
+
44
+ elif isinstance(argument, (Number, String, Text)) and isinstance(argument.value, str):
45
+ argument.value = rename_var_in_text_code(argument.value, var_to_rename, new_var_name)
46
+
47
+ if codeblock.type in {'call_func', 'start_process'}:
48
+ new_data = rename_var_in_text_code(codeblock.data.get('data'), var_to_rename, new_var_name)
49
+ codeblock.data['data'] = new_data
50
+
51
+ new_file_content = '\n'.join(t.build() for t in templates)
52
+ write_path = output_path if output_path else input_path
53
+ write_result = write_output_file(write_path, new_file_content)
54
+ return write_result
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,48 @@
1
+ import os
2
+ from result import Result, Ok, Err
3
+ from dfpyre import DFTemplate
4
+ from pyrecli.util import read_input_file, write_output_file, parse_templates_from_string
5
+
6
+
7
+ def write_to_directory(dir_name: str, templates: list[DFTemplate], flags: dict[str, int|bool]) -> Result[None, str]:
8
+ if not os.path.isdir(dir_name):
9
+ os.mkdir(dir_name)
10
+
11
+ for template in templates:
12
+ script_path = f'{dir_name}/{template._get_template_name()}.py'
13
+ script_string = template.generate_script(**flags)
14
+ try:
15
+ with open(script_path, 'w') as f:
16
+ f.write(script_string)
17
+ except OSError as e:
18
+ return Err(str(e))
19
+
20
+ return Ok(None)
21
+
22
+
23
+ def write_to_single_file(file_path: str, templates: list[DFTemplate], flags: dict[str, int|bool]) -> Result[None, str]:
24
+ file_content = []
25
+ for i, template in enumerate(templates):
26
+ if i == 0:
27
+ template_script = template.generate_script(include_import=True, assign_variable=True, **flags)
28
+ else:
29
+ template_script = template.generate_script(include_import=False, assign_variable=True, **flags)
30
+ file_content.append(template_script)
31
+
32
+ return write_output_file(file_path, '\n\n'.join(file_content))
33
+
34
+
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
+
40
+ templates_result = parse_templates_from_string(input_result.ok_value)
41
+ if templates_result.is_err():
42
+ return Err(templates_result.err_value)
43
+ templates = templates_result.ok_value
44
+
45
+ if one_file or output_path == '-':
46
+ return write_to_single_file(output_path, templates, flags)
47
+
48
+ return write_to_directory(output_path, templates, flags)
@@ -0,0 +1,26 @@
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)
@@ -0,0 +1,92 @@
1
+ import sys
2
+ import argparse
3
+ import importlib.metadata
4
+
5
+ from pyrecli.command.scan import scan_command
6
+ from pyrecli.command.send import send_command
7
+ from pyrecli.command.script import script_command
8
+ from pyrecli.command.rename import rename_command
9
+ from pyrecli.command.grabinv import grabinv_command
10
+ from pyrecli.command.docs import docs_command
11
+
12
+
13
+ def main():
14
+ parser = argparse.ArgumentParser(prog='pyrecli', description='Command line utilities for DiamondFire templates')
15
+ parser.add_argument('--version', '-v', action='version', version=f'pyrecli {importlib.metadata.version('pyrecli')}')
16
+ subparsers = parser.add_subparsers(dest='command', help='Available commands:', required=True, metavar='<command>')
17
+
18
+ parser_scan = subparsers.add_parser('scan', help='Scan the current plot templates with CodeClient')
19
+ parser_scan.add_argument('output_path', help='The file to output template data to', type=str)
20
+
21
+ parser_send = subparsers.add_parser('send', help='Send templates to DiamondFire with CodeClient')
22
+ parser_send.add_argument('input_path', help='The file containing template data', type=str)
23
+
24
+ parser_script = subparsers.add_parser('script', help='Create python scripts from template data')
25
+ parser_script.add_argument('input_path', help='The file containing template data', type=str)
26
+ parser_script.add_argument('output_path', help='The file or directory to output to', type=str)
27
+ parser_script.add_argument('--onefile', help='Output template data as a single script', action='store_true')
28
+ parser_script.add_argument('--indent_size', '-i', help='The multiple of spaces to add when indenting lines', type=int, default=4)
29
+ parser_script.add_argument('--literal_shorthand', '-ls', help='Output Text and Number items as strings and ints respectively', action='store_false')
30
+ parser_script.add_argument('--var_shorthand', '-vs', help='Write all variables using variable shorthand', action='store_true')
31
+ parser_script.add_argument('--preserve_slots', '-s', help='Save the positions of items within chests', action='store_true')
32
+ parser_script.add_argument('--build_and_send', '-b', help='Add `.build_and_send()` to the end of the generated template(s)', action='store_true')
33
+
34
+ parser_rename = subparsers.add_parser('rename', help='Rename a variable')
35
+ parser_rename.add_argument('input_path', help='The file containing template data', type=str)
36
+ parser_rename.add_argument('var_to_rename', help='The variable to rename', type=str)
37
+ parser_rename.add_argument('new_var_name', help='The new name for the variable', type=str)
38
+ parser_rename.add_argument('--var_to_rename_scope', '-s', help='The scope to match', type=str, default=None)
39
+ parser_rename.add_argument('--output_path', '-o', help='The file or directory to output to', type=str, default=None)
40
+
41
+ parser_grabinv = subparsers.add_parser('grabinv', help='Save all templates in the inventory to a file with CodeClient')
42
+ parser_grabinv.add_argument('output_path', help='The file to output template data to', type=str)
43
+
44
+ parser_docs = subparsers.add_parser('docs', help='Generate markdown documentation from template data')
45
+ parser_docs.add_argument('input_path', help='The file containing template data', type=str)
46
+ parser_docs.add_argument('output_path', help='The file or directory to output to', type=str)
47
+ parser_docs.add_argument('title', help='The title for the docs', type=str)
48
+ parser_docs.add_argument('--include_hidden', '-ih', help='Include hidden functions and processes', action='store_true')
49
+ parser_docs.add_argument('--notoc', help='Omit the table of contents', action='store_true')
50
+
51
+ parsed_args = parser.parse_args()
52
+
53
+ match parsed_args.command:
54
+ case 'scan':
55
+ command_result = scan_command(parsed_args.output_path)
56
+
57
+ case 'send':
58
+ command_result = send_command(parsed_args.input_path)
59
+
60
+ case 'script':
61
+ scriptgen_flags = {
62
+ 'indent_size': parsed_args.indent_size,
63
+ 'literal_shorthand': parsed_args.literal_shorthand,
64
+ 'var_shorthand': parsed_args.var_shorthand,
65
+ 'preserve_slots': parsed_args.preserve_slots,
66
+ 'build_and_send': parsed_args.build_and_send
67
+ }
68
+ command_result = script_command(parsed_args.input_path, parsed_args.output_path, parsed_args.onefile, scriptgen_flags)
69
+
70
+ case 'rename':
71
+ command_result = rename_command(
72
+ parsed_args.input_path, parsed_args.output_path,
73
+ parsed_args.var_to_rename, parsed_args.new_var_name, parsed_args.var_to_rename_scope
74
+ )
75
+
76
+ case 'grabinv':
77
+ command_result = grabinv_command(parsed_args.output_path)
78
+
79
+ case 'docs':
80
+ command_result = docs_command(
81
+ parsed_args.input_path, parsed_args.output_path,
82
+ parsed_args.title, parsed_args.include_hidden, parsed_args.notoc
83
+ )
84
+
85
+ if command_result.is_err():
86
+ print(command_result.err_value)
87
+ sys.exit(1)
88
+
89
+ sys.exit(0)
90
+
91
+ if __name__ == '__main__':
92
+ main()
@@ -0,0 +1,74 @@
1
+ import os
2
+ import re
3
+ from result import Result, Ok, Err
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
+
13
+ def connect_to_codeclient(scopes: str|None=None) -> Result[websocket.WebSocket, str]:
14
+ ws = websocket.WebSocket()
15
+ try:
16
+ ws.connect(CODECLIENT_URL)
17
+ except ConnectionRefusedError:
18
+ return Err('Failed to connect to CodeClient.')
19
+
20
+ print('Connected to CodeClient.')
21
+
22
+ if scopes:
23
+ print('Please run /auth in game.')
24
+ ws.send(f'scopes {scopes}')
25
+ auth_message = ws.recv()
26
+
27
+ if auth_message != 'auth':
28
+ return Err('Failed to authenticate.')
29
+ print('Authentication received.')
30
+
31
+ return Ok(ws)
32
+
33
+
34
+ def parse_templates_from_string(templates: str) -> Result[list[DFTemplate], str]:
35
+ template_codes = templates.split('\n')
36
+
37
+ for i, template_code in enumerate(template_codes):
38
+ if not BASE64_REGEX.match(template_code):
39
+ return Err(f'Template code at line {i+1} is not a base64 string.')
40
+
41
+ try:
42
+ return Ok([DFTemplate.from_code(c) for c in template_codes])
43
+ except Exception as e:
44
+ return Err(str(e))
45
+
46
+
47
+ def read_input_file(path: str) -> Result[str, str]:
48
+ if path == '-':
49
+ try:
50
+ input_string = input()
51
+ except EOFError:
52
+ return Ok(input_string)
53
+
54
+ if not os.path.isfile(path):
55
+ return Err(f'"{path}" is not a file.')
56
+
57
+ try:
58
+ with open(path, 'r') as f:
59
+ return Ok(f.read())
60
+ except OSError as e:
61
+ return Err(str(e))
62
+
63
+
64
+ def write_output_file(path: str, content: str) -> Result[None, str]:
65
+ if path == '-':
66
+ print(content, end='')
67
+ else:
68
+ try:
69
+ with open(path, 'w') as f:
70
+ f.write(content)
71
+ except OSError as e:
72
+ return Err(str(e))
73
+
74
+ return Ok(None)