[education/kstars] /: Add a script to generate the indidrivers.xml
Jasem Mutlaq <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 90c0662b2afdec432b33c76672bd539510ed82ab by Jasem Mutlaq.
Committed on 01/08/2026 at 07:42.
Pushed by mutlaqja into branch 'master'.
Add a script to generate the indidrivers.xml
M +130 -106 kstars/data/indidrivers.xml
A +174 -0 tools/generate_indidrivers_xml.py
https://invent.kde.org/education/kstars/-/commit/90c0662b2afdec432b33c76672bd539510ed82ab
diff --git a/kstars/data/indidrivers.xml b/kstars/data/indidrivers.xml
index aaa3e033c2..9c27b15f05 100644
--- a/kstars/data/indidrivers.xml
+++ b/kstars/data/indidrivers.xml
@@ -107,21 +107,33 @@
<driver name="Pegasus NYX-101">indi_lx200_pegasus_nyx101</driver>
<version>2.2</version>
</device>
+ <device label="ZWO AM7 WiFi" manufacturer="ZWO">
+ <driver name="ZWO AM5">indi_lx200am5</driver>
+ <version>1.5</version>
+ </device>
+ <device label="ZWO AM7 USB" manufacturer="ZWO">
+ <driver name="ZWO AM5">indi_lx200am5</driver>
+ <version>1.5</version>
+ </device>
<device label="ZWO AM5 WiFi" manufacturer="ZWO">
<driver name="ZWO AM5">indi_lx200am5</driver>
- <version>1.4</version>
+ <version>1.5</version>
</device>
<device label="ZWO AM5 USB" manufacturer="ZWO">
<driver name="ZWO AM5">indi_lx200am5</driver>
- <version>1.4</version>
+ <version>1.5</version>
</device>
<device label="ZWO AM3 WiFi" manufacturer="ZWO">
<driver name="ZWO AM5">indi_lx200am5</driver>
- <version>1.4</version>
+ <version>1.5</version>
</device>
<device label="ZWO AM3 USB" manufacturer="ZWO">
<driver name="ZWO AM5">indi_lx200am5</driver>
- <version>1.4</version>
+ <version>1.5</version>
+ </device>
+ <device label="TOUPTEK SH20" manufacturer="Touptek">
+ <driver name="TOUPTEK SH20">indi_touptek_sh20</driver>
+ <version>1.0</version>
</device>
<device label="Celestron GPS" manufacturer="Celestron">
<driver name="Celestron GPS">indi_celestron_gps</driver>
@@ -381,12 +393,16 @@
</device>
<device label="Planewave" manufacturer="Planewave Instruments">
<driver name="Planewave">indi_planewave_telescope</driver>
- <version>0.1</version>
+ <version>0.2</version>
</device>
<device label="ESP32go" manufacturer="DIY">
<driver name="LX200 ESP32go">indi_lx200_esp32go</driver>
<version>1.0</version>
</device>
+ <device label="MX-HD Telescope" manufacturer="GOTO INC">
+ <driver name="MX-HD Telescope">indi_mxhd_telescope</driver>
+ <version>1.0</version>
+ </device>
</devGroup>
<devGroup group="Rotators">
<device label="Rigelsys nFrame" manufacturer="Rigel Systems">
@@ -437,12 +453,20 @@
<driver name="Camelot Rotator">indi_camelot_rotator</driver>
<version>1.0</version>
</device>
+ <device label="PlaneWave Rotator" manufacturer="PlaneWave Instruments">
+ <driver name="PlaneWave Rotator">indi_planewave_rotator</driver>
+ <version>1.0</version>
+ </device>
</devGroup>
<devGroup group="Focusers">
<device label="Alpaca Focuser" manufacturer="Alpaca">
<driver name="Alpaca Focuser">indi_alpaca_focuser</driver>
<version>1.0</version>
</device>
+ <device label="Askar WAF" manufacturer="Askar">
+ <driver name="Askar WAF">indi_askar_waf_focus</driver>
+ <version>1.0</version>
+ </device>
<device label="Focuser Simulator" manufacturer="Simulator">
<driver name="Focuser Simulator">indi_simulator_focus</driver>
<version>1.0</version>
@@ -469,7 +493,7 @@
</device>
<device label="PlaneWave EFA" manufacturer="Planewave Instruments">
<driver name="PlaneWave EFA">indi_efa_focus</driver>
- <version>1.1</version>
+ <version>1.4</version>
</device>
<device label="Rainbow Astro RSF" manufacturer="Rainbow Astro">
<driver name="Rainbow Astro RSF">indi_rainbowrsf_focus</driver>
@@ -735,7 +759,7 @@
<devGroup group="Filter Wheels">
<device label="Alpaca FilterWheel" manufacturer="Alpaca">
<driver name="Alpaca FilterWheel">indi_alpaca_filterwheel</driver>
- <version>1.0</version>
+ <version>1.1</version>
</device>
<device label="XAGYL Wheel" manufacturer="XAGYL">
<driver name="XAGYL Wheel">indi_xagyl_wheel</driver>
@@ -785,6 +809,10 @@
<driver name="Wanderer Snowflake Filter Wheel">indi_wanderer_snowflake_wheel</driver>
<version>1.0</version>
</device>
+ <device label="Wanderer FilterCube" manufacturer="Wanderer Astro">
+ <driver name="RASA FilterCube Filter Wheel">indi_rasa_filtercube_wheel</driver>
+ <version>1.0</version>
+ </device>
</devGroup>
<devGroup group="Auxiliary">
<device label="Astrometry" manufacturer="Others">
@@ -811,6 +839,14 @@
<driver name="Pegasus FlatMaster">indi_pegasus_flatmaster</driver>
<version>1.2</version>
</device>
+ <device label="Pegasus FlatMaster Neo" manufacturer="Pegasus Astro">
+ <driver name="Pegasus FlatMaster Neo">indi_pegasus_flatmaster_neo</driver>
+ <version>1.0</version>
+ </device>
+ <device label="Pegasus FlatMaster WiFi" manufacturer="Pegasus Astro">
+ <driver name="Pegasus FlatMaster WiFi">indi_pegasus_flatmaster_wifi</driver>
+ <version>1.0</version>
+ </device>
<device label="PlaneWave Delta-T" manufacturer="Planewave Instruments">
<driver name="PlaneWave Delta-T">indi_planewave_deltat</driver>
<version>1.1</version>
@@ -1177,15 +1213,7 @@
<devGroup group="Focusers">
<device label="Altair AAF" mdpd="true" manufacturer="Altair">
<driver name="Altair">indi_altaircam_focuser</driver>
- <version>2.4</version>
- </device>
- </devGroup>
-</driversList>
-<driversList>
- <devGroup group="Spectrographs">
- <device label="XC Correlator" manufacturer="AHP">
- <driver name="AHP XC Correlator">indi_ahp_xc</driver>
- <version>1.0</version>
+ <version>2.6</version>
</device>
</devGroup>
</driversList>
@@ -1265,22 +1293,6 @@
</device>
</devGroup>
</driversList>
-<driversList>
- <devGroup group="Auxiliary">
- <device label="AstroLink 4">
- <driver name="AstroLink 4">indi_astrolink4</driver>
- <version>0.7</version>
- </device>
- </devGroup>
-</driversList>
-<driversList>
- <devGroup group="Focusers">
- <device label="Astromechanics FOC" manufacturer="Astromechanics">
- <driver name="Astromechanics FOC">indi_astromechfoc</driver>
- <version>0.2</version>
- </device>
- </devGroup>
-</driversList>
<driversList>
<devGroup group="CCDs">
<device label="Atik CCD" mdpd="true" manufacturer="Atik">
@@ -1295,6 +1307,22 @@
</device>
</devGroup>
</driversList>
+<driversList>
+ <devGroup group="Filter Wheels">
+ <device label="Atik EFW" mdpd="true" manufacturer="Atik">
+ <driver name="Atik EFW">indi_atik_efw</driver>
+ <version>1.2</version>
+ </device>
+ </devGroup>
+</driversList>
+<driversList>
+ <devGroup group="Telescopes">
+ <device label="Avalon StarGo" manufacturer="Avalon-Instruments">
+ <driver name="LX200 StarGo">indi_lx200stargo</driver>
+ <version>1.14</version>
+ </device>
+ </devGroup>
+</driversList>
<driversList>
<devGroup group="Telescopes">
<device label="AvalonUD Telescope" manufacturer="Avalon-Instruments">
@@ -1315,14 +1343,6 @@
</device>
</devGroup>
</driversList>
-<driversList>
- <devGroup group="Telescopes">
- <device label="Avalon StarGo" manufacturer="Avalon-Instruments">
- <driver name="LX200 StarGo">indi_lx200stargo</driver>
- <version>1.14</version>
- </device>
- </devGroup>
-</driversList>
<driversList>
<devGroup group="Focusers">
<device label="Bee Focuser" manufacturer="DIY">
@@ -1371,63 +1391,59 @@
<devGroup group="Telescopes">
<device label="Celestron AUX" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
+ </device>
+ <device label="Celestron Wedge" manufacturer="Celestron">
+ <driver name="Celestron AUX">indi_celestron_aux</driver>
+ <version>1.7</version>
</device>
<device label="Celestron WiFi" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron Evolution WiFi" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron Evolution WiFi Wedge" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron CPC Wired" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron CGEM II Wired" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron CGEM II WiFi" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron CGX Wired" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron CGX WiFi" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron Advanced VX Wired" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron Advanced VX WiFi" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron Advanced GT Wired" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.6</version>
+ <version>1.7</version>
</device>
<device label="Celestron Advanced GT WiFi" manufacturer="Celestron">
<driver name="Celestron AUX">indi_celestron_aux</driver>
- <version>1.5</version>
- </device>
- </devGroup>
-</driversList>
-<driversList>
- <devGroup group="Focusers">
- <device label="DreamFocuser" manufacturer="Astromaniak">
- <driver name="DreamFocuser">indi_dreamfocuser_focus</driver>
- <version>2.1</version>
+ <version>1.7</version>
</device>
</devGroup>
</driversList>
@@ -1549,14 +1565,6 @@
</device>
</devGroup>
</driversList>
-<driversList>
- <devGroup group="CCDs">
- <device label="FLI Kepler" manufacturer="Finger Lakes Instruments">
- <driver name="FLI Kepler">indi_kepler_ccd</driver>
- <version>2.0</version>
- </device>
- </devGroup>
-</driversList>
<driversList>
<devGroup group="CCDs">
<device label="FLI CCD" manufacturer="Finger Lakes Instruments">
@@ -1579,9 +1587,9 @@
</driversList>
<driversList>
<devGroup group="CCDs">
- <device label="Generic CCD" mdpd="true">
- <driver name="Generic CCD">indi_generic_ccd</driver>
- <version>1.0</version>
+ <device label="FLI Kepler" manufacturer="Finger Lakes Instruments">
+ <driver name="FLI Kepler">indi_kepler_ccd</driver>
+ <version>2.0</version>
</device>
</devGroup>
</driversList>
@@ -1589,27 +1597,27 @@
<devGroup group="CCDs">
<device label="Canon DSLR" mdpd="true" manufacturer="DSLRs">
<driver name="Canon DSLR">indi_canon_ccd</driver>
- <version>3.4</version>
+ <version>3.5</version>
</device>
<device label="Nikon DSLR" mdpd="true" manufacturer="DSLRs">
<driver name="Nikon DSLR">indi_nikon_ccd</driver>
- <version>3.4</version>
+ <version>3.5</version>
</device>
<device label="Pentax DSLR Legacy" mdpd="true" manufacturer="DSLRs">
<driver name="Pentax DSLR">indi_pentax_ccd</driver>
- <version>3.4</version>
+ <version>3.5</version>
</device>
<device label="Sony DSLR" mdpd="true" manufacturer="DSLRs">
<driver name="Sony DSLR">indi_sony_ccd</driver>
- <version>3.4</version>
+ <version>3.5</version>
</device>
<device label="Fuji DSLR" mdpd="true" manufacturer="DSLRs">
<driver name="Fuji DSLR">indi_fuji_ccd</driver>
- <version>3.4</version>
+ <version>3.5</version>
</device>
<device label="GPhoto CCD" manufacturer="DSLRs">
<driver name="GPhoto CCD">indi_gphoto_ccd</driver>
- <version>3.4</version>
+ <version>3.5</version>
</device>
</devGroup>
</driversList>
@@ -1809,15 +1817,7 @@
<devGroup group="Focusers">
<device label="Nncam AAF" mdpd="true" manufacturer="Nn">
<driver name="Nn">indi_nncam_focuser</driver>
- <version>2.4</version>
- </device>
- </devGroup>
-</driversList>
-<driversList>
- <devGroup group="Weather">
- <device label="NUTMonitor" manufacturer="Others">
- <driver name="NUTMonitor">indi_nut</driver>
- <version>0.1</version>
+ <version>2.6</version>
</device>
</devGroup>
</driversList>
@@ -1961,14 +1961,6 @@
</device>
</devGroup>
</driversList>
-<driversList>
- <devGroup group="Spectrographs">
- <device label="RTL-SDR" mdpd="false">
- <driver name="RTL-SDR">indi_rtlsdr</driver>
- <version>1.0</version>
- </device>
- </devGroup>
-</driversList>
<driversList>
<devGroup group="CCDs">
<device label="SBIG CCD" manufacturer="Diffraction Limited">
@@ -1983,11 +1975,11 @@
</driversList>
<driversList>
<devGroup group="Spectrographs">
- <device label="Shelyak eShel" manufacturer="Shelyak">
+ <device label="Shelyak eShel" manufacturer="Shelyak">
<driver name="Shelyak eShel">indi_shelyakeshel_spectrograph</driver>
<version>1.0</version>
</device>
- <device label="Shelyak SPOX" manufacturer="Shelyak">
+ <device label="Shelyak SPOX" manufacturer="Shelyak">
<driver name="Shelyak SPOX">indi_shelyakspox_spectrograph</driver>
<version>1.0</version>
</device>
@@ -2029,6 +2021,14 @@
</device>
</devGroup>
</driversList>
+<driversList>
+ <devGroup group="CCDs">
+ <device label="SVBONY CCD" mdpd="true" manufacturer="SVBONY">
+ <driver name="SVBONY CCD">indi_svbony_ccd</driver>
+ <version>1.4</version>
+ </device>
+ </devGroup>
+</driversList>
<driversList>
<devGroup group="CCDs">
<device label="SVbonycam" mdpd="true" manufacturer="SVBONY2">
@@ -2049,14 +2049,6 @@
</device>
</devGroup>
</driversList>
-<driversList>
- <devGroup group="CCDs">
- <device label="SVBONY CCD" mdpd="true" manufacturer="SVBONY">
- <driver name="SVBONY CCD">indi_svbony_ccd</driver>
- <version>1.4</version>
- </device>
- </devGroup>
-</driversList>
<driversList>
<devGroup group="CCDs">
<device label="SX CCD" mdpd="true" manufacturer="Starlight XPress">
@@ -2158,10 +2150,42 @@
</devGroup>
</driversList>
<driversList>
+ <devGroup group="Auxiliary">
+ <device label="AstroLink 4">
+ <driver name="AstroLink 4">indi_astrolink4</driver>
+ <version>0.7</version>
+ </device>
+ </devGroup>
<devGroup group="CCDs">
+ <device label="Generic CCD" mdpd="true">
+ <driver name="Generic CCD">indi_generic_ccd</driver>
+ <version>1.0</version>
+ </device>
<device label="Cam90 CCD" mdpd="true">
<driver name="Cam90 CCD">indi_cam90_ccd</driver>
<version>1.0</version>
</device>
</devGroup>
-</driversList>
\ No newline at end of file
+ <devGroup group="Focusers">
+ <device label="DreamFocuser" manufacturer="Astromaniak">
+ <driver name="DreamFocuser">indi_dreamfocuser_focus</driver>
+ <version>2.1</version>
+ </device>
+ </devGroup>
+ <devGroup group="Spectrographs">
+ <device label="XC Correlator" manufacturer="AHP">
+ <driver name="AHP XC Correlator">indi_ahp_xc</driver>
+ <version>1.0</version>
+ </device>
+ <device label="RTL-SDR" mdpd="false">
+ <driver name="RTL-SDR">indi_rtlsdr</driver>
+ <version>1.0</version>
+ </device>
+ </devGroup>
+ <devGroup group="Weather">
+ <device label="NUTMonitor" manufacturer="Others">
+ <driver name="NUTMonitor">indi_nut</driver>
+ <version>0.1</version>
+ </device>
+ </devGroup>
+</driversList>
diff --git a/tools/generate_indidrivers_xml.py b/tools/generate_indidrivers_xml.py
new file mode 100755
index 0000000000..e9959d595e
--- /dev/null
+++ b/tools/generate_indidrivers_xml.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python3
+"""
+Regenerate kstars/data/indidrivers.xml from the locally installed INDI driver
+XML files (mirrors the file selection logic in DriverManager::readXMLDrivers()).
+
+Unlike a plain `cat` of every driver XML file, this script:
+
+ * Pretty-prints every block with a consistent 4-space indent, regardless of
+ how the source file was formatted.
+ * Never silently drops a driver. Entries that already exist in the current
+ indidrivers.xml but are not found on the local machine (e.g. arm64-only
+ drivers such as libcamera, or externally maintained drivers such as
+ Cam90 that aren't installed here) are kept as-is, appended at the end.
+ Pass --no-preserve-missing to disable this and do a hard refresh instead.
+
+Usage:
+ tools/generate_indidrivers_xml.py [--indi-dir DIR] [--output FILE]
+ [--no-preserve-missing] [--dry-run]
+"""
+import argparse
+import re
+import sys
+import xml.etree.ElementTree as ET
+from pathlib import Path
+
+XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8"?>\n'
+INDENT = " "
+
+REPO_ROOT = Path(__file__).resolve().parent.parent
+DEFAULT_OUTPUT = REPO_ROOT / "kstars" / "data" / "indidrivers.xml"
+DEFAULT_INDI_DIRS = ["/usr/share/indi", "/usr/local/share/indi"]
+
+
+def find_indi_dir(explicit):
+ if explicit:
+ p = Path(explicit)
+ if not (p / "drivers.xml").is_file():
+ sys.exit(f"error: {p} does not contain a drivers.xml")
+ return p
+ for candidate in DEFAULT_INDI_DIRS:
+ p = Path(candidate)
+ if (p / "drivers.xml").is_file():
+ return p
+ sys.exit("error: could not find an INDI drivers directory; pass --indi-dir")
+
+
+def device_key(group, device_el):
+ driver_el = device_el.find("driver")
+ driver_name = driver_el.text.strip() if driver_el is not None and driver_el.text else ""
+ return (group, device_el.get("label", ""), driver_name)
+
+
+def load_devgroups(xml_path):
+ """Parse a <driversList> XML file and return its <devGroup> children."""
+ root = ET.parse(xml_path).getroot()
+ if root.tag != "driversList":
+ raise ValueError(f"unexpected root <{root.tag}>, expected <driversList>")
+ return list(root.findall("devGroup"))
+
+
+def collect_local_driver_files(indi_dir):
+ """Same file selection as DriverManager::readXMLDrivers(): drivers.xml plus
+ every indi_*.xml file, skipping the *_sk.xml skeleton files."""
+ primary = indi_dir / "drivers.xml"
+ others = sorted(
+ p for p in indi_dir.glob("indi_*.xml") if not p.name.endswith("_sk.xml")
+ )
+ return [primary] + others
+
+
+def load_existing_devices(existing_path):
+ """Return {key: (group, device_element)} for every device currently in
+ indidrivers.xml, in encounter order. The file is a concatenation of
+ multiple <driversList> roots (that's the format DriverManager streams
+ in), so it isn't well-formed standalone XML and needs wrapping first."""
+ if not existing_path.is_file():
+ return {}
+ raw = existing_path.read_text(encoding="utf-8")
+ raw = re.sub(r"<\?xml[^>]*\?>", "", raw)
+ wrapped = ET.fromstring(f"<root>{raw}</root>")
+ devices = {}
+ for drivers_list in wrapped.findall("driversList"):
+ for devgroup in drivers_list.findall("devGroup"):
+ group = devgroup.get("group")
+ for device in devgroup.findall("device"):
+ devices.setdefault(device_key(group, device), (group, device))
+ return devices
+
+
+def build_blocks(local_files, existing_devices, preserve_missing):
+ """Returns (blocks, num_local_devices, num_preserved) where blocks is a
+ list of lists of <devGroup> elements, one list per output <driversList>."""
+ blocks = []
+ found = set()
+ for path in local_files:
+ if not path.is_file():
+ continue
+ try:
+ devgroups = load_devgroups(path)
+ except (ET.ParseError, ValueError) as exc:
+ print(f"warning: skipping {path}: {exc}", file=sys.stderr)
+ continue
+ for dg in devgroups:
+ group = dg.get("group")
+ for device in dg.findall("device"):
+ found.add(device_key(group, device))
+ blocks.append(devgroups)
+
+ preserved = 0
+ if preserve_missing:
+ leftover_by_group = {}
+ for key, (group, device) in existing_devices.items():
+ if key in found:
+ continue
+ leftover_by_group.setdefault(group, []).append(device)
+ preserved += 1
+ if leftover_by_group:
+ leftover_devgroups = []
+ for group in sorted(leftover_by_group):
+ dg = ET.Element("devGroup", {"group": group})
+ for device in leftover_by_group[group]:
+ dg.append(device)
+ leftover_devgroups.append(dg)
+ blocks.append(leftover_devgroups)
+
+ return blocks, len(found), preserved
+
+
+def serialize(blocks):
+ parts = []
+ for devgroups in blocks:
+ root = ET.Element("driversList")
+ root.extend(devgroups)
+ ET.indent(root, space=INDENT)
+ parts.append(ET.tostring(root, encoding="unicode"))
+ return XML_DECLARATION + "\n".join(parts) + "\n"
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument("--indi-dir", help="Directory containing the local INDI driver XML files "
+ f"(default: first of {DEFAULT_INDI_DIRS} that exists)")
+ parser.add_argument("--output", default=str(DEFAULT_OUTPUT), help="Output path (default: %(default)s)")
+ parser.add_argument("--no-preserve-missing", action="store_true",
+ help="Do not keep drivers that are in the current output file but not "
+ "found locally (hard refresh instead of a safe merge)")
+ parser.add_argument("--dry-run", action="store_true", help="Print a summary without writing the file")
+ args = parser.parse_args()
+
+ indi_dir = find_indi_dir(args.indi_dir)
+ output_path = Path(args.output)
+
+ local_files = collect_local_driver_files(indi_dir)
+ existing_devices = load_existing_devices(output_path)
+ blocks, num_local, num_preserved = build_blocks(
+ local_files, existing_devices, preserve_missing=not args.no_preserve_missing
+ )
+
+ print(f"INDI driver dir : {indi_dir}")
+ print(f"Source files : {len(local_files)}")
+ print(f"Local devices : {num_local}")
+ print(f"Preserved : {num_preserved} (in current file, not found locally)")
+ print(f"Total devices : {num_local + num_preserved}")
+
+ if args.dry_run:
+ print("(dry run, nothing written)")
+ return
+
+ output_path.write_text(serialize(blocks), encoding="utf-8")
+ print(f"Wrote {output_path}")
+
+
+if __name__ == "__main__":
+ main()