From 22c12d20f22a224b9a6371c86e8d7672d0a0873b Mon Sep 17 00:00:00 2001 From: 0xtekgrinder <0xtekgrinder@protonmail.com> Date: Sun, 22 Sep 2024 16:24:26 +0200 Subject: [PATCH] feat(formatter): initial setup and config handling for formatter --- .gitignore | 3 +- libs/formatter/README.md | 0 libs/formatter/converter/__init__.py | 0 libs/formatter/converter/format.py | 50 ++++++++++++++++++++++++++++ libs/formatter/converter/format2.py | 2 ++ libs/formatter/formatter/__init__.py | 0 libs/formatter/formatter/lib.py | 42 +++++++++++++++++++++++ libs/formatter/formatter/main.py | 31 +++++++++++++++++ libs/formatter/poetry.lock | 30 +++++++++++++++++ libs/formatter/pyproject.toml | 19 +++++++++++ libs/formatter/rules/__init__.py | 0 libs/formatter/rules/config.py | 39 ++++++++++++++++++++++ libs/formatter/tests/__init__.py | 0 13 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 libs/formatter/README.md create mode 100644 libs/formatter/converter/__init__.py create mode 100644 libs/formatter/converter/format.py create mode 100644 libs/formatter/converter/format2.py create mode 100644 libs/formatter/formatter/__init__.py create mode 100644 libs/formatter/formatter/lib.py create mode 100644 libs/formatter/formatter/main.py create mode 100644 libs/formatter/poetry.lock create mode 100644 libs/formatter/pyproject.toml create mode 100644 libs/formatter/rules/__init__.py create mode 100644 libs/formatter/rules/config.py create mode 100644 libs/formatter/tests/__init__.py diff --git a/.gitignore b/.gitignore index 74413a3..5afb447 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ **/target -.idea \ No newline at end of file +.idea +__pycache__ \ No newline at end of file diff --git a/libs/formatter/README.md b/libs/formatter/README.md new file mode 100644 index 0000000..e69de29 diff --git a/libs/formatter/converter/__init__.py b/libs/formatter/converter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/formatter/converter/format.py b/libs/formatter/converter/format.py new file mode 100644 index 0000000..d007f82 --- /dev/null +++ b/libs/formatter/converter/format.py @@ -0,0 +1,50 @@ +def node_to_string(node): + ast_type = node["ast_type"] + if ast_type == "Module": + return node_to_string(node["body"]) + elif ast_type == "FunctionDef": + return f"def {node['name']}({node_to_string(node['args'])}):" + elif ast_type == "arguments": + return ", ".join([node_to_string(arg) for arg in node["args"]]) + elif ast_type == "arg": + return node["arg"] + elif ast_type == "Expr": + return node_to_string(node["value"]) + elif ast_type == "Call": + return f"{node_to_string(node['func'])}({node_to_string(node['args'])})" + elif ast_type == "Name": + return node["id"] + elif ast_type == "Constant": + return str(node["value"]) + elif ast_type == "BinOp": + return f"{node_to_string(node['left'])} {node['op']} {node_to_string(node['right'])}" + elif ast_type == "Return": + return f"return {node_to_string(node['value'])}" + elif ast_type == "Assign": + return f"{node_to_string(node['targets'])} = {node_to_string(node['value'])}" + elif ast_type == "List": + return f"[{node_to_string(node['elts'])}]" + elif ast_type == "Subscript": + return f"{node_to_string(node['value'])}[{node_to_string(node['slice'])}]" + elif ast_type == "Index": + return node_to_string(node["value"]) + elif ast_type == "Slice": + return f"{node_to_string(node['lower'])}:{node_to_string(node['upper'])}" + elif ast_type == "Attribute": + return f"{node_to_string(node['value'])}.{node['attr']}" + elif ast_type == "If": + return f"if {node_to_string(node['test'])}:" + elif ast_type == "Compare": + return f"{node_to_string(node['left'])} {node_to_string(node['ops'])} {node_to_string(node['comparators'])}" + elif ast_type == "Eq": + return "==" + elif ast_type == "Gt": + return ">" + elif ast_type == "Lt": + return "<" + elif ast_type == "GtE": + return ">=" + +def convert_ast_to_string(ast, config): + ## Handle the config and ast tabs + return node_to_string(ast["ast"]) \ No newline at end of file diff --git a/libs/formatter/converter/format2.py b/libs/formatter/converter/format2.py new file mode 100644 index 0000000..bf8a787 --- /dev/null +++ b/libs/formatter/converter/format2.py @@ -0,0 +1,2 @@ +def convert_ast_to_string(ast, config): + return "" \ No newline at end of file diff --git a/libs/formatter/formatter/__init__.py b/libs/formatter/formatter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/formatter/formatter/lib.py b/libs/formatter/formatter/lib.py new file mode 100644 index 0000000..6f1f768 --- /dev/null +++ b/libs/formatter/formatter/lib.py @@ -0,0 +1,42 @@ +from ast_extractor.ast import filepath_vyper_to_ast +from converter.format2 import convert_ast_to_string +from difflib import unified_diff +import os + +def diff_files(original, formatted): + original_lines = original.splitlines() + formatted_lines = formatted.splitlines() + + diff = unified_diff(original_lines, formatted_lines, lineterm='') + return '\n'.join(list(diff)) + +def format_file(file_path, config, check, raw): + ast = filepath_vyper_to_ast(file_path) + if ast is not None: + formatted_code = convert_ast_to_string(ast, config) + if check: + with open(file_path) as reader: + if raw: + return formatted_code + else: + original_code = reader.read() + diff = diff_files(original_code, formatted_code) + if diff != "": + return diff + else: + with open(file_path, "w") as writer: + writer.write(formatted_code) + +def format_root(root, config, check, raw): + output = [] + if os.path.isdir(root): + for root, _, files in os.walk(root): + for file in files: + if file.endswith(".vy"): + file_path = os.path.join(root, file) + output_file = format_file(file_path, config, check, raw) + if output_file is not None: + output.append({"filename": file_path, "output": output_file}) + else: + raise ValueError("Root path is not a directory") + return output \ No newline at end of file diff --git a/libs/formatter/formatter/main.py b/libs/formatter/formatter/main.py new file mode 100644 index 0000000..29fe20a --- /dev/null +++ b/libs/formatter/formatter/main.py @@ -0,0 +1,31 @@ +import argparse +import os + +from rules.config import init_config +from .lib import format_root + +def start() -> None: + parser = argparse.ArgumentParser(description='Vyper Formatter') + parser.add_argument('--root', type=str, help="The project's root path to format\n\nBy default root of the current working directory", default=".") + parser.add_argument('--check', action='store_true', help="Run in 'check' mode.\n\nExits with 0 if input is formatted correctly. Exits with 1 if formatting is required", default=False) + parser.add_argument('--raw', action='store_true', help="In 'check' and stdin modes, outputs raw formatted code instead of the diff", default=False) + parser.add_argument('--config', type=str, help="Path to the configuration file\n\nBy default search for a `vyfmt.toml` inside the defined root", default=None) + args = parser.parse_args() + + # Read root directory and check if it exists + if not os.path.exists(args.root): + print(f"Error: Root path '{args.root}' does not exist") + exit(1) + + config = init_config(args.root, args.config) + output = format_root(args.root, config, args.check, args.raw) + + if args.check: + if len(output) != 0: + for file in output: + print(file["filename"]) + print(file["output"]) + print() + exit(1) + else: + exit(0) \ No newline at end of file diff --git a/libs/formatter/poetry.lock b/libs/formatter/poetry.lock new file mode 100644 index 0000000..6592fd8 --- /dev/null +++ b/libs/formatter/poetry.lock @@ -0,0 +1,30 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "ast-extractor" +version = "0.1.0" +description = "" +optional = false +python-versions = "^3.10" +files = [] +develop = false + +[package.source] +type = "directory" +url = "../ast-extractor" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "b3b233517d521ccc69d2203b6a42c8da2f85a3653884de1f52921ba961904567" diff --git a/libs/formatter/pyproject.toml b/libs/formatter/pyproject.toml new file mode 100644 index 0000000..ec4b081 --- /dev/null +++ b/libs/formatter/pyproject.toml @@ -0,0 +1,19 @@ +[tool.poetry] +name = "formatter" +version = "0.1.0" +description = "" +authors = ["0xtekgrinder <0xtekgrinder@protonmail.com>"] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +ast-extractor = {path = "../../libs/ast-extractor"} +tomli = "^2.0.1" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +start = "formatter.main:start" \ No newline at end of file diff --git a/libs/formatter/rules/__init__.py b/libs/formatter/rules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/formatter/rules/config.py b/libs/formatter/rules/config.py new file mode 100644 index 0000000..129a5ae --- /dev/null +++ b/libs/formatter/rules/config.py @@ -0,0 +1,39 @@ +import os +import tomli + +rules_types = [ + { "field": "indentation", "type": int, "default": 4 } +] + +def init_config(root, config_file): + if config_file is None: + config_file = os.path.join(root, 'vyfmt.toml') + + config = {} + modified_fields = [] + if os.path.exists(config_file): + with open(config_file) as reader: + content = reader.read() + parsed_file = tomli.loads(content) + + for rule in rules_types: + field = rule["field"] + if field in parsed_file: + if type(parsed_file[field]) != rule["type"]: + raise TypeError(f"Field '{field}' in configuration file is not of type '{rule['type']}'") + else: + config[field] = parsed_file[field] + modified_fields.append(field) + + for rule in rules_types: + field = rule["field"] + if field not in modified_fields: + config[field] = rule["default"] + + return config + + + + + + diff --git a/libs/formatter/tests/__init__.py b/libs/formatter/tests/__init__.py new file mode 100644 index 0000000..e69de29