bot-formatter 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Timo (tibue99)
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.
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: bot-formatter
3
+ Version: 0.1.0
4
+ Summary: A formatter for Discord bots.
5
+ Author: tibue99
6
+ License: MIT
7
+ Project-URL: GitHub, https://github.com/CookieAppTeam/bot-formatter
8
+ Keywords: discord,discord.py,py-cord
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Software Development :: Quality Assurance
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: libcst
23
+ Requires-Dist: PyYAML
24
+ Dynamic: license-file
25
+
26
+ # bot-formatter
27
+ [![](https://img.shields.io/pypi/v/bot-formatter.svg?style=for-the-badge&logo=pypi&color=yellow&logoColor=white)](https://pypi.org/project/bot-formatter/)
28
+ [![](https://img.shields.io/readthedocs/bot-formatter?style=for-the-badge&color=blue&link=https%3A%2F%2Fbot-formatter.readthedocs.io%2F)](https://bot-formatter.readthedocs.io/)
29
+ [![](https://img.shields.io/pypi/l/bot-formatter?style=for-the-badge)](https://pypi.org/project/bot-formatter/)
30
+
31
+ A formatter for Discord bots.
32
+
33
+ ## Installing
34
+ Python 3.10 or higher is required.
35
+ ```
36
+ pip install bot-formatter
37
+ ```
38
+
39
+ ## Usage
40
+ For a full overview, see the [documentation](https://bot-formatter.readthedocs.io/).
41
+ ```
42
+ bot-formatter main.py
43
+ ```
44
+
45
+ ## Pre-Commit
46
+ ```yaml
47
+ - repo: https://github.com/CookieAppTeam/bot-formatter
48
+ rev: 0.1.0
49
+ hooks:
50
+ - id: bot-formatter
51
+ ```
@@ -0,0 +1,26 @@
1
+ # bot-formatter
2
+ [![](https://img.shields.io/pypi/v/bot-formatter.svg?style=for-the-badge&logo=pypi&color=yellow&logoColor=white)](https://pypi.org/project/bot-formatter/)
3
+ [![](https://img.shields.io/readthedocs/bot-formatter?style=for-the-badge&color=blue&link=https%3A%2F%2Fbot-formatter.readthedocs.io%2F)](https://bot-formatter.readthedocs.io/)
4
+ [![](https://img.shields.io/pypi/l/bot-formatter?style=for-the-badge)](https://pypi.org/project/bot-formatter/)
5
+
6
+ A formatter for Discord bots.
7
+
8
+ ## Installing
9
+ Python 3.10 or higher is required.
10
+ ```
11
+ pip install bot-formatter
12
+ ```
13
+
14
+ ## Usage
15
+ For a full overview, see the [documentation](https://bot-formatter.readthedocs.io/).
16
+ ```
17
+ bot-formatter main.py
18
+ ```
19
+
20
+ ## Pre-Commit
21
+ ```yaml
22
+ - repo: https://github.com/CookieAppTeam/bot-formatter
23
+ rev: 0.1.0
24
+ hooks:
25
+ - id: bot-formatter
26
+ ```
@@ -0,0 +1,13 @@
1
+ __title__ = "bot-formatter"
2
+ __author__ = "tibue99"
3
+ __license__ = "MIT"
4
+ __version__ = "0.1.0"
5
+
6
+
7
+ import sys
8
+
9
+ from bot_formatter.run import BotFormatter
10
+
11
+
12
+ def run():
13
+ BotFormatter(sys.argv[1:])
@@ -0,0 +1,3 @@
1
+ import bot_formatter
2
+
3
+ bot_formatter.run()
@@ -0,0 +1,11 @@
1
+ from .dpy import ConvertSetup
2
+ from .yml import remove_duplicate_new_lines
3
+ from .pycord import ConvertContext
4
+ from .lang import check_missing_keys, check_empty_line_diffs
5
+
6
+
7
+ EZCORD = [ConvertContext]
8
+ LANG = [check_missing_keys, check_empty_line_diffs]
9
+ PYCORD = []
10
+ DPY = [ConvertSetup]
11
+ YML = [remove_duplicate_new_lines]
@@ -0,0 +1,35 @@
1
+ """Formatters for Discord.py."""
2
+
3
+ import libcst as cst
4
+ import libcst.matchers as m
5
+ from libcst.codemod import VisitorBasedCodemodCommand
6
+
7
+
8
+ class ConvertSetup(VisitorBasedCodemodCommand):
9
+ DESCRIPTION = "Converts setup methods to their asynchronous equivalent."
10
+
11
+ ADD_COG = m.Expr(
12
+ m.Call(
13
+ m.Attribute(
14
+ attr=m.Name(
15
+ value="add_cog",
16
+ )
17
+ )
18
+ )
19
+ )
20
+
21
+ def __init__(self, context: cst.codemod.CodemodContext):
22
+ super().__init__(context)
23
+
24
+ def leave_FunctionDef(
25
+ self, node: cst.FunctionDef, updated_node: cst.FunctionDef
26
+ ) -> cst.FunctionDef:
27
+ if node.name.value == "setup":
28
+ return updated_node.with_changes(asynchronous=cst.Asynchronous())
29
+ return updated_node
30
+
31
+ @m.call_if_inside(ADD_COG)
32
+ def leave_Call(self, node: cst.Call, updated_node: cst.Call) -> cst.Call:
33
+ if isinstance(node.func, cst.Attribute):
34
+ return updated_node.with_changes(func=cst.Await(updated_node.func))
35
+ return updated_node
@@ -0,0 +1,70 @@
1
+ """
2
+ Formatters for YAML language files that compare content and keys across multiple files.
3
+ """
4
+
5
+ # A dictionary with a mapping of file names to their keys
6
+ LANG_KEYS = dict[str, dict]
7
+
8
+ # A dictionary with a mapping of file names to their content
9
+ LANG_CONTENT = dict[str, str]
10
+
11
+
12
+ def _collect_keys(dict_content: dict, parent_key: str | None = None) -> set[str]:
13
+ """Recursively collects all keys in a nested dictionary."""
14
+
15
+ keys = set()
16
+ for key, value in dict_content.items():
17
+ full_key = f"{parent_key}.{key}" if parent_key else key
18
+ keys.add(full_key)
19
+ if isinstance(value, dict):
20
+ keys.update(_collect_keys(value, full_key))
21
+
22
+ return keys
23
+
24
+
25
+ def check_missing_keys(lang_keys: LANG_KEYS, report):
26
+ """Checks that all language files have the same keys."""
27
+
28
+ for file_name, content in lang_keys.items():
29
+ for other_file_name, other_content in lang_keys.items():
30
+ if file_name == other_file_name:
31
+ continue
32
+
33
+ keys = _collect_keys(content)
34
+ other_keys = _collect_keys(other_content)
35
+
36
+ missing_keys = other_keys - keys
37
+
38
+ if missing_keys:
39
+ missing = '\n'.join(sorted([f"- {key}" for key in missing_keys]))
40
+ report.check_failed(file_name, f"Missing keys compared to {other_file_name}:\n{missing}")
41
+
42
+
43
+ def check_empty_line_diffs(lang_content: LANG_CONTENT, report):
44
+ """Checks if all YAML keys are in the same line across different language files."""
45
+
46
+ reference_file = None
47
+ reference_lines = []
48
+
49
+ for file_name, content in lang_content.items():
50
+ reference_file = file_name
51
+ reference_lines = content.splitlines()
52
+ break
53
+
54
+ if reference_file is None:
55
+ return
56
+
57
+ # Compare all files to reference file
58
+ for file_name, content in lang_content.items():
59
+ if file_name == reference_file:
60
+ continue
61
+
62
+ current_lines = content.splitlines()
63
+
64
+ for line, (ref_line, cur_line) in enumerate(zip(reference_lines, current_lines), start=1):
65
+ if ref_line.strip() == "" and ref_line.strip() != cur_line.strip():
66
+ report.check_failed(
67
+ file_name,
68
+ f"Empty line {line} differs from {reference_file}."
69
+ )
70
+ break
@@ -0,0 +1,77 @@
1
+ """Formatters for Pycord ."""
2
+
3
+ import libcst as cst
4
+ import libcst.matchers as m
5
+ from libcst.codemod import VisitorBasedCodemodCommand
6
+
7
+
8
+ class ConvertContext(VisitorBasedCodemodCommand):
9
+ DESCRIPTION = "Converts discord.ApplicationContext to ezcord.EzContext."
10
+
11
+ SLASH_DECORATOR = m.FunctionDef(
12
+ decorators=[m.Decorator(decorator=m.Call(func=m.Name("slash_command")))]
13
+ )
14
+
15
+ IMPORT_EZCORD = m.Import(names=[m.ImportAlias(name=m.Name(value="ezcord"))])
16
+
17
+ EZ_ANNOTATION_1 = m.Annotation(m.Attribute(value=m.Name("ezcord"), attr=m.Name("EzContext")))
18
+
19
+ EZ_ANNOTATION_2 = m.Annotation(
20
+ m.Name(
21
+ value="EzContext",
22
+ )
23
+ )
24
+
25
+ def __init__(self, context: cst.codemod.CodemodContext):
26
+ super().__init__(context)
27
+ self.has_changes = False
28
+ self.has_ezcord_import = False
29
+
30
+ def leave_Import(self, original_node: cst.Import, updated_node: cst.Import) -> cst.Import:
31
+ """Checks if Ezcord is already imported."""
32
+
33
+ if m.matches(updated_node, self.IMPORT_EZCORD):
34
+ self.has_ezcord_import = True
35
+
36
+ return updated_node
37
+
38
+ @m.call_if_inside(SLASH_DECORATOR)
39
+ def leave_FunctionDef(
40
+ self, node: cst.FunctionDef, updated_node: cst.FunctionDef
41
+ ) -> cst.FunctionDef:
42
+ """Replaces discord.ApplicationContext with ezcord.EzContext if it doesn't already exist."""
43
+
44
+ new_params = []
45
+ for param in node.params.params:
46
+ if param.annotation:
47
+ if m.matches(param.annotation, self.EZ_ANNOTATION_1) or m.matches(
48
+ param.annotation, self.EZ_ANNOTATION_2
49
+ ):
50
+ new_params.append(param)
51
+ continue
52
+
53
+ if param.name.value != "ctx":
54
+ new_params.append(param)
55
+ continue
56
+
57
+ modified_param = cst.Param(
58
+ cst.Name("ctx"),
59
+ cst.Annotation(cst.Attribute(value=cst.Name("ezcord"), attr=cst.Name("EzContext"))),
60
+ )
61
+ new_params.append(modified_param)
62
+ self.has_changes = True
63
+
64
+ return updated_node.with_changes(params=cst.Parameters(params=new_params))
65
+
66
+ def leave_Module(self, node: cst.Module, updated_node: cst.Module) -> cst.Module:
67
+ """Adds an import for ezcord if it doesn't exist and if changes were made."""
68
+
69
+ if self.has_changes and not self.has_ezcord_import:
70
+ new_import = cst.Import(names=[cst.ImportAlias(name=cst.Name("ezcord"))])
71
+ new_body = [
72
+ cst.SimpleStatementLine(body=[new_import]),
73
+ cst.Newline(),
74
+ ] + list(updated_node.body)
75
+ return updated_node.with_changes(body=new_body)
76
+
77
+ return updated_node
@@ -0,0 +1,10 @@
1
+ """Formatter for YAML files."""
2
+
3
+
4
+ def remove_duplicate_new_lines(content: str) -> str:
5
+ """Removes duplicate new lines from the content of a YAML file."""
6
+
7
+ while "\n\n\n" in content:
8
+ content = content.replace("\n\n\n", "\n\n")
9
+
10
+ return content
@@ -0,0 +1,191 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+ from typing import get_type_hints
6
+
7
+ from libcst import codemod
8
+ import yaml
9
+
10
+ from bot_formatter.formatters import DPY, EZCORD, PYCORD, LANG, YML
11
+
12
+
13
+ class Output:
14
+ modified_files: list[str] = []
15
+ failed_files: list[str] = []
16
+ failed_checks: dict[str, list[str]] = {}
17
+
18
+ def __init__(self, config: argparse.Namespace):
19
+ self.config = config
20
+
21
+ def success(self, file: str):
22
+ self.modified_files.append(file)
23
+
24
+ def error(self, file: str, error: Exception):
25
+ self.failed_files.append(f"{file}: {error}")
26
+
27
+ def check_failed(self, file: str, error_txt: str):
28
+ if file not in self.failed_checks:
29
+ self.failed_checks[file] = []
30
+ self.failed_checks[file].append(error_txt)
31
+
32
+ @staticmethod
33
+ def _check_plural(word: str, count: int) -> str:
34
+ return f"{count} {word}{'s' if count != 1 else ''}"
35
+
36
+ def print_output(self):
37
+ """Prints a report to the console if the silent mode isn't enabled."""
38
+
39
+ if self.config.silent:
40
+ return
41
+
42
+ modify = self._check_plural("file", len(self.modified_files))
43
+ check = self._check_plural("file", len(self.config.files))
44
+
45
+ if self.config.dry_run:
46
+ report = f"Done! Would modify {modify} (checked {check})"
47
+ else:
48
+ report = f"Done! Modified {modify} (checked {check})"
49
+
50
+ if self.modified_files:
51
+ report += "\n\n" + "\n".join(self.modified_files)
52
+
53
+ if self.failed_files:
54
+ report += f"\n\n{self._check_plural('error', len(self.failed_files))} occurred"
55
+ report += "\n" + "\n".join(self.failed_files)
56
+
57
+ for file, errors in self.failed_checks.items():
58
+ report += f"\n\n\n------ CHECKS FAILED IN {file.upper()} ------"
59
+ report += "\n\n" + "\n\n".join(errors)
60
+
61
+ print(report)
62
+
63
+
64
+ class BotFormatter:
65
+ def __init__(self, args: list[str]) -> None:
66
+ parser = argparse.ArgumentParser(prog="bot-formatter")
67
+ parser.add_argument("files", nargs="*", help="The files to format.")
68
+ parser.add_argument("--silent", action="store_true", help="Hide all log messages.")
69
+ parser.add_argument(
70
+ "--dry-run", action="store_true", help="Scan files without modifying them."
71
+ )
72
+ parser.add_argument(
73
+ "--lib", default="pycord", choices=["dpy", "pycord"], help="The library to use."
74
+ )
75
+ parser.add_argument("--ezcord", action="store_true", help="Use Ezcord formatters.")
76
+ parser.add_argument("--no-yaml", action="store_true", help="Disable YAML formatters.")
77
+ parser.add_argument("--lang", help="The language directory to check.")
78
+
79
+ self.config = parser.parse_args(args)
80
+ self.report = Output(self.config)
81
+
82
+ if not self.config.files:
83
+ parser.print_help()
84
+ return
85
+
86
+ # Format each file
87
+ for file in self.config.files:
88
+ self.format_file(file)
89
+
90
+ # Check language files
91
+ if self.config.lang:
92
+ self.lang_dir = Path(self.config.lang)
93
+ if not self.lang_dir.is_dir():
94
+ raise ValueError(f"The language directory '{self.lang_dir}' is not a valid directory.")
95
+ self.check_lang_files()
96
+
97
+ # Print report and exit with error code if needed
98
+ self.report.print_output()
99
+ if len(self.report.failed_checks) > 0:
100
+ raise SystemExit(1)
101
+
102
+ def log(self, message: str):
103
+ """Prints a message to the console if the silent mode isn't enabled."""
104
+
105
+ if not self.config.silent:
106
+ print(message)
107
+
108
+ def check_lang_files(self):
109
+ """Ensure consistency across all language files."""
110
+
111
+ lang_files = list(self.lang_dir.glob("*.yaml")) + list(self.lang_dir.glob("*.yml"))
112
+
113
+ # All keys as a dictionary
114
+ lang_keys = {}
115
+ for file_path in lang_files:
116
+ with open(file_path, encoding="utf-8") as f:
117
+ content = yaml.safe_load(f)
118
+ lang_keys[file_path.name] = content
119
+
120
+ # All contents as a string
121
+ lang_contents = {}
122
+ for file_path in lang_files:
123
+ with open(file_path, encoding="utf-8") as f:
124
+ code = f.read()
125
+ lang_contents[file_path.name] = code
126
+
127
+ for formatter in LANG:
128
+ params = get_type_hints(formatter)
129
+
130
+ if "lang_keys" in params and "lang_content" in params:
131
+ formatter(lang_keys, lang_contents, self.report)
132
+ elif "lang_content" in params:
133
+ formatter(lang_contents, self.report)
134
+ elif "lang_keys" in params:
135
+ formatter(lang_keys, self.report)
136
+ else:
137
+ raise ValueError(
138
+ "Formatter must accept either 'lang_keys' or 'lang_contents' parameter."
139
+ )
140
+
141
+
142
+ def format_file(self, filename: str):
143
+ """Runs all enabled formatters on a given file."""
144
+ try:
145
+ with open(filename, encoding="utf-8") as f:
146
+ code = f.read()
147
+ except Exception as e:
148
+ self.report.error(filename, e)
149
+ return
150
+
151
+ formatters = []
152
+ if self.config.lib == "pycord":
153
+ formatters.extend(PYCORD)
154
+ elif self.config.lib == "dpy":
155
+ formatters.extend(DPY)
156
+ if self.config.ezcord:
157
+ formatters.extend(EZCORD)
158
+
159
+ ext = filename.split(".")[-1]
160
+
161
+ # Run Python formatters
162
+ for formatter in formatters:
163
+ if ext != "py":
164
+ continue
165
+
166
+ transformer = formatter(codemod.CodemodContext(filename=filename))
167
+ result = codemod.transform_module(transformer, code)
168
+
169
+ if isinstance(result, codemod.TransformSuccess):
170
+ if result.code != code:
171
+ self.report.success(filename)
172
+
173
+ if not self.config.dry_run:
174
+ with open(filename, "w", encoding="utf-8") as f:
175
+ f.write(result.code)
176
+
177
+ elif isinstance(result, codemod.TransformFailure):
178
+ self.report.error(filename, result.error)
179
+
180
+ # Run YAML formatters
181
+ if self.config.no_yaml or ext not in ["yaml", "yml"]:
182
+ return
183
+
184
+ for lang_formatter in YML:
185
+ new_code = lang_formatter(code)
186
+
187
+ if new_code != code:
188
+ self.report.success(filename)
189
+ if not self.config.dry_run:
190
+ with open(filename, "w", encoding="utf-8") as f:
191
+ f.write(new_code)
@@ -0,0 +1,51 @@
1
+ Metadata-Version: 2.4
2
+ Name: bot-formatter
3
+ Version: 0.1.0
4
+ Summary: A formatter for Discord bots.
5
+ Author: tibue99
6
+ License: MIT
7
+ Project-URL: GitHub, https://github.com/CookieAppTeam/bot-formatter
8
+ Keywords: discord,discord.py,py-cord
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Software Development :: Quality Assurance
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: libcst
23
+ Requires-Dist: PyYAML
24
+ Dynamic: license-file
25
+
26
+ # bot-formatter
27
+ [![](https://img.shields.io/pypi/v/bot-formatter.svg?style=for-the-badge&logo=pypi&color=yellow&logoColor=white)](https://pypi.org/project/bot-formatter/)
28
+ [![](https://img.shields.io/readthedocs/bot-formatter?style=for-the-badge&color=blue&link=https%3A%2F%2Fbot-formatter.readthedocs.io%2F)](https://bot-formatter.readthedocs.io/)
29
+ [![](https://img.shields.io/pypi/l/bot-formatter?style=for-the-badge)](https://pypi.org/project/bot-formatter/)
30
+
31
+ A formatter for Discord bots.
32
+
33
+ ## Installing
34
+ Python 3.10 or higher is required.
35
+ ```
36
+ pip install bot-formatter
37
+ ```
38
+
39
+ ## Usage
40
+ For a full overview, see the [documentation](https://bot-formatter.readthedocs.io/).
41
+ ```
42
+ bot-formatter main.py
43
+ ```
44
+
45
+ ## Pre-Commit
46
+ ```yaml
47
+ - repo: https://github.com/CookieAppTeam/bot-formatter
48
+ rev: 0.1.0
49
+ hooks:
50
+ - id: bot-formatter
51
+ ```
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ requirements.txt
5
+ bot_formatter/__init__.py
6
+ bot_formatter/__main__.py
7
+ bot_formatter/run.py
8
+ bot_formatter.egg-info/PKG-INFO
9
+ bot_formatter.egg-info/SOURCES.txt
10
+ bot_formatter.egg-info/dependency_links.txt
11
+ bot_formatter.egg-info/entry_points.txt
12
+ bot_formatter.egg-info/requires.txt
13
+ bot_formatter.egg-info/top_level.txt
14
+ bot_formatter/formatters/__init__.py
15
+ bot_formatter/formatters/dpy.py
16
+ bot_formatter/formatters/lang.py
17
+ bot_formatter/formatters/pycord.py
18
+ bot_formatter/formatters/yml.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ bot-formatter = bot_formatter:run
@@ -0,0 +1,2 @@
1
+ libcst
2
+ PyYAML
@@ -0,0 +1 @@
1
+ bot_formatter
@@ -0,0 +1,37 @@
1
+ [project]
2
+ name = "bot-formatter"
3
+ description = "A formatter for Discord bots."
4
+ readme = "README.md"
5
+ requires-python = ">=3.10"
6
+ license = {text = "MIT"}
7
+ keywords = ["discord", "discord.py", "py-cord"]
8
+ authors = [
9
+ { name = "tibue99" }
10
+ ]
11
+ classifiers = [
12
+ "Development Status :: 5 - Production/Stable",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Intended Audience :: Developers",
15
+ "Operating System :: OS Independent",
16
+ "Topic :: Software Development :: Quality Assurance",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13",
21
+ "Programming Language :: Python :: 3.14",
22
+ ]
23
+ dynamic = ["version", "dependencies"]
24
+
25
+ [tool.setuptools.dynamic]
26
+ version = {attr = "bot_formatter.__version__"}
27
+ dependencies = {file = "requirements.txt"}
28
+
29
+ [project.urls]
30
+ GitHub = "https://github.com/CookieAppTeam/bot-formatter"
31
+
32
+ [project.scripts]
33
+ bot-formatter = "bot_formatter:run"
34
+
35
+ [build-system]
36
+ requires = ["setuptools>=62.6", "wheel"]
37
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,2 @@
1
+ libcst
2
+ PyYAML
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+