#!/usr/bin/env python3 import argparse from collections import defaultdict import jinja2 import os from enum import Enum from natsort import natsorted from typing import Dict, List from cereal import car from common.basedir import BASEDIR from selfdrive.car import gen_empty_fingerprint from selfdrive.car.docs_definitions import CarInfo, Column, CommonFootnote from selfdrive.car.car_helpers import interfaces, get_interface_attr def get_all_footnotes() -> Dict[Enum, int]: all_footnotes = list(CommonFootnote) for footnotes in get_interface_attr("Footnote", ignore_none=True).values(): all_footnotes.extend(footnotes) return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)} CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md") CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md") def get_all_car_info() -> List[CarInfo]: all_car_info: List[CarInfo] = [] footnotes = get_all_footnotes() for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items(): CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), car_fw=[car.CarParams.CarFw(ecu="unknown")]) if CP.dashcamOnly or car_info is None: continue # A platform can include multiple car models if not isinstance(car_info, list): car_info = (car_info,) for _car_info in car_info: if not hasattr(_car_info, "row"): _car_info.init_make(CP) _car_info.init(CP, footnotes) all_car_info.append(_car_info) # Sort cars by make and model + year sorted_cars: List[CarInfo] = natsorted(all_car_info, key=lambda car: car.name.lower()) return sorted_cars def group_by_make(all_car_info: List[CarInfo]) -> Dict[str, List[CarInfo]]: sorted_car_info = defaultdict(list) for car_info in all_car_info: sorted_car_info[car_info.make].append(car_info) return dict(sorted_car_info) def generate_cars_md(all_car_info: List[CarInfo], template_fn: str) -> str: with open(template_fn, "r") as f: template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True) footnotes = [fn.value.text for fn in get_all_footnotes()] cars_md: str = template.render(all_car_info=all_car_info, group_by_make=group_by_make, footnotes=footnotes, Column=Column) return cars_md if __name__ == "__main__": parser = argparse.ArgumentParser(description="Auto generates supported cars documentation", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("--template", default=CARS_MD_TEMPLATE, help="Override default template filename") parser.add_argument("--out", default=CARS_MD_OUT, help="Override default generated filename") args = parser.parse_args() with open(args.out, 'w') as f: f.write(generate_cars_md(get_all_car_info(), args.template)) print(f"Generated and written to {args.out}")