#!/usr/bin/env python3 import importlib import os import re from pathlib import Path generator_path = os.path.dirname(os.path.realpath(__file__)) include_pattern = re.compile(r'CM_ "IMPORT (.*?)";\n') def _read_dbc(src_dir: str, filename: str, extra_files: dict[str, str] | None = None) -> str: if extra_files and filename in extra_files: return extra_files[filename] with open(os.path.join(src_dir, filename), encoding='utf-8') as file_in: return file_in.read() def _create_dbc_content(src_dir: str, filename: str, extra_files: dict[str, str] | None = None) -> str: dbc_file_in = _read_dbc(src_dir, filename, extra_files) includes = include_pattern.findall(dbc_file_in) parts = ['CM_ "AUTOGENERATED FILE, DO NOT EDIT";\n'] for include_filename in includes: parts.append(f'\n\nCM_ "Imported file {include_filename} starts here";\n') parts.append(_read_dbc(src_dir, include_filename, extra_files)) parts.append(f'\nCM_ "{filename} starts here";\n') core_dbc = include_pattern.sub('', dbc_file_in) parts.append(core_dbc) return ''.join(parts) def _collect_script_outputs() -> dict[str, dict[str, str]]: """Import and call generate() from each sub-generator script. Returns {dir_name: {filename: content}}.""" outputs: dict[str, dict[str, str]] = {} for py_file in sorted(Path(generator_path).rglob("*.py")): if py_file.name.startswith("test_") or py_file.name == "generator.py": continue dir_name = py_file.parent.name module_name = f"opendbc.dbc.generator.{dir_name}.{py_file.stem}" mod = importlib.import_module(module_name) if hasattr(mod, 'generate'): outputs.setdefault(dir_name, {}).update(mod.generate()) return outputs def generate_all() -> dict[str, str]: """Generate all DBC content in memory. Returns {name: content} where name has no .dbc extension.""" script_outputs = _collect_script_outputs() result = {} for src_dir, _, filenames in os.walk(generator_path): if src_dir == generator_path: continue dir_name = os.path.basename(src_dir) extra = script_outputs.get(dir_name, {}) # all non-_ .dbc files: on-disk templates + script-generated all_dbc_files = {f for f in filenames if f.endswith('.dbc') and not f.startswith('_')} all_dbc_files |= {f for f in extra if not f.startswith('_')} for filename in sorted(all_dbc_files): output_name = filename.replace('.dbc', '_generated') content = _create_dbc_content(src_dir, filename, extra) result[output_name] = content return result def create_all(output_path: str): """Generate all DBC files and write them to output_path (for backward compatibility).""" import glob generated_suffix = '_generated.dbc' # clear out old generated DBCs for f in glob.glob(os.path.join(output_path, f"*{generated_suffix}")): os.remove(f) for name, content in generate_all().items(): with open(os.path.join(output_path, name + '.dbc'), 'w', encoding='utf-8') as f: f.write(content) if __name__ == "__main__": opendbc_root = os.path.join(generator_path, '../') create_all(opendbc_root)