Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: formatter #10

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@

**/target

.idea
.idea
__pycache__
Empty file added libs/formatter/README.md
Empty file.
Empty file.
50 changes: 50 additions & 0 deletions libs/formatter/converter/format.py
Original file line number Diff line number Diff line change
@@ -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"])
2 changes: 2 additions & 0 deletions libs/formatter/converter/format2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def convert_ast_to_string(ast, config):
return ""
Empty file.
42 changes: 42 additions & 0 deletions libs/formatter/formatter/lib.py
Original file line number Diff line number Diff line change
@@ -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
31 changes: 31 additions & 0 deletions libs/formatter/formatter/main.py
Original file line number Diff line number Diff line change
@@ -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)
30 changes: 30 additions & 0 deletions libs/formatter/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions libs/formatter/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[tool.poetry]
name = "formatter"
version = "0.1.0"
description = ""
authors = ["0xtekgrinder <[email protected]>"]
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"
Empty file.
39 changes: 39 additions & 0 deletions libs/formatter/rules/config.py
Original file line number Diff line number Diff line change
@@ -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






Empty file.
Loading