From 367c3e9f45d818ad73c0a01fe16035648fa2a8ac Mon Sep 17 00:00:00 2001
From: William Edwards
Date: Tue, 20 Jan 2026 23:51:46 -0800
Subject: [PATCH 1/2] chore(Docs): migrate to mkdocs
---
.github/workflows/docs.yaml | 28 +
.gitignore | 1 +
.readthedocs.yaml | 18 +
.releaserc.yaml | 4 +-
Makefile | 29 +-
README.md | 8 +-
docs/.nav.yml | 4 +
docs/api/.gitignore | 2 -
docs/api/Makefile | 21 -
docs/api/tools/.gitignore | 1 -
docs/api/tools/doc_status.py | 502 ----
docs/api/tools/make_rst.py | 2264 -----------------
docs/api/tools/version.py | 1 -
docs/assets/icon.svg | 133 +
docs/{media => assets}/makefile.png | Bin
docs/assets/scene-tree.png | Bin 0 -> 91440 bytes
docs/{media => assets}/screenshot01.png | Bin
docs/{media => assets}/screenshot02.png | Bin
docs/{media => assets}/screenshot03.png | Bin
docs/{media => assets}/screenshot04.png | Bin
docs/class-reference/.nav.yml | 234 ++
docs/class-reference/APUDatabase.md | 93 +
docs/class-reference/APUEntry.md | 57 +
.../ActionStartInputPlumber.md | 25 +
.../ActionStartPowerStation.md | 25 +
docs/class-reference/ActionTurboTakeover.md | 25 +
docs/class-reference/AppLifecycleHook.md | 42 +
docs/class-reference/AudioManager.md | 106 +
docs/class-reference/BackInputHandler.md | 49 +
docs/class-reference/BehaviorNode.md | 28 +
docs/class-reference/Bitwise.md | 75 +
docs/class-reference/BlockDevice.md | 67 +
docs/class-reference/BluetoothAdapter.md | 257 ++
docs/class-reference/BluetoothDevice.md | 297 +++
docs/class-reference/BluetoothManager.md | 30 +
docs/class-reference/BluezInstance.md | 55 +
docs/class-reference/BoxArtManager.md | 115 +
docs/class-reference/BoxArtProvider.md | 90 +
docs/class-reference/CPU.md | 107 +
docs/class-reference/CPUCore.md | 55 +
docs/class-reference/Cache.md | 105 +
docs/class-reference/CardButton.md | 177 ++
docs/class-reference/CardButtonSetting.md | 127 +
docs/class-reference/CardIconButton.md | 107 +
docs/class-reference/CardInputIconButton.md | 93 +
docs/class-reference/CardMappingButton.md | 215 ++
.../class-reference/CardMappingButtonGroup.md | 171 ++
docs/class-reference/Command.md | 137 +
docs/class-reference/ComponentTextInput.md | 137 +
docs/class-reference/CompositeDevice.md | 177 ++
docs/class-reference/Cpu.md | 107 +
docs/class-reference/CpuCore.md | 67 +
docs/class-reference/CustomLogger.md | 85 +
docs/class-reference/DBusDevice.md | 27 +
docs/class-reference/DRMCardInfo.md | 162 ++
docs/class-reference/DRMCardInfoAMD.md | 31 +
docs/class-reference/DRMCardInfoIntel.md | 31 +
docs/class-reference/DRMCardPort.md | 152 ++
docs/class-reference/Dialog.md | 117 +
docs/class-reference/DiskManager.md | 27 +
.../DisplayManager.BacklightProvider.md | 55 +
.../DisplayManager.BrightnessctlBacklight.md | 87 +
.../DisplayManager.SteamOsBacklight.md | 87 +
docs/class-reference/DisplayManager.md | 108 +
docs/class-reference/DriveCard.md | 185 ++
docs/class-reference/DriveDevice.md | 47 +
docs/class-reference/Dropdown.md | 137 +
docs/class-reference/Effect.md | 28 +
.../EnhancedScrollContainer.md | 27 +
docs/class-reference/EventDevice.md | 77 +
docs/class-reference/ExpandableCard.md | 97 +
docs/class-reference/FadeEffect.md | 81 +
docs/class-reference/FifoReader.md | 57 +
docs/class-reference/FilesystemDevice.md | 47 +
docs/class-reference/FocusGroup.md | 174 ++
docs/class-reference/FocusGroupSetter.md | 45 +
docs/class-reference/FocusManager.md | 97 +
docs/class-reference/FocusSetter.md | 53 +
docs/class-reference/FocusStack.md | 106 +
docs/class-reference/GameCard.md | 193 ++
docs/class-reference/GamepadDevice.md | 47 +
docs/class-reference/GamepadMapper.md | 203 ++
docs/class-reference/Gamescope.md | 40 +
docs/class-reference/GamescopeInstance.md | 55 +
docs/class-reference/GamescopeXWayland.md | 507 ++++
docs/class-reference/GitHubClient.md | 23 +
docs/class-reference/Gpu.md | 35 +
docs/class-reference/GpuCard.md | 277 ++
docs/class-reference/GpuConnector.md | 107 +
docs/class-reference/GrowerEffect.md | 110 +
docs/class-reference/GutHookScript.md | 97 +
docs/class-reference/GutStringUtils.md | 67 +
docs/class-reference/GutTest.md | 1127 ++++++++
docs/class-reference/GutUtils.md | 767 ++++++
.../class-reference/HTTPAPIClient.Response.md | 87 +
docs/class-reference/HTTPAPIClient.md | 87 +
docs/class-reference/HTTPImageFetcher.md | 47 +
docs/class-reference/HandheldIconMapping.md | 37 +
docs/class-reference/HandheldPlatform.md | 49 +
.../HardwareManager.GPUInfo.md | 147 ++
docs/class-reference/HardwareManager.md | 258 ++
docs/class-reference/InputIcon.md | 115 +
.../InputIconKeyboardMapping.md | 943 +++++++
docs/class-reference/InputIconManager.md | 140 +
docs/class-reference/InputIconMapping.md | 541 ++++
docs/class-reference/InputIconProcessor.md | 37 +
docs/class-reference/InputManager.md | 196 ++
docs/class-reference/InputPlumber.md | 48 +
docs/class-reference/InputPlumberAxisEvent.md | 77 +
docs/class-reference/InputPlumberEvent.md | 181 ++
.../InputPlumberGamepadEvent.md | 87 +
docs/class-reference/InputPlumberGyroEvent.md | 87 +
docs/class-reference/InputPlumberInstance.md | 137 +
docs/class-reference/InputPlumberMapping.md | 88 +
.../class-reference/InputPlumberMouseEvent.md | 67 +
.../InputPlumberMouseMotionEvent.md | 67 +
docs/class-reference/InputPlumberProfile.md | 150 ++
.../class-reference/InputPlumberTouchEvent.md | 67 +
.../InputPlumberTouchMotionEvent.md | 67 +
.../InputPlumberTouchpadEvent.md | 67 +
.../InputPlumberTriggerEvent.md | 67 +
docs/class-reference/InputWatcher.md | 54 +
docs/class-reference/InstallLocationCard.md | 105 +
docs/class-reference/InstallLocationDialog.md | 125 +
.../class-reference/InstallManager.Request.md | 77 +
docs/class-reference/InstallManager.md | 93 +
docs/class-reference/InstallOptionDialog.md | 147 ++
docs/class-reference/InteractiveProcess.md | 170 ++
docs/class-reference/KeyboardContext.md | 44 +
docs/class-reference/KeyboardDevice.md | 67 +
docs/class-reference/KeyboardInstance.md | 70 +
docs/class-reference/KeyboardKeyConfig.md | 88 +
docs/class-reference/KeyboardLayout.md | 36 +
docs/class-reference/KeyboardOpener.md | 115 +
docs/class-reference/KeyboardRow.md | 28 +
docs/class-reference/LaunchManager.md | 273 ++
docs/class-reference/Launcher.md | 47 +
docs/class-reference/LevelIndicator.md | 57 +
.../Library.InstallLocation.md | 77 +
docs/class-reference/Library.InstallOption.md | 67 +
docs/class-reference/Library.md | 231 ++
docs/class-reference/LibraryDeck.md | 75 +
docs/class-reference/LibraryItem.md | 110 +
docs/class-reference/LibraryLaunchItem.md | 168 ++
docs/class-reference/LibraryManager.md | 214 ++
docs/class-reference/LibraryRefreshState.md | 30 +
docs/class-reference/LibraryRefresher.md | 35 +
docs/class-reference/Log.md | 26 +
docs/class-reference/LogManager.md | 83 +
docs/class-reference/MangoApp.md | 55 +
docs/class-reference/MouseDevice.md | 67 +
docs/class-reference/MultiHTTPRequest.md | 67 +
docs/class-reference/NetworkAccessPoint.md | 137 +
.../NetworkActiveConnection.md | 47 +
docs/class-reference/NetworkDevice.md | 77 +
docs/class-reference/NetworkDeviceWireless.md | 87 +
docs/class-reference/NetworkIpv4Config.md | 47 +
docs/class-reference/NetworkManager.md | 48 +
.../class-reference/NetworkManagerInstance.md | 97 +
docs/class-reference/NodeThread.md | 36 +
docs/class-reference/Notification.md | 77 +
docs/class-reference/NotificationContainer.md | 77 +
docs/class-reference/NotificationManager.md | 115 +
docs/class-reference/OSPlatform.md | 43 +
docs/class-reference/OnScreenKeyboard.md | 185 ++
docs/class-reference/OverlayContainer.md | 64 +
docs/class-reference/OverlayInputManager.md | 78 +
docs/class-reference/OverlayProvider.md | 54 +
docs/class-reference/PackageVerifier.md | 116 +
docs/class-reference/PartitionCard.md | 145 ++
docs/class-reference/PartitionDevice.md | 77 +
docs/class-reference/PerformanceManager.md | 152 ++
docs/class-reference/PerformanceProfile.md | 147 ++
docs/class-reference/PipeManager.md | 114 +
docs/class-reference/Platform.OSInfo.md | 77 +
docs/class-reference/Platform.md | 100 +
docs/class-reference/PlatformAction.md | 45 +
docs/class-reference/PlatformBazzite.md | 25 +
docs/class-reference/PlatformChimeraOS.md | 47 +
docs/class-reference/PlatformManjaro.md | 47 +
docs/class-reference/PlatformNixOS.md | 133 +
docs/class-reference/PlatformProvider.md | 72 +
docs/class-reference/PlatformSteamOS.md | 25 +
docs/class-reference/PlayAudioEffect.md | 67 +
docs/class-reference/Plugin.md | 122 +
docs/class-reference/PluginLoader.md | 296 +++
docs/class-reference/PluginManager.md | 27 +
docs/class-reference/PowerManager.md | 40 +
docs/class-reference/PowerSaver.md | 227 ++
docs/class-reference/PowerStation.md | 40 +
docs/class-reference/PowerStationInstance.md | 67 +
docs/class-reference/ProgressDialog.md | 125 +
docs/class-reference/Pty.md | 117 +
docs/class-reference/QuickBarCard.md | 163 ++
docs/class-reference/RaiseEffect.md | 157 ++
docs/class-reference/Reaper.md | 147 ++
docs/class-reference/ResourceProcessor.md | 27 +
docs/class-reference/ResourceRegistry.md | 85 +
docs/class-reference/RunningApp.md | 380 +++
docs/class-reference/Sandbox.md | 39 +
docs/class-reference/SandboxBubblewrap.md | 31 +
docs/class-reference/SandboxFirejail.md | 31 +
docs/class-reference/ScrollerJoystick.md | 70 +
docs/class-reference/SearchBar.md | 57 +
docs/class-reference/SelectableText.md | 87 +
docs/class-reference/SemanticVersion.md | 50 +
docs/class-reference/SettingsManager.md | 130 +
.../SharedThread.ExecutingTask.md | 77 +
.../SharedThread.ScheduledTask.md | 67 +
docs/class-reference/SharedThread.md | 278 ++
docs/class-reference/SlideEffect.md | 99 +
docs/class-reference/SmoothScrollEffect.md | 77 +
docs/class-reference/SoftwareUpdater.md | 133 +
docs/class-reference/State.md | 36 +
docs/class-reference/StateChanger.md | 77 +
docs/class-reference/StateMachine.md | 160 ++
docs/class-reference/StateMachineWatcher.md | 28 +
docs/class-reference/StateManager.md | 137 +
docs/class-reference/StateUpdater.md | 52 +
docs/class-reference/StateWatcher.md | 28 +
docs/class-reference/StatesWatcher.md | 28 +
docs/class-reference/StatusPanel.md | 107 +
.../SteamRemovableMediaManager.md | 147 ++
docs/class-reference/Store.md | 137 +
docs/class-reference/StoreItem.md | 57 +
docs/class-reference/StoreItemDetails.md | 5 +
docs/class-reference/StoreManager.md | 77 +
docs/class-reference/SubReaper.md | 25 +
docs/class-reference/TabContainerState.md | 66 +
docs/class-reference/TabLabel.md | 57 +
docs/class-reference/TabSetter.md | 36 +
docs/class-reference/TabsHeader.md | 107 +
docs/class-reference/TextSetter.md | 36 +
docs/class-reference/ThemeSetter.md | 67 +
docs/class-reference/ThemeUtils.md | 23 +
docs/class-reference/ThreadGroup.md | 5 +
docs/class-reference/ThreadPool.Task.md | 47 +
docs/class-reference/ThreadPool.md | 138 +
docs/class-reference/Toggle.md | 127 +
docs/class-reference/Transition.md | 37 +
docs/class-reference/TransitionContainer.md | 57 +
docs/class-reference/UDisks2Instance.md | 45 +
docs/class-reference/UPowerDevice.md | 317 +++
docs/class-reference/UPowerInstance.md | 67 +
docs/class-reference/UUID.md | 45 +
docs/class-reference/UserInterface.md | 27 +
docs/class-reference/ValueSlider.md | 167 ++
docs/class-reference/Vdf.md | 77 +
docs/class-reference/Version.md | 37 +
docs/class-reference/VisibilityManager.md | 94 +
docs/class-reference/WatchdogThread.md | 125 +
docs/class-reference/WebsocketRPCClient.md | 87 +
docs/class-reference/WifiNetworkTree.md | 73 +
docs/class-reference/Xdg.md | 31 +
docs/documentation/.nav.yml | 4 +
docs/documentation/contributing/.nav.yml | 10 +
.../contributing/adding_a_new_platform.md | 1 +
.../contributing/before_you_start.md | 29 +
.../contributing/best_practices.md | 36 +
.../contributing/building_from_source.md | 73 +
.../contributing/code_style_guidelines.md | 24 +
.../core_systems_and_architecture.md | 155 ++
.../contributing/creating_a_menu.md | 51 +
docs/documentation/contributing/deploying.md | 26 +
.../contributing/release_policy.md | 45 +
.../contributing/writing_decoupled_code.md | 113 +
.../getting-started/installation.md | 289 +++
docs/documentation/getting-started/usage.md | 40 +
docs/documentation/plugins/.nav.yml | 5 +
docs/documentation/plugins/getting_started.md | 97 +
docs/documentation/plugins/introduction.md | 36 +
docs/documentation/plugins/submitting.md | 17 +
docs/documentation/plugins/tutorials/.nav.yml | 3 +
.../plugins/tutorials/boxart_plugin.md | 118 +
.../plugins/tutorials/library_plugin.md | 76 +
docs/index.md | 98 +
extensions/Cargo.lock | 25 +-
extensions/Cargo.toml | 2 +-
extensions/docgen/Cargo.toml | 8 +
extensions/docgen/src/main.rs | 446 ++++
mkdocs.yml | 67 +
281 files changed, 27277 insertions(+), 2809 deletions(-)
create mode 100644 .github/workflows/docs.yaml
create mode 100644 .readthedocs.yaml
create mode 100644 docs/.nav.yml
delete mode 100644 docs/api/.gitignore
delete mode 100644 docs/api/Makefile
delete mode 100644 docs/api/tools/.gitignore
delete mode 100755 docs/api/tools/doc_status.py
delete mode 100755 docs/api/tools/make_rst.py
delete mode 100644 docs/api/tools/version.py
create mode 100644 docs/assets/icon.svg
rename docs/{media => assets}/makefile.png (100%)
create mode 100644 docs/assets/scene-tree.png
rename docs/{media => assets}/screenshot01.png (100%)
rename docs/{media => assets}/screenshot02.png (100%)
rename docs/{media => assets}/screenshot03.png (100%)
rename docs/{media => assets}/screenshot04.png (100%)
create mode 100644 docs/class-reference/.nav.yml
create mode 100644 docs/class-reference/APUDatabase.md
create mode 100644 docs/class-reference/APUEntry.md
create mode 100644 docs/class-reference/ActionStartInputPlumber.md
create mode 100644 docs/class-reference/ActionStartPowerStation.md
create mode 100644 docs/class-reference/ActionTurboTakeover.md
create mode 100644 docs/class-reference/AppLifecycleHook.md
create mode 100644 docs/class-reference/AudioManager.md
create mode 100644 docs/class-reference/BackInputHandler.md
create mode 100644 docs/class-reference/BehaviorNode.md
create mode 100644 docs/class-reference/Bitwise.md
create mode 100644 docs/class-reference/BlockDevice.md
create mode 100644 docs/class-reference/BluetoothAdapter.md
create mode 100644 docs/class-reference/BluetoothDevice.md
create mode 100644 docs/class-reference/BluetoothManager.md
create mode 100644 docs/class-reference/BluezInstance.md
create mode 100644 docs/class-reference/BoxArtManager.md
create mode 100644 docs/class-reference/BoxArtProvider.md
create mode 100644 docs/class-reference/CPU.md
create mode 100644 docs/class-reference/CPUCore.md
create mode 100644 docs/class-reference/Cache.md
create mode 100644 docs/class-reference/CardButton.md
create mode 100644 docs/class-reference/CardButtonSetting.md
create mode 100644 docs/class-reference/CardIconButton.md
create mode 100644 docs/class-reference/CardInputIconButton.md
create mode 100644 docs/class-reference/CardMappingButton.md
create mode 100644 docs/class-reference/CardMappingButtonGroup.md
create mode 100644 docs/class-reference/Command.md
create mode 100644 docs/class-reference/ComponentTextInput.md
create mode 100644 docs/class-reference/CompositeDevice.md
create mode 100644 docs/class-reference/Cpu.md
create mode 100644 docs/class-reference/CpuCore.md
create mode 100644 docs/class-reference/CustomLogger.md
create mode 100644 docs/class-reference/DBusDevice.md
create mode 100644 docs/class-reference/DRMCardInfo.md
create mode 100644 docs/class-reference/DRMCardInfoAMD.md
create mode 100644 docs/class-reference/DRMCardInfoIntel.md
create mode 100644 docs/class-reference/DRMCardPort.md
create mode 100644 docs/class-reference/Dialog.md
create mode 100644 docs/class-reference/DiskManager.md
create mode 100644 docs/class-reference/DisplayManager.BacklightProvider.md
create mode 100644 docs/class-reference/DisplayManager.BrightnessctlBacklight.md
create mode 100644 docs/class-reference/DisplayManager.SteamOsBacklight.md
create mode 100644 docs/class-reference/DisplayManager.md
create mode 100644 docs/class-reference/DriveCard.md
create mode 100644 docs/class-reference/DriveDevice.md
create mode 100644 docs/class-reference/Dropdown.md
create mode 100644 docs/class-reference/Effect.md
create mode 100644 docs/class-reference/EnhancedScrollContainer.md
create mode 100644 docs/class-reference/EventDevice.md
create mode 100644 docs/class-reference/ExpandableCard.md
create mode 100644 docs/class-reference/FadeEffect.md
create mode 100644 docs/class-reference/FifoReader.md
create mode 100644 docs/class-reference/FilesystemDevice.md
create mode 100644 docs/class-reference/FocusGroup.md
create mode 100644 docs/class-reference/FocusGroupSetter.md
create mode 100644 docs/class-reference/FocusManager.md
create mode 100644 docs/class-reference/FocusSetter.md
create mode 100644 docs/class-reference/FocusStack.md
create mode 100644 docs/class-reference/GameCard.md
create mode 100644 docs/class-reference/GamepadDevice.md
create mode 100644 docs/class-reference/GamepadMapper.md
create mode 100644 docs/class-reference/Gamescope.md
create mode 100644 docs/class-reference/GamescopeInstance.md
create mode 100644 docs/class-reference/GamescopeXWayland.md
create mode 100644 docs/class-reference/GitHubClient.md
create mode 100644 docs/class-reference/Gpu.md
create mode 100644 docs/class-reference/GpuCard.md
create mode 100644 docs/class-reference/GpuConnector.md
create mode 100644 docs/class-reference/GrowerEffect.md
create mode 100644 docs/class-reference/GutHookScript.md
create mode 100644 docs/class-reference/GutStringUtils.md
create mode 100644 docs/class-reference/GutTest.md
create mode 100644 docs/class-reference/GutUtils.md
create mode 100644 docs/class-reference/HTTPAPIClient.Response.md
create mode 100644 docs/class-reference/HTTPAPIClient.md
create mode 100644 docs/class-reference/HTTPImageFetcher.md
create mode 100644 docs/class-reference/HandheldIconMapping.md
create mode 100644 docs/class-reference/HandheldPlatform.md
create mode 100644 docs/class-reference/HardwareManager.GPUInfo.md
create mode 100644 docs/class-reference/HardwareManager.md
create mode 100644 docs/class-reference/InputIcon.md
create mode 100644 docs/class-reference/InputIconKeyboardMapping.md
create mode 100644 docs/class-reference/InputIconManager.md
create mode 100644 docs/class-reference/InputIconMapping.md
create mode 100644 docs/class-reference/InputIconProcessor.md
create mode 100644 docs/class-reference/InputManager.md
create mode 100644 docs/class-reference/InputPlumber.md
create mode 100644 docs/class-reference/InputPlumberAxisEvent.md
create mode 100644 docs/class-reference/InputPlumberEvent.md
create mode 100644 docs/class-reference/InputPlumberGamepadEvent.md
create mode 100644 docs/class-reference/InputPlumberGyroEvent.md
create mode 100644 docs/class-reference/InputPlumberInstance.md
create mode 100644 docs/class-reference/InputPlumberMapping.md
create mode 100644 docs/class-reference/InputPlumberMouseEvent.md
create mode 100644 docs/class-reference/InputPlumberMouseMotionEvent.md
create mode 100644 docs/class-reference/InputPlumberProfile.md
create mode 100644 docs/class-reference/InputPlumberTouchEvent.md
create mode 100644 docs/class-reference/InputPlumberTouchMotionEvent.md
create mode 100644 docs/class-reference/InputPlumberTouchpadEvent.md
create mode 100644 docs/class-reference/InputPlumberTriggerEvent.md
create mode 100644 docs/class-reference/InputWatcher.md
create mode 100644 docs/class-reference/InstallLocationCard.md
create mode 100644 docs/class-reference/InstallLocationDialog.md
create mode 100644 docs/class-reference/InstallManager.Request.md
create mode 100644 docs/class-reference/InstallManager.md
create mode 100644 docs/class-reference/InstallOptionDialog.md
create mode 100644 docs/class-reference/InteractiveProcess.md
create mode 100644 docs/class-reference/KeyboardContext.md
create mode 100644 docs/class-reference/KeyboardDevice.md
create mode 100644 docs/class-reference/KeyboardInstance.md
create mode 100644 docs/class-reference/KeyboardKeyConfig.md
create mode 100644 docs/class-reference/KeyboardLayout.md
create mode 100644 docs/class-reference/KeyboardOpener.md
create mode 100644 docs/class-reference/KeyboardRow.md
create mode 100644 docs/class-reference/LaunchManager.md
create mode 100644 docs/class-reference/Launcher.md
create mode 100644 docs/class-reference/LevelIndicator.md
create mode 100644 docs/class-reference/Library.InstallLocation.md
create mode 100644 docs/class-reference/Library.InstallOption.md
create mode 100644 docs/class-reference/Library.md
create mode 100644 docs/class-reference/LibraryDeck.md
create mode 100644 docs/class-reference/LibraryItem.md
create mode 100644 docs/class-reference/LibraryLaunchItem.md
create mode 100644 docs/class-reference/LibraryManager.md
create mode 100644 docs/class-reference/LibraryRefreshState.md
create mode 100644 docs/class-reference/LibraryRefresher.md
create mode 100644 docs/class-reference/Log.md
create mode 100644 docs/class-reference/LogManager.md
create mode 100644 docs/class-reference/MangoApp.md
create mode 100644 docs/class-reference/MouseDevice.md
create mode 100644 docs/class-reference/MultiHTTPRequest.md
create mode 100644 docs/class-reference/NetworkAccessPoint.md
create mode 100644 docs/class-reference/NetworkActiveConnection.md
create mode 100644 docs/class-reference/NetworkDevice.md
create mode 100644 docs/class-reference/NetworkDeviceWireless.md
create mode 100644 docs/class-reference/NetworkIpv4Config.md
create mode 100644 docs/class-reference/NetworkManager.md
create mode 100644 docs/class-reference/NetworkManagerInstance.md
create mode 100644 docs/class-reference/NodeThread.md
create mode 100644 docs/class-reference/Notification.md
create mode 100644 docs/class-reference/NotificationContainer.md
create mode 100644 docs/class-reference/NotificationManager.md
create mode 100644 docs/class-reference/OSPlatform.md
create mode 100644 docs/class-reference/OnScreenKeyboard.md
create mode 100644 docs/class-reference/OverlayContainer.md
create mode 100644 docs/class-reference/OverlayInputManager.md
create mode 100644 docs/class-reference/OverlayProvider.md
create mode 100644 docs/class-reference/PackageVerifier.md
create mode 100644 docs/class-reference/PartitionCard.md
create mode 100644 docs/class-reference/PartitionDevice.md
create mode 100644 docs/class-reference/PerformanceManager.md
create mode 100644 docs/class-reference/PerformanceProfile.md
create mode 100644 docs/class-reference/PipeManager.md
create mode 100644 docs/class-reference/Platform.OSInfo.md
create mode 100644 docs/class-reference/Platform.md
create mode 100644 docs/class-reference/PlatformAction.md
create mode 100644 docs/class-reference/PlatformBazzite.md
create mode 100644 docs/class-reference/PlatformChimeraOS.md
create mode 100644 docs/class-reference/PlatformManjaro.md
create mode 100644 docs/class-reference/PlatformNixOS.md
create mode 100644 docs/class-reference/PlatformProvider.md
create mode 100644 docs/class-reference/PlatformSteamOS.md
create mode 100644 docs/class-reference/PlayAudioEffect.md
create mode 100644 docs/class-reference/Plugin.md
create mode 100644 docs/class-reference/PluginLoader.md
create mode 100644 docs/class-reference/PluginManager.md
create mode 100644 docs/class-reference/PowerManager.md
create mode 100644 docs/class-reference/PowerSaver.md
create mode 100644 docs/class-reference/PowerStation.md
create mode 100644 docs/class-reference/PowerStationInstance.md
create mode 100644 docs/class-reference/ProgressDialog.md
create mode 100644 docs/class-reference/Pty.md
create mode 100644 docs/class-reference/QuickBarCard.md
create mode 100644 docs/class-reference/RaiseEffect.md
create mode 100644 docs/class-reference/Reaper.md
create mode 100644 docs/class-reference/ResourceProcessor.md
create mode 100644 docs/class-reference/ResourceRegistry.md
create mode 100644 docs/class-reference/RunningApp.md
create mode 100644 docs/class-reference/Sandbox.md
create mode 100644 docs/class-reference/SandboxBubblewrap.md
create mode 100644 docs/class-reference/SandboxFirejail.md
create mode 100644 docs/class-reference/ScrollerJoystick.md
create mode 100644 docs/class-reference/SearchBar.md
create mode 100644 docs/class-reference/SelectableText.md
create mode 100644 docs/class-reference/SemanticVersion.md
create mode 100644 docs/class-reference/SettingsManager.md
create mode 100644 docs/class-reference/SharedThread.ExecutingTask.md
create mode 100644 docs/class-reference/SharedThread.ScheduledTask.md
create mode 100644 docs/class-reference/SharedThread.md
create mode 100644 docs/class-reference/SlideEffect.md
create mode 100644 docs/class-reference/SmoothScrollEffect.md
create mode 100644 docs/class-reference/SoftwareUpdater.md
create mode 100644 docs/class-reference/State.md
create mode 100644 docs/class-reference/StateChanger.md
create mode 100644 docs/class-reference/StateMachine.md
create mode 100644 docs/class-reference/StateMachineWatcher.md
create mode 100644 docs/class-reference/StateManager.md
create mode 100644 docs/class-reference/StateUpdater.md
create mode 100644 docs/class-reference/StateWatcher.md
create mode 100644 docs/class-reference/StatesWatcher.md
create mode 100644 docs/class-reference/StatusPanel.md
create mode 100644 docs/class-reference/SteamRemovableMediaManager.md
create mode 100644 docs/class-reference/Store.md
create mode 100644 docs/class-reference/StoreItem.md
create mode 100644 docs/class-reference/StoreItemDetails.md
create mode 100644 docs/class-reference/StoreManager.md
create mode 100644 docs/class-reference/SubReaper.md
create mode 100644 docs/class-reference/TabContainerState.md
create mode 100644 docs/class-reference/TabLabel.md
create mode 100644 docs/class-reference/TabSetter.md
create mode 100644 docs/class-reference/TabsHeader.md
create mode 100644 docs/class-reference/TextSetter.md
create mode 100644 docs/class-reference/ThemeSetter.md
create mode 100644 docs/class-reference/ThemeUtils.md
create mode 100644 docs/class-reference/ThreadGroup.md
create mode 100644 docs/class-reference/ThreadPool.Task.md
create mode 100644 docs/class-reference/ThreadPool.md
create mode 100644 docs/class-reference/Toggle.md
create mode 100644 docs/class-reference/Transition.md
create mode 100644 docs/class-reference/TransitionContainer.md
create mode 100644 docs/class-reference/UDisks2Instance.md
create mode 100644 docs/class-reference/UPowerDevice.md
create mode 100644 docs/class-reference/UPowerInstance.md
create mode 100644 docs/class-reference/UUID.md
create mode 100644 docs/class-reference/UserInterface.md
create mode 100644 docs/class-reference/ValueSlider.md
create mode 100644 docs/class-reference/Vdf.md
create mode 100644 docs/class-reference/Version.md
create mode 100644 docs/class-reference/VisibilityManager.md
create mode 100644 docs/class-reference/WatchdogThread.md
create mode 100644 docs/class-reference/WebsocketRPCClient.md
create mode 100644 docs/class-reference/WifiNetworkTree.md
create mode 100644 docs/class-reference/Xdg.md
create mode 100644 docs/documentation/.nav.yml
create mode 100644 docs/documentation/contributing/.nav.yml
create mode 100644 docs/documentation/contributing/adding_a_new_platform.md
create mode 100644 docs/documentation/contributing/before_you_start.md
create mode 100644 docs/documentation/contributing/best_practices.md
create mode 100644 docs/documentation/contributing/building_from_source.md
create mode 100644 docs/documentation/contributing/code_style_guidelines.md
create mode 100644 docs/documentation/contributing/core_systems_and_architecture.md
create mode 100644 docs/documentation/contributing/creating_a_menu.md
create mode 100644 docs/documentation/contributing/deploying.md
create mode 100644 docs/documentation/contributing/release_policy.md
create mode 100644 docs/documentation/contributing/writing_decoupled_code.md
create mode 100644 docs/documentation/getting-started/installation.md
create mode 100644 docs/documentation/getting-started/usage.md
create mode 100644 docs/documentation/plugins/.nav.yml
create mode 100644 docs/documentation/plugins/getting_started.md
create mode 100644 docs/documentation/plugins/introduction.md
create mode 100644 docs/documentation/plugins/submitting.md
create mode 100644 docs/documentation/plugins/tutorials/.nav.yml
create mode 100644 docs/documentation/plugins/tutorials/boxart_plugin.md
create mode 100644 docs/documentation/plugins/tutorials/library_plugin.md
create mode 100644 docs/index.md
create mode 100644 extensions/docgen/Cargo.toml
create mode 100644 extensions/docgen/src/main.rs
create mode 100644 mkdocs.yml
diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
new file mode 100644
index 000000000..81e0564fa
--- /dev/null
+++ b/.github/workflows/docs.yaml
@@ -0,0 +1,28 @@
+name: "π Documentation"
+on:
+ push:
+ branches:
+ - main
+permissions:
+ contents: write
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Configure Git Credentials
+ run: |
+ git config user.name github-actions[bot]
+ git config user.email 41898282+github-actions[bot]@users.noreply.github.com
+ - uses: actions/setup-python@v5
+ with:
+ python-version: 3.x
+ - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
+ - uses: actions/cache@v4
+ with:
+ key: mkdocs-material-${{ env.cache_id }}
+ path: ~/.cache
+ restore-keys: |
+ mkdocs-material-
+ - run: pip install mkdocs-material mkdocs-awesome-nav
+ - run: mkdocs gh-deploy --force
diff --git a/.gitignore b/.gitignore
index 517c866f1..4d6ed0ef9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ settings.mk
RyzenAdj
.gut_editor_config.json
result
+target
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 000000000..4adb33d48
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,18 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+# Set the OS, Python version and other tools you might need
+build:
+ os: ubuntu-24.04
+ tools:
+ python: "3"
+ jobs:
+ pre_install:
+ - pip install mkdocs-material mkdocs-awesome-nav
+
+mkdocs:
+ configuration: mkdocs.yml
diff --git a/.releaserc.yaml b/.releaserc.yaml
index 00f86b5ab..76c60c692 100644
--- a/.releaserc.yaml
+++ b/.releaserc.yaml
@@ -35,7 +35,7 @@ plugins:
# Execute commands to build the project
- - "@semantic-release/exec"
- shell: true
- prepareCmd: "make in-docker IMAGE_TAG=4.5.1 TARGET='force-import dist update-pkgbuild-hash'"
+ prepareCmd: "make in-docker IMAGE_TAG=4.5.1 TARGET='force-import docs dist update-pkgbuild-hash'"
publishCmd: "echo '${nextRelease.version}' > .version.txt"
# Commit the following changes to git after other plugins have run
@@ -44,6 +44,8 @@ plugins:
- core/global/version.tres
- package/rpm/opengamepadui.spec
- package/archlinux/PKGBUILD
+ - docs/class-reference/.nav.yml
+ - docs/class-reference/*.md
# Publish artifacts as a GitHub release
- - "@semantic-release/github"
diff --git a/Makefile b/Makefile
index 7c419f5c1..1ac624295 100644
--- a/Makefile
+++ b/Makefile
@@ -207,25 +207,38 @@ debug-overlay: $(IMPORT_DIR) ## Run the project in debug mode in gamescope with
--position 320,140 res://entrypoint.tscn --overlay-mode -- steam -gamepadui -steamos3 -steampal -steamdeck
.PHONY: docs
-docs: docs/api/classes/.generated ## Generate docs
+docs: docs/api/classes/.generated ## Generate class reference docs
docs/api/classes/.generated: $(IMPORT_DIR) $(ALL_GDSCRIPT)
- rm -rf docs/api/classes
- mkdir -p docs/api/classes
+ rm -rf $(CACHE_DIR)/docs/api/classes
+ mkdir -p $(CACHE_DIR)/docs/api/classes
+ @echo "Generating GDExtension class references"
$(GODOT) \
--editor \
--quit \
- --doctool docs/api/classes \
+ --doctool $(CACHE_DIR)/docs/api/classes \
--no-docbase \
--gdextension-docs
+ mv $(CACHE_DIR)/docs/api/classes/doc_classes/*.xml $(CACHE_DIR)/docs/api/classes
+ rmdir $(CACHE_DIR)/docs/api/classes/doc_classes
+ @echo "Generating GDScript class references"
$(GODOT) \
--editor \
--path $(PWD) \
--quit \
- --doctool docs/api/classes \
+ --doctool $(CACHE_DIR)/docs/api/classes \
--no-docbase \
- --gdscript-docs core
- rm -rf docs/api/classes/core--*
- $(MAKE) -C docs/api rst
+ --gdscript-docs .
+ @echo "Removing non-class documentation"
+ rm $(CACHE_DIR)/docs/api/classes/entrypoint.gd.xml
+ rm -rf $(CACHE_DIR)/docs/api/classes/core--*
+ rm -rf $(CACHE_DIR)/docs/api/classes/addons--*
+ rm -rf $(CACHE_DIR)/docs/api/classes/plugins--*
+ $(MAKE) docgen
+
+docgen:
+ rm -f ./docs/class-reference/*.md
+ cd ./extensions && \
+ cargo run --bin docgen -- ../$(CACHE_DIR)/docs/api/classes ../docs/class-reference
.PHONY: inspect
inspect: $(IMPORT_DIR) ## Launch Gamescope inspector
diff --git a/README.md b/README.md
index 3c7c413d2..a02eb8588 100644
--- a/README.md
+++ b/README.md
@@ -11,11 +11,11 @@
-
+
-
-
-
+
+
+
diff --git a/docs/.nav.yml b/docs/.nav.yml
new file mode 100644
index 000000000..193f56c62
--- /dev/null
+++ b/docs/.nav.yml
@@ -0,0 +1,4 @@
+nav:
+ - Home: ./index.md
+ - ./documentation
+ - ./class-reference
diff --git a/docs/api/.gitignore b/docs/api/.gitignore
deleted file mode 100644
index 51b6dc595..000000000
--- a/docs/api/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-_build
-classes
diff --git a/docs/api/Makefile b/docs/api/Makefile
deleted file mode 100644
index 495affdbd..000000000
--- a/docs/api/Makefile
+++ /dev/null
@@ -1,21 +0,0 @@
-BASEDIR = .
-CLASSES = "$(BASEDIR)/classes/"
-OUTPUTDIR = $(BASEDIR)/_build
-TOOLSDIR = $(BASEDIR)/tools
-LANGARG ?= en
-LANGCMD = -l $(LANGARG)
-
-.ONESHELL:
-
-clean:
- rm -rf "$(OUTPUTDIR)"
-
-doxygen:
- rm -rf "$(OUTPUTDIR)/doxygen"
- mkdir -p "$(OUTPUTDIR)/doxygen"
- doxygen Doxyfile
-
-rst:
- rm -rf "$(OUTPUTDIR)/rst"
- mkdir -p "$(OUTPUTDIR)/rst"
- python3 "$(TOOLSDIR)/make_rst.py" -o "$(OUTPUTDIR)/rst" "$(LANGCMD)" $(CLASSES)
diff --git a/docs/api/tools/.gitignore b/docs/api/tools/.gitignore
deleted file mode 100644
index bee8a64b7..000000000
--- a/docs/api/tools/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-__pycache__
diff --git a/docs/api/tools/doc_status.py b/docs/api/tools/doc_status.py
deleted file mode 100755
index 717a468b3..000000000
--- a/docs/api/tools/doc_status.py
+++ /dev/null
@@ -1,502 +0,0 @@
-#!/usr/bin/env python3
-
-import fnmatch
-import os
-import sys
-import re
-import math
-import platform
-import xml.etree.ElementTree as ET
-from typing import Dict, List, Set
-
-################################################################################
-# Config #
-################################################################################
-
-flags = {
- "c": platform.platform() != "Windows", # Disable by default on windows, since we use ANSI escape codes
- "b": False,
- "g": False,
- "s": False,
- "u": False,
- "h": False,
- "p": False,
- "o": True,
- "i": False,
- "a": True,
- "e": False,
-}
-flag_descriptions = {
- "c": "Toggle colors when outputting.",
- "b": "Toggle showing only not fully described classes.",
- "g": "Toggle showing only completed classes.",
- "s": "Toggle showing comments about the status.",
- "u": "Toggle URLs to docs.",
- "h": "Show help and exit.",
- "p": "Toggle showing percentage as well as counts.",
- "o": "Toggle overall column.",
- "i": "Toggle collapse of class items columns.",
- "a": "Toggle showing all items.",
- "e": "Toggle hiding empty items.",
-}
-long_flags = {
- "colors": "c",
- "use-colors": "c",
- "bad": "b",
- "only-bad": "b",
- "good": "g",
- "only-good": "g",
- "comments": "s",
- "status": "s",
- "urls": "u",
- "gen-url": "u",
- "help": "h",
- "percent": "p",
- "use-percentages": "p",
- "overall": "o",
- "use-overall": "o",
- "items": "i",
- "collapse": "i",
- "all": "a",
- "empty": "e",
-}
-table_columns = [
- "name",
- "brief_description",
- "description",
- "methods",
- "constants",
- "members",
- "theme_items",
- "signals",
- "operators",
- "constructors",
-]
-table_column_names = [
- "Name",
- "Brief Desc.",
- "Desc.",
- "Methods",
- "Constants",
- "Members",
- "Theme Items",
- "Signals",
- "Operators",
- "Constructors",
-]
-colors = {
- "name": [36], # cyan
- "part_big_problem": [4, 31], # underline, red
- "part_problem": [31], # red
- "part_mostly_good": [33], # yellow
- "part_good": [32], # green
- "url": [4, 34], # underline, blue
- "section": [1, 4], # bold, underline
- "state_off": [36], # cyan
- "state_on": [1, 35], # bold, magenta/plum
- "bold": [1], # bold
-}
-overall_progress_description_weight = 10
-
-
-################################################################################
-# Utils #
-################################################################################
-
-
-def validate_tag(elem: ET.Element, tag: str) -> None:
- if elem.tag != tag:
- print('Tag mismatch, expected "' + tag + '", got ' + elem.tag)
- sys.exit(255)
-
-
-def color(color: str, string: str) -> str:
- if flags["c"] and terminal_supports_color():
- color_format = ""
- for code in colors[color]:
- color_format += "\033[" + str(code) + "m"
- return color_format + string + "\033[0m"
- else:
- return string
-
-
-ansi_escape = re.compile(r"\x1b[^m]*m")
-
-
-def nonescape_len(s: str) -> int:
- return len(ansi_escape.sub("", s))
-
-
-def terminal_supports_color():
- p = sys.platform
- supported_platform = p != "Pocket PC" and (p != "win32" or "ANSICON" in os.environ)
-
- is_a_tty = hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
- if not supported_platform or not is_a_tty:
- return False
- return True
-
-
-################################################################################
-# Classes #
-################################################################################
-
-
-class ClassStatusProgress:
- def __init__(self, described: int = 0, total: int = 0):
- self.described: int = described
- self.total: int = total
-
- def __add__(self, other: "ClassStatusProgress"):
- return ClassStatusProgress(self.described + other.described, self.total + other.total)
-
- def increment(self, described: bool):
- if described:
- self.described += 1
- self.total += 1
-
- def is_ok(self):
- return self.described >= self.total
-
- def to_configured_colored_string(self):
- if flags["p"]:
- return self.to_colored_string("{percent}% ({has}/{total})", "{pad_percent}{pad_described}{s}{pad_total}")
- else:
- return self.to_colored_string()
-
- def to_colored_string(self, format: str = "{has}/{total}", pad_format: str = "{pad_described}{s}{pad_total}"):
- ratio = float(self.described) / float(self.total) if self.total != 0 else 1
- percent = int(round(100 * ratio))
- s = format.format(has=str(self.described), total=str(self.total), percent=str(percent))
- if self.described >= self.total:
- s = color("part_good", s)
- elif self.described >= self.total / 4 * 3:
- s = color("part_mostly_good", s)
- elif self.described > 0:
- s = color("part_problem", s)
- else:
- s = color("part_big_problem", s)
- pad_size = max(len(str(self.described)), len(str(self.total)))
- pad_described = "".ljust(pad_size - len(str(self.described)))
- pad_percent = "".ljust(3 - len(str(percent)))
- pad_total = "".ljust(pad_size - len(str(self.total)))
- return pad_format.format(pad_described=pad_described, pad_total=pad_total, pad_percent=pad_percent, s=s)
-
-
-class ClassStatus:
- def __init__(self, name: str = ""):
- self.name: str = name
- self.has_brief_description: bool = True
- self.has_description: bool = True
- self.progresses: Dict[str, ClassStatusProgress] = {
- "methods": ClassStatusProgress(),
- "constants": ClassStatusProgress(),
- "members": ClassStatusProgress(),
- "theme_items": ClassStatusProgress(),
- "signals": ClassStatusProgress(),
- "operators": ClassStatusProgress(),
- "constructors": ClassStatusProgress(),
- }
-
- def __add__(self, other: "ClassStatus"):
- new_status = ClassStatus()
- new_status.name = self.name
- new_status.has_brief_description = self.has_brief_description and other.has_brief_description
- new_status.has_description = self.has_description and other.has_description
- for k in self.progresses:
- new_status.progresses[k] = self.progresses[k] + other.progresses[k]
- return new_status
-
- def is_ok(self):
- ok = True
- ok = ok and self.has_brief_description
- ok = ok and self.has_description
- for k in self.progresses:
- ok = ok and self.progresses[k].is_ok()
- return ok
-
- def is_empty(self):
- sum = 0
- for k in self.progresses:
- if self.progresses[k].is_ok():
- continue
- sum += self.progresses[k].total
- return sum < 1
-
- def make_output(self) -> Dict[str, str]:
- output: Dict[str, str] = {}
- output["name"] = color("name", self.name)
-
- ok_string = color("part_good", "OK")
- missing_string = color("part_big_problem", "MISSING")
-
- output["brief_description"] = ok_string if self.has_brief_description else missing_string
- output["description"] = ok_string if self.has_description else missing_string
-
- description_progress = ClassStatusProgress(
- (self.has_brief_description + self.has_description) * overall_progress_description_weight,
- 2 * overall_progress_description_weight,
- )
- items_progress = ClassStatusProgress()
-
- for k in ["methods", "constants", "members", "theme_items", "signals", "constructors", "operators"]:
- items_progress += self.progresses[k]
- output[k] = self.progresses[k].to_configured_colored_string()
-
- output["items"] = items_progress.to_configured_colored_string()
-
- output["overall"] = (description_progress + items_progress).to_colored_string(
- color("bold", "{percent}%"), "{pad_percent}{s}"
- )
-
- if self.name.startswith("Total"):
- output["url"] = color("url", "https://docs.godotengine.org/en/latest/classes/")
- if flags["s"]:
- output["comment"] = color("part_good", "ALL OK")
- else:
- output["url"] = color(
- "url", "https://docs.godotengine.org/en/latest/classes/class_{name}.html".format(name=self.name.lower())
- )
-
- if flags["s"] and not flags["g"] and self.is_ok():
- output["comment"] = color("part_good", "ALL OK")
-
- return output
-
- @staticmethod
- def generate_for_class(c: ET.Element):
- status = ClassStatus()
- status.name = c.attrib["name"]
-
- for tag in list(c):
- len_tag_text = 0 if (tag.text is None) else len(tag.text.strip())
-
- if tag.tag == "brief_description":
- status.has_brief_description = len_tag_text > 0
-
- elif tag.tag == "description":
- status.has_description = len_tag_text > 0
-
- elif tag.tag in ["methods", "signals", "operators", "constructors"]:
- for sub_tag in list(tag):
- descr = sub_tag.find("description")
- increment = (descr is not None) and (descr.text is not None) and len(descr.text.strip()) > 0
- status.progresses[tag.tag].increment(increment)
- elif tag.tag in ["constants", "members", "theme_items"]:
- for sub_tag in list(tag):
- if not sub_tag.text is None:
- status.progresses[tag.tag].increment(len(sub_tag.text.strip()) > 0)
-
- elif tag.tag in ["tutorials"]:
- pass # Ignore those tags for now
-
- else:
- print(tag.tag, tag.attrib)
-
- return status
-
-
-################################################################################
-# Arguments #
-################################################################################
-
-input_file_list: List[str] = []
-input_class_list: List[str] = []
-merged_file: str = ""
-
-for arg in sys.argv[1:]:
- try:
- if arg.startswith("--"):
- flags[long_flags[arg[2:]]] = not flags[long_flags[arg[2:]]]
- elif arg.startswith("-"):
- for f in arg[1:]:
- flags[f] = not flags[f]
- elif os.path.isdir(arg):
- for f in os.listdir(arg):
- if f.endswith(".xml"):
- input_file_list.append(os.path.join(arg, f))
- else:
- input_class_list.append(arg)
- except KeyError:
- print("Unknown command line flag: " + arg)
- sys.exit(1)
-
-if flags["i"]:
- for r in ["methods", "constants", "members", "signals", "theme_items"]:
- index = table_columns.index(r)
- del table_column_names[index]
- del table_columns[index]
- table_column_names.append("Items")
- table_columns.append("items")
-
-if flags["o"] == (not flags["i"]):
- table_column_names.append(color("bold", "Overall"))
- table_columns.append("overall")
-
-if flags["u"]:
- table_column_names.append("Docs URL")
- table_columns.append("url")
-
-
-################################################################################
-# Help #
-################################################################################
-
-if len(input_file_list) < 1 or flags["h"]:
- if not flags["h"]:
- print(color("section", "Invalid usage") + ": Please specify a classes directory")
- print(color("section", "Usage") + ": doc_status.py [flags] [class names]")
- print("\t< and > signify required parameters, while [ and ] signify optional parameters.")
- print(color("section", "Available flags") + ":")
- possible_synonym_list = list(long_flags)
- possible_synonym_list.sort()
- flag_list = list(flags)
- flag_list.sort()
- for flag in flag_list:
- synonyms = [color("name", "-" + flag)]
- for synonym in possible_synonym_list:
- if long_flags[synonym] == flag:
- synonyms.append(color("name", "--" + synonym))
-
- print(
- (
- "{synonyms} (Currently "
- + color("state_" + ("on" if flags[flag] else "off"), "{value}")
- + ")\n\t{description}"
- ).format(
- synonyms=", ".join(synonyms),
- value=("on" if flags[flag] else "off"),
- description=flag_descriptions[flag],
- )
- )
- sys.exit(0)
-
-
-################################################################################
-# Parse class list #
-################################################################################
-
-class_names: List[str] = []
-classes: Dict[str, ET.Element] = {}
-
-for file in input_file_list:
- tree = ET.parse(file)
- doc = tree.getroot()
-
- if doc.attrib["name"] in class_names:
- continue
- class_names.append(doc.attrib["name"])
- classes[doc.attrib["name"]] = doc
-
-class_names.sort()
-
-if len(input_class_list) < 1:
- input_class_list = ["*"]
-
-filtered_classes_set: Set[str] = set()
-for pattern in input_class_list:
- filtered_classes_set |= set(fnmatch.filter(class_names, pattern))
-filtered_classes = list(filtered_classes_set)
-filtered_classes.sort()
-
-################################################################################
-# Make output table #
-################################################################################
-
-table = [table_column_names]
-table_row_chars = "| - "
-table_column_chars = "|"
-
-total_status = ClassStatus("Total")
-
-for cn in filtered_classes:
- c = classes[cn]
- validate_tag(c, "class")
- status = ClassStatus.generate_for_class(c)
-
- total_status = total_status + status
-
- if (flags["b"] and status.is_ok()) or (flags["g"] and not status.is_ok()) or (not flags["a"]):
- continue
-
- if flags["e"] and status.is_empty():
- continue
-
- out = status.make_output()
- row: List[str] = []
- for column in table_columns:
- if column in out:
- row.append(out[column])
- else:
- row.append("")
-
- if "comment" in out and out["comment"] != "":
- row.append(out["comment"])
-
- table.append(row)
-
-
-################################################################################
-# Print output table #
-################################################################################
-
-if len(table) == 1 and flags["a"]:
- print(color("part_big_problem", "No classes suitable for printing!"))
- sys.exit(0)
-
-if len(table) > 2 or not flags["a"]:
- total_status.name = "Total = {0}".format(len(table) - 1)
- out = total_status.make_output()
- row = []
- for column in table_columns:
- if column in out:
- row.append(out[column])
- else:
- row.append("")
- table.append(row)
-
-if flags["a"]:
- # Duplicate the headers at the bottom of the table so they can be viewed
- # without having to scroll back to the top.
- table.append(table_column_names)
-
-table_column_sizes: List[int] = []
-for row in table:
- for cell_i, cell in enumerate(row):
- if cell_i >= len(table_column_sizes):
- table_column_sizes.append(0)
-
- table_column_sizes[cell_i] = max(nonescape_len(cell), table_column_sizes[cell_i])
-
-divider_string = table_row_chars[0]
-for cell_i in range(len(table[0])):
- divider_string += (
- table_row_chars[1] + table_row_chars[2] * (table_column_sizes[cell_i]) + table_row_chars[1] + table_row_chars[0]
- )
-
-for row_i, row in enumerate(table):
- row_string = table_column_chars
- for cell_i, cell in enumerate(row):
- padding_needed = table_column_sizes[cell_i] - nonescape_len(cell) + 2
- if cell_i == 0:
- row_string += table_row_chars[3] + cell + table_row_chars[3] * (padding_needed - 1)
- else:
- row_string += (
- table_row_chars[3] * int(math.floor(float(padding_needed) / 2))
- + cell
- + table_row_chars[3] * int(math.ceil(float(padding_needed) / 2))
- )
- row_string += table_column_chars
-
- print(row_string)
-
- # Account for the possible double header (if the `a` flag is enabled).
- # No need to have a condition for the flag, as this will behave correctly
- # if the flag is disabled.
- if row_i == 0 or row_i == len(table) - 3 or row_i == len(table) - 2:
- print(divider_string)
-
-print(divider_string)
-
-if total_status.is_ok() and not flags["g"]:
- print("All listed classes are " + color("part_good", "OK") + "!")
diff --git a/docs/api/tools/make_rst.py b/docs/api/tools/make_rst.py
deleted file mode 100755
index 3c65d2f5b..000000000
--- a/docs/api/tools/make_rst.py
+++ /dev/null
@@ -1,2264 +0,0 @@
-#!/usr/bin/env python3
-
-# This script makes RST files from the XML class reference for use with the online docs.
-
-import argparse
-import os
-import platform
-import re
-import sys
-import xml.etree.ElementTree as ET
-from collections import OrderedDict
-from typing import List, Dict, TextIO, Tuple, Optional, Any, Union
-
-# Import hardcoded version information from version.py
-root_directory = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")
-sys.path.append(root_directory) # Include the root directory
-import version
-
-# $DOCS_URL/path/to/page.html(#fragment-tag)
-GODOT_DOCS_PATTERN = re.compile(r"^\$DOCS_URL/(.*)\.html(#.*)?$")
-
-# Based on reStructuredText inline markup recognition rules
-# https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#inline-markup-recognition-rules
-MARKUP_ALLOWED_PRECEDENT = " -:/'\"<([{"
-MARKUP_ALLOWED_SUBSEQUENT = " -.,:;!?\\/'\")]}>"
-
-# Used to translate section headings and other hardcoded strings when required with
-# the --lang argument. The BASE_STRINGS list should be synced with what we actually
-# write in this script (check `translate()` uses), and also hardcoded in
-# `scripts/extract_classes.py` (godotengine/godot-editor-l10n repo) to include them in the source POT file.
-BASE_STRINGS = [
- "All classes",
- "Globals",
- "Nodes",
- "Resources",
- "Editor-only",
- "Other objects",
- "Variant types",
- "Description",
- "Tutorials",
- "Properties",
- "Constructors",
- "Methods",
- "Operators",
- "Theme Properties",
- "Signals",
- "Enumerations",
- "Constants",
- "Annotations",
- "Property Descriptions",
- "Constructor Descriptions",
- "Method Descriptions",
- "Operator Descriptions",
- "Theme Property Descriptions",
- "Inherits:",
- "Inherited By:",
- "(overrides %s)",
- "Default",
- "Setter",
- "value",
- "Getter",
- "This method should typically be overridden by the user to have any effect.",
- "This method has no side effects. It doesn't modify any of the instance's member variables.",
- "This method accepts any number of arguments after the ones described here.",
- "This method is used to construct a type.",
- "This method doesn't need an instance to be called, so it can be called directly using the class name.",
- "This method describes a valid operator to use with this type as left-hand operand.",
- "This value is an integer composed as a bitmask of the following flags.",
- "There is currently no description for this class. Please help us by :ref:`contributing one `!",
- "There is currently no description for this signal. Please help us by :ref:`contributing one `!",
- "There is currently no description for this annotation. Please help us by :ref:`contributing one `!",
- "There is currently no description for this property. Please help us by :ref:`contributing one `!",
- "There is currently no description for this constructor. Please help us by :ref:`contributing one `!",
- "There is currently no description for this method. Please help us by :ref:`contributing one `!",
- "There is currently no description for this operator. Please help us by :ref:`contributing one `!",
- "There is currently no description for this theme property. Please help us by :ref:`contributing one `!",
- "There are notable differences when using this API with C#. See :ref:`doc_c_sharp_differences` for more information.",
-]
-strings_l10n: Dict[str, str] = {}
-
-STYLES: Dict[str, str] = {}
-
-CLASS_GROUPS: Dict[str, str] = {
- "global": "Globals",
- "node": "Nodes",
- "resource": "Resources",
- "object": "Other objects",
- "editor": "Editor-only",
- "variant": "Variant types",
-}
-CLASS_GROUPS_BASE: Dict[str, str] = {
- "node": "Node",
- "resource": "Resource",
- "object": "Object",
- "variant": "Variant",
-}
-# Sync with editor\register_editor_types.cpp
-EDITOR_CLASSES: List[str] = [
- "FileSystemDock",
- "ScriptCreateDialog",
- "ScriptEditor",
- "ScriptEditorBase",
-]
-# Sync with the types mentioned in https://docs.godotengine.org/en/stable/tutorials/scripting/c_sharp/c_sharp_differences.html
-CLASSES_WITH_CSHARP_DIFFERENCES: List[str] = [
- "@GlobalScope",
- "String",
- "NodePath",
- "Signal",
- "Callable",
- "RID",
- "Basis",
- "Transform2D",
- "Transform3D",
- "Rect2",
- "Rect2i",
- "AABB",
- "Quaternion",
- "Projection",
- "Color",
- "Array",
- "Dictionary",
- "PackedByteArray",
- "PackedColorArray",
- "PackedFloat32Array",
- "PackedFloat64Array",
- "PackedInt32Array",
- "PackedInt64Array",
- "PackedStringArray",
- "PackedVector2Array",
- "PackedVector3Array",
- "Variant",
-]
-
-
-class State:
- def __init__(self) -> None:
- self.num_errors = 0
- self.num_warnings = 0
- self.classes: OrderedDict[str, ClassDef] = OrderedDict()
- self.current_class: str = ""
-
- def parse_class(self, class_root: ET.Element, filepath: str) -> None:
- class_name = class_root.attrib["name"]
- self.current_class = class_name
-
- class_def = ClassDef(class_name)
- self.classes[class_name] = class_def
- class_def.filepath = filepath
-
- inherits = class_root.get("inherits")
- if inherits is not None:
- class_def.inherits = inherits
-
- brief_desc = class_root.find("brief_description")
- if brief_desc is not None and brief_desc.text:
- class_def.brief_description = brief_desc.text
-
- desc = class_root.find("description")
- if desc is not None and desc.text:
- class_def.description = desc.text
-
- properties = class_root.find("members")
- if properties is not None:
- for property in properties:
- assert property.tag == "member"
-
- property_name = property.attrib["name"]
- if property_name in class_def.properties:
- print_error(f'{class_name}.xml: Duplicate property "{property_name}".', self)
- continue
-
- type_name = TypeName.from_element(property)
- setter = property.get("setter") or None # Use or None so '' gets turned into None.
- getter = property.get("getter") or None
- default_value = property.get("default") or None
- if default_value is not None:
- default_value = f"``{default_value}``"
- overrides = property.get("overrides") or None
-
- property_def = PropertyDef(
- property_name, type_name, setter, getter, property.text, default_value, overrides
- )
- class_def.properties[property_name] = property_def
-
- constructors = class_root.find("constructors")
- if constructors is not None:
- for constructor in constructors:
- assert constructor.tag == "constructor"
-
- method_name = constructor.attrib["name"]
- qualifiers = constructor.get("qualifiers")
-
- return_element = constructor.find("return")
- if return_element is not None:
- return_type = TypeName.from_element(return_element)
- else:
- return_type = TypeName("void")
-
- params = self.parse_params(constructor, "constructor")
-
- desc_element = constructor.find("description")
- method_desc = None
- if desc_element is not None:
- method_desc = desc_element.text
-
- method_def = MethodDef(method_name, return_type, params, method_desc, qualifiers)
- method_def.definition_name = "constructor"
- if method_name not in class_def.constructors:
- class_def.constructors[method_name] = []
-
- class_def.constructors[method_name].append(method_def)
-
- methods = class_root.find("methods")
- if methods is not None:
- for method in methods:
- assert method.tag == "method"
-
- method_name = method.attrib["name"]
- qualifiers = method.get("qualifiers")
-
- return_element = method.find("return")
- if return_element is not None:
- return_type = TypeName.from_element(return_element)
-
- else:
- return_type = TypeName("void")
-
- params = self.parse_params(method, "method")
-
- desc_element = method.find("description")
- method_desc = None
- if desc_element is not None:
- method_desc = desc_element.text
-
- method_def = MethodDef(method_name, return_type, params, method_desc, qualifiers)
- if method_name not in class_def.methods:
- class_def.methods[method_name] = []
-
- class_def.methods[method_name].append(method_def)
-
- operators = class_root.find("operators")
- if operators is not None:
- for operator in operators:
- assert operator.tag == "operator"
-
- method_name = operator.attrib["name"]
- qualifiers = operator.get("qualifiers")
-
- return_element = operator.find("return")
- if return_element is not None:
- return_type = TypeName.from_element(return_element)
-
- else:
- return_type = TypeName("void")
-
- params = self.parse_params(operator, "operator")
-
- desc_element = operator.find("description")
- method_desc = None
- if desc_element is not None:
- method_desc = desc_element.text
-
- method_def = MethodDef(method_name, return_type, params, method_desc, qualifiers)
- method_def.definition_name = "operator"
- if method_name not in class_def.operators:
- class_def.operators[method_name] = []
-
- class_def.operators[method_name].append(method_def)
-
- constants = class_root.find("constants")
- if constants is not None:
- for constant in constants:
- assert constant.tag == "constant"
-
- constant_name = constant.attrib["name"]
- value = constant.attrib["value"]
- enum = constant.get("enum")
- is_bitfield = constant.get("is_bitfield") == "true"
- constant_def = ConstantDef(constant_name, value, constant.text, is_bitfield)
- if enum is None:
- if constant_name in class_def.constants:
- print_error(f'{class_name}.xml: Duplicate constant "{constant_name}".', self)
- continue
-
- class_def.constants[constant_name] = constant_def
-
- else:
- if enum in class_def.enums:
- enum_def = class_def.enums[enum]
-
- else:
- enum_def = EnumDef(enum, TypeName("int", enum), is_bitfield)
- class_def.enums[enum] = enum_def
-
- enum_def.values[constant_name] = constant_def
-
- annotations = class_root.find("annotations")
- if annotations is not None:
- for annotation in annotations:
- assert annotation.tag == "annotation"
-
- annotation_name = annotation.attrib["name"]
- qualifiers = annotation.get("qualifiers")
-
- params = self.parse_params(annotation, "annotation")
-
- desc_element = annotation.find("description")
- annotation_desc = None
- if desc_element is not None:
- annotation_desc = desc_element.text
-
- annotation_def = AnnotationDef(annotation_name, params, annotation_desc, qualifiers)
- if annotation_name not in class_def.annotations:
- class_def.annotations[annotation_name] = []
-
- class_def.annotations[annotation_name].append(annotation_def)
-
- signals = class_root.find("signals")
- if signals is not None:
- for signal in signals:
- assert signal.tag == "signal"
-
- signal_name = signal.attrib["name"]
-
- if signal_name in class_def.signals:
- print_error(f'{class_name}.xml: Duplicate signal "{signal_name}".', self)
- continue
-
- params = self.parse_params(signal, "signal")
-
- desc_element = signal.find("description")
- signal_desc = None
- if desc_element is not None:
- signal_desc = desc_element.text
-
- signal_def = SignalDef(signal_name, params, signal_desc)
- class_def.signals[signal_name] = signal_def
-
- theme_items = class_root.find("theme_items")
- if theme_items is not None:
- for theme_item in theme_items:
- assert theme_item.tag == "theme_item"
-
- theme_item_name = theme_item.attrib["name"]
- theme_item_data_name = theme_item.attrib["data_type"]
- theme_item_id = "{}_{}".format(theme_item_data_name, theme_item_name)
- if theme_item_id in class_def.theme_items:
- print_error(
- f'{class_name}.xml: Duplicate theme item "{theme_item_name}" of type "{theme_item_data_name}".',
- self,
- )
- continue
-
- default_value = theme_item.get("default") or None
- if default_value is not None:
- default_value = f"``{default_value}``"
-
- theme_item_def = ThemeItemDef(
- theme_item_name,
- TypeName.from_element(theme_item),
- theme_item_data_name,
- theme_item.text,
- default_value,
- )
- class_def.theme_items[theme_item_name] = theme_item_def
-
- tutorials = class_root.find("tutorials")
- if tutorials is not None:
- for link in tutorials:
- assert link.tag == "link"
-
- if link.text is not None:
- class_def.tutorials.append((link.text.strip(), link.get("title", "")))
-
- self.current_class = ""
-
- def parse_params(self, root: ET.Element, context: str) -> List["ParameterDef"]:
- param_elements = root.findall("param")
- params: Any = [None] * len(param_elements)
-
- for param_index, param_element in enumerate(param_elements):
- param_name = param_element.attrib["name"]
- index = int(param_element.attrib["index"])
- type_name = TypeName.from_element(param_element)
- default = param_element.get("default")
-
- if param_name.strip() == "" or param_name.startswith("_unnamed_arg"):
- print_error(
- f'{self.current_class}.xml: Empty argument name in {context} "{root.attrib["name"]}" at position {param_index}.',
- self,
- )
-
- params[index] = ParameterDef(param_name, type_name, default)
-
- cast: List[ParameterDef] = params
-
- return cast
-
- def sort_classes(self) -> None:
- self.classes = OrderedDict(sorted(self.classes.items(), key=lambda t: t[0].lower()))
-
-
-class TypeName:
- def __init__(self, type_name: str, enum: Optional[str] = None, is_bitfield: bool = False) -> None:
- self.type_name = type_name
- self.enum = enum
- self.is_bitfield = is_bitfield
-
- def to_rst(self, state: State) -> str:
- if self.enum is not None:
- return make_enum(self.enum, self.is_bitfield, state)
- elif self.type_name == "void":
- return "void"
- else:
- return make_type(self.type_name, state)
-
- @classmethod
- def from_element(cls, element: ET.Element) -> "TypeName":
- return cls(element.attrib["type"], element.get("enum"), element.get("is_bitfield") == "true")
-
-
-class DefinitionBase:
- def __init__(
- self,
- definition_name: str,
- name: str,
- ) -> None:
- self.definition_name = definition_name
- self.name = name
-
-
-class PropertyDef(DefinitionBase):
- def __init__(
- self,
- name: str,
- type_name: TypeName,
- setter: Optional[str],
- getter: Optional[str],
- text: Optional[str],
- default_value: Optional[str],
- overrides: Optional[str],
- ) -> None:
- super().__init__("property", name)
-
- self.type_name = type_name
- self.setter = setter
- self.getter = getter
- self.text = text
- self.default_value = default_value
- self.overrides = overrides
-
-
-class ParameterDef(DefinitionBase):
- def __init__(self, name: str, type_name: TypeName, default_value: Optional[str]) -> None:
- super().__init__("parameter", name)
-
- self.type_name = type_name
- self.default_value = default_value
-
-
-class SignalDef(DefinitionBase):
- def __init__(self, name: str, parameters: List[ParameterDef], description: Optional[str]) -> None:
- super().__init__("signal", name)
-
- self.parameters = parameters
- self.description = description
-
-
-class AnnotationDef(DefinitionBase):
- def __init__(
- self,
- name: str,
- parameters: List[ParameterDef],
- description: Optional[str],
- qualifiers: Optional[str],
- ) -> None:
- super().__init__("annotation", name)
-
- self.parameters = parameters
- self.description = description
- self.qualifiers = qualifiers
-
-
-class MethodDef(DefinitionBase):
- def __init__(
- self,
- name: str,
- return_type: TypeName,
- parameters: List[ParameterDef],
- description: Optional[str],
- qualifiers: Optional[str],
- ) -> None:
- super().__init__("method", name)
-
- self.return_type = return_type
- self.parameters = parameters
- self.description = description
- self.qualifiers = qualifiers
-
-
-class ConstantDef(DefinitionBase):
- def __init__(self, name: str, value: str, text: Optional[str], bitfield: bool) -> None:
- super().__init__("constant", name)
-
- self.value = value
- self.text = text
- self.is_bitfield = bitfield
-
-
-class EnumDef(DefinitionBase):
- def __init__(self, name: str, type_name: TypeName, bitfield: bool) -> None:
- super().__init__("enum", name)
-
- self.type_name = type_name
- self.values: OrderedDict[str, ConstantDef] = OrderedDict()
- self.is_bitfield = bitfield
-
-
-class ThemeItemDef(DefinitionBase):
- def __init__(
- self, name: str, type_name: TypeName, data_name: str, text: Optional[str], default_value: Optional[str]
- ) -> None:
- super().__init__("theme item", name)
-
- self.type_name = type_name
- self.data_name = data_name
- self.text = text
- self.default_value = default_value
-
-
-class ClassDef(DefinitionBase):
- def __init__(self, name: str) -> None:
- super().__init__("class", name)
-
- self.constants: OrderedDict[str, ConstantDef] = OrderedDict()
- self.enums: OrderedDict[str, EnumDef] = OrderedDict()
- self.properties: OrderedDict[str, PropertyDef] = OrderedDict()
- self.constructors: OrderedDict[str, List[MethodDef]] = OrderedDict()
- self.methods: OrderedDict[str, List[MethodDef]] = OrderedDict()
- self.operators: OrderedDict[str, List[MethodDef]] = OrderedDict()
- self.signals: OrderedDict[str, SignalDef] = OrderedDict()
- self.annotations: OrderedDict[str, List[AnnotationDef]] = OrderedDict()
- self.theme_items: OrderedDict[str, ThemeItemDef] = OrderedDict()
- self.inherits: Optional[str] = None
- self.brief_description: Optional[str] = None
- self.description: Optional[str] = None
- self.tutorials: List[Tuple[str, str]] = []
-
- # Used to match the class with XML source for output filtering purposes.
- self.filepath: str = ""
-
-
-# Entry point for the RST generator.
-def main() -> None:
- # Enable ANSI escape code support on Windows 10 and later (for colored console output).
- #
- if platform.system().lower() == "windows":
- from ctypes import windll, c_int, byref # type: ignore
-
- stdout_handle = windll.kernel32.GetStdHandle(c_int(-11))
- mode = c_int(0)
- windll.kernel32.GetConsoleMode(c_int(stdout_handle), byref(mode))
- mode = c_int(mode.value | 4)
- windll.kernel32.SetConsoleMode(c_int(stdout_handle), mode)
-
- parser = argparse.ArgumentParser()
- parser.add_argument("path", nargs="+", help="A path to an XML file or a directory containing XML files to parse.")
- parser.add_argument("--filter", default="", help="The filepath pattern for XML files to filter.")
- parser.add_argument("--lang", "-l", default="en", help="Language to use for section headings.")
- parser.add_argument(
- "--color",
- action="store_true",
- help="If passed, force colored output even if stdout is not a TTY (useful for continuous integration).",
- )
- group = parser.add_mutually_exclusive_group()
- group.add_argument("--output", "-o", default=".", help="The directory to save output .rst files in.")
- group.add_argument(
- "--dry-run",
- action="store_true",
- help="If passed, no output will be generated and XML files are only checked for errors.",
- )
- args = parser.parse_args()
-
- should_color = args.color or (hasattr(sys.stdout, "isatty") and sys.stdout.isatty())
- STYLES["red"] = "\x1b[91m" if should_color else ""
- STYLES["green"] = "\x1b[92m" if should_color else ""
- STYLES["yellow"] = "\x1b[93m" if should_color else ""
- STYLES["bold"] = "\x1b[1m" if should_color else ""
- STYLES["regular"] = "\x1b[22m" if should_color else ""
- STYLES["reset"] = "\x1b[0m" if should_color else ""
-
- # Retrieve heading translations for the given language.
- if not args.dry_run and args.lang != "en":
- lang_file = os.path.join(
- os.path.dirname(os.path.realpath(__file__)), "..", "translations", "{}.po".format(args.lang)
- )
- if os.path.exists(lang_file):
- try:
- import polib # type: ignore
- except ImportError:
- print("Base template strings localization requires `polib`.")
- exit(1)
-
- pofile = polib.pofile(lang_file)
- for entry in pofile.translated_entries():
- if entry.msgid in BASE_STRINGS:
- strings_l10n[entry.msgid] = entry.msgstr
- else:
- print(f'No PO file at "{lang_file}" for language "{args.lang}".')
-
- print("Checking for errors in the XML class reference...")
-
- file_list: List[str] = []
-
- for path in args.path:
- # Cut off trailing slashes so os.path.basename doesn't choke.
- if path.endswith("/") or path.endswith("\\"):
- path = path[:-1]
-
- if os.path.basename(path) in ["modules", "platform"]:
- for subdir, dirs, _ in os.walk(path):
- if "doc_classes" in dirs:
- doc_dir = os.path.join(subdir, "doc_classes")
- class_file_names = (f for f in os.listdir(doc_dir) if f.endswith(".xml"))
- file_list += (os.path.join(doc_dir, f) for f in class_file_names)
-
- elif os.path.isdir(path):
- file_list += (os.path.join(path, f) for f in os.listdir(path) if f.endswith(".xml"))
-
- elif os.path.isfile(path):
- if not path.endswith(".xml"):
- print(f'Got non-.xml file "{path}" in input, skipping.')
- continue
-
- file_list.append(path)
-
- classes: Dict[str, Tuple[ET.Element, str]] = {}
- state = State()
-
- for cur_file in file_list:
- try:
- tree = ET.parse(cur_file)
- except ET.ParseError as e:
- print_error(f"{cur_file}: Parse error while reading the file: {e}", state)
- continue
- doc = tree.getroot()
-
- name = doc.attrib["name"]
- if name in classes:
- print_error(f'{cur_file}: Duplicate class "{name}".', state)
- continue
-
- classes[name] = (doc, cur_file)
-
- for name, data in classes.items():
- try:
- state.parse_class(data[0], data[1])
- except Exception as e:
- print_error(f"{name}.xml: Exception while parsing class: {e}", state)
-
- state.sort_classes()
-
- pattern = re.compile(args.filter)
-
- # Create the output folder recursively if it doesn't already exist.
- os.makedirs(args.output, exist_ok=True)
-
- print("Generating the RST class reference...")
-
- grouped_classes: Dict[str, List[str]] = {}
-
- for class_name, class_def in state.classes.items():
- if args.filter and not pattern.search(class_def.filepath):
- continue
- state.current_class = class_name
- make_rst_class(class_def, state, args.dry_run, args.output)
-
- group_name = get_class_group(class_def, state)
-
- if group_name not in grouped_classes:
- grouped_classes[group_name] = []
- grouped_classes[group_name].append(class_name)
-
- if is_editor_class(class_def):
- if "editor" not in grouped_classes:
- grouped_classes["editor"] = []
- grouped_classes["editor"].append(class_name)
-
- print("")
- print("Generating the index file...")
-
- make_rst_index(grouped_classes, args.dry_run, args.output)
-
- print("")
-
- if state.num_warnings >= 2:
- print(
- f'{STYLES["yellow"]}{state.num_warnings} warnings were found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
- )
- elif state.num_warnings == 1:
- print(
- f'{STYLES["yellow"]}1 warning was found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
- )
-
- if state.num_errors == 0:
- print(f'{STYLES["green"]}No errors found in the class reference XML.{STYLES["reset"]}')
- if not args.dry_run:
- print(f"Wrote reStructuredText files for each class to: {args.output}")
- else:
- if state.num_errors >= 2:
- print(
- f'{STYLES["red"]}{state.num_errors} errors were found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
- )
- else:
- print(
- f'{STYLES["red"]}1 error was found in the class reference XML. Please check the messages above.{STYLES["reset"]}'
- )
- exit(1)
-
-
-# Common helpers.
-
-
-def print_error(error: str, state: State) -> None:
- print(f'{STYLES["red"]}{STYLES["bold"]}ERROR:{STYLES["regular"]} {error}{STYLES["reset"]}')
- state.num_errors += 1
-
-
-def print_warning(warning: str, state: State) -> None:
- print(f'{STYLES["yellow"]}{STYLES["bold"]}WARNING:{STYLES["regular"]} {warning}{STYLES["reset"]}')
- state.num_warnings += 1
-
-
-def translate(string: str) -> str:
- """Translate a string based on translations sourced from `doc/translations/*.po`
- for a language if defined via the --lang command line argument.
- Returns the original string if no translation exists.
- """
- return strings_l10n.get(string, string)
-
-
-def get_git_branch() -> str:
- if hasattr(version, "docs") and version.docs != "latest":
- return version.docs
-
- return "master"
-
-
-def get_class_group(class_def: ClassDef, state: State) -> str:
- group_name = "node"
- class_name = class_def.name
-
- if class_name.startswith("@"):
- group_name = "global"
- elif class_def.inherits:
- inherits = class_def.inherits.strip()
-
- while inherits in state.classes:
- if inherits == "Node":
- group_name = "node"
- break
- if inherits == "Resource":
- group_name = "resource"
- break
- if inherits == "Object":
- group_name = "object"
- break
-
- inode = state.classes[inherits].inherits
- if inode:
- inherits = inode.strip()
- else:
- break
-
- # Assume the group
- if inherits == "Node":
- group_name = "node"
- if inherits == "Resource":
- group_name = "resource"
- if inherits == "Object":
- group_name = "object"
- if inherits == "RefCounted":
- group_name = "object"
-
- return group_name
-
-
-def is_editor_class(class_def: ClassDef) -> bool:
- class_name = class_def.name
-
- if class_name.startswith("Editor"):
- return True
- if class_name in EDITOR_CLASSES:
- return True
-
- return False
-
-
-# Generator methods.
-
-
-def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: str) -> None:
- class_name = class_def.name
-
- if dry_run:
- f = open(os.devnull, "w", encoding="utf-8")
- else:
- f = open(os.path.join(output_dir, f"class_{class_name.lower()}.rst"), "w", encoding="utf-8")
-
- # Remove the "Edit on Github" button from the online docs page.
- f.write(":github_url: hide\n\n")
-
- # Warn contributors not to edit this file directly.
- # Also provide links to the source files for reference.
-
- git_branch = get_git_branch()
- source_xml_path = os.path.relpath(class_def.filepath, root_directory).replace("\\", "/")
- source_github_url = f"https://github.com/godotengine/godot/tree/{git_branch}/{source_xml_path}"
- generator_github_url = f"https://github.com/godotengine/godot/tree/{git_branch}/doc/tools/make_rst.py"
-
- f.write(".. DO NOT EDIT THIS FILE!!!\n")
- f.write(".. Generated automatically from Godot engine sources.\n")
- f.write(f".. Generator: {generator_github_url}.\n")
- f.write(f".. XML source: {source_github_url}.\n\n")
-
- # Document reference id and header.
- f.write(f".. _class_{class_name}:\n\n")
- f.write(make_heading(class_name, "=", False))
-
- ### INHERITANCE TREE ###
-
- # Ascendants
- if class_def.inherits:
- inherits = class_def.inherits.strip()
- f.write(f'**{translate("Inherits:")}** ')
- first = True
- while inherits in state.classes:
- if not first:
- f.write(" **<** ")
- else:
- first = False
-
- f.write(make_type(inherits, state))
- inode = state.classes[inherits].inherits
- if inode:
- inherits = inode.strip()
- else:
- break
-
- # Assume we inherit from a Godot class if not found
- if inherits not in state.classes:
- if not first:
- f.write(" **<** ")
- f.write(make_type(inherits, state))
-
- f.write("\n\n")
-
- # Descendants
- inherited: List[str] = []
- for c in state.classes.values():
- if c.inherits and c.inherits.strip() == class_name:
- inherited.append(c.name)
-
- if len(inherited):
- f.write(f'**{translate("Inherited By:")}** ')
- for i, child in enumerate(inherited):
- if i > 0:
- f.write(", ")
- f.write(make_type(child, state))
- f.write("\n\n")
-
- ### INTRODUCTION ###
-
- has_description = False
-
- # Brief description
- if class_def.brief_description is not None and class_def.brief_description.strip() != "":
- has_description = True
-
- f.write(f"{format_text_block(class_def.brief_description.strip(), class_def, state)}\n\n")
-
- # Class description
- if class_def.description is not None and class_def.description.strip() != "":
- has_description = True
-
- f.write(".. rst-class:: classref-introduction-group\n\n")
- f.write(make_heading("Description", "-"))
-
- f.write(f"{format_text_block(class_def.description.strip(), class_def, state)}\n\n")
-
- if not has_description:
- f.write(".. container:: contribute\n\n\t")
- f.write(
- translate(
- "There is currently no description for this class. Please help us by :ref:`contributing one `!"
- )
- + "\n\n"
- )
-
- if class_def.name in CLASSES_WITH_CSHARP_DIFFERENCES:
- f.write(".. note::\n\n\t")
- f.write(
- translate(
- "There are notable differences when using this API with C#. See :ref:`doc_c_sharp_differences` for more information."
- )
- + "\n\n"
- )
-
- # Online tutorials
- if len(class_def.tutorials) > 0:
- f.write(".. rst-class:: classref-introduction-group\n\n")
- f.write(make_heading("Tutorials", "-"))
-
- for url, title in class_def.tutorials:
- f.write(f"- {make_link(url, title)}\n\n")
-
- ### REFERENCE TABLES ###
-
- # Reused container for reference tables.
- ml: List[Tuple[Optional[str], ...]] = []
-
- # Properties reference table
- if len(class_def.properties) > 0:
- f.write(".. rst-class:: classref-reftable-group\n\n")
- f.write(make_heading("Properties", "-"))
-
- ml = []
- for property_def in class_def.properties.values():
- type_rst = property_def.type_name.to_rst(state)
- default = property_def.default_value
- if default is not None and property_def.overrides:
- ref = f":ref:`{property_def.overrides}`"
- # Not using translate() for now as it breaks table formatting.
- ml.append((type_rst, property_def.name, f"{default} (overrides {ref})"))
- else:
- ref = f":ref:`{property_def.name}`"
- ml.append((type_rst, ref, default))
-
- format_table(f, ml, True)
-
- # Constructors, Methods, Operators reference tables
- if len(class_def.constructors) > 0:
- f.write(".. rst-class:: classref-reftable-group\n\n")
- f.write(make_heading("Constructors", "-"))
-
- ml = []
- for method_list in class_def.constructors.values():
- for m in method_list:
- ml.append(make_method_signature(class_def, m, "constructor", state))
-
- format_table(f, ml)
-
- if len(class_def.methods) > 0:
- f.write(".. rst-class:: classref-reftable-group\n\n")
- f.write(make_heading("Methods", "-"))
-
- ml = []
- for method_list in class_def.methods.values():
- for m in method_list:
- ml.append(make_method_signature(class_def, m, "method", state))
-
- format_table(f, ml)
-
- if len(class_def.operators) > 0:
- f.write(".. rst-class:: classref-reftable-group\n\n")
- f.write(make_heading("Operators", "-"))
-
- ml = []
- for method_list in class_def.operators.values():
- for m in method_list:
- ml.append(make_method_signature(class_def, m, "operator", state))
-
- format_table(f, ml)
-
- # Theme properties reference table
- if len(class_def.theme_items) > 0:
- f.write(".. rst-class:: classref-reftable-group\n\n")
- f.write(make_heading("Theme Properties", "-"))
-
- ml = []
- for theme_item_def in class_def.theme_items.values():
- ref = f":ref:`{theme_item_def.name}`"
- ml.append((theme_item_def.type_name.to_rst(state), ref, theme_item_def.default_value))
-
- format_table(f, ml, True)
-
- ### DETAILED DESCRIPTIONS ###
-
- # Signal descriptions
- if len(class_def.signals) > 0:
- f.write(make_separator(True))
- f.write(".. rst-class:: classref-descriptions-group\n\n")
- f.write(make_heading("Signals", "-"))
-
- index = 0
-
- for signal in class_def.signals.values():
- if index != 0:
- f.write(make_separator())
-
- # Create signal signature and anchor point.
-
- f.write(f".. _class_{class_name}_signal_{signal.name}:\n\n")
- f.write(".. rst-class:: classref-signal\n\n")
-
- _, signature = make_method_signature(class_def, signal, "", state)
- f.write(f"{signature}\n\n")
-
- # Add signal description, or a call to action if it's missing.
-
- if signal.description is not None and signal.description.strip() != "":
- f.write(f"{format_text_block(signal.description.strip(), signal, state)}\n\n")
- else:
- f.write(".. container:: contribute\n\n\t")
- f.write(
- translate(
- "There is currently no description for this signal. Please help us by :ref:`contributing one `!"
- )
- + "\n\n"
- )
-
- index += 1
-
- # Enumeration descriptions
- if len(class_def.enums) > 0:
- f.write(make_separator(True))
- f.write(".. rst-class:: classref-descriptions-group\n\n")
- f.write(make_heading("Enumerations", "-"))
-
- index = 0
-
- for e in class_def.enums.values():
- if index != 0:
- f.write(make_separator())
-
- # Create enumeration signature and anchor point.
-
- f.write(f".. _enum_{class_name}_{e.name}:\n\n")
- f.write(".. rst-class:: classref-enumeration\n\n")
-
- if e.is_bitfield:
- f.write(f"flags **{e.name}**:\n\n")
- else:
- f.write(f"enum **{e.name}**:\n\n")
-
- for value in e.values.values():
- # Also create signature and anchor point for each enum constant.
-
- f.write(f".. _class_{class_name}_constant_{value.name}:\n\n")
- f.write(".. rst-class:: classref-enumeration-constant\n\n")
-
- f.write(f"{e.type_name.to_rst(state)} **{value.name}** = ``{value.value}``\n\n")
-
- # Add enum constant description.
-
- if value.text is not None and value.text.strip() != "":
- f.write(f"{format_text_block(value.text.strip(), value, state)}")
-
- f.write("\n\n")
-
- index += 1
-
- # Constant descriptions
- if len(class_def.constants) > 0:
- f.write(make_separator(True))
- f.write(".. rst-class:: classref-descriptions-group\n\n")
- f.write(make_heading("Constants", "-"))
-
- for constant in class_def.constants.values():
- # Create constant signature and anchor point.
-
- f.write(f".. _class_{class_name}_constant_{constant.name}:\n\n")
- f.write(".. rst-class:: classref-constant\n\n")
-
- f.write(f"**{constant.name}** = ``{constant.value}``\n\n")
-
- # Add enum constant description.
-
- if constant.text is not None and constant.text.strip() != "":
- f.write(f"{format_text_block(constant.text.strip(), constant, state)}")
-
- f.write("\n\n")
-
- # Annotation descriptions
- if len(class_def.annotations) > 0:
- f.write(make_separator(True))
- f.write(make_heading("Annotations", "-"))
-
- index = 0
-
- for method_list in class_def.annotations.values(): # type: ignore
- for i, m in enumerate(method_list):
- if index != 0:
- f.write(make_separator())
-
- # Create annotation signature and anchor point.
-
- if i == 0:
- f.write(f".. _class_{class_name}_annotation_{m.name}:\n\n")
-
- f.write(".. rst-class:: classref-annotation\n\n")
-
- _, signature = make_method_signature(class_def, m, "", state)
- f.write(f"{signature}\n\n")
-
- # Add annotation description, or a call to action if it's missing.
-
- if m.description is not None and m.description.strip() != "":
- f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n")
- else:
- f.write(".. container:: contribute\n\n\t")
- f.write(
- translate(
- "There is currently no description for this annotation. Please help us by :ref:`contributing one `!"
- )
- + "\n\n"
- )
-
- index += 1
-
- # Property descriptions
- if any(not p.overrides for p in class_def.properties.values()) > 0:
- f.write(make_separator(True))
- f.write(".. rst-class:: classref-descriptions-group\n\n")
- f.write(make_heading("Property Descriptions", "-"))
-
- index = 0
-
- for property_def in class_def.properties.values():
- if property_def.overrides:
- continue
-
- if index != 0:
- f.write(make_separator())
-
- # Create property signature and anchor point.
-
- f.write(f".. _class_{class_name}_property_{property_def.name}:\n\n")
- f.write(".. rst-class:: classref-property\n\n")
-
- property_default = ""
- if property_def.default_value is not None:
- property_default = f" = {property_def.default_value}"
- f.write(f"{property_def.type_name.to_rst(state)} **{property_def.name}**{property_default}\n\n")
-
- # Create property setter and getter records.
-
- property_setget = ""
-
- if property_def.setter is not None and not property_def.setter.startswith("_"):
- property_setter = make_setter_signature(class_def, property_def, state)
- property_setget += f"- {property_setter}\n"
-
- if property_def.getter is not None and not property_def.getter.startswith("_"):
- property_getter = make_getter_signature(class_def, property_def, state)
- property_setget += f"- {property_getter}\n"
-
- if property_setget != "":
- f.write(".. rst-class:: classref-property-setget\n\n")
- f.write(property_setget)
- f.write("\n")
-
- # Add property description, or a call to action if it's missing.
-
- if property_def.text is not None and property_def.text.strip() != "":
- f.write(f"{format_text_block(property_def.text.strip(), property_def, state)}\n\n")
- else:
- f.write(".. container:: contribute\n\n\t")
- f.write(
- translate(
- "There is currently no description for this property. Please help us by :ref:`contributing one `!"
- )
- + "\n\n"
- )
-
- index += 1
-
- # Constructor, Method, Operator descriptions
- if len(class_def.constructors) > 0:
- f.write(make_separator(True))
- f.write(".. rst-class:: classref-descriptions-group\n\n")
- f.write(make_heading("Constructor Descriptions", "-"))
-
- index = 0
-
- for method_list in class_def.constructors.values():
- for i, m in enumerate(method_list):
- if index != 0:
- f.write(make_separator())
-
- # Create constructor signature and anchor point.
-
- if i == 0:
- f.write(f".. _class_{class_name}_constructor_{m.name}:\n\n")
-
- f.write(".. rst-class:: classref-constructor\n\n")
-
- ret_type, signature = make_method_signature(class_def, m, "", state)
- f.write(f"{ret_type} {signature}\n\n")
-
- # Add constructor description, or a call to action if it's missing.
-
- if m.description is not None and m.description.strip() != "":
- f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n")
- else:
- f.write(".. container:: contribute\n\n\t")
- f.write(
- translate(
- "There is currently no description for this constructor. Please help us by :ref:`contributing one `!"
- )
- + "\n\n"
- )
-
- index += 1
-
- if len(class_def.methods) > 0:
- f.write(make_separator(True))
- f.write(".. rst-class:: classref-descriptions-group\n\n")
- f.write(make_heading("Method Descriptions", "-"))
-
- index = 0
-
- for method_list in class_def.methods.values():
- for i, m in enumerate(method_list):
- if index != 0:
- f.write(make_separator())
-
- # Create method signature and anchor point.
-
- if i == 0:
- f.write(f".. _class_{class_name}_method_{m.name}:\n\n")
-
- f.write(".. rst-class:: classref-method\n\n")
-
- ret_type, signature = make_method_signature(class_def, m, "", state)
- f.write(f"{ret_type} {signature}\n\n")
-
- # Add method description, or a call to action if it's missing.
-
- if m.description is not None and m.description.strip() != "":
- f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n")
- else:
- f.write(".. container:: contribute\n\n\t")
- f.write(
- translate(
- "There is currently no description for this method. Please help us by :ref:`contributing one `!"
- )
- + "\n\n"
- )
-
- index += 1
-
- if len(class_def.operators) > 0:
- f.write(make_separator(True))
- f.write(".. rst-class:: classref-descriptions-group\n\n")
- f.write(make_heading("Operator Descriptions", "-"))
-
- index = 0
-
- for method_list in class_def.operators.values():
- for i, m in enumerate(method_list):
- if index != 0:
- f.write(make_separator())
-
- # Create operator signature and anchor point.
-
- operator_anchor = f".. _class_{class_name}_operator_{sanitize_operator_name(m.name, state)}"
- for parameter in m.parameters:
- operator_anchor += f"_{parameter.type_name.type_name}"
- operator_anchor += f":\n\n"
- f.write(operator_anchor)
-
- f.write(".. rst-class:: classref-operator\n\n")
-
- ret_type, signature = make_method_signature(class_def, m, "", state)
- f.write(f"{ret_type} {signature}\n\n")
-
- # Add operator description, or a call to action if it's missing.
-
- if m.description is not None and m.description.strip() != "":
- f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n")
- else:
- f.write(".. container:: contribute\n\n\t")
- f.write(
- translate(
- "There is currently no description for this operator. Please help us by :ref:`contributing one `!"
- )
- + "\n\n"
- )
-
- index += 1
-
- # Theme property descriptions
- if len(class_def.theme_items) > 0:
- f.write(make_separator(True))
- f.write(".. rst-class:: classref-descriptions-group\n\n")
- f.write(make_heading("Theme Property Descriptions", "-"))
-
- index = 0
-
- for theme_item_def in class_def.theme_items.values():
- if index != 0:
- f.write(make_separator())
-
- # Create theme property signature and anchor point.
-
- f.write(f".. _class_{class_name}_theme_{theme_item_def.data_name}_{theme_item_def.name}:\n\n")
- f.write(".. rst-class:: classref-themeproperty\n\n")
-
- theme_item_default = ""
- if theme_item_def.default_value is not None:
- theme_item_default = f" = {theme_item_def.default_value}"
- f.write(f"{theme_item_def.type_name.to_rst(state)} **{theme_item_def.name}**{theme_item_default}\n\n")
-
- # Add theme property description, or a call to action if it's missing.
-
- if theme_item_def.text is not None and theme_item_def.text.strip() != "":
- f.write(f"{format_text_block(theme_item_def.text.strip(), theme_item_def, state)}\n\n")
- else:
- f.write(".. container:: contribute\n\n\t")
- f.write(
- translate(
- "There is currently no description for this theme property. Please help us by :ref:`contributing one `!"
- )
- + "\n\n"
- )
-
- index += 1
-
- f.write(make_footer())
-
-
-def make_type(klass: str, state: State) -> str:
- if klass.find("*") != -1: # Pointer, ignore
- return klass
- link_type = klass
- if link_type.endswith("[]"): # Typed array, strip [] to link to contained type.
- link_type = link_type[:-2]
- if link_type in state.classes:
- return f":ref:`{klass}`"
- # Assume failures should link to Godot docs
- return f"`{link_type} `_"
- #print_error(f'{state.current_class}.xml: Unresolved type "{klass}".', state)
- #return klass
-
-
-def make_enum(t: str, is_bitfield: bool, state: State) -> str:
- p = t.find(".")
- if p >= 0:
- c = t[0:p]
- e = t[p + 1 :]
- # Variant enums live in GlobalScope but still use periods.
- if c == "Variant":
- c = "@GlobalScope"
- e = "Variant." + e
- else:
- c = state.current_class
- e = t
- if c in state.classes and e not in state.classes[c].enums:
- c = "@GlobalScope"
-
- if c in state.classes and e in state.classes[c].enums:
- if is_bitfield:
- if not state.classes[c].enums[e].is_bitfield:
- print_error(f'{state.current_class}.xml: Enum "{t}" is not bitfield.', state)
- return f"|bitfield|\<:ref:`{e}`\>"
- else:
- return f":ref:`{e}`"
-
- # HashingContext.HashType
- # https://docs.godotengine.org/en/stable/classes/class_hashingcontext.html#enum-hashingcontext-hashtype
- parts = t.split(".")
- if len(parts) > 1:
- class_name = parts[0]
- child = parts[1]
- return f"`{t} `_"
-
- # Assume failures will link to Godot docs
- return f"`{t} `_"
-
- # Don't fail for `Vector3.Axis`, as this enum is a special case which is expected not to be resolved.
- if f"{c}.{e}" != "Vector3.Axis":
- print_error(f'{state.current_class}.xml: Unresolved enum "{t}".', state)
-
- return t
-
-
-def make_method_signature(
- class_def: ClassDef, definition: Union[AnnotationDef, MethodDef, SignalDef], ref_type: str, state: State
-) -> Tuple[str, str]:
- ret_type = ""
-
- if isinstance(definition, MethodDef):
- ret_type = definition.return_type.to_rst(state)
-
- qualifiers = None
- if isinstance(definition, (MethodDef, AnnotationDef)):
- qualifiers = definition.qualifiers
-
- out = ""
- if isinstance(definition, MethodDef) and ref_type != "":
- if ref_type == "operator":
- op_name = definition.name.replace("<", "\\<") # So operator "<" gets correctly displayed.
- out += f":ref:`{op_name}` "
- else:
- out += f":ref:`{definition.name}` "
- else:
- out += f"**{definition.name}** "
-
- out += "**(**"
- for i, arg in enumerate(definition.parameters):
- if i > 0:
- out += ", "
- else:
- out += " "
-
- out += f"{arg.type_name.to_rst(state)} {arg.name}"
-
- if arg.default_value is not None:
- out += f"={arg.default_value}"
-
- if qualifiers is not None and "vararg" in qualifiers:
- if len(definition.parameters) > 0:
- out += ", ..."
- else:
- out += " ..."
-
- out += " **)**"
-
- if qualifiers is not None:
- # Use substitutions for abbreviations. This is used to display tooltips on hover.
- # See `make_footer()` for descriptions.
- for qualifier in qualifiers.split():
- out += f" |{qualifier}|"
-
- return ret_type, out
-
-
-def make_setter_signature(class_def: ClassDef, property_def: PropertyDef, state: State) -> str:
- if property_def.setter is None:
- return ""
-
- # If setter is a method available as a method definition, we use that.
- if property_def.setter in class_def.methods:
- setter = class_def.methods[property_def.setter][0]
- # Otherwise we fake it with the information we have available.
- else:
- setter_params: List[ParameterDef] = []
- setter_params.append(ParameterDef("value", property_def.type_name, None))
- setter = MethodDef(property_def.setter, TypeName("void"), setter_params, None, None)
-
- ret_type, signature = make_method_signature(class_def, setter, "", state)
- return f"{ret_type} {signature}"
-
-
-def make_getter_signature(class_def: ClassDef, property_def: PropertyDef, state: State) -> str:
- if property_def.getter is None:
- return ""
-
- # If getter is a method available as a method definition, we use that.
- if property_def.getter in class_def.methods:
- getter = class_def.methods[property_def.getter][0]
- # Otherwise we fake it with the information we have available.
- else:
- getter_params: List[ParameterDef] = []
- getter = MethodDef(property_def.getter, property_def.type_name, getter_params, None, None)
-
- ret_type, signature = make_method_signature(class_def, getter, "", state)
- return f"{ret_type} {signature}"
-
-
-def make_heading(title: str, underline: str, l10n: bool = True) -> str:
- if l10n:
- new_title = translate(title)
- if new_title != title:
- title = new_title
- underline *= 2 # Double length to handle wide chars.
- return f"{title}\n{(underline * len(title))}\n\n"
-
-
-def make_footer() -> str:
- # Generate reusable abbreviation substitutions.
- # This way, we avoid bloating the generated rST with duplicate abbreviations.
- virtual_msg = translate("This method should typically be overridden by the user to have any effect.")
- const_msg = translate("This method has no side effects. It doesn't modify any of the instance's member variables.")
- vararg_msg = translate("This method accepts any number of arguments after the ones described here.")
- constructor_msg = translate("This method is used to construct a type.")
- static_msg = translate(
- "This method doesn't need an instance to be called, so it can be called directly using the class name."
- )
- operator_msg = translate("This method describes a valid operator to use with this type as left-hand operand.")
- bitfield_msg = translate("This value is an integer composed as a bitmask of the following flags.")
-
- return (
- f".. |virtual| replace:: :abbr:`virtual ({virtual_msg})`\n"
- f".. |const| replace:: :abbr:`const ({const_msg})`\n"
- f".. |vararg| replace:: :abbr:`vararg ({vararg_msg})`\n"
- f".. |constructor| replace:: :abbr:`constructor ({constructor_msg})`\n"
- f".. |static| replace:: :abbr:`static ({static_msg})`\n"
- f".. |operator| replace:: :abbr:`operator ({operator_msg})`\n"
- f".. |bitfield| replace:: :abbr:`BitField ({bitfield_msg})`\n"
- )
-
-
-def make_separator(section_level: bool = False) -> str:
- separator_class = "item"
- if section_level:
- separator_class = "section"
-
- return f".. rst-class:: classref-{separator_class}-separator\n\n----\n\n"
-
-
-def make_link(url: str, title: str) -> str:
- match = GODOT_DOCS_PATTERN.search(url)
- if match:
- groups = match.groups()
- if match.lastindex == 2:
- # Doc reference with fragment identifier: emit direct link to section with reference to page, for example:
- # `#calling-javascript-from-script in Exporting For Web`
- # Or use the title if provided.
- if title != "":
- return f"`{title} <../{groups[0]}.html{groups[1]}>`__"
- return f"`{groups[1]} <../{groups[0]}.html{groups[1]}>`__ in :doc:`../{groups[0]}`"
- elif match.lastindex == 1:
- # Doc reference, for example:
- # `Math`
- if title != "":
- return f":doc:`{title} <../{groups[0]}>`"
- return f":doc:`../{groups[0]}`"
-
- # External link, for example:
- # `http://enet.bespin.org/usergroup0.html`
- if title != "":
- return f"`{title} <{url}>`__"
- return f"`{url} <{url}>`__"
-
-
-def make_rst_index(grouped_classes: Dict[str, List[str]], dry_run: bool, output_dir: str) -> None:
- if dry_run:
- f = open(os.devnull, "w", encoding="utf-8")
- else:
- f = open(os.path.join(output_dir, "index.rst"), "w", encoding="utf-8")
-
- # Remove the "Edit on Github" button from the online docs page.
- f.write(":github_url: hide\n\n")
-
- # Warn contributors not to edit this file directly.
- # Also provide links to the source files for reference.
-
- git_branch = get_git_branch()
- generator_github_url = f"https://github.com/godotengine/godot/tree/{git_branch}/doc/tools/make_rst.py"
-
- f.write(".. DO NOT EDIT THIS FILE!!!\n")
- f.write(".. Generated automatically from Godot engine sources.\n")
- f.write(f".. Generator: {generator_github_url}.\n\n")
-
- f.write(".. _doc_class_reference:\n\n")
-
- main_title = translate("All classes")
- f.write(f"{main_title}\n")
- f.write(f"{'=' * len(main_title)}\n\n")
-
- for group_name in CLASS_GROUPS:
- if group_name in grouped_classes:
- group_title = translate(CLASS_GROUPS[group_name])
-
- f.write(f"{group_title}\n")
- f.write(f"{'=' * len(group_title)}\n\n")
-
- f.write(".. toctree::\n")
- f.write(" :maxdepth: 1\n")
- f.write(f" :name: toc-class-ref-{group_name}s\n")
- f.write("\n")
-
- if group_name in CLASS_GROUPS_BASE:
- f.write(f" class_{CLASS_GROUPS_BASE[group_name].lower()}\n")
-
- for class_name in grouped_classes[group_name]:
- if group_name in CLASS_GROUPS_BASE and CLASS_GROUPS_BASE[group_name].lower() == class_name.lower():
- continue
-
- f.write(f" class_{class_name.lower()}\n")
-
- f.write("\n")
-
-
-# Formatting helpers.
-
-
-RESERVED_FORMATTING_TAGS = ["i", "b", "u", "code", "kbd", "center", "url", "br"]
-RESERVED_CODEBLOCK_TAGS = ["codeblocks", "codeblock", "gdscript", "csharp"]
-RESERVED_CROSSLINK_TAGS = ["method", "member", "signal", "constant", "enum", "annotation", "theme_item", "param"]
-
-
-def is_in_tagset(tag_text: str, tagset: List[str]) -> bool:
- for tag in tagset:
- # Complete match.
- if tag_text == tag:
- return True
- # Tag with arguments.
- if tag_text.startswith(tag + " "):
- return True
- # Tag with arguments, special case for [url].
- if tag_text.startswith(tag + "="):
- return True
-
- return False
-
-
-def format_text_block(
- text: str,
- context: Union[DefinitionBase, None],
- state: State,
-) -> str:
- # Linebreak + tabs in the XML should become two line breaks unless in a "codeblock"
- pos = 0
- while True:
- pos = text.find("\n", pos)
- if pos == -1:
- break
-
- pre_text = text[:pos]
- indent_level = 0
- while pos + 1 < len(text) and text[pos + 1] == "\t":
- pos += 1
- indent_level += 1
- post_text = text[pos + 1 :]
-
- # Handle codeblocks
- if (
- post_text.startswith("[codeblock]")
- or post_text.startswith("[gdscript]")
- or post_text.startswith("[csharp]")
- ):
- block_type = post_text[1:].split("]")[0]
- result = format_codeblock(block_type, post_text, indent_level, state)
- if result is None:
- return ""
- text = f"{pre_text}{result[0]}"
- pos += result[1] - indent_level
-
- # Handle normal text
- else:
- text = f"{pre_text}\n\n{post_text}"
- pos += 2 - indent_level
-
- next_brac_pos = text.find("[")
- text = escape_rst(text, next_brac_pos)
-
- context_name = format_context_name(context)
-
- # Handle [tags]
- inside_code = False
- inside_code_tag = ""
- inside_code_tabs = False
- pos = 0
- tag_depth = 0
- while True:
- pos = text.find("[", pos)
- if pos == -1:
- break
-
- endq_pos = text.find("]", pos + 1)
- if endq_pos == -1:
- break
-
- pre_text = text[:pos]
- post_text = text[endq_pos + 1 :]
- tag_text = text[pos + 1 : endq_pos]
-
- escape_pre = False
- escape_post = False
-
- # Tag is a reference to a class.
- if tag_text in state.classes and not inside_code:
- if tag_text == state.current_class:
- # Don't create a link to the same class, format it as strong emphasis.
- tag_text = f"**{tag_text}**"
- else:
- tag_text = make_type(tag_text, state)
- escape_pre = True
- escape_post = True
-
- # Tag is a cross-reference or a formatting directive.
- else:
- cmd = tag_text
- space_pos = tag_text.find(" ")
-
- # Anything identified as a tag inside of a code block is valid,
- # unless it's a matching closing tag.
- if inside_code:
- # Exiting codeblocks and inline code tags.
-
- if inside_code_tag == cmd[1:]:
- if cmd == "/codeblock" or cmd == "/gdscript" or cmd == "/csharp":
- tag_text = ""
- tag_depth -= 1
- inside_code = False
- # Strip newline if the tag was alone on one
- if pre_text[-1] == "\n":
- pre_text = pre_text[:-1]
-
- elif cmd == "/code":
- tag_text = "``"
- tag_depth -= 1
- inside_code = False
- escape_post = True
-
- else:
- if cmd.startswith("/"):
- print_warning(
- f'{state.current_class}.xml: Potential error inside of a code tag, found a string that looks like a closing tag "[{cmd}]" in {context_name}.',
- state,
- )
-
- tag_text = f"[{tag_text}]"
-
- # Entering codeblocks and inline code tags.
-
- elif cmd == "codeblocks":
- tag_depth += 1
- tag_text = "\n.. tabs::"
- inside_code_tabs = True
- elif cmd == "/codeblocks":
- tag_depth -= 1
- tag_text = ""
- inside_code_tabs = False
-
- elif cmd == "codeblock" or cmd == "gdscript" or cmd == "csharp":
- tag_depth += 1
-
- if cmd == "gdscript":
- if not inside_code_tabs:
- print_error(
- f"{state.current_class}.xml: GDScript code block is used outside of [codeblocks] in {context_name}.",
- state,
- )
- tag_text = "\n .. code-tab:: gdscript\n"
- elif cmd == "csharp":
- if not inside_code_tabs:
- print_error(
- f"{state.current_class}.xml: C# code block is used outside of [codeblocks] in {context_name}.",
- state,
- )
- tag_text = "\n .. code-tab:: csharp\n"
- else:
- tag_text = "\n::\n"
-
- inside_code = True
- inside_code_tag = cmd
-
- elif cmd == "code":
- tag_text = "``"
- tag_depth += 1
- inside_code = True
- inside_code_tag = cmd
- escape_pre = True
-
- valid_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
- if valid_context:
- endcode_pos = text.find("[/code]", endq_pos + 1)
- if endcode_pos == -1:
- print_error(
- f"{state.current_class}.xml: Tag depth mismatch for [code]: no closing [/code] in {context_name}.",
- state,
- )
- break
-
- inside_code_text = text[endq_pos + 1 : endcode_pos]
- context_params: List[ParameterDef] = context.parameters # type: ignore
- for param_def in context_params:
- if param_def.name == inside_code_text:
- print_warning(
- f'{state.current_class}.xml: Potential error inside of a code tag, found a string "{inside_code_text}" that matches one of the parameters in {context_name}.',
- state,
- )
- break
-
- # Cross-references to items in this or other class documentation pages.
- elif is_in_tagset(cmd, RESERVED_CROSSLINK_TAGS):
- link_type: str = ""
- link_target: str = ""
- if space_pos >= 0:
- link_type = tag_text[:space_pos]
- link_target = tag_text[space_pos + 1 :].strip()
-
- if link_target == "":
- print_error(
- f'{state.current_class}.xml: Empty cross-reference link "{cmd}" in {context_name}.',
- state,
- )
- tag_text = ""
- else:
- if (
- cmd.startswith("method")
- or cmd.startswith("constructor")
- or cmd.startswith("operator")
- or cmd.startswith("member")
- or cmd.startswith("signal")
- or cmd.startswith("annotation")
- or cmd.startswith("theme_item")
- or cmd.startswith("constant")
- ):
- if link_target.find(".") != -1:
- ss = link_target.split(".")
- if len(ss) > 2:
- print_error(
- f'{state.current_class}.xml: Bad reference "{link_target}" in {context_name}.',
- state,
- )
- class_param, method_param = ss
-
- else:
- class_param = state.current_class
- method_param = link_target
-
- # Default to the tag command name. This works by default for most tags,
- # but member and theme_item have special cases.
- ref_type = "_{}".format(link_type)
- if link_type == "member":
- ref_type = "_property"
-
- if class_param in state.classes:
- class_def = state.classes[class_param]
-
- if cmd.startswith("method") and method_param not in class_def.methods:
- print_error(
- f'{state.current_class}.xml: Unresolved method reference "{link_target}" in {context_name}.',
- state,
- )
-
- elif cmd.startswith("constructor") and method_param not in class_def.constructors:
- print_error(
- f'{state.current_class}.xml: Unresolved constructor reference "{link_target}" in {context_name}.',
- state,
- )
-
- elif cmd.startswith("operator") and method_param not in class_def.operators:
- print_error(
- f'{state.current_class}.xml: Unresolved operator reference "{link_target}" in {context_name}.',
- state,
- )
-
- elif cmd.startswith("member") and method_param not in class_def.properties:
- print_error(
- f'{state.current_class}.xml: Unresolved member reference "{link_target}" in {context_name}.',
- state,
- )
-
- elif cmd.startswith("signal") and method_param not in class_def.signals:
- print_error(
- f'{state.current_class}.xml: Unresolved signal reference "{link_target}" in {context_name}.',
- state,
- )
-
- elif cmd.startswith("annotation") and method_param not in class_def.annotations:
- print_error(
- f'{state.current_class}.xml: Unresolved annotation reference "{link_target}" in {context_name}.',
- state,
- )
-
- elif cmd.startswith("theme_item"):
- if method_param not in class_def.theme_items:
- print_error(
- f'{state.current_class}.xml: Unresolved theme item reference "{link_target}" in {context_name}.',
- state,
- )
- else:
- # Needs theme data type to be properly linked, which we cannot get without a class.
- name = class_def.theme_items[method_param].data_name
- ref_type = f"_theme_{name}"
-
- elif cmd.startswith("constant"):
- found = False
-
- # Search in the current class
- search_class_defs = [class_def]
-
- if link_target.find(".") == -1:
- # Also search in @GlobalScope as a last resort if no class was specified
- search_class_defs.append(state.classes["@GlobalScope"])
-
- for search_class_def in search_class_defs:
- if method_param in search_class_def.constants:
- class_param = search_class_def.name
- found = True
-
- else:
- for enum in search_class_def.enums.values():
- if method_param in enum.values:
- class_param = search_class_def.name
- found = True
- break
-
- if not found:
- print_error(
- f'{state.current_class}.xml: Unresolved constant reference "{link_target}" in {context_name}.',
- state,
- )
-
- else:
- print_error(
- f'{state.current_class}.xml: Unresolved type reference "{class_param}" in method reference "{link_target}" in {context_name}.',
- state,
- )
-
- repl_text = method_param
- if class_param != state.current_class:
- repl_text = f"{class_param}.{method_param}"
- tag_text = f":ref:`{repl_text}`"
- escape_pre = True
- escape_post = True
-
- elif cmd.startswith("enum"):
- tag_text = make_enum(link_target, False, state)
- escape_pre = True
- escape_post = True
-
- elif cmd.startswith("param"):
- valid_context = isinstance(context, (MethodDef, SignalDef, AnnotationDef))
- if not valid_context:
- print_error(
- f'{state.current_class}.xml: Argument reference "{link_target}" used outside of method, signal, or annotation context in {context_name}.',
- state,
- )
- else:
- context_params: List[ParameterDef] = context.parameters # type: ignore
- found = False
- for param_def in context_params:
- if param_def.name == link_target:
- found = True
- break
- if not found:
- print_error(
- f'{state.current_class}.xml: Unresolved argument reference "{link_target}" in {context_name}.',
- state,
- )
-
- tag_text = f"``{link_target}``"
- escape_pre = True
- escape_post = True
-
- # Formatting directives.
-
- elif is_in_tagset(cmd, ["url"]):
- if cmd.startswith("url="):
- # URLs are handled in full here as we need to extract the optional link
- # title to use `make_link`.
- link_url = cmd[4:]
- endurl_pos = text.find("[/url]", endq_pos + 1)
- if endurl_pos == -1:
- print_error(
- f"{state.current_class}.xml: Tag depth mismatch for [url]: no closing [/url] in {context_name}.",
- state,
- )
- break
- link_title = text[endq_pos + 1 : endurl_pos]
- tag_text = make_link(link_url, link_title)
-
- pre_text = text[:pos]
- post_text = text[endurl_pos + 6 :]
-
- if pre_text and pre_text[-1] not in MARKUP_ALLOWED_PRECEDENT:
- pre_text += "\ "
- if post_text and post_text[0] not in MARKUP_ALLOWED_SUBSEQUENT:
- post_text = "\ " + post_text
-
- text = pre_text + tag_text + post_text
- pos = len(pre_text) + len(tag_text)
- continue
- else:
- print_error(
- f'{state.current_class}.xml: Misformatted [url] tag "{cmd}" in {context_name}.',
- state,
- )
-
- elif cmd == "br":
- # Make a new paragraph instead of a linebreak, rst is not so linebreak friendly
- tag_text = "\n\n"
- # Strip potential leading spaces
- while post_text[0] == " ":
- post_text = post_text[1:]
-
- elif cmd == "center" or cmd == "/center":
- if cmd == "/center":
- tag_depth -= 1
- else:
- tag_depth += 1
- tag_text = ""
-
- elif cmd == "i" or cmd == "/i":
- if cmd == "/i":
- tag_depth -= 1
- escape_post = True
- else:
- tag_depth += 1
- escape_pre = True
- tag_text = "*"
-
- elif cmd == "b" or cmd == "/b":
- if cmd == "/b":
- tag_depth -= 1
- escape_post = True
- else:
- tag_depth += 1
- escape_pre = True
- tag_text = "**"
-
- elif cmd == "u" or cmd == "/u":
- if cmd == "/u":
- tag_depth -= 1
- escape_post = True
- else:
- tag_depth += 1
- escape_pre = True
- tag_text = ""
-
- elif cmd == "kbd" or cmd == "/kbd":
- tag_text = "`"
- if cmd == "/kbd":
- tag_depth -= 1
- escape_post = True
- else:
- tag_text = ":kbd:" + tag_text
- tag_depth += 1
- escape_pre = True
-
- # Invalid syntax checks.
- elif cmd.startswith("/"):
- print_error(f'{state.current_class}.xml: Unrecognized closing tag "{cmd}" in {context_name}.', state)
-
- tag_text = f"[{tag_text}]"
-
- else:
- print_error(f'{state.current_class}.xml: Unrecognized opening tag "{cmd}" in {context_name}.', state)
-
- tag_text = f"``{tag_text}``"
- escape_pre = True
- escape_post = True
-
- # Properly escape things like `[Node]s`
- if escape_pre and pre_text and pre_text[-1] not in MARKUP_ALLOWED_PRECEDENT:
- pre_text += "\ "
- if escape_post and post_text and post_text[0] not in MARKUP_ALLOWED_SUBSEQUENT:
- post_text = "\ " + post_text
-
- next_brac_pos = post_text.find("[", 0)
- iter_pos = 0
- while not inside_code:
- iter_pos = post_text.find("*", iter_pos, next_brac_pos)
- if iter_pos == -1:
- break
- post_text = f"{post_text[:iter_pos]}\*{post_text[iter_pos + 1 :]}"
- iter_pos += 2
-
- iter_pos = 0
- while not inside_code:
- iter_pos = post_text.find("_", iter_pos, next_brac_pos)
- if iter_pos == -1:
- break
- if not post_text[iter_pos + 1].isalnum(): # don't escape within a snake_case word
- post_text = f"{post_text[:iter_pos]}\_{post_text[iter_pos + 1 :]}"
- iter_pos += 2
- else:
- iter_pos += 1
-
- text = pre_text + tag_text + post_text
- pos = len(pre_text) + len(tag_text)
-
- if tag_depth > 0:
- print_error(
- f"{state.current_class}.xml: Tag depth mismatch: too many (or too few) open/close tags in {context_name}.",
- state,
- )
-
- return text
-
-
-def format_context_name(context: Union[DefinitionBase, None]) -> str:
- context_name: str = "unknown context"
- if context is not None:
- context_name = f'{context.definition_name} "{context.name}" description'
-
- return context_name
-
-
-def escape_rst(text: str, until_pos: int = -1) -> str:
- # Escape \ character, otherwise it ends up as an escape character in rst
- pos = 0
- while True:
- pos = text.find("\\", pos, until_pos)
- if pos == -1:
- break
- text = f"{text[:pos]}\\\\{text[pos + 1 :]}"
- pos += 2
-
- # Escape * character to avoid interpreting it as emphasis
- pos = 0
- while True:
- pos = text.find("*", pos, until_pos)
- if pos == -1:
- break
- text = f"{text[:pos]}\*{text[pos + 1 :]}"
- pos += 2
-
- # Escape _ character at the end of a word to avoid interpreting it as an inline hyperlink
- pos = 0
- while True:
- pos = text.find("_", pos, until_pos)
- if pos == -1:
- break
- if not text[pos + 1].isalnum(): # don't escape within a snake_case word
- text = f"{text[:pos]}\_{text[pos + 1 :]}"
- pos += 2
- else:
- pos += 1
-
- return text
-
-
-def format_codeblock(code_type: str, post_text: str, indent_level: int, state: State) -> Union[Tuple[str, int], None]:
- end_pos = post_text.find("[/" + code_type + "]")
- if end_pos == -1:
- print_error(f"{state.current_class}.xml: [{code_type}] without a closing tag.", state)
- return None
-
- code_text = post_text[len(f"[{code_type}]") : end_pos]
- post_text = post_text[end_pos:]
-
- # Remove extraneous tabs
- code_pos = 0
- while True:
- code_pos = code_text.find("\n", code_pos)
- if code_pos == -1:
- break
-
- to_skip = 0
- while code_pos + to_skip + 1 < len(code_text) and code_text[code_pos + to_skip + 1] == "\t":
- to_skip += 1
-
- if to_skip > indent_level:
- print_error(
- f"{state.current_class}.xml: Four spaces should be used for indentation within [{code_type}].",
- state,
- )
-
- if len(code_text[code_pos + to_skip + 1 :]) == 0:
- code_text = f"{code_text[:code_pos]}\n"
- code_pos += 1
- else:
- code_text = f"{code_text[:code_pos]}\n {code_text[code_pos + to_skip + 1 :]}"
- code_pos += 5 - to_skip
- return (f"\n[{code_type}]{code_text}{post_text}", len(f"\n[{code_type}]{code_text}"))
-
-
-def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_columns: bool = False) -> None:
- if len(data) == 0:
- return
-
- f.write(".. table::\n")
- f.write(" :widths: auto\n\n")
-
- # Calculate the width of each column first, we will use this information
- # to properly format RST-style tables.
- column_sizes = [0] * len(data[0])
- for row in data:
- for i, text in enumerate(row):
- text_length = len(text or "")
- if text_length > column_sizes[i]:
- column_sizes[i] = text_length
-
- # Each table row is wrapped in two separators, consecutive rows share the same separator.
- # All separators, or rather borders, have the same shape and content. We compose it once,
- # then reuse it.
-
- sep = ""
- for size in column_sizes:
- if size == 0 and remove_empty_columns:
- continue
- sep += "+" + "-" * (size + 2) # Content of each cell is padded by 1 on each side.
- sep += "+\n"
-
- # Draw the first separator.
- f.write(f" {sep}")
-
- # Draw each row and close it with a separator.
- for row in data:
- row_text = "|"
- for i, text in enumerate(row):
- if column_sizes[i] == 0 and remove_empty_columns:
- continue
- row_text += f' {(text or "").ljust(column_sizes[i])} |'
- row_text += "\n"
-
- f.write(f" {row_text}")
- f.write(f" {sep}")
-
- f.write("\n")
-
-
-def sanitize_operator_name(dirty_name: str, state: State) -> str:
- clear_name = dirty_name.replace("operator ", "")
-
- if clear_name == "!=":
- clear_name = "neq"
- elif clear_name == "==":
- clear_name = "eq"
-
- elif clear_name == "<":
- clear_name = "lt"
- elif clear_name == "<=":
- clear_name = "lte"
- elif clear_name == ">":
- clear_name = "gt"
- elif clear_name == ">=":
- clear_name = "gte"
-
- elif clear_name == "+":
- clear_name = "sum"
- elif clear_name == "-":
- clear_name = "dif"
- elif clear_name == "*":
- clear_name = "mul"
- elif clear_name == "/":
- clear_name = "div"
- elif clear_name == "%":
- clear_name = "mod"
- elif clear_name == "**":
- clear_name = "pow"
-
- elif clear_name == "unary+":
- clear_name = "unplus"
- elif clear_name == "unary-":
- clear_name = "unminus"
-
- elif clear_name == "<<":
- clear_name = "bwsl"
- elif clear_name == ">>":
- clear_name = "bwsr"
- elif clear_name == "&":
- clear_name = "bwand"
- elif clear_name == "|":
- clear_name = "bwor"
- elif clear_name == "^":
- clear_name = "bwxor"
- elif clear_name == "~":
- clear_name = "bwnot"
-
- elif clear_name == "[]":
- clear_name = "idx"
-
- else:
- clear_name = "xxx"
- print_error(f'Unsupported operator type "{dirty_name}", please add the missing rule.', state)
-
- return clear_name
-
-
-if __name__ == "__main__":
- main()
diff --git a/docs/api/tools/version.py b/docs/api/tools/version.py
deleted file mode 100644
index 066d5e1c2..000000000
--- a/docs/api/tools/version.py
+++ /dev/null
@@ -1 +0,0 @@
-docs = "latest"
diff --git a/docs/assets/icon.svg b/docs/assets/icon.svg
new file mode 100644
index 000000000..70b92722f
--- /dev/null
+++ b/docs/assets/icon.svg
@@ -0,0 +1,133 @@
+
+
+
+
diff --git a/docs/media/makefile.png b/docs/assets/makefile.png
similarity index 100%
rename from docs/media/makefile.png
rename to docs/assets/makefile.png
diff --git a/docs/assets/scene-tree.png b/docs/assets/scene-tree.png
new file mode 100644
index 0000000000000000000000000000000000000000..71373a757d12cf608b3af61a3041b81554cf0123
GIT binary patch
literal 91440
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 C$
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