commit 5b22cacc1312d1f4650a0088535f954cd456af35 Author: xiao Date: Sat Mar 21 22:09:36 2026 +0800 first commit diff --git a/MultiMicphoneLocat.ioc b/MultiMicphoneLocat.ioc new file mode 100644 index 0000000..19dd5a4 --- /dev/null +++ b/MultiMicphoneLocat.ioc @@ -0,0 +1,222 @@ +#MicroXplorer Configuration settings - do not modify +CAD.formats=[] +CAD.pinconfig=Dual +CAD.provider=Component Search Engine +DFSDM2.Filter_regChannel_FIL0=DFSDM_CHANNEL_0 +DFSDM2.Filter_regChannel_FIL1=DFSDM_CHANNEL_1 +DFSDM2.Filter_regChannel_FIL2=DFSDM_CHANNEL_2 +DFSDM2.IPParameters=Filter_regChannel_FIL0,Filter_regChannel_FIL1,Filter_regChannel_FIL2,Selection +DFSDM2.Selection=DFSDM_CHANNEL_OUTPUT_CLOCK_SYSTEM +Dma.DFSDM2_FLT0.0.Direction=DMA_PERIPH_TO_MEMORY +Dma.DFSDM2_FLT0.0.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.DFSDM2_FLT0.0.Instance=DMA2_Stream0 +Dma.DFSDM2_FLT0.0.MemDataAlignment=DMA_MDATAALIGN_WORD +Dma.DFSDM2_FLT0.0.MemInc=DMA_MINC_ENABLE +Dma.DFSDM2_FLT0.0.Mode=DMA_NORMAL +Dma.DFSDM2_FLT0.0.PeriphDataAlignment=DMA_PDATAALIGN_WORD +Dma.DFSDM2_FLT0.0.PeriphInc=DMA_PINC_DISABLE +Dma.DFSDM2_FLT0.0.Priority=DMA_PRIORITY_LOW +Dma.DFSDM2_FLT0.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode +Dma.DFSDM2_FLT1.1.Direction=DMA_PERIPH_TO_MEMORY +Dma.DFSDM2_FLT1.1.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.DFSDM2_FLT1.1.Instance=DMA2_Stream1 +Dma.DFSDM2_FLT1.1.MemDataAlignment=DMA_MDATAALIGN_WORD +Dma.DFSDM2_FLT1.1.MemInc=DMA_MINC_ENABLE +Dma.DFSDM2_FLT1.1.Mode=DMA_NORMAL +Dma.DFSDM2_FLT1.1.PeriphDataAlignment=DMA_PDATAALIGN_WORD +Dma.DFSDM2_FLT1.1.PeriphInc=DMA_PINC_DISABLE +Dma.DFSDM2_FLT1.1.Priority=DMA_PRIORITY_LOW +Dma.DFSDM2_FLT1.1.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode +Dma.DFSDM2_FLT2.2.Direction=DMA_PERIPH_TO_MEMORY +Dma.DFSDM2_FLT2.2.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.DFSDM2_FLT2.2.Instance=DMA2_Stream2 +Dma.DFSDM2_FLT2.2.MemDataAlignment=DMA_MDATAALIGN_WORD +Dma.DFSDM2_FLT2.2.MemInc=DMA_MINC_ENABLE +Dma.DFSDM2_FLT2.2.Mode=DMA_NORMAL +Dma.DFSDM2_FLT2.2.PeriphDataAlignment=DMA_PDATAALIGN_WORD +Dma.DFSDM2_FLT2.2.PeriphInc=DMA_PINC_DISABLE +Dma.DFSDM2_FLT2.2.Priority=DMA_PRIORITY_LOW +Dma.DFSDM2_FLT2.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode +Dma.Request0=DFSDM2_FLT0 +Dma.Request1=DFSDM2_FLT1 +Dma.Request2=DFSDM2_FLT2 +Dma.RequestsNb=3 +File.Version=6 +GPIO.groupedBy=Group By Peripherals +KeepUserPlacement=false +Mcu.CPN=STM32F413RGT6 +Mcu.Family=STM32F4 +Mcu.IP0=DFSDM2 +Mcu.IP1=DMA +Mcu.IP2=NVIC +Mcu.IP3=RCC +Mcu.IP4=SPI1 +Mcu.IP5=SYS +Mcu.IP6=USART1 +Mcu.IPNb=7 +Mcu.Name=STM32F413R(G-H)Tx +Mcu.Package=LQFP64 +Mcu.Pin0=PH0 - OSC_IN +Mcu.Pin1=PH1 - OSC_OUT +Mcu.Pin10=PB3 +Mcu.Pin11=PB4 +Mcu.Pin12=PB5 +Mcu.Pin13=VP_SYS_VS_Systick +Mcu.Pin2=PA7 +Mcu.Pin3=PC5 +Mcu.Pin4=PB10 +Mcu.Pin5=PC9 +Mcu.Pin6=PA9 +Mcu.Pin7=PA10 +Mcu.Pin8=PA13 +Mcu.Pin9=PA14 +Mcu.PinsNb=14 +Mcu.ThirdPartyNb=0 +Mcu.UserConstants= +Mcu.UserName=STM32F413RGTx +MxCube.Version=6.16.1 +MxDb.Version=DB.6.0.161 +NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.DMA2_Stream0_IRQn=true\:0\:0\:false\:false\:true\:false\:true\:true +NVIC.DMA2_Stream1_IRQn=true\:0\:0\:false\:false\:true\:false\:true\:true +NVIC.DMA2_Stream2_IRQn=true\:0\:0\:false\:false\:true\:false\:true\:true +NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.ForceEnableDMAVector=true +NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC.SPI1_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true +NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false +NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +PA10.Mode=Asynchronous +PA10.Signal=USART1_RX +PA13.Mode=Serial_Wire +PA13.Signal=SYS_JTMS-SWDIO +PA14.Mode=Serial_Wire +PA14.Signal=SYS_JTCK-SWCLK +PA7.Signal=S_DATAIN1DFSDM2 +PA9.Mode=Asynchronous +PA9.Signal=USART1_TX +PB10.Signal=S_CKOUTDFSDM2 +PB3.Locked=true +PB3.Mode=Full_Duplex_Slave +PB3.Signal=SPI1_SCK +PB4.Locked=true +PB4.Mode=Full_Duplex_Slave +PB4.Signal=SPI1_MISO +PB5.Mode=Full_Duplex_Slave +PB5.Signal=SPI1_MOSI +PC5.Signal=S_DATAIN2DFSDM2 +PC9.Signal=S_DATAIN3DFSDM2 +PCC.Checker=false +PCC.Line=STM32F413/423 +PCC.MCU=STM32F413R(G-H)Tx +PCC.PartNumber=STM32F413RGTx +PCC.Series=STM32F4 +PCC.Temperature=25 +PCC.Vdd=1.7 +PH0\ -\ OSC_IN.Mode=HSE-External-Oscillator +PH0\ -\ OSC_IN.Signal=RCC_OSC_IN +PH1\ -\ OSC_OUT.Mode=HSE-External-Oscillator +PH1\ -\ OSC_OUT.Signal=RCC_OSC_OUT +PinOutPanel.RotationAngle=0 +ProjectManager.AskForMigrate=true +ProjectManager.BackupPrevious=false +ProjectManager.CompilerLinker=GCC +ProjectManager.CompilerOptimize=6 +ProjectManager.ComputerToolchain=false +ProjectManager.CoupleFile=false +ProjectManager.CustomerFirmwarePackage= +ProjectManager.DefaultFWLocation=true +ProjectManager.DeletePrevious=true +ProjectManager.DeviceId=STM32F413RGTx +ProjectManager.FirmwarePackage=STM32Cube FW_F4 V1.28.3 +ProjectManager.FreePins=false +ProjectManager.FreePinsContext= +ProjectManager.HalAssertFull=false +ProjectManager.HeapSize=0x200 +ProjectManager.KeepUserCode=true +ProjectManager.LastFirmware=true +ProjectManager.LibraryCopy=0 +ProjectManager.MainLocation=Core/Src +ProjectManager.NoMain=false +ProjectManager.PreviousToolchain= +ProjectManager.ProjectBuild=false +ProjectManager.ProjectFileName=MultiMicphoneLocat.ioc +ProjectManager.ProjectName=MultiMicphoneLocat +ProjectManager.ProjectStructure= +ProjectManager.RegisterCallBack= +ProjectManager.StackSize=0x400 +ProjectManager.TargetToolchain=MDK-ARM V5.32 +ProjectManager.ToolChainLocation= +ProjectManager.UAScriptAfterPath= +ProjectManager.UAScriptBeforePath= +ProjectManager.UnderRoot=false +ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_DMA_Init-DMA-false-HAL-true,4-MX_DFSDM2_Init-DFSDM2-false-HAL-true,5-MX_SPI1_Init-SPI1-false-HAL-true,6-MX_USART1_UART_Init-USART1-false-HAL-true +RCC.AHBFreq_Value=100000000 +RCC.APB1CLKDivider=RCC_HCLK_DIV2 +RCC.APB1Freq_Value=50000000 +RCC.APB1TimFreq_Value=100000000 +RCC.APB2Freq_Value=100000000 +RCC.APB2TimFreq_Value=100000000 +RCC.CortexFreq_Value=100000000 +RCC.DFSDM2AudioFreq_Value=48000000 +RCC.DFSDM2Freq_Value=100000000 +RCC.DFSDMAudioFreq_Value=48000000 +RCC.DFSDMFreq_Value=100000000 +RCC.FCLKCortexFreq_Value=100000000 +RCC.FMPI2C1Freq_Value=50000000 +RCC.FamilyName=M +RCC.HCLKFreq_Value=100000000 +RCC.HSE_VALUE=8000000 +RCC.I2S1Freq_Value=48000000 +RCC.I2S2Freq_Value=48000000 +RCC.IPParameters=AHBFreq_Value,APB1CLKDivider,APB1Freq_Value,APB1TimFreq_Value,APB2Freq_Value,APB2TimFreq_Value,CortexFreq_Value,DFSDM2AudioFreq_Value,DFSDM2Freq_Value,DFSDMAudioFreq_Value,DFSDMFreq_Value,FCLKCortexFreq_Value,FMPI2C1Freq_Value,FamilyName,HCLKFreq_Value,HSE_VALUE,I2S1Freq_Value,I2S2Freq_Value,LPTimerFreq_Value,MCO2PinFreq_Value,PLLCLKFreq_Value,PLLI2SQCLKFreq_Value,PLLI2SRCLKFreq_Value,PLLM,PLLN,PLLP,PLLQCLKFreq_Value,PLLQoutputFreq_Value,PLLRCLKFreq_Value,PLLRoutputFreq_Value,PLLSourceVirtual,PWRFreq_Value,RNGFreq_Value,SAI1AFreq_Value,SAI1BFreq_Value,SDIOFreq_Value,SYSCLKFreq_VALUE,SYSCLKSource,USBFreq_Value,VCOI2SInputFreq_Value,VCOI2SOutputFreq_Value,VCOInputFreq_Value,VCOOutputFreq_Value +RCC.LPTimerFreq_Value=50000000 +RCC.MCO2PinFreq_Value=100000000 +RCC.PLLCLKFreq_Value=100000000 +RCC.PLLI2SQCLKFreq_Value=48000000 +RCC.PLLI2SRCLKFreq_Value=48000000 +RCC.PLLM=8 +RCC.PLLN=400 +RCC.PLLP=RCC_PLLP_DIV4 +RCC.PLLQCLKFreq_Value=200000000 +RCC.PLLQoutputFreq_Value=200000000 +RCC.PLLRCLKFreq_Value=200000000 +RCC.PLLRoutputFreq_Value=200000000 +RCC.PLLSourceVirtual=RCC_PLLSOURCE_HSE +RCC.PWRFreq_Value=100000000 +RCC.RNGFreq_Value=200000000 +RCC.SAI1AFreq_Value=8000000 +RCC.SAI1BFreq_Value=8000000 +RCC.SDIOFreq_Value=200000000 +RCC.SYSCLKFreq_VALUE=100000000 +RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK +RCC.USBFreq_Value=200000000 +RCC.VCOI2SInputFreq_Value=500000 +RCC.VCOI2SOutputFreq_Value=96000000 +RCC.VCOInputFreq_Value=1000000 +RCC.VCOOutputFreq_Value=400000000 +SH.S_CKOUTDFSDM2.0=DFSDM2_CKOUT,PDM_SPI_Input_from_ch01_and_Internal_Clock +SH.S_CKOUTDFSDM2.1=DFSDM2_CKOUT,CKOUT +SH.S_CKOUTDFSDM2.2=DFSDM2_CKOUT,PDM_SPI_Input_from_ch12_and_Internal_Clock +SH.S_CKOUTDFSDM2.3=DFSDM2_CKOUT,PDM_SPI_Input_from_ch23_and_Internal_Clock +SH.S_CKOUTDFSDM2.ConfNb=4 +SH.S_DATAIN1DFSDM2.0=DFSDM2_DATIN1,PDM_SPI_Input_from_ch01_and_Internal_Clock +SH.S_DATAIN1DFSDM2.ConfNb=1 +SH.S_DATAIN2DFSDM2.0=DFSDM2_DATIN2,PDM_SPI_Input_from_ch12_and_Internal_Clock +SH.S_DATAIN2DFSDM2.ConfNb=1 +SH.S_DATAIN3DFSDM2.0=DFSDM2_DATIN3,PDM_SPI_Input_from_ch23_and_Internal_Clock +SH.S_DATAIN3DFSDM2.ConfNb=1 +SPI1.Direction=SPI_DIRECTION_2LINES +SPI1.IPParameters=VirtualType,Mode,Direction +SPI1.Mode=SPI_MODE_SLAVE +SPI1.VirtualType=VM_SLAVE +USART1.IPParameters=VirtualMode +USART1.VirtualMode=VM_ASYNC +VP_SYS_VS_Systick.Mode=SysTick +VP_SYS_VS_Systick.Signal=SYS_VS_Systick +board=custom diff --git a/stm32-cubemx-ioc-reader/SKILL.md b/stm32-cubemx-ioc-reader/SKILL.md new file mode 100644 index 0000000..a81b56b --- /dev/null +++ b/stm32-cubemx-ioc-reader/SKILL.md @@ -0,0 +1,57 @@ +# STM32CubeMX IOC Reader + +## Use this skill when + +- The user asks to read, analyze, summarize, or validate an STM32CubeMX `.ioc` file. +- The user wants pin mapping, clock config, peripheral setup, middleware settings, or project metadata from `.ioc`. +- The user wants a machine-readable export (JSON) of `.ioc` content. + +## Do not use this skill when + +- The request is about generated C code (`main.c`, `stm32xx_hal_msp.c`) and not `.ioc` itself. +- The file is not an STM32CubeMX `.ioc` file. + +## What this skill does + +- Parses `.ioc` key-value entries. +- Groups configuration by domain: MCU, pins, RCC/clock, peripherals, middleware, and project manager. +- Produces readable summaries for fast review. +- Optionally exports structured JSON for tooling. + +## Workflow + +1. Confirm the target `.ioc` path. +2. Run parser script: + +```bash +python scripts/parse_ioc.py --ioc +``` + +3. For JSON output: + +```bash +python scripts/parse_ioc.py --ioc --json +``` + +4. For saved JSON file: + +```bash +python scripts/parse_ioc.py --ioc --json --out parsed_ioc.json +``` + +## Response style guidelines + +- Start with a concise project overview (MCU, board, toolchain, project name). +- Then list enabled peripherals and notable pin assignments. +- Then call out clock source and important RCC settings. +- Mention anomalies (empty values, duplicate keys, unknown domains). + +## Notes about `.ioc` format + +- `.ioc` is an INI-like key-value format (not strict XML). +- Typical key prefixes: + - `Mcu.` for target MCU/package/family + - `PAx`/`PBx`... for pin assignment and signal labels + - `RCC.` for clocks + - `.` (for example `USART1.`, `I2C1.`, `TIM3.`) for peripheral settings + - `ProjectManager.` for generated project metadata diff --git a/stm32-cubemx-ioc-reader/scripts/parse_ioc.py b/stm32-cubemx-ioc-reader/scripts/parse_ioc.py new file mode 100644 index 0000000..68cb3cb --- /dev/null +++ b/stm32-cubemx-ioc-reader/scripts/parse_ioc.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +"""Parse and summarize STM32CubeMX .ioc files.""" + +from __future__ import annotations + +import argparse +import json +from collections import defaultdict +from pathlib import Path +from typing import TypedDict + + +StrMap = dict[str, str] + + +class ClassifiedIOC(TypedDict): + domains: dict[str, StrMap] + peripherals: dict[str, StrMap] + other: StrMap + + +def parse_ioc(ioc_path: Path) -> tuple[StrMap, list[str]]: + entries: StrMap = {} + anomalies: list[str] = [] + + lines = ioc_path.read_text(encoding="utf-8", errors="replace").splitlines() + for index, raw in enumerate(lines, start=1): + line = raw.strip() + if not line or line.startswith("#"): + continue + + if "=" not in line: + anomalies.append(f"line {index}: no '=' separator") + continue + + key, value = line.split("=", 1) + key = key.strip() + value = value.strip() + + if not key: + anomalies.append(f"line {index}: empty key") + continue + + if key in entries: + anomalies.append(f"line {index}: duplicate key '{key}' (last value kept)") + + entries[key] = value + + return entries, anomalies + + +def classify(entries: StrMap) -> ClassifiedIOC: + domains: dict[str, StrMap] = { + "mcu": {}, + "rcc": {}, + "project_manager": {}, + "pins": {}, + "middleware": {}, + } + peripherals: dict[str, StrMap] = defaultdict(dict) + other: StrMap = {} + + middleware_roots = { + "freertos", + "fatfs", + "lwip", + "usb", + "touchgfx", + "azure_rtos", + "threadx", + "netxduo", + "filex", + "ux_device", + "ux_host", + } + + for key, value in entries.items(): + if key.startswith("Mcu."): + domains["mcu"][key] = value + continue + if key.startswith("RCC."): + domains["rcc"][key] = value + continue + if key.startswith("ProjectManager."): + domains["project_manager"][key] = value + continue + + # Pin records usually look like PA0.Mode, PA0.Signal, PB6.GPIO_Label... + if len(key) >= 3 and key[0] == "P" and key[1].isalpha(): + pin = key.split(".", 1)[0] + if len(pin) >= 3 and pin[2].isdigit(): + domains["pins"][key] = value + continue + + root = key.split(".", 1)[0] + root_l = root.lower() + if root_l in middleware_roots or root_l.startswith("cmsis"): + domains["middleware"][key] = value + continue + + # Heuristic: uppercase root token often indicates a peripheral (USART1, I2C1, TIM3...) + if root and root.upper() == root and any(ch.isdigit() for ch in root): + peripherals[root][key] = value + continue + + other[key] = value + + return { + "domains": domains, + "peripherals": dict(sorted(peripherals.items())), + "other": other, + } + + +def summarize(classified: ClassifiedIOC, ioc_path: Path, anomalies: list[str]) -> str: + domains = classified["domains"] + peripherals = classified["peripherals"] + other = classified["other"] + + mcu = domains["mcu"] + project_manager = domains["project_manager"] + rcc = domains["rcc"] + pins = domains["pins"] + middleware = domains["middleware"] + + lines: list[str] = [] + lines.append(f"IOC file: {ioc_path}") + lines.append("") + + lines.append("== Project Overview ==") + lines.append(f"- MCU keys: {len(mcu)}") + lines.append(f"- ProjectManager keys: {len(project_manager)}") + lines.append(f"- RCC keys: {len(rcc)}") + + top_overview = [ + "Mcu.Name", + "Mcu.Package", + "Mcu.Family", + "Mcu.UserName", + "ProjectManager.ProjectName", + "ProjectManager.ToolChain", + "ProjectManager.TargetToolchain", + ] + for key in top_overview: + if key in mcu: + lines.append(f"- {key}: {mcu[key]}") + elif key in project_manager: + lines.append(f"- {key}: {project_manager[key]}") + + lines.append("") + lines.append("== Peripherals ==") + lines.append(f"- Count: {len(peripherals)}") + if peripherals: + lines.append("- Enabled/peripheral roots: " + ", ".join(peripherals.keys())) + + lines.append("") + lines.append("== Pins ==") + lines.append(f"- Pin-related keys: {len(pins)}") + + # Extract a compact map of pin -> signal + pin_signals: dict[str, str] = {} + for key, value in pins.items(): + if key.endswith(".Signal"): + pin = key.split(".", 1)[0] + pin_signals[pin] = value + if pin_signals: + preview = sorted(pin_signals.items())[:20] + for pin, signal in preview: + lines.append(f"- {pin}: {signal}") + if len(pin_signals) > len(preview): + lines.append(f"- ... ({len(pin_signals) - len(preview)} more)") + + lines.append("") + lines.append("== Middleware ==") + lines.append(f"- Middleware keys: {len(middleware)}") + + lines.append("") + lines.append("== Diagnostics ==") + lines.append(f"- Other keys: {len(other)}") + lines.append(f"- Anomalies: {len(anomalies)}") + if anomalies: + for item in anomalies[:20]: + lines.append(f"- {item}") + if len(anomalies) > 20: + lines.append(f"- ... ({len(anomalies) - 20} more)") + + return "\n".join(lines) + + +def build_json( + ioc_path: Path, + entries: StrMap, + classified: ClassifiedIOC, + anomalies: list[str], +) -> dict[str, object]: + return { + "ioc_path": str(ioc_path), + "counts": { + "entries": len(entries), + "mcu": len(classified["domains"]["mcu"]), + "rcc": len(classified["domains"]["rcc"]), + "project_manager": len(classified["domains"]["project_manager"]), + "pins": len(classified["domains"]["pins"]), + "middleware": len(classified["domains"]["middleware"]), + "peripherals": len(classified["peripherals"]), + "other": len(classified["other"]), + "anomalies": len(anomalies), + }, + "domains": classified["domains"], + "peripherals": classified["peripherals"], + "other": classified["other"], + "anomalies": anomalies, + } + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Read and summarize STM32CubeMX .ioc files" + ) + parser.add_argument("--ioc", required=True, help="Path to .ioc file") + parser.add_argument("--json", action="store_true", help="Print JSON output") + parser.add_argument( + "--out", help="Optional output file path (for --json or text summary)" + ) + args = parser.parse_args() + + ioc_path = Path(args.ioc) + if not ioc_path.exists() or not ioc_path.is_file(): + raise SystemExit(f"error: ioc file not found: {ioc_path}") + + entries, anomalies = parse_ioc(ioc_path) + classified = classify(entries) + + if args.json: + payload = build_json(ioc_path, entries, classified, anomalies) + rendered = json.dumps(payload, ensure_ascii=True, indent=2) + else: + rendered = summarize(classified, ioc_path, anomalies) + + if args.out: + Path(args.out).write_text(rendered + "\n", encoding="utf-8") + else: + print(rendered) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())