diff --git a/selfdrive/debug/print_docs_diff.py b/selfdrive/debug/print_docs_diff.py
index 5cf3867b2d..c02649e3fa 100755
--- a/selfdrive/debug/print_docs_diff.py
+++ b/selfdrive/debug/print_docs_diff.py
@@ -1,13 +1,16 @@
#!/usr/bin/env python3
import argparse
+from collections import defaultdict
+import difflib
import pickle
from selfdrive.car.docs import get_all_car_info
from selfdrive.car.docs_definitions import Column
+FOOTNOTE_TAG = "{}"
STAR_ICON = '
'
COLUMNS = "|" + "|".join([column.value for column in Column]) + "|"
-COLUMN_HEADER = "|---|---|---|:---:|:---:|:---:|:---:|:---:|"
+COLUMN_HEADER = "|---|---|---|:---:|:---:|:---:|:---:|"
ARROW_SYMBOL = "➡️"
@@ -16,69 +19,84 @@ def load_base_car_info(path):
return pickle.load(f)
-def get_star_diff(base_car, new_car):
- return [column for column, value in base_car.row.items() if value != new_car.row[column]]
-
+def match_cars(base_cars, new_cars):
+ """Matches CarInfo by name similarity and finds additions and removals"""
+ changes = []
+ additions = []
+ for new in new_cars:
+ closest_match = difflib.get_close_matches(new.name, [b.name for b in base_cars], cutoff=0.)[0]
-def format_row(builder):
- return "|" + "|".join(builder) + "|"
+ if closest_match not in [c[1].name for c in changes]:
+ changes.append((new, next(car for car in base_cars if car.name == closest_match)))
+ else:
+ additions.append(new)
+ removals = [b for b in base_cars if b.name not in [c[1].name for c in changes]]
+ return changes, additions, removals
-def print_car_info_diff(path):
- base_car_info = {f"{i.make} {i.model}": i for i in load_base_car_info(path)}
- new_car_info = {f"{i.make} {i.model}": i for i in get_all_car_info()}
- tier_changes = []
- star_changes = []
- removals = []
- additions = []
+def build_column_diff(base_car, new_car):
+ row_builder = []
+ for column in Column:
+ base_column = base_car.get_column(column, STAR_ICON, FOOTNOTE_TAG)
+ new_column = new_car.get_column(column, STAR_ICON, FOOTNOTE_TAG)
- # Changes (tier + stars)
- for base_car_model, base_car in base_car_info.items():
- if base_car_model not in new_car_info:
- continue
+ if base_column != new_column:
+ row_builder.append(f"{base_column} {ARROW_SYMBOL} {new_column}")
+ else:
+ row_builder.append(new_column)
- new_car = new_car_info[base_car_model]
+ return format_row(row_builder)
- # Tier changes
- if base_car.tier != new_car.tier:
- tier_changes.append(f"- Tier for {base_car.make} {base_car.model} changed! ({base_car.tier.name.title()} {ARROW_SYMBOL} {new_car.tier.name.title()})")
- # Star changes
- diff = get_star_diff(base_car, new_car)
- if not len(diff):
- continue
-
- row_builder = []
- for column in list(Column):
- if column not in diff:
- row_builder.append(new_car.get_column(column, STAR_ICON, "{}"))
- else:
- row_builder.append(base_car.get_column(column, STAR_ICON, "{}") + ARROW_SYMBOL + new_car.get_column(column, STAR_ICON, "{}"))
-
- star_changes.append(format_row(row_builder))
+def format_row(builder):
+ return "|" + "|".join(builder) + "|"
- # Removals
- for model in set(base_car_info) - set(new_car_info):
- car_info = base_car_info[model]
- removals.append(format_row([car_info.get_column(column, STAR_ICON, "{}") for column in Column]))
- # Additions
- for model in set(new_car_info) - set(base_car_info):
- car_info = new_car_info[model]
- additions.append(format_row([car_info.get_column(column, STAR_ICON, "{}") for column in Column]))
+def print_car_info_diff(path):
+ base_car_info = defaultdict(list)
+ new_car_info = defaultdict(list)
+
+ for car in load_base_car_info(path):
+ base_car_info[car.car_fingerprint].append(car)
+ for car in get_all_car_info():
+ new_car_info[car.car_fingerprint].append(car)
+
+ changes = defaultdict(list)
+ for base_car_model, base_cars in base_car_info.items():
+ # Match car info changes, and get additions and removals
+ new_cars = new_car_info[base_car_model]
+ car_changes, car_additions, car_removals = match_cars(base_cars, new_cars)
+
+ # Removals
+ for car_info in car_removals:
+ changes["removals"].append(format_row([car_info.get_column(column, STAR_ICON, FOOTNOTE_TAG) for column in Column]))
+
+ # Additions
+ for car_info in car_additions:
+ changes["additions"].append(format_row([car_info.get_column(column, STAR_ICON, FOOTNOTE_TAG) for column in Column]))
+
+ for new_car, base_car in car_changes:
+ # Tier changes
+ if base_car.tier != new_car.tier:
+ changes["tier"].append(f"- Tier for {base_car.make} {base_car.model} changed! ({base_car.tier.name.title()} {ARROW_SYMBOL} {new_car.tier.name.title()})")
+
+ # Column changes
+ row_diff = build_column_diff(base_car, new_car)
+ if ARROW_SYMBOL in row_diff:
+ changes["column"].append(row_diff)
# Print diff
- if len(star_changes) or len(tier_changes) or len(removals) or len(additions):
+ if any(len(c) for c in changes.values()):
markdown_builder = ["### ⚠️ This PR makes changes to [CARS.md](../blob/master/docs/CARS.md) ⚠️"]
- for title, category in (("## 🏅 Tier Changes", tier_changes), ("## 🔀 Star Changes", star_changes), ("## ❌ Removed", removals), ("## ➕ Added", additions)):
- if len(category):
+ for title, category in (("## 🏅 Tier Changes", "tier"), ("## 🔀 Column Changes", "column"), ("## ❌ Removed", "removals"), ("## ➕ Added", "additions")):
+ if len(changes[category]):
markdown_builder.append(title)
if "Tier" not in title:
markdown_builder.append(COLUMNS)
markdown_builder.append(COLUMN_HEADER)
- markdown_builder.extend(category)
+ markdown_builder.extend(changes[category])
print("\n".join(markdown_builder))