Compare commits

...

30 Commits

Author SHA1 Message Date
Lazox12 aa0609d143 fix: auto restart of KIAUH after update (#781)
* fixed auto update

* fixed the restarting logic

* testing

* removed test

* fix: colored output and remove subshell usage in kiauh_update_dialog

---------

Co-authored-by: dw-0 <th33xitus@gmail.com>
2026-04-19 20:10:36 +02:00
dw-0 2175bd55f9 refactor: integrate simple_config_parser as internal module 2026-04-19 15:48:06 +02:00
Patrick Gehrsitz a935e67431 refactor: use correct crowsnest branch for v5 and future releases (#792)
fix: fix crowsnest for v5 and future releases

Signed-off-by: Patrick Gehrsitz <github@mryel.de>
2026-04-19 14:52:16 +02:00
dw-0 2f41e52189 fix(core): refresh system package list in update menu after update (#791) 2026-04-19 12:21:17 +02:00
Théo Gaillard 757344128a fix(extensions_menu): prevent extension index collisions during loading (#783)
* fix(extensions_menu): prevent extension index collisions during loading

* feat(extensions_menu): add GITHUB_ISSUES_URL for reporting extension loading issues
2026-03-22 13:43:20 +01:00
Théo Gaillard 7ca08f9b30 feat(instance_manager): add interactive stopping of Klipper instances (#784)
* feat(instance_manager): add interactive stopping of Klipper instances with user confirmation

* fix(instance_utils): remove unnecessary 'self' parameter from stop_klipper_instances_interactively function

* refactor(tmc_autotune): replace internal stop function with direct call to stop_klipper_instances_interactively
2026-03-22 12:01:15 +01:00
David Rios 308079a821 fix: incorrect Logger.warn (#778)
Fix method name
2026-02-27 22:45:58 +01:00
Théo Gaillard 09a5d96b63 feat(tmc_autotune): add initial implementation of TMC Autotune extens… (#771)
* feat(tmc_autotune): add initial implementation of TMC Autotune extension with installation and update functionalities

* fix: remove useless comments

* fix: added support for Kalico style plugins directory

* refactor: extract method for moonraker update manager section removal, automatically reloading moonraker upon call
2026-02-03 20:46:24 +01:00
dw-0 1f9d4c823a fix(settings_menu): fix regression by checking for the correct condition (#773) 2026-01-31 11:13:37 +01:00
dw-0 c8df9427b3 fix(backup): improve reusability of backup service and enhance file handling
- Refactor `BackupService` instance management for better reuse across methods.
- Avoid redundant file backups by checking for existing files.
- Enhance directory backup logic to handle nested files and directories more efficiently.
- Standardize timestamp initialization for consistent time-based backup operations.
2026-01-31 10:59:53 +01:00
Théo Gaillard 5414aba299 fix(backup_service): backup methods use proper paths (#769) (#770)
* fix(backup_service): streamline backup methods for proper paths and add docstring

* fix(backup_service): add proper destination_path in verbose output

* fix(backup_service): replaces html headers with { } to avoid rendering

* nitpick(backup_service): correct variable name for backup destination path in logging
2026-01-29 18:52:48 +01:00
Théo Gaillard 80948edbb4 fix(gcode_shell_cmd): update comment to clarify config directory usage (#767) 2026-01-19 17:04:28 +01:00
Théo Gaillard a455edba93 docs(fs_utils): add the documentation for create_symlink (#768) 2026-01-19 17:03:04 +01:00
Théo Gaillard 810ab3a2fa fix(fs-utils): enhance check_file_exist to support symlink resolution (#766) 2026-01-19 17:02:18 +01:00
dw-0 6c9a78496a fix(client_utils): ensure proper type conversion and hints for improved safety
- Standardize `str()` wrapping for color-applied strings in various returns.
- Refine type hints for better code clarity and robustness.
- Add null checks for port inputs to prevent potential errors.
2026-01-18 15:58:25 +01:00
dw-0 123ccde378 fix(core): standardize handling of None values for repo and version fields
- Improve local and remote version comparison by replacing default placeholders with None.
- Update repo and branch logic to handle None values consistently.
- Refactor type hints for better readability and accuracy.
2026-01-18 15:49:48 +01:00
dw-0 45fde808d2 fix(update_menu): refresh component status after updates (#764)
fix(update_menu): refresh component status after updates to ensure consistency
2026-01-18 14:53:04 +01:00
dw-0 8ba134f574 fix(ui): handle missing or empty local version values and standardize defaults (#763) 2026-01-18 14:18:27 +01:00
Théo Gaillard f951936b20 fix: navigation fallthrough in 'Get MCU ID' (Advanced Menu) (#762)
* fix: corrected back navigation in advanced menu in reference to #761

* ci: add delay after warning when no MCUs are found
2026-01-18 12:16:45 +01:00
dw-0 657d919378 chore: extend copyright year to 2026 in all headers 2026-01-11 09:37:17 +01:00
Staubgeborener 24c9b9daa9 chore(Klipper-Backup): Update Copyright year to 2026 (#758)
* Update copyright year to 2026

* Update copyright year in klipper_backup_extension.py
2026-01-11 09:29:33 +01:00
Thijs Triemstra 4c511017f1 README: Update KlipperScreen link and contributor name (#753)
Update KlipperScreen link and contributor name

update maintainer for klipperscreen
2025-11-30 17:42:36 +01:00
Clifford 372bab8847 feat(gcode_shell_command): allowing for expanding env vars (#747)
allowing for expanding env vars
2025-11-23 12:53:52 +01:00
Charlie Lima d5062d41de refactor: remove dependency on libatlas-base-dev (#744)
Remove dependency on libatlas-base-dev

Co-authored-by: charlie-lima-bean <ktoaster@pm.me>
2025-11-23 09:25:05 +01:00
dw-0 e9459bd68e fix(backup): correct backup folder path display in menu 2025-11-09 11:58:03 +01:00
dw-0 ee460663c9 fix(spoolman): ensure proper file handling when adding Spoolman entry 2025-10-28 12:12:36 +01:00
dw-0 6f0e0146ef fix(client): improve version retrieval logic and handle JSON errors 2025-10-27 19:00:08 +01:00
dw-0 229f317025 fix(backup): do not create redundant subdirectory on single file backup 2025-10-27 09:47:09 +01:00
dw-0 48c0ae7227 fix(backup): allow reusing existing backup directory and enhance copy options 2025-10-27 09:30:33 +01:00
dw-0 9c7b5fcb10 fix: update scp submodule so duplicate sections are preserved while editing configs (#738)
* fix: improve repository parsing logic to handle empty lines and comments more effectively

* fix: update scp submodule so duplicate sections are preserved while editing configs (#735)

* Squashed 'kiauh/core/submodules/simple_config_parser/' changes from f5eee99..5bc9e0a

5bc9e0a docs: update README
394dd7b refactor!: improve parsing and writing for config (#5)

git-subtree-dir: kiauh/core/submodules/simple_config_parser
git-subtree-split: 5bc9e0a50947f1be2f4877a10ab3a632774f82ea

* fix(logging): change warning to error message for config creation failure

* fix(config): improve readability by using descriptive variable names for options

(cherry picked from commit ae0a6b697e)

* Squashed 'kiauh/core/submodules/simple_config_parser/' changes from 5bc9e0a..eef8861

eef8861 refactor: update type hint for fallback parameter to Any
5d04325 Revert "chore: use Optional instead of | and None instead of _UNSET"

git-subtree-dir: kiauh/core/submodules/simple_config_parser
git-subtree-split: eef8861f126ddf84012ac8bed77b467926016d3e

* Squashed 'kiauh/core/submodules/simple_config_parser/' changes from eef8861..9c89612

9c89612 fix: correct assignment of raw value in option handling

git-subtree-dir: kiauh/core/submodules/simple_config_parser
git-subtree-split: 9c896124cf624e25410714649d306001250482f1

* fix: remove unnecessary whitespace in trusted_clients formatting
2025-10-26 22:03:26 +01:00
186 changed files with 2244 additions and 2075 deletions
+1
View File
@@ -7,6 +7,7 @@
*.tmp
__pycache__
.kiauh-env
.venv
*.code-workspace
*.iml
kiauh.cfg
+2 -2
View File
@@ -143,7 +143,7 @@ changes!**
<tr>
<th><h3><a href="https://github.com/fluidd-core/fluidd">Fluidd</a></h3></th>
<th><h3><a href="https://github.com/jordanruthe/KlipperScreen">KlipperScreen</a></h3></th>
<th><h3><a href="https://github.com/KlipperScreen/KlipperScreen">KlipperScreen</a></h3></th>
<th><h3><a href="https://github.com/OctoPrint/OctoPrint">OctoPrint</a></h3></th>
</tr>
<tr>
@@ -153,7 +153,7 @@ changes!**
</tr>
<tr>
<th>by <a href="https://github.com/fluidd-core">fluidd-core</a></th>
<th>by <a href="https://github.com/jordanruthe">jordanruthe</a></th>
<th>by <a href="https://github.com/alfrix">alfrix</a></th>
<th>by <a href="https://github.com/OctoPrint">OctoPrint</a></th>
</tr>
+21 -7
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
#=======================================================================#
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -15,18 +15,30 @@ clear -x
# make sure we have the correct permissions while running the script
umask 022
# gets the path where this script is located
KIAUH_SRCDIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)
# colors
white="\033[37m"
cyan="\033[96m"
red="\033[91m"
yellow="\033[93m"
green="\033[92m"
#===================================================#
#=================== UPDATE KIAUH ==================#
#===================================================#
function update_kiauh() {
status_msg "Updating KIAUH ..."
echo "Updating KIAUH ..."
cd "${KIAUH_SRCDIR}"
git reset --hard && git pull
ok_msg "Update complete! Please restart KIAUH."
exit 0
echo "Update complete! Restarting..."
sleep 1
exec "$0" "$@" # restarts the script
}
#===================================================#
@@ -65,16 +77,18 @@ function kiauh_update_dialog() {
echo -e "\-------------------------------------------------------/"
local yn
read -p "${cyan}###### Do you want to update now? (Y/n):${white} " yn
echo -ne "${cyan}###### Do you want to update now? (Y/n):${white} "
read yn
while true; do
case "${yn}" in
Y|y|Yes|yes|"")
do_action "update_kiauh"
update_kiauh
break;;
N|n|No|no)
break;;
*)
deny_action "kiauh_update_dialog";;
echo -e "${red}Invalid input. Please try again.${white}"
kiauh_update_dialog;;
esac
done
}
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+2 -2
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -47,7 +47,7 @@ from utils.sys_utils import (
def install_crowsnest() -> None:
# Step 1: Clone crowsnest repo
git_clone_wrapper(CROWSNEST_REPO, CROWSNEST_DIR, "master")
git_clone_wrapper(CROWSNEST_REPO, CROWSNEST_DIR)
# Step 2: Install dependencies
check_install_dependencies({"make"})
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+3 -5
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -34,7 +34,7 @@ from core.constants import CURRENT_USER
from core.instance_manager.base_instance import SUFFIX_BLACKLIST
from core.logger import DialogType, Logger
from core.services.backup_service import BackupService
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
from core.simple_config_parser.simple_config_parser import (
SimpleConfigParser,
)
from core.types.component_status import ComponentStatus
@@ -113,7 +113,7 @@ def check_user_groups() -> None:
if not get_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"):
log = "Skipped adding user to required groups. You might encounter issues."
Logger.warn(log)
Logger.print_warn(log)
return
try:
@@ -237,7 +237,6 @@ def install_input_shaper_deps() -> None:
"If you agree, the following additional system packages will be installed:",
"● python3-numpy",
"● python3-matplotlib",
"● libatlas-base-dev",
"● libopenblas-dev",
"\n\n",
"Also, the following Python package will be installed:",
@@ -253,7 +252,6 @@ def install_input_shaper_deps() -> None:
apt_deps = (
"python3-numpy",
"python3-matplotlib",
"libatlas-base-dev",
"libopenblas-dev",
)
check_install_dependencies({*apt_deps})
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -236,9 +236,11 @@ class KlipperSelectMcuConnectionMenu(BaseMenu):
if len(self.flash_options.mcu_list) < 1:
Logger.print_warn("No MCUs found!")
Logger.print_warn("Make sure they are connected and repeat this step.")
time.sleep(3)
return
# if standalone is True, we only display the MCUs to the user and return
if self.__standalone and len(self.flash_options.mcu_list) > 0:
if self.__standalone:
Logger.print_ok("The following MCUs were found:", prefix=False)
for i, mcu in enumerate(self.flash_options.mcu_list):
print(f" ● MCU #{i}: {Color.CYAN}{mcu}{Color.RST}")
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+2 -2
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -25,7 +25,7 @@ from components.moonraker import (
from core.constants import CURRENT_USER
from core.instance_manager.base_instance import BaseInstance
from core.logger import Logger
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
from core.simple_config_parser.simple_config_parser import (
SimpleConfigParser,
)
from utils.fs_utils import create_folders
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+3 -3
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -25,7 +25,7 @@ from components.moonraker.utils.sysdeps_parser import SysDepsParser
from components.webui_client.base_data import BaseWebClient
from core.logger import Logger
from core.services.backup_service import BackupService
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
from core.simple_config_parser.simple_config_parser import (
SimpleConfigParser,
)
from core.types.component_status import ComponentStatus
@@ -123,7 +123,7 @@ def create_example_moonraker_conf(
scp = SimpleConfigParser()
scp.read_file(target)
trusted_clients: List[str] = [
f" {'.'.join(ip)}\n",
f"{'.'.join(ip)}",
*scp.getvals("authorization", "trusted_clients"),
]
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -8,7 +8,7 @@
# ======================================================================= #
from typing import List
from typing import List, Optional
from components.klipper.klipper import Klipper
from components.moonraker.moonraker import Moonraker
@@ -27,6 +27,7 @@ def run_client_config_removal(
client_config: BaseWebClientConfig,
kl_instances: List[Klipper],
mr_instances: List[Moonraker],
svc: Optional[BackupService] = None,
) -> Message:
completion_msg = Message(
title=f"{client_config.display_name} Removal Process completed",
@@ -36,12 +37,15 @@ def run_client_config_removal(
if run_remove_routines(client_config.config_dir):
completion_msg.text.append(f"{client_config.display_name} removed")
BackupService().backup_printer_config_dir()
if svc is None:
svc = BackupService()
svc.backup_moonraker_conf()
completion_msg = remove_moonraker_config_section(
completion_msg, client_config, mr_instances
)
svc.backup_printer_cfg()
completion_msg = remove_printer_config_section(
completion_msg, client_config, kl_instances
)
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -41,6 +41,7 @@ def run_client_removal(
)
mr_instances: List[Moonraker] = get_instances(Moonraker)
kl_instances: List[Klipper] = get_instances(Klipper)
svc = BackupService()
if backup_config:
version = ""
@@ -49,7 +50,6 @@ def run_client_removal(
with open(src.joinpath(".version"), "r") as v:
version = v.readlines()[0]
svc = BackupService()
target_path = svc.backup_root.joinpath(f"{client.client_dir.name}_{version}")
success = svc.backup_file(
source_path=client.config_file,
@@ -67,7 +67,7 @@ def run_client_removal(
if remove_client_nginx_logs(client, kl_instances):
completion_msg.text.append("● NGINX logs removed")
BackupService().backup_moonraker_conf()
svc.backup_moonraker_conf()
section = f"update_manager {client_name}"
handled_instances: List[Moonraker] = remove_config_section(
section, mr_instances
@@ -83,6 +83,7 @@ def run_client_removal(
client.client_config,
kl_instances,
mr_instances,
svc,
)
if cfg_completion_msg.color == Color.GREEN:
completion_msg.text.extend(cfg_completion_msg.text[1:])
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+35 -17
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -11,6 +11,7 @@ from __future__ import annotations
import json
import re
import shutil
from json import JSONDecodeError
from pathlib import Path
from subprocess import PIPE, CalledProcessError, run
from typing import List, get_args
@@ -32,7 +33,7 @@ from core.constants import (
from core.logger import Logger
from core.services.backup_service import BackupService
from core.settings.kiauh_settings import KiauhSettings, WebUiSettings
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
from core.simple_config_parser.simple_config_parser import (
SimpleConfigParser,
)
from core.types.color import Color
@@ -77,10 +78,10 @@ def get_current_client_config() -> str:
installed = [c for c in clients if c.client_config.config_dir.exists()]
if not installed:
return Color.apply("-", Color.CYAN)
return str(Color.apply("-", Color.CYAN))
elif len(installed) == 1:
cfg = installed[0].client_config
return Color.apply(cfg.display_name, Color.CYAN)
return str(Color.apply(cfg.display_name, Color.CYAN))
# at this point, both client config folders exists, so we need to check
# which are actually included in the printer.cfg of all klipper instances
@@ -99,18 +100,18 @@ def get_current_client_config() -> str:
# if both are included in the same file, we have a potential conflict
if includes_mainsail and includes_fluidd:
return Color.apply("Conflict", Color.YELLOW)
return str(Color.apply("Conflict", Color.YELLOW))
if not mainsail_includes and not fluidd_includes:
# there are no includes at all, even though the client config folders exist
return Color.apply("-", Color.CYAN)
return str(Color.apply("-", Color.CYAN))
elif len(fluidd_includes) > len(mainsail_includes):
# there are more instances that include fluidd than mainsail
return Color.apply(fluidd.client_config.display_name, Color.CYAN)
return str(Color.apply(fluidd.client_config.display_name, Color.CYAN))
else:
# there are the same amount of non-conflicting includes for each config
# or more instances include mainsail than fluidd
return Color.apply(mainsail.client_config.display_name, Color.CYAN)
return str(Color.apply(mainsail.client_config.display_name, Color.CYAN))
def enable_mainsail_remotemode() -> None:
@@ -154,15 +155,32 @@ def get_local_client_version(client: BaseWebClient) -> str | None:
if not client.client_dir.exists():
return None
if not relinfo_file.is_file() and not version_file.is_file():
return "n/a"
# try to get version from release_info.json first
if relinfo_file.is_file():
with open(relinfo_file, "r") as f:
return str(json.load(f)["version"])
else:
with open(version_file, "r") as f:
return f.readlines()[0]
try:
if relinfo_file.stat().st_size == 0:
raise JSONDecodeError("Empty file", "", 0)
with open(relinfo_file, "r", encoding="utf-8") as f:
data = json.load(f)
raw_version = data.get("version")
if raw_version is not None:
parsed = str(raw_version).strip()
if parsed:
return parsed
except (JSONDecodeError, OSError):
Logger.print_error("Invalid 'release_info.json'")
# fallback to .version file
if version_file.is_file():
try:
with open(version_file, "r") as f:
line = f.readline().strip()
return line or None
except OSError:
Logger.print_error("Unable to read '.version'")
return None
def get_remote_client_version(client: BaseWebClient) -> str | None:
@@ -427,9 +445,9 @@ def get_client_port_selection(
while True:
_type = "Reconfigure" if reconfigure else "Configure"
question = f"{_type} {client.display_name} for port"
port_input = get_number_input(question, min_value=80, default=port)
port_input: int | None = get_number_input(question, min_value=80, default=port)
if port_input not in ports_in_use:
if port_input and port_input not in ports_in_use:
client_settings: WebUiSettings = settings[client.name]
client_settings.port = port_input
settings.save()
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -97,7 +97,7 @@ class ClientInstallMenu(BaseMenu):
self.message_service.set_message(message)
def _get_current_port(self) -> int:
curr_port = get_nginx_listen_port(self.client.nginx_config)
curr_port: int | None = get_nginx_listen_port(self.client.nginx_config)
if curr_port is None:
# if the port is not found in the config file we use
# the default port from the kiauh settings as fallback
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+2 -2
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -58,7 +58,7 @@ class BackupMenu(BaseMenu):
def print_menu(self) -> None:
line1 = Color.apply(
"INFO: Backups are located in '~/kiauh-backups'", Color.YELLOW
"INFO: Backups are located in '~/kiauh_backups'", Color.YELLOW
)
menu = textwrap.dedent(
f"""
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+3 -3
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -95,8 +95,8 @@ class MainMenu(BaseMenu):
status_data: ComponentStatus = status_fn(*args)
code: int = status_data.status
status: StatusText = StatusMap[code]
owner: str = trunc_string(status_data.owner, 23)
repo: str = trunc_string(status_data.repo, 23)
owner: str = trunc_string(status_data.owner, 23) if status_data.owner else '-'
repo: str = trunc_string(status_data.repo, 23) if status_data.repo else '-'
instance_count: int = status_data.instances
count_txt: str = ""
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+3 -3
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -100,11 +100,11 @@ class SettingsMenu(BaseMenu):
def trim_repo_url(repo: str) -> str:
return repo.replace(".git", "").replace("https://", "").replace("git@", "")
if not klipper_status.repo == "-":
if klipper_status.repo:
url = trim_repo_url(klipper_status.repo_url)
self.kl_repo_url = Color.apply(url, Color.CYAN)
self.kl_branch = Color.apply(klipper_status.branch, Color.CYAN)
if not moonraker_status.repo == "-":
if moonraker_status.repo:
url = trim_repo_url(moonraker_status.repo_url)
self.mr_repo_url = Color.apply(url, Color.CYAN)
self.mr_branch = Color.apply(moonraker_status.branch, Color.CYAN)
+40 -5
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -251,20 +251,23 @@ class UpdateMenu(BaseMenu):
self._set_status_data("klipperscreen", get_klipperscreen_status)
self._set_status_data("crowsnest", get_crowsnest_status)
self._fetch_system_package_update_status()
def _fetch_system_package_update_status(self) -> None:
update_system_package_lists(silent=True)
self.packages = get_upgradable_packages()
self.package_count = len(self.packages)
def _format_local_status(self, local_version, remote_version) -> str:
color = Color.RED
if not local_version:
if local_version is None:
color = Color.RED
elif local_version == remote_version:
color = Color.GREEN
elif local_version != remote_version:
color = Color.YELLOW
return Color.apply(local_version or "-", color)
return str(Color.apply(local_version or '-', color))
def _set_status_data(self, name: str, status_fn: Callable, *args) -> None:
comp_status: ComponentStatus = status_fn(*args)
@@ -290,7 +293,13 @@ class UpdateMenu(BaseMenu):
return self.status_data[name]["installed"]
def _is_update_available(self, name: str) -> bool:
return self.status_data[name]["local"] != self.status_data[name]["remote"]
local = self.status_data[name]["local"]
remote = self.status_data[name]["remote"]
if local is None or remote is None:
return False
return local != remote
def _run_update_routine(self, name: str, update_fn: Callable, *args) -> None:
display_name = self.status_data[name]["display_name"]
@@ -306,6 +315,27 @@ class UpdateMenu(BaseMenu):
update_fn(*args)
self._refresh_component_status(name)
def _refresh_component_status(self, name: str) -> None:
"""Refresh the status data for a component after an update."""
if name == "klipper":
self._set_status_data("klipper", get_klipper_status)
elif name == "moonraker":
self._set_status_data("moonraker", get_moonraker_status)
elif name == "mainsail":
self._set_status_data("mainsail", get_client_status, self.mainsail_data, True)
elif name == "mainsail_config":
self._set_status_data("mainsail_config", get_client_config_status, self.mainsail_data)
elif name == "fluidd":
self._set_status_data("fluidd", get_client_status, self.fluidd_data, True)
elif name == "fluidd_config":
self._set_status_data("fluidd_config", get_client_config_status, self.fluidd_data)
elif name == "klipperscreen":
self._set_status_data("klipperscreen", get_klipperscreen_status)
elif name == "crowsnest":
self._set_status_data("crowsnest", get_crowsnest_status)
def _run_system_updates(self) -> None:
if not self.packages:
Logger.print_info("No system upgrades available!")
@@ -313,15 +343,20 @@ class UpdateMenu(BaseMenu):
try:
pkgs: str = ", ".join(self.packages)
Logger.print_dialog(
DialogType.CUSTOM,
["The following packages will be upgraded:", "\n\n", pkgs],
custom_title="UPGRADABLE SYSTEM UPDATES",
)
if not get_confirm("Continue?"):
if not get_confirm("Upgrade packages?"):
return
Logger.print_status("Upgrading system packages ...")
upgrade_system_packages(self.packages)
self._fetch_system_package_update_status()
except Exception as e:
Logger.print_error(f"Error upgrading system packages:\n{e}")
raise
+46 -18
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -22,6 +22,7 @@ from utils.instance_utils import get_instances
class BackupService:
def __init__(self):
self._backup_root = Path.home().joinpath("kiauh_backups")
self._timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
@property
def backup_root(self) -> Path:
@@ -29,7 +30,7 @@ class BackupService:
@property
def timestamp(self) -> str:
return datetime.now().strftime("%Y%m%d-%H%M%S")
return self._timestamp
################################################
# GENERIC BACKUP METHODS
@@ -62,16 +63,21 @@ class BackupService:
target_name
or f"{source_path.stem}_{self.timestamp}{source_path.suffix}"
)
if target_path is not None:
backup_path = self._backup_root.joinpath(target_path, filename)
else:
backup_path = self._backup_root.joinpath(filename)
backup_path.mkdir(parents=True, exist_ok=True)
shutil.copy2(source_path, backup_path)
backup_dir = self._backup_root
if target_path is not None:
backup_dir = self._backup_root.joinpath(target_path)
backup_dir.mkdir(parents=True, exist_ok=True)
target_path = backup_dir.joinpath(filename)
if target_path.exists():
Logger.print_info(f"File '{target_path}' already exists. Skipping ...")
return True
shutil.copy2(source_path, target_path)
Logger.print_ok(
f"Successfully backed up '{source_path}' to '{backup_path}'"
f"Successfully backed up '{source_path}' to '{target_path}'"
)
return True
@@ -109,7 +115,27 @@ class BackupService:
else:
backup_path = self._backup_root.joinpath(backup_dir_name)
shutil.copytree(source_path, backup_path)
if backup_path.exists():
Logger.print_info(f"Reusing existing backup directory '{backup_path}'")
for item in source_path.rglob("*"):
relative_path = item.relative_to(source_path)
target_item = backup_path.joinpath(relative_path)
if item.is_file():
if not target_item.exists():
target_item.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(item, target_item)
else:
Logger.print_info(f"File '{target_item}' already exists. Skipping...")
elif item.is_dir():
target_item.mkdir(parents=True, exist_ok=True)
else:
shutil.copytree(
source_path,
backup_path,
dirs_exist_ok=True,
symlinks=True,
ignore_dangling_symlinks=True,
)
Logger.print_ok(
f"Successfully backed up '{source_path}' to '{backup_path}'"
@@ -125,27 +151,29 @@ class BackupService:
################################################
def backup_printer_cfg(self):
"""Backup printer.cfg files of all Klipper instances.
Files are backed up to:
{backup_root}/{instance_data_dir_name}/printer_{timestamp}.cfg
"""
klipper_instances: List[Klipper] = get_instances(Klipper)
for instance in klipper_instances:
target_path: Path = self._backup_root.joinpath(
instance.data_dir.name, f"config_{self.timestamp}"
)
target_path: Path = self._backup_root.joinpath(instance.data_dir.name)
self.backup_file(
source_path=instance.cfg_file,
target_path=target_path,
target_name=instance.cfg_file.name,
)
def backup_moonraker_conf(self):
"""Backup moonraker.conf files of all Moonraker instances.
Files are backed up to:
{backup_root}/{instance_data_dir_name}/moonraker_{timestamp}.conf
"""
moonraker_instances: List[Moonraker] = get_instances(Moonraker)
for instance in moonraker_instances:
target_path: Path = self._backup_root.joinpath(
instance.data_dir.name, f"config_{self.timestamp}"
)
target_path: Path = self._backup_root.joinpath(instance.data_dir.name)
self.backup_file(
source_path=instance.cfg_file,
target_path=target_path,
target_name=instance.cfg_file.name,
)
def backup_printer_config_dir(self) -> None:
+1 -1
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
+16 -14
View File
@@ -1,5 +1,5 @@
# ======================================================================= #
# Copyright (C) 2020 - 2025 Dominik Willner <th33xitus@gmail.com> #
# Copyright (C) 2020 - 2026 Dominik Willner <th33xitus@gmail.com> #
# #
# This file is part of KIAUH - Klipper Installation And Update Helper #
# https://github.com/dw-0/kiauh #
@@ -16,7 +16,7 @@ from components.klipper import KLIPPER_REPO_URL
from components.moonraker import MOONRAKER_REPO_URL
from core.logger import DialogType, Logger
from core.services.backup_service import BackupService
from core.submodules.simple_config_parser.src.simple_config_parser.simple_config_parser import (
from core.simple_config_parser.simple_config_parser import (
SimpleConfigParser,
)
from utils.input_utils import get_confirm
@@ -254,32 +254,34 @@ class KiauhSettings:
section: str,
option: str,
getter: Callable[[str, str, T | None], T],
fallback: T = None,
fallback: T | None = None,
silent: bool = False,
) -> T:
) -> T | None:
if not self.__check_option_exists(section, option, fallback, silent):
return fallback
return getter(section, option, fallback)
def __set_repo_state(self, section: str, repos: List[str]) -> List[Repository]:
_repos: List[Repository] = []
for repo in repos:
try:
if repo.strip().startswith("#") or repo.strip().startswith(";"):
continue
if "," in repo:
url, branch = repo.strip().split(",")
for raw in repos:
line = raw.strip()
if not branch:
branch = "master"
if not line or line.startswith("#") or line.startswith(";"):
continue
try:
if "," in line:
url_part, branch_part = line.split(",")
url = url_part.strip()
branch = branch_part.strip() or "master"
else:
url = repo.strip()
url = line
branch = "master"
# url must not be empty otherwise it's considered
# as an unrecoverable, invalid configuration
if not url:
raise InvalidValueError(section, "repositories", repo)
raise InvalidValueError(section, "repositories", line)
_repos.append(Repository(url.strip(), branch.strip()))
+19
View File
@@ -0,0 +1,19 @@
# Simple Config Parser
A custom config parser inspired by Python's configparser module.
Specialized for handling Klipper style config files.
---
### When parsing a config file, it will be split into the following elements:
- Header: All lines before the first section
- Section: A section is defined by a line starting with a `[` and ending with a `]`
- Option: A line starting with a word, followed by a `:` or `=` and a value
- Option Block: A line starting with a word, followed by a `:` or `=` and a newline
- The word `gcode` is excluded from being treated as an option block
- Gcode Block: A line starting with the word `gcode`, followed by a `:` or `=` and a newline
- All indented lines following the gcode line are considered part of the gcode block
- Comment: A line starting with a `#` or `;`
- Blank: A line containing only whitespace characters
- SaveConfig Block: Klippers auto-generated SAVE_CONFIG section that can be found at the very end of the config file
@@ -0,0 +1,646 @@
# ======================================================================= #
# Copyright (C) 2025 Dominik Willner <th33xitus@gmail.com> #
# #
# https://github.com/dw-0/simple-config-parser #
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
from __future__ import annotations
import re
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any, Callable, Dict, List, Set, Union
# definition of section line:
# - the line MUST start with an opening square bracket - it is the first section marker
# - the section marker MUST be followed by at least one character - it is the section name
# - the section name MUST be followed by a closing square bracket - it is the second section marker
# - the second section marker MAY be followed by any amount of whitespace characters
# - the second section marker MAY be followed by a # or ; - it is the comment marker
# - the inline comment MAY be of any length and character
SECTION_RE = re.compile(r"^\[(\S.*\S|\S)]\s*([#;].*)?$")
# definition of option line:
# - the line MUST start with a word - it is the option name
# - the option name MUST be followed by a colon or an equal sign - it is the separator
# - the separator MUST be followed by a value
# - the separator MAY have any amount of leading or trailing whitespaces
# - the separator MUST NOT be directly followed by a colon or equal sign
# - the value MAY be of any length and character
# - the value MAY contain any amount of trailing whitespaces
# - the value MAY be followed by a # or ; - it is the comment marker
# - the inline comment MAY be of any length and character
OPTION_RE = re.compile(r"^([^;#:=\s]+)\s?[:=]\s*([^;#:=\s][^;#]*?)\s*([#;].*)?$")
# definition of options block start line:
# - the line MUST start with a word - it is the option name
# - the option name MUST NOT be "gcode"
# - the option name MUST be followed by a colon or an equal sign - it is the separator
# - the separator MUST NOT be followed by a value
# - the separator MAY have any amount of leading or trailing whitespaces
# - the separator MUST NOT be directly followed by a colon or equal sign
# - the separator MAY be followed by a # or ; - it is the comment marker
# - the inline comment MAY be of any length and character
OPTIONS_BLOCK_START_RE = re.compile(
r"^(?!\s*gcode\s*[:=])([^;#:=\s]+)\s*[:=]\s*([#;].*)?$"
)
# definition of gcode block start line:
# - the line MUST start with the word "gcode"
# - the word "gcode" MUST be followed by a colon or an equal sign - it is the separator
# - the separator MUST NOT be followed by a value
# - the separator MAY have any amount of leading or trailing whitespaces
# - the separator MUST NOT be directly followed by a colon or equal sign
# - the separator MAY be followed by a # or ; - it is the comment marker
# - the inline comment MAY be of any length and character
GCODE_BLOCK_START_RE = re.compile(r"^\s*gcode\s*[:=]\s*(?:[#;].*)?$")
# definition of comment line:
# - the line MAY start with any amount of whitespace characters
# - the line MUST contain a # or ; - it is the comment marker
# - the comment marker MAY be followed by any amount of whitespace characters
# - the comment MAY be of any length and character
LINE_COMMENT_RE = re.compile(r"^\s*[#;].*")
# definition of empty line:
# - the line MUST contain only whitespace characters
EMPTY_LINE_RE = re.compile(r"^\s*$")
SAVE_CONFIG_START_RE = re.compile(r"^#\*# <-+ SAVE_CONFIG -+>$")
SAVE_CONFIG_CONTENT_RE = re.compile(r"^#\*#.*$")
BOOLEAN_STATES = {
"1": True,
"yes": True,
"true": True,
"on": True,
"0": False,
"no": False,
"false": False,
"off": False,
}
class LineType(Enum):
OPTION = "option"
OPTION_BLOCK = "option_block"
COMMENT = "comment"
BLANK = "blank"
_UNSET = object()
class NoSectionError(Exception):
"""Raised when a section is not defined"""
def __init__(self, section: str):
msg = f"Section '{section}' is not defined"
super().__init__(msg)
class DuplicateSectionError(Exception):
"""Raised when a section is defined more than once"""
def __init__(self, section: str):
msg = f"Section '{section}' is defined more than once"
super().__init__(msg)
class NoOptionError(Exception):
"""Raised when an option is not defined in a section"""
def __init__(self, option: str, section: str):
msg = f"Option '{option}' in section '{section}' is not defined"
super().__init__(msg)
class UnknownLineError(Exception):
"""Raised when a line is not recognized as any known type"""
def __init__(self, line: str):
msg = f"Unknown line: '{line}'"
super().__init__(msg)
@dataclass
class Option:
"""Dataclass representing a (pseudo) config option"""
name: str
raw: str
value: str
@dataclass
class MultiLineOption:
"""Dataclass representing a multi-line config option"""
name: str
raw: str
values: List[MLOptionValue] = field(default_factory=list)
@dataclass
class MLOptionValue:
"""Dataclass representing a value in a multi-line option"""
raw: str
indent: int
value: str
@dataclass
class Gcode:
"""Dataclass representing a gcode block"""
name: str
raw: str
gcode: List[str] = field(default_factory=list)
@dataclass
class BlankLine:
"""Dataclass representing a blank line"""
raw: str = "\n"
@dataclass
class CommentLine:
"""Dataclass representing a comment line"""
raw: str
SectionItem = Union[Option, MultiLineOption, Gcode, BlankLine, CommentLine]
@dataclass
class Section:
"""Dataclass representing a config section"""
name: str
raw: str
items: List[SectionItem] = field(default_factory=list)
# noinspection PyMethodMayBeStatic
class SimpleConfigParser:
"""A customized config parser targeted at handling Klipper style config files"""
def __init__(self) -> None:
self.config: Dict = {}
self._header: List[str] = []
self._save_config_block: List[str] = []
self._config: List[Section] = []
self._curr_sect: Union[Section, None] = None
self._curr_ml_opt: Union[MultiLineOption, None] = None
self._curr_gcode: Union[Gcode, None] = None
def _match_section(self, line: str) -> bool:
"""Whether the given line matches the definition of a section"""
return SECTION_RE.match(line) is not None
def _match_option(self, line: str) -> bool:
"""Whether the given line matches the definition of an option"""
return OPTION_RE.match(line) is not None
def _match_options_block_start(self, line: str) -> bool:
"""Whether the given line matches the definition of a multiline option"""
return OPTIONS_BLOCK_START_RE.match(line) is not None
def _match_gcode_block_start(self, line: str) -> bool:
"""Whether the given line matches the definition of a gcode block start"""
return GCODE_BLOCK_START_RE.match(line) is not None
def _match_save_config_start(self, line: str) -> bool:
"""Whether the given line matches the definition of a save config start"""
return SAVE_CONFIG_START_RE.match(line) is not None
def _match_save_config_content(self, line: str) -> bool:
"""Whether the given line matches the definition of a save config content"""
return SAVE_CONFIG_CONTENT_RE.match(line) is not None
def _match_line_comment(self, line: str) -> bool:
"""Whether the given line matches the definition of a comment"""
return LINE_COMMENT_RE.match(line) is not None
def _match_empty_line(self, line: str) -> bool:
"""Wheter or not the given line matches the definition of an empty line"""
return EMPTY_LINE_RE.match(line) is not None
def _parse_line(self, line: str) -> None:
"""Parses a line and determines its type"""
if self._curr_sect is None and not self._match_section(line):
# we are at the beginning of the file, so we consider the part
# up to the first section as the file header and store it separately
self._header.append(line)
return
if self._match_section(line):
self._reset_special_items()
sect_name: str = SECTION_RE.match(line).group(1)
sect = Section(name=sect_name, raw=line)
self._curr_sect = sect
self._config.append(sect)
return
if self._match_option(line):
self._reset_special_items()
name: str = OPTION_RE.match(line).group(1)
val: str = OPTION_RE.match(line).group(2)
opt = Option(
name=name,
raw=line,
value=val,
)
self._curr_sect.items.append(opt)
return
if self._match_options_block_start(line):
self._reset_special_items()
name: str = OPTIONS_BLOCK_START_RE.match(line).group(1)
ml_opt = MultiLineOption(
name=name,
raw=line,
)
self._curr_ml_opt = ml_opt
self._curr_sect.items.append(ml_opt)
return
if self._curr_ml_opt is not None:
# we are in an option block, so we consecutively add values
# to the current multiline option until we hit a different line type
if "#" in line:
value = line.split("#", 1)[0].strip()
elif ";" in line:
value = line.split(";", 1)[0].strip()
else:
value = line.strip()
ml_value = MLOptionValue(
raw=line,
indent=self._get_indent(line),
value=value,
)
self._curr_ml_opt.values.append(ml_value)
return
if self._match_gcode_block_start(line):
self._curr_gcode = Gcode(
name="gcode",
raw=line,
)
self._curr_sect.items.append(self._curr_gcode)
return
if self._curr_gcode is not None:
# we are in a gcode block, so we add any following line
# without further checks to the gcode block
self._curr_gcode.gcode.append(line)
return
if self._match_save_config_start(line):
self._reset_special_items()
self._save_config_block.append(line)
return
if self._match_save_config_content(line):
self._reset_special_items()
self._save_config_block.append(line)
return
if self._match_empty_line(line):
self._reset_special_items()
self._curr_sect.items.append(BlankLine(raw=line))
return
if self._match_line_comment(line):
self._reset_special_items()
self._curr_sect.items.append(CommentLine(raw=line))
return
def _reset_special_items(self) -> None:
"""Reset special items like current multine option and gcode block"""
self._curr_ml_opt = None
self._curr_gcode = None
def _get_indent(self, line: str) -> int:
"""Return the indentation level of a line"""
return len(line) - len(line.lstrip())
def read_file(self, file: Path) -> None:
"""Read and parse a config file"""
self._config = []
with open(file, "r", encoding="utf-8") as file:
for line in file:
self._parse_line(line)
def write_file(self, path: str | Path) -> None:
"""Write the config to a file"""
if path is None:
raise ValueError("File path cannot be None")
# first write the header
content: List[str] = list(self._header)
# then write all sections
for i in self._config:
content.append(i.raw)
for item in i.items:
content.append(item.raw)
if isinstance(item, MultiLineOption):
content.extend(val.raw for val in item.values)
elif isinstance(item, Gcode):
content.extend(item.gcode)
# then write the save config block
content.extend(self._save_config_block)
# ensure file ends with a newline
if content and not content[-1].endswith("\n"):
content.append("\n")
with open(path, "w", encoding="utf-8", newline="\n") as f:
f.writelines(content)
def get_sections(self) -> Set[str]:
"""Return a set of all section names"""
return {s.name for s in self._config} if self._config else set()
def has_section(self, section: str) -> bool:
"""Check if a section exists"""
return section in self.get_sections()
def add_section(self, section: str) -> Section:
"""Add a new section to the config"""
if section in self.get_sections():
raise DuplicateSectionError(section)
if not self._config:
new_sect = Section(name=section, raw=f"[{section}]\n")
self._config.append(new_sect)
return new_sect
last_sect: Section = self._config[-1]
if not last_sect.items or (
last_sect.items and not isinstance(last_sect.items[-1], BlankLine)
):
last_sect.items.append(BlankLine())
new_sect = Section(name=section, raw=f"[{section}]\n")
self._config.append(new_sect)
return new_sect
def remove_section(self, section: str) -> None:
"""Remove a section from the config
This will remove ALL occurences of sections with the given name.
"""
self._config = [s for s in self._config if s.name != section]
def get_options(self, section: str) -> Set[str]:
"""Return a set of all option names for a given section"""
sections: List[Section] = [s for s in self._config if s.name == section]
all_items: List[SectionItem] = [
item for section in sections for item in section.items
]
return {o.name for o in all_items if isinstance(o, (Option, MultiLineOption))}
def has_option(self, section: str, option: str) -> bool:
"""Check if an option exists in a section"""
return self.has_section(section) and option in self.get_options(section)
def set_option(self, section: str, option: str, value: str | List[str]) -> None:
"""
Set the value of an option in a section. If the section does not exist,
it is created. If the option does not exist, it is created.
"""
# when adding options, we add them to the first matching section
# if the section does not exist, we create it
section: Section = (
self.add_section(section)
if not self.has_section(section)
else next(s for s in self._config if s.name == section)
)
opt = self._find_option_by_name(option, section=section)
if opt is None:
if isinstance(value, list):
indent = 4
_opt = MultiLineOption(
name=option,
raw=f"{option}:\n",
values=[
MLOptionValue(
raw=f"{' ' * indent}{val}\n",
indent=indent,
value=val,
)
for val in value
],
)
else:
_opt = Option(
name=option,
raw=f"{option}: {value}\n",
value=value,
)
last_opt_idx: int = 0
for idx, item in enumerate(section.items):
if isinstance(item, (Option, MultiLineOption)):
last_opt_idx = idx
# insert the new option after the last existing option
section.items.insert(last_opt_idx + 1, _opt)
elif opt and isinstance(opt, Option) and isinstance(value, str):
curr_val = opt.value
new_val = value
opt.value = new_val
opt.raw = opt.raw.replace(curr_val, new_val)
elif opt and isinstance(opt, MultiLineOption) and isinstance(value, list):
# note: we completely replace the existing values
# so any existing indentation, comments, etc. will be lost
indent = 4
opt.values = [
MLOptionValue(
raw=f"{' ' * indent}{val}\n",
indent=indent,
value=val,
)
for val in value
]
def _find_section_by_name(
self, sect_name: str
) -> Union[None, Section, List[Section]]:
"""Find a section by name"""
_sects = [s for s in self._config if s.name == sect_name]
if len(_sects) > 1:
return _sects
elif len(_sects) == 1:
return _sects[0]
else:
return None
def _find_option_by_name(
self,
opt_name: str,
section: Union[Section, None] = None,
sections: Union[List[Section], None] = None,
) -> Union[None, Option, MultiLineOption]:
"""Find an option or multi-line option by name in a section"""
# if a single section is provided, search its items for the option
if section is not None:
for item in section.items:
if (
isinstance(item, (Option, MultiLineOption))
and item.name == opt_name
):
return item
# if multiple sections with the same name are provided, merge their
# items and search for the option
if sections is not None:
all_items: List[SectionItem] = [
item for sect in sections for item in sect.items
]
for item in all_items:
if (
isinstance(item, (Option, MultiLineOption))
and item.name == opt_name
):
return item
return None
def remove_option(self, section: str, option: str) -> None:
"""Remove an option from a section
This will remove the option from ALL occurences of sections with the given name.
Other non-option items (comments, blank lines, etc.) are preserved.
"""
sections: List[Section] = [s for s in self._config if s.name == section]
if not sections:
return
for sect in sections:
sect.items = [
item
for item in sect.items
if not (
isinstance(item, (Option, MultiLineOption)) and item.name == option
)
]
def _get_option(
self, section: str, option: str
) -> Union[Option, MultiLineOption, None]:
"""Internal helper to resolve an option or multi-line option."""
if section not in self.get_sections():
raise NoSectionError(section)
if option not in self.get_options(section):
raise NoOptionError(option, section)
sects: List[Section] = [s for s in self._config if s.name == section]
return (
self._find_option_by_name(option, sections=sects)
if len(sects) > 1
else self._find_option_by_name(option, section=sects[0])
)
def getval(self, section: str, option: str, fallback: str | _UNSET = _UNSET) -> str:
"""
Return the value of the given option in the given section
If the key is not found and 'fallback' is provided, it is used as
a fallback value.
"""
try:
opt = self._get_option(section, option)
if not isinstance(opt, Option):
raise NoOptionError(option, section)
return opt.value if opt else ""
except (NoSectionError, NoOptionError):
if fallback is _UNSET:
raise
return fallback
def getvals(
self, section: str, option: str, fallback: List[str] | _UNSET = _UNSET
) -> List[str]:
"""
Return the values of the given multi-line option in the given section
If the key is not found and 'fallback' is provided, it is used as
a fallback value.
"""
try:
opt = self._get_option(section, option)
if not isinstance(opt, MultiLineOption):
raise NoOptionError(option, section)
return [v.value for v in opt.values] if opt else []
except (NoSectionError, NoOptionError):
if fallback is _UNSET:
raise
return fallback
def getint(self, section: str, option: str, fallback: int | _UNSET = _UNSET) -> int:
"""Return the value of the given option in the given section as an int"""
return self._get_conv(section, option, int, fallback=fallback)
def getfloat(
self, section: str, option: str, fallback: float | _UNSET = _UNSET
) -> float:
"""Return the value of the given option in the given section as a float"""
return self._get_conv(section, option, float, fallback=fallback)
def getboolean(
self, section: str, option: str, fallback: bool | _UNSET = _UNSET
) -> bool:
"""Return the value of the given option in the given section as a boolean"""
return self._get_conv(
section, option, self._convert_to_boolean, fallback=fallback
)
def _convert_to_boolean(self, value: str) -> bool:
"""Convert a string to a boolean"""
if isinstance(value, bool):
return value
if value.lower() not in BOOLEAN_STATES:
raise ValueError("Not a boolean: %s" % value)
return BOOLEAN_STATES[value.lower()]
def _get_conv(
self,
section: str,
option: str,
conv: Callable[[str], int | float | bool],
fallback: Any = _UNSET,
) -> int | float | bool:
"""Return the value of the given option in the given section as a converted value"""
try:
return conv(self.getval(section, option, fallback))
except (ValueError, TypeError, AttributeError) as e:
if fallback is not _UNSET:
return fallback
raise ValueError(
f"Cannot convert {self.getval(section, option)} to {conv.__name__}"
) from e
@@ -0,0 +1,14 @@
# Header line
# Another header comment
[toolhead]
option_a: 1
option_b: true
[gcode_macro test]
gcode: # start gcode block
G28 ; home all
M118 Done ; echo
G1 X10 Y10 F3000
@@ -10,8 +10,8 @@ from pathlib import Path
import pytest
from src.simple_config_parser.simple_config_parser import SimpleConfigParser
from tests.utils import load_testdata_from_file
from core.simple_config_parser.simple_config_parser import SimpleConfigParser
from core.simple_config_parser.tests.utils import load_testdata_from_file
BASE_DIR = Path(__file__).parent.joinpath("test_data")
MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt")
@@ -0,0 +1,16 @@
gcode:
gcode:
gcode: # comment
gcode: ; comment
gcode :
gcode :
gcode : # comment
gcode : ; comment
gcode=
gcode=
gcode= # comment
gcode= ; comment
gcode =
gcode =
gcode = # comment
gcode = ; comment
@@ -0,0 +1,39 @@
type: jsonfile
path: /dev/shm/drying_box.json
baud: 250000
minimum_cruise_ratio: 0.5
square_corner_velocity: 5.0
full_steps_per_rotation: 200
position_min: 0
homing_speed: 5.0
# baud: 250000
# minimum_cruise_ratio: 0.5
# square_corner_velocity: 5.0
# full_steps_per_rotation: 200
# position_min: 0
# homing_speed: 5.0
option:
option :
option :
option=
option =
option =
### this is a comment
; this is also a comment
;
#
homing_speed::
homing_speed::
homing_speed ::
homing_speed ::
homing_speed==
homing_speed==
homing_speed ==
homing_speed ==
homing_speed :=
homing_speed :=
homing_speed =:
homing_speed =:
@@ -0,0 +1,39 @@
# ======================================================================= #
# Copyright (C) 2024 Dominik Willner <th33xitus@gmail.com> #
# #
# https://github.com/dw-0/simple-config-parser #
# #
# This file may be distributed under the terms of the GNU GPLv3 license #
# ======================================================================= #
from pathlib import Path
import pytest
from core.simple_config_parser.simple_config_parser import SimpleConfigParser
from core.simple_config_parser.tests.utils import load_testdata_from_file
BASE_DIR = Path(__file__).parent.joinpath("test_data")
MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt")
NON_MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("non_matching_data.txt")
@pytest.fixture
def parser():
return SimpleConfigParser()
@pytest.mark.parametrize("line", load_testdata_from_file(MATCHING_TEST_DATA_PATH))
def test_match_gcode_block_start(parser, line):
"""Test that a line matches the definition of an options block start"""
assert parser._match_gcode_block_start(line) is True, (
f"Expected line '{line}' to match gcode block start definition!"
)
@pytest.mark.parametrize("line", load_testdata_from_file(NON_MATCHING_TEST_DATA_PATH))
def test_non_matching_gcode_block_start(parser, line):
"""Test that a line does not match the definition of an options block start"""
assert parser._match_gcode_block_start(line) is False, (
f"Expected line '{line}' to not match gcode block start definition!"
)
@@ -10,8 +10,8 @@ from pathlib import Path
import pytest
from src.simple_config_parser.simple_config_parser import SimpleConfigParser
from tests.utils import load_testdata_from_file
from core.simple_config_parser.simple_config_parser import SimpleConfigParser
from core.simple_config_parser.tests.utils import load_testdata_from_file
BASE_DIR = Path(__file__).parent.joinpath("test_data")
MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt")
@@ -10,8 +10,8 @@ from pathlib import Path
import pytest
from src.simple_config_parser.simple_config_parser import SimpleConfigParser
from tests.utils import load_testdata_from_file
from core.simple_config_parser.simple_config_parser import SimpleConfigParser
from core.simple_config_parser.tests.utils import load_testdata_from_file
BASE_DIR = Path(__file__).parent.joinpath("test_data")
MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt")
@@ -1,5 +1,4 @@
trusted_clients:
gcode:
cors_domains:
an_options_block_start_with_comment: ; this is a comment
an_options_block_start_with_comment: # this is a comment
@@ -29,3 +29,9 @@ homing_speed :=
homing_speed :=
homing_speed =:
homing_speed =:
gcode:
gcode :
gcode :
gcode=
gcode =
gcode =
@@ -10,8 +10,8 @@ from pathlib import Path
import pytest
from src.simple_config_parser.simple_config_parser import SimpleConfigParser
from tests.utils import load_testdata_from_file
from core.simple_config_parser.simple_config_parser import SimpleConfigParser
from core.simple_config_parser.tests.utils import load_testdata_from_file
BASE_DIR = Path(__file__).parent.joinpath("test_data")
MATCHING_TEST_DATA_PATH = BASE_DIR.joinpath("matching_data.txt")

Some files were not shown because too many files have changed in this diff Show More