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

andrewi/293 cangen refactor #315

Merged
merged 12 commits into from
Nov 12, 2024
1 change: 0 additions & 1 deletion docs/includes/gen_glossary.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

glossary: list[dict] = []
with open(HERE / file_glossary_input, "r") as f:

for idx, line in enumerate(f.readlines()):
match = re.match(term_pattern, line)
if match:
Expand Down
73 changes: 42 additions & 31 deletions scripts/cangen/cangen/can_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import re
import shutil
import time
from typing import Dict, List, Tuple
from typing import Dict, List, Tuple, Callable, Any

import numpy as np
from cantools.database import Database, Message, Signal
Expand All @@ -25,8 +25,9 @@
MSG_REGISTRY_FILE_NAME = "_msg_registry.h"
AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved
CAN_MESSAGES_FILE_NAME = "_can_messages.h"

CAN_MESSAGES_TEMPLATE_FILENAME = "can_messages.h.jinja2"
MSG_REGISTRY_TEMPLATE_FILENAME = "msg_registry.h.jinja2"
TEMPLATE_FILE_NAMES = ["can_messages.h.jinja2", "msg_registry.h.jinja2"]

PACKAGE_NAME = "cangen"
AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved


def _parse_dbc_files(dbc_file: str) -> Database:
Expand Down Expand Up @@ -171,29 +172,33 @@ def _camel_to_snake(text):
return re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1).lower()


def _generate_from_jinja2_template(
template_path: str, output_path: str, context_dict: dict
def _create_jninja_environment(
package_name: str, filters: dict[str, Callable[..., Any]]
):
# Create the environment with trim_blocks and lstrip_blocks settings
env = Environment(
loader=PackageLoader("cangen"), trim_blocks=True, lstrip_blocks=True
loader=PackageLoader(package_name), trim_blocks=True, lstrip_blocks=True
)
for filter_name, filter_function in filters.items():
env.filters[filter_name] = filter_function

# Register the camel_to_snake filter
env.filters["camel_to_snake"] = _camel_to_snake
env.filters["decimal_to_hex"] = hex
return env

# Load and render template
template = env.get_template(template_path)
rendered_code = template.render(**context_dict)

with open(output_path, "w") as output_file:
output_file.write(rendered_code)
def _create_output_file_name(output_dir: str, bus_name: str, template_file_name: str):
return os.path.join(output_dir, bus_name.lower() + "_" + template_file_name[:-7])
AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved

logger.info(f"Rendered code written to '{os.path.abspath(output_path)}'")

def _generate_jninja_template(template_file_name, output_path, context, env):
template = env.get_template(template_file_name)
rendered_code = template.render(**context)

output_file_name = output_path
with open(output_file_name, "w") as output_file:
output_file.write(rendered_code)
logger.info(f"Rendered code written to '{os.path.abspath(output_file_name)}'")

def generate_code(bus: Bus, config: Config):

def _generate_code(bus: Bus, config: Config):
"""
Parses DBC files, extracts information, and generates code using Jinja2
templates.
Expand All @@ -220,39 +225,45 @@ def generate_code(bus: Bus, config: Config):
"bus_name": bus.bus_name,
}

logger.debug("Generating code for can messages")
_generate_from_jinja2_template(
CAN_MESSAGES_TEMPLATE_FILENAME,
os.path.join(config.output_dir, bus.bus_name.lower() + CAN_MESSAGES_FILE_NAME),
context,
)
logger.debug("Generating code for can messages and msg registry.")

logger.debug("Generating code for msg registry")
_generate_from_jinja2_template(
MSG_REGISTRY_TEMPLATE_FILENAME,
os.path.join(config.output_dir, bus.bus_name.lower() + MSG_REGISTRY_FILE_NAME),
context,
env = _create_jninja_environment(
AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved
PACKAGE_NAME, {"camel_to_snake": _camel_to_snake, "decimal_to_hex": hex}
)
for template_file_name in TEMPLATE_FILE_NAMES:
_generate_jninja_template(
AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved
template_file_name,
_create_output_file_name(
config.output_dir, bus.bus_name, template_file_name
),
context,
env,
)

logger.info("Code generation complete")


def _prepare_output_directory(output_dir):
def _prepare_output_directory(output_dir: str):
"""Deletes previously generated files and creates a gitignore for the directory"""
if os.path.exists(output_dir):
logger.info("Deleting previously generated code")
shutil.rmtree(output_dir)

logger.info("Creating generated/can folder")
os.makedirs(output_dir, exist_ok=True)

gitignore_path = os.path.join(output_dir, ".gitignore")
with open(gitignore_path, "w") as f:
f.write("*")


def generate_can_from_dbc(project_folder_name: str):
def generate_can_for_project(project_folder_name: str):
"""Generates C code for a given project.
Ensure the project folder contains a config.yaml file which specifies the relative path to its corresponding dbc file."""
os.chdir(project_folder_name)
config = Config.from_yaml("config.yaml")

_prepare_output_directory(config.output_dir)

AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved
for bus in config.busses:
generate_code(bus, config)
_generate_code(bus, config)
36 changes: 25 additions & 11 deletions scripts/cangen/cangen/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import argparse
import logging

from .can_generator import generate_can_from_dbc
from .can_generator import generate_can_for_project

logging.basicConfig(level="INFO", format="%(levelname)-8s| (%(name)s) %(message)s")
logger = logging.getLogger(__name__)
Expand All @@ -16,27 +16,41 @@ def parse():
parser = argparse.ArgumentParser(description="DBC to C code generator")
parser.add_argument("project", type=str, help="Name of the project")
parser.add_argument(
AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved
"--log-level",
"-v",
"--verbose",
dest="is_verbose",
action="store_true",
help="Enable verbose output",
)
parser.add_argument(
"--cmake-log-level",
AndrewI26 marked this conversation as resolved.
Show resolved Hide resolved
dest="level",
choices=["STATUS", "INFO", "VERBOSE", "DEBUG"],
default="INFO",
help="Log verbosity threshold",
)

# If parsing fails (ex incorrect or no arguments provided) then this exits with
# code 2.
return parser.parse_args()


def main():
args = parse()
# If parsing fails (ex incorrect or no arguments provided) then this code will be 2
args = parser.parse_args()

cmake_to_python_level = {
"STATUS": "INFO",
"INFO": "INFO",
"VERBOSE": "DEBUG",
"DEBUG": "DEBUG",
}
logger.setLevel(cmake_to_python_level[args.level])

generate_can_from_dbc(args.project)
args.level = (
cmake_to_python_level["VERBOSE"]
if args.is_verbose
else cmake_to_python_level[args.level]
)
return args


def main():
args = parse()

logger.setLevel(args.level)

generate_can_for_project(args.project)
Loading