-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit e7495db
Showing
46 changed files
with
1,956 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
* text=auto eol=lf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# These are supported funding model platforms | ||
|
||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] | ||
patreon: # Replace with a single Patreon username | ||
open_collective: # Replace with a single Open Collective username | ||
ko_fi: HolonProduction | ||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel | ||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry | ||
liberapay: # Replace with a single Liberapay username | ||
issuehunt: # Replace with a single IssueHunt username | ||
otechie: # Replace with a single Otechie username | ||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry | ||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
.venv/ | ||
.vscode/ | ||
.pytest_cache/ | ||
dist/ | ||
|
||
__pycache__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
Copyright (c) 2022-2023 HolonProduction | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
# <img src="./plugins/godot/icon.svg" style="vertical-align: top; height: 1.3em" /> CodexGD - *Give your code a dress code*. | ||
|
||
[![Godot 4.x](https://img.shields.io/static/v1?label=Godot&message=4.x&color=grey&logo=godotengine&logoColor=white&labelColor=478cbf)](https://godotengine.org) | ||
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) | ||
[![Linting: pylint](https://img.shields.io/badge/linting-pylint-yellowgreen.svg)](https://github.com/PyCQA/pylint) | ||
|
||
> :construction: CodexGD is currently in development. If you are looking for a way to enforce the official style guide you should have a look at [gdtoolkit](https://github.com/Scony/godot-gdscript-toolkit). I am in fact using gdtoolkit internally myself. It has its own linter that does a very good job when it comes to the official style. The main reason for me to develop this package is that I have my own GDScript style and extending gdlint is not very simple. | ||
CodexGD is a configurable and extendable Godot style analyzer written in python. CodexGD comes with a set of rules that you can configure to your liking but it also gives you an easy way to write own rules using python. | ||
|
||
## :electric_plug: Installation | ||
In the future CodexGD will be available via PyPi and the Godot Asset Lib. Until then you will have to set it up manually. Start by cloning this repo. | ||
To use the backend you will need a python 3.11 installation. Install the cloned repo as python module by calling the following command in the cloned repo's root folder. | ||
```shell | ||
> python -m pip install -e . | ||
``` | ||
It is recommended to install CodexGD into a [virtual environment](https://docs.python.org/3/tutorial/venv.html). When doing this the codexgd command will not be available globaly. You will also have to configure your venv when using the editor plugin (more on this later). | ||
|
||
To install the editor plugin copy the `plugins/godot/addons/codexgd` folder into your project's `addons` folder. You should also create a `codex.yml` file at the root of your project before enabeling it. | ||
|
||
## :computer: General Usage | ||
CodexGD is built around a `codex.yml` file which you will use to configure your style. The codex file will define the rules that will be used on your code. Here is a simple example: | ||
```yml | ||
extends: "official" # Use the official style as base. | ||
|
||
rules: | ||
no-invalid-chars: "warn" # Downgrade the severity of the no-invalid-chars rule. | ||
``` | ||
The easiest way to use CodexGD is to use the provided CLI. | ||
```shell | ||
> python -m codexgd project_directory | ||
``` | ||
CodexGD will look for a `codex.yml` file in the provided directory and apply it to all `.gd` files in the directory. | ||
|
||
You can also use the package from your own python scripts. The API is centered around `Codex` objects which you can create by providing a `codex.yml` file. | ||
|
||
```python | ||
from codexgd.gdscript import GDScriptCodex | ||
|
||
with open("codex.yml", "r") as file: | ||
codex = GDScriptCodex(file) | ||
|
||
with open("file.gd", "r") as file: | ||
for problem in codex.check(file): | ||
print(problem) | ||
``` | ||
|
||
## :jigsaw: Editor Plugin | ||
> :construction: The editor plugin is in development and not yet feature complete. | ||
CodexGD comes with an editor integration. It will display the problems that CodexGD detected in the script editor. It relies on the CLI, therefore an installation of the python package is needed. If the python package was installed globaly the plugin should work out of the box. Otherwise you will have to select the path to the `codexgd` command in the EditorSettings `"codexgd/general/command_path"`. The file will be located in your venv folder in the `scripts` subfolder. It should be named `codexgd` with an executable file ending. | ||
|
||
State of the editor integration: | ||
- [x] Display problems in the script editor. | ||
- [ ] Support for embedded scripts. | ||
- [ ] Handle errors during the execution. (The plugin may fail silently.) | ||
- [ ] Display overlapping problems in a good way. | ||
- [ ] Help with the setup of a `codex.yml` file. | ||
- [ ] Provide a simple way to see all problems of the project. | ||
|
||
## :wrench: Configuration | ||
|
||
### :scroll: The codex file | ||
The codex file is a yaml file that may contain the following keys. | ||
|
||
**`extends`** | ||
Which style to use as base. Currently only `"official"` and `"recommended"` are supported values. You may overwrite the rule settings with the `rules` key. | ||
|
||
**`rules`** | ||
A list of rules with associated options. | ||
```yaml | ||
rules: | ||
no-invalid-chars: | ||
level: "warn" # The severity of the rule. Allowed values: off, warn, error | ||
options: | ||
codec: "utf8" | ||
# If no options are specified the severity may be specified directly. | ||
require-extends: "error" | ||
``` | ||
There are multiple ways to specify which rule is meant. You can use the rule name as above but this is only possible if the rule was loaded before. You can use the name `no-invalid-chars` because the `official` base file loaded it. To load a rule you specify the python module name. You can use a global module specification like `codexgd.rules.no_invalid_chars`. You may also use relative module specifications. In this case codexgd will try to resolve them as relative path from your codex file. In this way you can load custom rules which are located in your project directory. | ||
```yaml | ||
rules: | ||
.rules.custom_rule: "error" | ||
``` | ||
The code above will load a rule from `res://rules/custom_rule.py` given your codex file is located at `res://codex.yml`. | ||
> :warning: CodexGD will not load rules from outside the codexgd package by default because an untrusted project may execute code on your machine in this way. Pass the `--load-unsafe-code` option to the CLI to confirm that you know about the risks. | ||
|
||
**`variables`** | ||
A dictionary of named values which may be used in the options of rules later. This allows to unify values which are needed by multiple rules like the prefix of private methods. The name of a variable should start with `<` and end with `>`. | ||
```yaml | ||
variables: | ||
<private-prefix>: "_" | ||
rules: | ||
function-names: | ||
level: "warn" | ||
options: | ||
private-prefix: <private-prefix> | ||
``` | ||
|
||
### :heavy_check_mark: Rules | ||
The rules contain documentation comments explaining them. Currently implemented rules can be found at `"codexgd/rules"`. | ||
|
||
### :see_no_evil: Ignore comments | ||
CodexGD alows you to disable certain rules in gdscript files by using special comments. | ||
Ignore comments are not meant to be placed on the same line with gdscript code. | ||
|
||
```python | ||
# Ignore only the next line. | ||
# codexgd-ignore | ||
# Disable rules until they are enabled again. | ||
# codexgd-disable | ||
# Enable rules. | ||
# codexgd-enable | ||
``` | ||
All ignore comments may optionally recieve a list of rule names. If no rule names are provided all rules will be affected. | ||
```python | ||
# codexgd-disable: function-names, no-invalid-chars | ||
# codexgd-enable: no-invalid-chars | ||
``` | ||
|
||
Problems are ignored based on the line on which they beginn. Therefore problems which span the complete file can only be ignored by a `codexgd-disable` statement on the first line. | ||
|
||
It is considered good practice to state the reason for disabeling the rule behind the ignore comment in a rational and calm manner... | ||
```python | ||
# codexgd-ignore: no-invalid-chars Why would CodexGD not allow me to use utf8 inside of strings?! What is the developer even thinking! | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__version__ = "0.1.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from .problem import Problem | ||
from .exceptions import CodexGDError | ||
from .codex import Codex |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
"""CodexGD | ||
A configurable and extendable Godot style analyzer. | ||
CodexGD can also be used in your own python scripts | ||
with an API that is more powerfull than this CLI. | ||
Usage: | ||
codexgd <dir> [options] | ||
codexgd <codex> <gdscript>... [options] | ||
Options: | ||
-h --help Show this screen. | ||
-v --version Show the current version. | ||
--encoding=codec The encoding of the files. [default: "utf-8"] | ||
--load-unsafe-code Load untrusted code from outside the CodexGD package if the codex file says so. | ||
--json Output json data. | ||
-- | ||
Returns: | ||
0 The input conforms to the given codex file. | ||
1 The input does not conform to the give codex. | ||
2 During execution an exception occured. | ||
""" | ||
|
||
import os.path | ||
import sys | ||
import glob | ||
import json | ||
from docopt import docopt | ||
from codexgd import CodexGDError | ||
from codexgd.gdscript import GDScriptCodex | ||
from codexgd.__about__ import __version__ | ||
|
||
|
||
def main(): | ||
arguments = docopt(__doc__, version=__version__) | ||
|
||
if arguments["<dir>"]: | ||
try: | ||
with open( | ||
os.path.join(arguments["<dir>"], "codex.yml"), | ||
"r", | ||
encoding=arguments["--encoding"], | ||
) as file: | ||
codex = GDScriptCodex(file, arguments["--load-unsafe-code"]) | ||
except (FileExistsError, CodexGDError) as error: | ||
if not arguments["--json"]: | ||
print(error) | ||
sys.exit(2) | ||
elif arguments["<codex>"]: | ||
try: | ||
with open( | ||
arguments["<codex>"], "r", encoding=arguments["--encoding"] | ||
) as file: | ||
codex = GDScriptCodex(file, arguments["--load-unsafe-code"]) | ||
except (FileExistsError, CodexGDError) as error: | ||
if not arguments["--json"]: | ||
print(error) | ||
sys.exit(2) | ||
else: | ||
if not arguments["--json"]: | ||
print("No configuration was provided.") | ||
sys.exit(2) | ||
|
||
file_paths = [] | ||
file_paths += arguments["<gdscript>"] | ||
|
||
if arguments["<dir>"]: | ||
file_paths += glob.glob( | ||
os.path.join(arguments["<dir>"], "**", "*.gd"), recursive=True | ||
) | ||
|
||
if arguments["--json"]: | ||
print("[", end="") | ||
|
||
found_problems = 0 | ||
for file_path in file_paths: | ||
with open(file_path, "r", encoding=arguments["--encoding"]) as file: | ||
for problem in codex.check(file): | ||
if arguments["--json"]: | ||
if found_problems > 0: | ||
print(", ", end="") | ||
print( | ||
json.dumps( | ||
{ | ||
"severity": problem.rule.severity, | ||
"info": problem.info, | ||
"file": file_path, | ||
"start": problem.start, | ||
"end": problem.end, | ||
"rule": problem.rule.name, | ||
} | ||
), | ||
end="", | ||
) | ||
else: | ||
print( | ||
problem.rule.severity.upper(), | ||
": ", | ||
problem.info, | ||
"\t", | ||
os.path.realpath(file_path), | ||
" Ln ", | ||
problem.start[0], | ||
":", | ||
problem.start[1], | ||
" (", | ||
problem.rule.name, | ||
")", | ||
sep="", | ||
) | ||
found_problems += 1 | ||
|
||
if not arguments["--json"]: | ||
print( | ||
"\n" if found_problems > 0 else "", | ||
"CodexGD found ", | ||
str(found_problems), | ||
" problem", | ||
"s" if found_problems != 1 else "", | ||
" in ", | ||
str(len(file_paths)), | ||
" file", | ||
"s" if len(file_paths) > 1 else "", | ||
".", | ||
" Go fix " + ("them" if found_problems > 1 else "it") + "!" | ||
if found_problems > 0 | ||
else " \U0001f389", | ||
sep="", | ||
) | ||
else: | ||
print("]", end="") | ||
|
||
sys.exit(int(found_problems > 0)) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
from typing import TypeVar, TypeVarTuple, Generic, Self, Type | ||
|
||
Values = TypeVarTuple("Values") | ||
|
||
|
||
class Callback(Generic[*Values]): | ||
"""A callback allows the rules to hook into some processing step of the codex.""" | ||
|
||
|
||
Identifier = TypeVar("Identifier") | ||
|
||
|
||
class DynamicCallback(Callback[*Values], Generic[*Values, Identifier]): | ||
""" | ||
A dynamic callback allows to pass an identifier. | ||
This allows the codex to notify only certain group of listeners. | ||
""" | ||
|
||
_identifier: Identifier | ||
|
||
def __init__(self, identifier: Identifier) -> None: | ||
self._identifier = identifier | ||
|
||
def __eq__(self, other: "DynamicCallback") -> bool: | ||
return self._identifier == other._identifier | ||
|
||
def __hash__(self) -> int: | ||
return hash((self.__class__, self._identifier)) | ||
|
||
@classmethod | ||
def factory(cls: Type[Self], identifier: Identifier) -> Self: | ||
return cls(identifier) |
Oops, something went wrong.