docs.py 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. #!/usr/bin/env python3
  2. import argparse
  3. from collections import defaultdict
  4. import jinja2
  5. import os
  6. from enum import Enum
  7. from natsort import natsorted
  8. from cereal import car
  9. from openpilot.common.basedir import BASEDIR
  10. from openpilot.selfdrive.car import gen_empty_fingerprint
  11. from openpilot.selfdrive.car.docs_definitions import CarInfo, Column, CommonFootnote, PartType
  12. from openpilot.selfdrive.car.car_helpers import interfaces, get_interface_attr
  13. from openpilot.selfdrive.car.values import PLATFORMS
  14. def get_all_footnotes() -> dict[Enum, int]:
  15. all_footnotes = list(CommonFootnote)
  16. for footnotes in get_interface_attr("Footnote", ignore_none=True).values():
  17. all_footnotes.extend(footnotes)
  18. return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)}
  19. CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md")
  20. CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md")
  21. def get_all_car_info() -> list[CarInfo]:
  22. all_car_info: list[CarInfo] = []
  23. footnotes = get_all_footnotes()
  24. for model, platform in PLATFORMS.items():
  25. car_info = platform.config.car_info
  26. # If available, uses experimental longitudinal limits for the docs
  27. CP = interfaces[model][0].get_params(platform, fingerprint=gen_empty_fingerprint(),
  28. car_fw=[car.CarParams.CarFw(ecu="unknown")], experimental_long=True, docs=True)
  29. if CP.dashcamOnly or car_info is None:
  30. continue
  31. # A platform can include multiple car models
  32. if not isinstance(car_info, list):
  33. car_info = [car_info,]
  34. for _car_info in car_info:
  35. if not hasattr(_car_info, "row"):
  36. _car_info.init_make(CP)
  37. _car_info.init(CP, footnotes)
  38. all_car_info.append(_car_info)
  39. # Sort cars by make and model + year
  40. sorted_cars: list[CarInfo] = natsorted(all_car_info, key=lambda car: car.name.lower())
  41. return sorted_cars
  42. def group_by_make(all_car_info: list[CarInfo]) -> dict[str, list[CarInfo]]:
  43. sorted_car_info = defaultdict(list)
  44. for car_info in all_car_info:
  45. sorted_car_info[car_info.make].append(car_info)
  46. return dict(sorted_car_info)
  47. def generate_cars_md(all_car_info: list[CarInfo], template_fn: str) -> str:
  48. with open(template_fn) as f:
  49. template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True)
  50. footnotes = [fn.value.text for fn in get_all_footnotes()]
  51. cars_md: str = template.render(all_car_info=all_car_info, PartType=PartType,
  52. group_by_make=group_by_make, footnotes=footnotes,
  53. Column=Column)
  54. return cars_md
  55. if __name__ == "__main__":
  56. parser = argparse.ArgumentParser(description="Auto generates supported cars documentation",
  57. formatter_class=argparse.ArgumentDefaultsHelpFormatter)
  58. parser.add_argument("--template", default=CARS_MD_TEMPLATE, help="Override default template filename")
  59. parser.add_argument("--out", default=CARS_MD_OUT, help="Override default generated filename")
  60. args = parser.parse_args()
  61. with open(args.out, 'w') as f:
  62. f.write(generate_cars_md(get_all_car_info(), args.template))
  63. print(f"Generated and written to {args.out}")