mirror of
https://github.com/dw-0/kiauh.git
synced 2025-12-27 17:53:35 +05:00
Compare commits
12 Commits
6225ee59d0
...
v6.0.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d06bf76f3 | ||
|
|
e438081c35 | ||
|
|
9f50f6fdd7 | ||
|
|
0ee0fa3325 | ||
|
|
8547942986 | ||
|
|
d33ac6b15a | ||
|
|
6cd9133a15 | ||
|
|
a929c6983d | ||
|
|
bce92001a6 | ||
|
|
7993b98ee1 | ||
|
|
62296e112e | ||
|
|
a374ac8fac |
@@ -2,13 +2,54 @@
|
|||||||
|
|
||||||
This document covers possible important changes to KIAUH.
|
This document covers possible important changes to KIAUH.
|
||||||
|
|
||||||
|
### 2024-08-31 (v6.0.0-alpha.1)
|
||||||
|
Long time no see, but here we are again!
|
||||||
|
A lot has happened in the background, but now it is time to take it out into the wild.
|
||||||
|
|
||||||
|
#### KIAUH has now reached version 6! Well, at least in an alpha state...
|
||||||
|
|
||||||
|
The project has seen a complete rewrite of the script from scratch in Python.
|
||||||
|
It requires Python 3.8 or newer to run. Because this update is still in an alpha state, bugs may or will occur.
|
||||||
|
During startup, you will be asked if you want to start the new version 6 or the old version 5.
|
||||||
|
As long as version 6 is in a pre-release state, version 5 will still be available. If there are any critical issues
|
||||||
|
with the new version that were overlooked, you can always switch back to the old version.
|
||||||
|
|
||||||
|
In case you selected not to get asked about which version to start (option 3 or 4 in the startup dialog) and you want to
|
||||||
|
revert that decision, you will find a line called `version_to_launch=` within the `.kiauh.ini` file in your home directory.
|
||||||
|
Just delete that line, save the file and restart KIAUH. KIAUH will then ask you again which version you want to start.
|
||||||
|
|
||||||
|
Here is a list of the most important changes to KIAUH in regard to version 6:
|
||||||
|
- The majority of features available in KIAUH v5 are still available; they just got migrated from Bash to Python.
|
||||||
|
- It is now possible to add new/remove instances to/from existing multi-instance installations of Klipper and Moonraker
|
||||||
|
- KIAUH now has an Extension-System. This allows contributors to add new installers to KIAUH without having to modify the main script.
|
||||||
|
- You will now find some of the features that were previously available in the Installer-Menu in the Extensions-Menu.
|
||||||
|
- The current extensions are:
|
||||||
|
- G-Code Shell Command (previously found in the Advanced-Menu)
|
||||||
|
- Mainsail Theme Installer (previously found in the Advanced-Menu)
|
||||||
|
- Klipper-Backup (new in v6!)
|
||||||
|
- Moonraker Telegram Bot (previously found in the Installer-Menu)
|
||||||
|
- PrettyGCode for Klipper (previously found in the Installer-Menu)
|
||||||
|
- Obico for Klipper (previously found in the Installer-Menu)
|
||||||
|
- The following additional extensions are planned, but not yet available:
|
||||||
|
- Spoolman (available in v5 in the Installer-Menu)
|
||||||
|
- OctoApp (available in v5 in the Installer-Menu)
|
||||||
|
- KIAUH has its own config file now
|
||||||
|
- The file has some default values for the currently supported options
|
||||||
|
- There might be more options in the future
|
||||||
|
- It is located in KIAUH's root directory and is called `default.kiauh.cfg`
|
||||||
|
- DO NOT EDIT the default file directly, instead make a copy of it and call it `kiauh.cfg`
|
||||||
|
- Settings changed via the Advanced-Menu will be written to the `kiauh.cfg`
|
||||||
|
- Support for OctoPrint was removed
|
||||||
|
|
||||||
|
Feel free to give version 6 a try and report any bugs or issues you encounter! Every feedback is appreciated.
|
||||||
|
|
||||||
### 2023-06-17
|
### 2023-06-17
|
||||||
KIAUH has now added support for installing Mobileraker's companion!
|
KIAUH has now added support for installing Mobileraker's companion!
|
||||||
Mobileraker is a free and Open Source Android and iOS App for Klipper, utilizing the Moonraker API, allowing you
|
Mobileraker is a free and Open Source Android and iOS App for Klipper, utilizing the Moonraker API, allowing you
|
||||||
to control your printer. Thank you to [Clon1998](https://github.com/Clon1998) for adding this feature!
|
to control your printer. Thank you to [Clon1998](https://github.com/Clon1998) for adding this feature!
|
||||||
|
|
||||||
### 2023-02-03
|
### 2023-02-03
|
||||||
The installer for MJPG-Streamer got replaced by crowsnest. It is an improved webcam service, utilizing ustreamer.
|
The installer for MJPG-Streamer got replaced by crowsnest. It is an improved webcam service, utilizing ustreamer.
|
||||||
Please have a look here for additional info about crowsnest and how to configure it: https://github.com/mainsail-crew/crowsnest \
|
Please have a look here for additional info about crowsnest and how to configure it: https://github.com/mainsail-crew/crowsnest \
|
||||||
It's unsure if the previous MJPG-Streamer installer will be updated and make its way back into KIAUH.
|
It's unsure if the previous MJPG-Streamer installer will be updated and make its way back into KIAUH.
|
||||||
A big thanks to [KwadFan](https://github.com/KwadFan) for writing the crowsnest implementation.
|
A big thanks to [KwadFan](https://github.com/KwadFan) for writing the crowsnest implementation.
|
||||||
@@ -115,7 +156,7 @@ membership for example caused issues when installing mjpg-streamer while not usi
|
|||||||
Other issues could occur when trying to flash an MCU on Debian or Ubuntu distributions where a user might not be part
|
Other issues could occur when trying to flash an MCU on Debian or Ubuntu distributions where a user might not be part
|
||||||
of the dialout group by default. A check for the tty group is also done. The tty group is needed for setting
|
of the dialout group by default. A check for the tty group is also done. The tty group is needed for setting
|
||||||
up a linux MCU (currently not yet supported by KIAUH).
|
up a linux MCU (currently not yet supported by KIAUH).
|
||||||
* There is an issue when trying to install Mainsail or Fluidd on Ubuntu 21.10. Permissions on that distro seem to have seen a rework
|
* There is an issue when trying to install Mainsail or Fluidd on Ubuntu 21.10. Permissions on that distro seem to have seen a rework
|
||||||
in comparison to 20.04 and users will be greeted with an "Error 403 - Permission denied" message after installing one of Klippers webinterfaces.
|
in comparison to 20.04 and users will be greeted with an "Error 403 - Permission denied" message after installing one of Klippers webinterfaces.
|
||||||
I still have to figure out a viable solution for that.
|
I still have to figure out a viable solution for that.
|
||||||
|
|
||||||
|
|||||||
2
kiauh.sh
2
kiauh.sh
@@ -164,7 +164,6 @@ function main() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
check_if_ratos
|
check_if_ratos
|
||||||
check_euid
|
check_euid
|
||||||
init_logfile
|
init_logfile
|
||||||
@@ -173,4 +172,3 @@ kiauh_update_dialog
|
|||||||
read_kiauh_ini
|
read_kiauh_ini
|
||||||
init_ini
|
init_ini
|
||||||
main
|
main
|
||||||
|
|
||||||
|
|||||||
@@ -234,8 +234,6 @@ def display_moonraker_info(moonraker_list: List[Moonraker]) -> bool:
|
|||||||
"The following Klipper instances will be installed:",
|
"The following Klipper instances will be installed:",
|
||||||
*[f"● klipper-{m.suffix}" for m in moonraker_list],
|
*[f"● klipper-{m.suffix}" for m in moonraker_list],
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
_input: bool = get_confirm("Proceed with installation?")
|
_input: bool = get_confirm("Proceed with installation?")
|
||||||
return _input
|
return _input
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ def check_user_groups() -> None:
|
|||||||
"INFO:",
|
"INFO:",
|
||||||
"Relog required for group assignments to take effect!",
|
"Relog required for group assignments to take effect!",
|
||||||
],
|
],
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not get_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"):
|
if not get_confirm(f"Add user '{CURRENT_USER}' to group(s) now?"):
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ class KlipperBuildFirmwareMenu(BaseMenu):
|
|||||||
line = f"{COLOR_RED}Dependencies are missing!{RESET_FORMAT}"
|
line = f"{COLOR_RED}Dependencies are missing!{RESET_FORMAT}"
|
||||||
|
|
||||||
menu += f"║ {line:<62} ║\n"
|
menu += f"║ {line:<62} ║\n"
|
||||||
|
menu += "╟───────────────────────────────────────────────────────╢\n"
|
||||||
|
|
||||||
print(menu, end="")
|
print(menu, end="")
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class KlipperNoFirmwareErrorMenu(BaseMenu):
|
|||||||
self.previous_menu = previous_menu
|
self.previous_menu = previous_menu
|
||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.default_option = Option(self.go_back, False)
|
self.default_option = Option(method=self.go_back)
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
header = "!!! NO FIRMWARE FILE FOUND !!!"
|
header = "!!! NO FIRMWARE FILE FOUND !!!"
|
||||||
@@ -79,7 +79,7 @@ class KlipperNoBoardTypesErrorMenu(BaseMenu):
|
|||||||
self.previous_menu = previous_menu
|
self.previous_menu = previous_menu
|
||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.default_option = Option(self.go_back, False)
|
self.default_option = Option(method=self.go_back)
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
header = "!!! ERROR GETTING BOARD LIST !!!"
|
header = "!!! ERROR GETTING BOARD LIST !!!"
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ class KlipperSelectMcuIdMenu(BaseMenu):
|
|||||||
self.flash_options = FlashOptions()
|
self.flash_options = FlashOptions()
|
||||||
self.mcu_list = self.flash_options.mcu_list
|
self.mcu_list = self.flash_options.mcu_list
|
||||||
self.input_label_txt = "Select MCU to flash"
|
self.input_label_txt = "Select MCU to flash"
|
||||||
self.footer_type = FooterType.BACK_HELP
|
self.footer_type = FooterType.BACK
|
||||||
|
|
||||||
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
|
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
|
||||||
self.previous_menu = (
|
self.previous_menu = (
|
||||||
@@ -260,13 +260,12 @@ class KlipperSelectMcuIdMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
f"{i}": Option(self.flash_mcu, False, f"{i}")
|
f"{i}": Option(self.flash_mcu, f"{i}") for i in range(len(self.mcu_list))
|
||||||
for i in range(len(self.mcu_list))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def print_menu(self) -> None:
|
def print_menu(self) -> None:
|
||||||
header = "!!! ATTENTION !!!"
|
header = "!!! ATTENTION !!!"
|
||||||
header2 = f"[{COLOR_CYAN}List of available MCUs{RESET_FORMAT}]"
|
header2 = f"[{COLOR_CYAN}List of detected MCUs{RESET_FORMAT}]"
|
||||||
color = COLOR_RED
|
color = COLOR_RED
|
||||||
count = 62 - len(color) - len(RESET_FORMAT)
|
count = 62 - len(color) - len(RESET_FORMAT)
|
||||||
menu = textwrap.dedent(
|
menu = textwrap.dedent(
|
||||||
@@ -278,15 +277,21 @@ class KlipperSelectMcuIdMenu(BaseMenu):
|
|||||||
║ ONLY flash a firmware created for the respective MCU! ║
|
║ ONLY flash a firmware created for the respective MCU! ║
|
||||||
║ ║
|
║ ║
|
||||||
╟{header2:─^64}╢
|
╟{header2:─^64}╢
|
||||||
|
║ ║
|
||||||
"""
|
"""
|
||||||
)[1:]
|
)[1:]
|
||||||
|
|
||||||
for i, mcu in enumerate(self.mcu_list):
|
for i, mcu in enumerate(self.mcu_list):
|
||||||
mcu = mcu.split("/")[-1]
|
mcu = mcu.split("/")[-1]
|
||||||
menu += f" ● MCU #{i}: {COLOR_CYAN}{mcu}{RESET_FORMAT}\n"
|
menu += f"║ {i}) {COLOR_CYAN}{mcu:<51}{RESET_FORMAT}║\n"
|
||||||
menu += "╟───────────────────────────┬───────────────────────────╢"
|
|
||||||
|
|
||||||
print(menu, end="\n")
|
menu += textwrap.dedent(
|
||||||
|
"""
|
||||||
|
║ ║
|
||||||
|
╟───────────────────────────────────────────────────────╢
|
||||||
|
"""
|
||||||
|
)[1:]
|
||||||
|
print(menu, end="")
|
||||||
|
|
||||||
def flash_mcu(self, **kwargs):
|
def flash_mcu(self, **kwargs):
|
||||||
try:
|
try:
|
||||||
@@ -323,7 +328,7 @@ class KlipperSelectSDFlashBoardMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
f"{i}": Option(self.board_select, False, f"{i}")
|
f"{i}": Option(self.board_select, f"{i}")
|
||||||
for i in range(len(self.available_boards))
|
for i in range(len(self.available_boards))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,8 +349,8 @@ class KlipperSelectSDFlashBoardMenu(BaseMenu):
|
|||||||
|
|
||||||
for i, board in enumerate(self.available_boards):
|
for i, board in enumerate(self.available_boards):
|
||||||
line = f" {i}) {board}"
|
line = f" {i}) {board}"
|
||||||
menu += f"|{line:<55}|\n"
|
menu += f"║{line:<55}║\n"
|
||||||
|
menu += "╟───────────────────────────────────────────────────────╢"
|
||||||
print(menu, end="")
|
print(menu, end="")
|
||||||
|
|
||||||
def board_select(self, **kwargs):
|
def board_select(self, **kwargs):
|
||||||
@@ -393,8 +398,8 @@ class KlipperFlashOverviewMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
"Y": Option(self.execute_flash),
|
"y": Option(self.execute_flash),
|
||||||
"N": Option(self.abort_process),
|
"n": Option(self.abort_process),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.default_option = Option(self.execute_flash)
|
self.default_option = Option(self.execute_flash)
|
||||||
@@ -407,7 +412,7 @@ class KlipperFlashOverviewMenu(BaseMenu):
|
|||||||
method = self.flash_options.flash_method.value
|
method = self.flash_options.flash_method.value
|
||||||
command = self.flash_options.flash_command.value
|
command = self.flash_options.flash_command.value
|
||||||
conn_type = self.flash_options.connection_type.value
|
conn_type = self.flash_options.connection_type.value
|
||||||
mcu = self.flash_options.selected_mcu
|
mcu = self.flash_options.selected_mcu.split("/")[-1]
|
||||||
board = self.flash_options.selected_board
|
board = self.flash_options.selected_board
|
||||||
baudrate = self.flash_options.selected_baudrate
|
baudrate = self.flash_options.selected_baudrate
|
||||||
subheader = f"[{COLOR_CYAN}Overview{RESET_FORMAT}]"
|
subheader = f"[{COLOR_CYAN}Overview{RESET_FORMAT}]"
|
||||||
@@ -421,26 +426,37 @@ class KlipperFlashOverviewMenu(BaseMenu):
|
|||||||
║ sure everything is correct, start the process. If any ║
|
║ sure everything is correct, start the process. If any ║
|
||||||
║ parameter needs to be changed, you can go back (B) ║
|
║ parameter needs to be changed, you can go back (B) ║
|
||||||
║ step by step or abort and start from the beginning. ║
|
║ step by step or abort and start from the beginning. ║
|
||||||
║{subheader:-^64}║
|
║{subheader:─^64}║
|
||||||
|
║ ║
|
||||||
"""
|
"""
|
||||||
)[1:]
|
)[1:]
|
||||||
|
|
||||||
menu += f" ● MCU: {COLOR_CYAN}{mcu}{RESET_FORMAT}\n"
|
menu += textwrap.dedent(
|
||||||
menu += f" ● Connection: {COLOR_CYAN}{conn_type}{RESET_FORMAT}\n"
|
f"""
|
||||||
menu += f" ● Flash method: {COLOR_CYAN}{method}{RESET_FORMAT}\n"
|
║ MCU: {COLOR_CYAN}{mcu:<48}{RESET_FORMAT} ║
|
||||||
menu += f" ● Flash command: {COLOR_CYAN}{command}{RESET_FORMAT}\n"
|
║ Connection: {COLOR_CYAN}{conn_type:<41}{RESET_FORMAT} ║
|
||||||
|
║ Flash method: {COLOR_CYAN}{method:<39}{RESET_FORMAT} ║
|
||||||
|
║ Flash command: {COLOR_CYAN}{command:<38}{RESET_FORMAT} ║
|
||||||
|
"""
|
||||||
|
)[1:]
|
||||||
|
|
||||||
if self.flash_options.flash_method is FlashMethod.SD_CARD:
|
if self.flash_options.flash_method is FlashMethod.SD_CARD:
|
||||||
menu += f" ● Board type: {COLOR_CYAN}{board}{RESET_FORMAT}\n"
|
menu += textwrap.dedent(
|
||||||
menu += f" ● Baudrate: {COLOR_CYAN}{baudrate}{RESET_FORMAT}\n"
|
f"""
|
||||||
|
║ Board type: {COLOR_CYAN}{board:<41}{RESET_FORMAT} ║
|
||||||
|
║ Baudrate: {COLOR_CYAN}{baudrate:<43}{RESET_FORMAT} ║
|
||||||
|
"""
|
||||||
|
)[1:]
|
||||||
|
|
||||||
menu += textwrap.dedent(
|
menu += textwrap.dedent(
|
||||||
"""
|
"""
|
||||||
|
║ ║
|
||||||
╟───────────────────────────────────────────────────────╢
|
╟───────────────────────────────────────────────────────╢
|
||||||
║ Y) Start flash process ║
|
║ Y) Start flash process ║
|
||||||
║ N) Abort - Return to Advanced Menu ║
|
║ N) Abort - Return to Advanced Menu ║
|
||||||
|
╟───────────────────────────────────────────────────────╢
|
||||||
"""
|
"""
|
||||||
)
|
)[1:]
|
||||||
print(menu, end="")
|
print(menu, end="")
|
||||||
|
|
||||||
def execute_flash(self, **kwargs):
|
def execute_flash(self, **kwargs):
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ def install_klipperscreen() -> None:
|
|||||||
"KlipperScreens update manager configuration for Moonraker "
|
"KlipperScreens update manager configuration for Moonraker "
|
||||||
"will not be added to any moonraker.conf.",
|
"will not be added to any moonraker.conf.",
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
if not get_confirm(
|
if not get_confirm(
|
||||||
"Continue KlipperScreen installation?",
|
"Continue KlipperScreen installation?",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class LogUploadMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
f"{index}": Option(self.upload, False, opt_index=f"{index}")
|
f"{index}": Option(self.upload, opt_index=f"{index}")
|
||||||
for index in range(len(self.logfile_list))
|
for index in range(len(self.logfile_list))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,8 +63,6 @@ def install_octoeverywhere() -> None:
|
|||||||
"It is safe to run the installer again to link your "
|
"It is safe to run the installer again to link your "
|
||||||
"printer or repair any issues.",
|
"printer or repair any issues.",
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
if not get_confirm("Re-run OctoEverywhere installation?"):
|
if not get_confirm("Re-run OctoEverywhere installation?"):
|
||||||
Logger.print_info("Exiting OctoEverywhere for Klipper installation ...")
|
Logger.print_info("Exiting OctoEverywhere for Klipper installation ...")
|
||||||
@@ -85,8 +83,6 @@ def install_octoeverywhere() -> None:
|
|||||||
"\n\n",
|
"\n\n",
|
||||||
"The setup will apply the same names to OctoEverywhere!",
|
"The setup will apply the same names to OctoEverywhere!",
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not get_confirm(
|
if not get_confirm(
|
||||||
|
|||||||
@@ -24,8 +24,6 @@ def print_moonraker_not_found_dialog() -> None:
|
|||||||
"another machine in your network. Otherwise Mainsail will NOT work "
|
"another machine in your network. Otherwise Mainsail will NOT work "
|
||||||
"correctly.",
|
"correctly.",
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -36,8 +34,6 @@ def print_client_already_installed_dialog(name: str) -> None:
|
|||||||
f"{name} seems to be already installed!",
|
f"{name} seems to be already installed!",
|
||||||
f"If you continue, your current {name} installation will be overwritten.",
|
f"If you continue, your current {name} installation will be overwritten.",
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -57,8 +53,6 @@ def print_client_port_select_dialog(
|
|||||||
"The following ports were found to be in use already:",
|
"The following ports were found to be in use already:",
|
||||||
*[f"● {port}" for port in ports_in_use],
|
*[f"● {port}" for port in ports_in_use],
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -77,8 +71,6 @@ def print_install_client_config_dialog(client: BaseWebClient) -> None:
|
|||||||
"If you already use these macros skip this step. Otherwise you should "
|
"If you already use these macros skip this step. Otherwise you should "
|
||||||
"consider to answer with 'Y' to download the recommended macros.",
|
"consider to answer with 'Y' to download the recommended macros.",
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ from core.types import ComponentStatus
|
|||||||
from utils.common import get_install_status
|
from utils.common import get_install_status
|
||||||
from utils.fs_utils import create_symlink, remove_file
|
from utils.fs_utils import create_symlink, remove_file
|
||||||
from utils.git_utils import (
|
from utils.git_utils import (
|
||||||
get_latest_tag,
|
get_latest_remote_tag,
|
||||||
get_latest_unstable_tag,
|
get_latest_unstable_tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -137,7 +137,7 @@ def get_local_client_version(client: BaseWebClient) -> str | None:
|
|||||||
|
|
||||||
def get_remote_client_version(client: BaseWebClient) -> str | None:
|
def get_remote_client_version(client: BaseWebClient) -> str | None:
|
||||||
try:
|
try:
|
||||||
if (tag := get_latest_tag(client.repo_path)) != "":
|
if (tag := get_latest_remote_tag(client.repo_path)) != "":
|
||||||
return str(tag)
|
return str(tag)
|
||||||
return None
|
return None
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
@@ -92,8 +92,8 @@ class Logger:
|
|||||||
center_content: bool = False,
|
center_content: bool = False,
|
||||||
custom_title: str | None = None,
|
custom_title: str | None = None,
|
||||||
custom_color: DialogCustomColor | None = None,
|
custom_color: DialogCustomColor | None = None,
|
||||||
padding_top: int = 1,
|
margin_top: int = 0,
|
||||||
padding_bottom: int = 1,
|
margin_bottom: int = 0,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Prints a dialog with the given title and content.
|
Prints a dialog with the given title and content.
|
||||||
@@ -106,8 +106,8 @@ class Logger:
|
|||||||
:param center_content: Whether to center the content or not.
|
:param center_content: Whether to center the content or not.
|
||||||
:param custom_title: A custom title for the dialog.
|
:param custom_title: A custom title for the dialog.
|
||||||
:param custom_color: A custom color for the dialog.
|
:param custom_color: A custom color for the dialog.
|
||||||
:param padding_top: The number of empty lines to print before the dialog.
|
:param margin_top: The number of empty lines to print before the dialog.
|
||||||
:param padding_bottom: The number of empty lines to print after the dialog.
|
:param margin_bottom: The number of empty lines to print after the dialog.
|
||||||
"""
|
"""
|
||||||
dialog_color = Logger._get_dialog_color(title, custom_color)
|
dialog_color = Logger._get_dialog_color(title, custom_color)
|
||||||
dialog_title = Logger._get_dialog_title(title, custom_title)
|
dialog_title = Logger._get_dialog_title(title, custom_title)
|
||||||
@@ -116,12 +116,12 @@ class Logger:
|
|||||||
top = Logger._format_top_border(dialog_color)
|
top = Logger._format_top_border(dialog_color)
|
||||||
bottom = Logger._format_bottom_border()
|
bottom = Logger._format_bottom_border()
|
||||||
|
|
||||||
print("\n" * padding_top)
|
print("\n" * margin_top)
|
||||||
print(
|
print(
|
||||||
f"{top}{dialog_title_formatted}{dialog_content}{bottom}",
|
f"{top}{dialog_title_formatted}{dialog_content}{bottom}",
|
||||||
end="",
|
end="",
|
||||||
)
|
)
|
||||||
print("\n" * padding_bottom)
|
print("\n" * margin_bottom)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_dialog_title(
|
def _get_dialog_title(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable, Type
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -22,7 +22,10 @@ class Option:
|
|||||||
:param opt_data: Can be used to pass any additional data to the menu option
|
:param opt_data: Can be used to pass any additional data to the menu option
|
||||||
"""
|
"""
|
||||||
|
|
||||||
method: Callable | None = None
|
def __repr__(self):
|
||||||
|
return f"Option(method={self.method.__name__}, opt_index={self.opt_index}, opt_data={self.opt_data})"
|
||||||
|
|
||||||
|
method: Type[Callable]
|
||||||
opt_index: str = ""
|
opt_index: str = ""
|
||||||
opt_data: Any = None
|
opt_data: Any = None
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from core.constants import (
|
|||||||
)
|
)
|
||||||
from core.logger import Logger
|
from core.logger import Logger
|
||||||
from core.menus import FooterType, Option
|
from core.menus import FooterType, Option
|
||||||
|
from utils.input_utils import get_selection_input
|
||||||
|
|
||||||
|
|
||||||
def clear() -> None:
|
def clear() -> None:
|
||||||
@@ -141,7 +142,7 @@ class BaseMenu(metaclass=PostInitCaller):
|
|||||||
def __go_to_help(self, **kwargs) -> None:
|
def __go_to_help(self, **kwargs) -> None:
|
||||||
if self.help_menu is None:
|
if self.help_menu is None:
|
||||||
return
|
return
|
||||||
self.help_menu(previous_menu=self).run()
|
self.help_menu(previous_menu=self.__class__).run()
|
||||||
|
|
||||||
def __exit(self, **kwargs) -> None:
|
def __exit(self, **kwargs) -> None:
|
||||||
Logger.print_ok("###### Happy printing!", False)
|
Logger.print_ok("###### Happy printing!", False)
|
||||||
@@ -177,46 +178,20 @@ class BaseMenu(metaclass=PostInitCaller):
|
|||||||
self.print_menu()
|
self.print_menu()
|
||||||
self.print_footer()
|
self.print_footer()
|
||||||
|
|
||||||
def validate_user_input(self, usr_input: str) -> Option:
|
|
||||||
"""
|
|
||||||
Validate the user input and either return an Option, a string or None
|
|
||||||
:param usr_input: The user input in form of a string
|
|
||||||
:return: Option, str or None
|
|
||||||
"""
|
|
||||||
usr_input = usr_input.lower()
|
|
||||||
option = self.options.get(
|
|
||||||
usr_input,
|
|
||||||
Option(method=None, opt_index="", opt_data=None),
|
|
||||||
)
|
|
||||||
|
|
||||||
# if option/usr_input is None/empty string, we execute the menus default option if specified
|
|
||||||
if (option is None or usr_input == "") and self.default_option is not None:
|
|
||||||
self.default_option.opt_index = usr_input
|
|
||||||
return self.default_option
|
|
||||||
|
|
||||||
# user selected a regular option
|
|
||||||
option.opt_index = usr_input
|
|
||||||
return option
|
|
||||||
|
|
||||||
def handle_user_input(self) -> Option:
|
|
||||||
"""Handle the user input, return the validated input or print an error."""
|
|
||||||
while True:
|
|
||||||
print(f"{COLOR_CYAN}###### {self.input_label_txt}: {RESET_FORMAT}", end="")
|
|
||||||
usr_input = input().lower()
|
|
||||||
validated_input = self.validate_user_input(usr_input)
|
|
||||||
|
|
||||||
if validated_input.method is not None:
|
|
||||||
return validated_input
|
|
||||||
else:
|
|
||||||
Logger.print_error("Invalid input!", False)
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Start the menu lifecycle. When this function returns, the lifecycle of the menu ends."""
|
"""Start the menu lifecycle. When this function returns, the lifecycle of the menu ends."""
|
||||||
try:
|
try:
|
||||||
self.display_menu()
|
self.display_menu()
|
||||||
option = self.handle_user_input()
|
option = get_selection_input(self.input_label_txt, self.options)
|
||||||
option.method(opt_index=option.opt_index, opt_data=option.opt_data)
|
selected_option: Option = self.options.get(option)
|
||||||
|
|
||||||
|
selected_option.method(
|
||||||
|
opt_index=selected_option.opt_index,
|
||||||
|
opt_data=selected_option.opt_data,
|
||||||
|
)
|
||||||
|
|
||||||
self.run()
|
self.run()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.print_error(
|
Logger.print_error(
|
||||||
f"An unexpected error occured:\n{e}\n{traceback.format_exc()}"
|
f"An unexpected error occured:\n{e}\n{traceback.format_exc()}"
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ from core.menus.settings_menu import SettingsMenu
|
|||||||
from core.menus.update_menu import UpdateMenu
|
from core.menus.update_menu import UpdateMenu
|
||||||
from core.types import ComponentStatus, StatusMap, StatusText
|
from core.types import ComponentStatus, StatusMap, StatusText
|
||||||
from extensions.extensions_menu import ExtensionsMenu
|
from extensions.extensions_menu import ExtensionsMenu
|
||||||
|
from utils.common import get_kiauh_version
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
# noinspection PyUnusedLocal
|
||||||
@@ -55,6 +56,7 @@ class MainMenu(BaseMenu):
|
|||||||
self.header: bool = True
|
self.header: bool = True
|
||||||
self.footer_type: FooterType = FooterType.QUIT
|
self.footer_type: FooterType = FooterType.QUIT
|
||||||
|
|
||||||
|
self.version = ""
|
||||||
self.kl_status = self.kl_repo = self.mr_status = self.mr_repo = ""
|
self.kl_status = self.kl_repo = self.mr_status = self.mr_repo = ""
|
||||||
self.ms_status = self.fl_status = self.ks_status = self.mb_status = ""
|
self.ms_status = self.fl_status = self.ks_status = self.mb_status = ""
|
||||||
self.cn_status = self.cc_status = self.oe_status = ""
|
self.cn_status = self.cc_status = self.oe_status = ""
|
||||||
@@ -86,6 +88,7 @@ class MainMenu(BaseMenu):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _fetch_status(self) -> None:
|
def _fetch_status(self) -> None:
|
||||||
|
self.version = get_kiauh_version()
|
||||||
self._get_component_status("kl", get_klipper_status)
|
self._get_component_status("kl", get_klipper_status)
|
||||||
self._get_component_status("mr", get_moonraker_status)
|
self._get_component_status("mr", get_moonraker_status)
|
||||||
self._get_component_status("ms", get_client_status, MainsailData())
|
self._get_component_status("ms", get_client_status, MainsailData())
|
||||||
@@ -125,7 +128,7 @@ class MainMenu(BaseMenu):
|
|||||||
self._fetch_status()
|
self._fetch_status()
|
||||||
|
|
||||||
header = " [ Main Menu ] "
|
header = " [ Main Menu ] "
|
||||||
footer1 = f"{COLOR_CYAN}KIAUH v6.0.0{RESET_FORMAT}"
|
footer1 = f"{COLOR_CYAN}{self.version}{RESET_FORMAT}"
|
||||||
footer2 = f"Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT}"
|
footer2 = f"Changelog: {COLOR_MAGENTA}https://git.io/JnmlX{RESET_FORMAT}"
|
||||||
color = COLOR_CYAN
|
color = COLOR_CYAN
|
||||||
count = 62 - len(color) - len(RESET_FORMAT)
|
count = 62 - len(color) - len(RESET_FORMAT)
|
||||||
|
|||||||
@@ -281,8 +281,6 @@ class UpdateMenu(BaseMenu):
|
|||||||
DialogType.CUSTOM,
|
DialogType.CUSTOM,
|
||||||
["The following packages will be upgraded:", "\n\n", pkgs],
|
["The following packages will be upgraded:", "\n\n", pkgs],
|
||||||
custom_title="UPGRADABLE SYSTEM UPDATES",
|
custom_title="UPGRADABLE SYSTEM UPDATES",
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
if not get_confirm("Continue?"):
|
if not get_confirm("Continue?"):
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -85,10 +85,46 @@ class DuplicateOptionError(Exception):
|
|||||||
class SimpleConfigParser:
|
class SimpleConfigParser:
|
||||||
"""A customized config parser targeted at handling Klipper style config files"""
|
"""A customized config parser targeted at handling Klipper style config files"""
|
||||||
|
|
||||||
_SECTION_RE = re.compile(r"\s*\[(\w+\s?.+)]\s*([#;].*)?$")
|
# definition of section line:
|
||||||
_OPTION_RE = re.compile(r"^\s*(\w+)\s*[:=]\s*([^=:].*)\s*([#;].*)?$")
|
# - then line MUST start with an opening square bracket - it is the first section marker
|
||||||
_MLOPTION_RE = re.compile(r"^\s*(\w+)\s*[:=]\s*([#;].*)?$")
|
# - 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*([#;].*)?$")
|
||||||
|
|
||||||
|
# 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*([#;].*)?$")
|
||||||
|
|
||||||
|
# definition of multiline 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 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
|
||||||
|
_MLOPTION_RE = re.compile(r"^([^:=\s]+)\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
|
||||||
_COMMENT_RE = re.compile(r"^\s*([#;].*)?$")
|
_COMMENT_RE = re.compile(r"^\s*([#;].*)?$")
|
||||||
|
|
||||||
|
# definition of empty line:
|
||||||
|
# - the line MUST contain only whitespace characters
|
||||||
_EMPTY_LINE_RE = re.compile(r"^\s*$")
|
_EMPTY_LINE_RE = re.compile(r"^\s*$")
|
||||||
|
|
||||||
BOOLEAN_STATES = {
|
BOOLEAN_STATES = {
|
||||||
|
|||||||
@@ -21,4 +21,8 @@ testcases = [
|
|||||||
"serial",
|
"serial",
|
||||||
"/dev/serial/by-id/<your-mcu-id>",
|
"/dev/serial/by-id/<your-mcu-id>",
|
||||||
),
|
),
|
||||||
|
("parameter_temperature_(°C): 155", "parameter_temperature_(°C)", "155"),
|
||||||
|
("parameter_humidity_(%_RH): 45", "parameter_humidity_(%_RH)", "45"),
|
||||||
|
("parameter_spool_weight_(%): 10", "parameter_spool_weight_(%)", "10"),
|
||||||
|
("path: /dev/shm/drying_box.json", "path", "/dev/shm/drying_box.json"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ def parser():
|
|||||||
return SimpleConfigParser()
|
return SimpleConfigParser()
|
||||||
|
|
||||||
|
|
||||||
class TestLineParsing:
|
class TestSingleLineParsing:
|
||||||
@pytest.mark.parametrize("given, expected", [*case_parse_section])
|
@pytest.mark.parametrize("given, expected", [*case_parse_section])
|
||||||
def test_parse_section(self, parser, given, expected):
|
def test_parse_section(self, parser, given, expected):
|
||||||
parser._parse_section(given)
|
parser._parse_section(given)
|
||||||
@@ -14,4 +14,14 @@ testcases = [
|
|||||||
("", False),
|
("", False),
|
||||||
("# that's a comment", False),
|
("# that's a comment", False),
|
||||||
("; that's a comment", False),
|
("; that's a comment", False),
|
||||||
|
("parameter_humidity_(%_RH):", True),
|
||||||
|
("parameter_spool_weight_(%):", True),
|
||||||
|
("parameter_temperature_(°C):", True),
|
||||||
|
("parameter_humidity_(%_RH): 18.123", False),
|
||||||
|
("parameter_spool_weight_(%): 150", False),
|
||||||
|
("parameter_temperature_(°C): 30,5", False),
|
||||||
|
("trusted_clients:", True),
|
||||||
|
("trusted_clients: 192.168.1.0/24", False),
|
||||||
|
("cors_domains:", True),
|
||||||
|
("cors_domains: http://*.lan", False),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -26,5 +26,6 @@ testcases = [
|
|||||||
("description: homing!", True),
|
("description: homing!", True),
|
||||||
("description: inline macro :-)", True),
|
("description: inline macro :-)", True),
|
||||||
("path: %GCODES_DIR%", True),
|
("path: %GCODES_DIR%", True),
|
||||||
|
("path: /dev/shm/drying_box.json", True),
|
||||||
("serial = /dev/serial/by-id/<your-mcu-id>", True),
|
("serial = /dev/serial/by-id/<your-mcu-id>", True),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -38,9 +38,7 @@ class ExtensionsMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
i: Option(
|
i: Option(self.extension_submenu, opt_data=self.extensions.get(i))
|
||||||
self.extension_submenu, menu=True, opt_data=self.extensions.get(i)
|
|
||||||
)
|
|
||||||
for i in self.extensions
|
for i in self.extensions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ class MainsailThemeInstallMenu(BaseMenu):
|
|||||||
|
|
||||||
def set_options(self) -> None:
|
def set_options(self) -> None:
|
||||||
self.options = {
|
self.options = {
|
||||||
f"{index}": Option(self.install_theme, False, opt_index=f"{index}")
|
f"{index}": Option(self.install_theme, opt_index=f"{index}")
|
||||||
for index in range(len(self.themes))
|
for index in range(len(self.themes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,8 +49,6 @@ class TelegramBotExtension(BaseExtension):
|
|||||||
"Moonraker Telegram Bot requires Moonraker to be installed. "
|
"Moonraker Telegram Bot requires Moonraker to be installed. "
|
||||||
"Please install Moonraker first!",
|
"Please install Moonraker first!",
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -65,8 +63,6 @@ class TelegramBotExtension(BaseExtension):
|
|||||||
"\n\n",
|
"\n\n",
|
||||||
"The setup will apply the same names to Telegram Bot!",
|
"The setup will apply the same names to Telegram Bot!",
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
if not get_confirm(
|
if not get_confirm(
|
||||||
"Continue Moonraker Telegram Bot installation?",
|
"Continue Moonraker Telegram Bot installation?",
|
||||||
@@ -88,8 +84,6 @@ class TelegramBotExtension(BaseExtension):
|
|||||||
instance = MoonrakerTelegramBot(suffix=name)
|
instance = MoonrakerTelegramBot(suffix=name)
|
||||||
instance.create()
|
instance.create()
|
||||||
|
|
||||||
print(instance)
|
|
||||||
|
|
||||||
cmd_sysctl_service(instance.service_file_path.name, "enable")
|
cmd_sysctl_service(instance.service_file_path.name, "enable")
|
||||||
|
|
||||||
if create_example_cfg:
|
if create_example_cfg:
|
||||||
@@ -126,6 +120,7 @@ class TelegramBotExtension(BaseExtension):
|
|||||||
"following wiki page for further information:",
|
"following wiki page for further information:",
|
||||||
"https://github.com/nlef/moonraker-telegram-bot/wiki",
|
"https://github.com/nlef/moonraker-telegram-bot/wiki",
|
||||||
],
|
],
|
||||||
|
margin_bottom=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
Logger.print_ok("Telegram Bot installation complete!")
|
Logger.print_ok("Telegram Bot installation complete!")
|
||||||
|
|||||||
@@ -35,8 +35,6 @@ def change_system_hostname() -> None:
|
|||||||
"browser.",
|
"browser.",
|
||||||
],
|
],
|
||||||
custom_title="CHANGE SYSTEM HOSTNAME",
|
custom_title="CHANGE SYSTEM HOSTNAME",
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
if not get_confirm("Do you want to change the hostname?", default_choice=False):
|
if not get_confirm("Do you want to change the hostname?", default_choice=False):
|
||||||
return
|
return
|
||||||
@@ -50,8 +48,6 @@ def change_system_hostname() -> None:
|
|||||||
"● Any special characters",
|
"● Any special characters",
|
||||||
"● No leading or trailing '-'",
|
"● No leading or trailing '-'",
|
||||||
],
|
],
|
||||||
padding_top=0,
|
|
||||||
padding_bottom=0,
|
|
||||||
)
|
)
|
||||||
hostname = get_string_input(
|
hostname = get_string_input(
|
||||||
"Enter the new hostname",
|
"Enter the new hostname",
|
||||||
|
|||||||
@@ -22,7 +22,12 @@ from core.constants import (
|
|||||||
)
|
)
|
||||||
from core.logger import DialogType, Logger
|
from core.logger import DialogType, Logger
|
||||||
from core.types import ComponentStatus, StatusCode
|
from core.types import ComponentStatus, StatusCode
|
||||||
from utils.git_utils import get_local_commit, get_remote_commit, get_repo_name
|
from utils.git_utils import (
|
||||||
|
get_local_commit,
|
||||||
|
get_local_tags,
|
||||||
|
get_remote_commit,
|
||||||
|
get_repo_name,
|
||||||
|
)
|
||||||
from utils.instance_utils import get_instances
|
from utils.instance_utils import get_instances
|
||||||
from utils.sys_utils import (
|
from utils.sys_utils import (
|
||||||
check_package_install,
|
check_package_install,
|
||||||
@@ -31,6 +36,14 @@ from utils.sys_utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_kiauh_version() -> str:
|
||||||
|
"""
|
||||||
|
Helper method to get the current KIAUH version by reading the latest tag
|
||||||
|
:return: string of the latest tag
|
||||||
|
"""
|
||||||
|
return get_local_tags(Path(__file__).parent.parent)[-1]
|
||||||
|
|
||||||
|
|
||||||
def convert_camelcase_to_kebabcase(name: str) -> str:
|
def convert_camelcase_to_kebabcase(name: str) -> str:
|
||||||
return re.sub(r"(?<!^)(?=[A-Z])", "-", name).lower()
|
return re.sub(r"(?<!^)(?=[A-Z])", "-", name).lower()
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,38 @@ def get_repo_name(repo: Path) -> str | None:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_tags(repo_path: str) -> List[str]:
|
def get_local_tags(repo_path: Path, _filter: str | None = None) -> List[str]:
|
||||||
|
"""
|
||||||
|
Get all tags of a local Git repository
|
||||||
|
:param repo_path: Path to the local Git repository
|
||||||
|
:param _filter: Optional filter to filter the tags by
|
||||||
|
:return: List of tags
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cmd = ["git", "tag", "-l"]
|
||||||
|
|
||||||
|
if _filter is not None:
|
||||||
|
cmd.append(f"'${_filter}'")
|
||||||
|
|
||||||
|
result: str = check_output(
|
||||||
|
cmd,
|
||||||
|
stderr=DEVNULL,
|
||||||
|
cwd=repo_path.as_posix(),
|
||||||
|
).decode(encoding="utf-8")
|
||||||
|
|
||||||
|
tags = result.split("\n")
|
||||||
|
return tags[:-1]
|
||||||
|
|
||||||
|
except CalledProcessError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_tags(repo_path: str) -> List[str]:
|
||||||
|
"""
|
||||||
|
Gets the tags of a GitHub repostiory
|
||||||
|
:param repo_path: path of the GitHub repository - e.g. `<owner>/<name>`
|
||||||
|
:return: List of tags
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
url = f"https://api.github.com/repos/{repo_path}/tags"
|
url = f"https://api.github.com/repos/{repo_path}/tags"
|
||||||
with urllib.request.urlopen(url) as r:
|
with urllib.request.urlopen(url) as r:
|
||||||
@@ -102,14 +133,14 @@ def get_tags(repo_path: str) -> List[str]:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def get_latest_tag(repo_path: str) -> str:
|
def get_latest_remote_tag(repo_path: str) -> str:
|
||||||
"""
|
"""
|
||||||
Gets the latest stable tag of a GitHub repostiory
|
Gets the latest stable tag of a GitHub repostiory
|
||||||
:param repo_path: path of the GitHub repository - e.g. `<owner>/<name>`
|
:param repo_path: path of the GitHub repository - e.g. `<owner>/<name>`
|
||||||
:return: tag or empty string
|
:return: tag or empty string
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if len(latest_tag := get_tags(repo_path)) > 0:
|
if len(latest_tag := get_remote_tags(repo_path)) > 0:
|
||||||
return latest_tag[0]
|
return latest_tag[0]
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
@@ -124,7 +155,10 @@ def get_latest_unstable_tag(repo_path: str) -> str:
|
|||||||
:return: tag or empty string
|
:return: tag or empty string
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if len(unstable_tags := [t for t in get_tags(repo_path) if "-" in t]) > 0:
|
if (
|
||||||
|
len(unstable_tags := [t for t in get_remote_tags(repo_path) if "-" in t])
|
||||||
|
> 0
|
||||||
|
):
|
||||||
return unstable_tags[0]
|
return unstable_tags[0]
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
@@ -133,6 +167,34 @@ def get_latest_unstable_tag(repo_path: str) -> str:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def compare_semver_tags(tag1: str, tag2: str) -> bool:
|
||||||
|
"""
|
||||||
|
Compare two semver version strings.
|
||||||
|
Does not support comparing pre-release versions (e.g. 1.0.0-rc.1, 1.0.0-beta.1)
|
||||||
|
:param tag1: First version string
|
||||||
|
:param tag2: Second version string
|
||||||
|
:return: True if tag1 is greater than tag2, False otherwise
|
||||||
|
"""
|
||||||
|
if tag1 == tag2:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def parse_version(v):
|
||||||
|
return list(map(int, v[1:].split(".")))
|
||||||
|
|
||||||
|
tag1_parts = parse_version(tag1)
|
||||||
|
tag2_parts = parse_version(tag2)
|
||||||
|
|
||||||
|
max_len = max(len(tag1_parts), len(tag2_parts))
|
||||||
|
tag1_parts += [0] * (max_len - len(tag1_parts))
|
||||||
|
tag2_parts += [0] * (max_len - len(tag2_parts))
|
||||||
|
|
||||||
|
for part1, part2 in zip(tag1_parts, tag2_parts):
|
||||||
|
if part1 != part2:
|
||||||
|
return part1 > part2
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_local_commit(repo: Path) -> str | None:
|
def get_local_commit(repo: Path) -> str | None:
|
||||||
if not repo.exists() or not repo.joinpath(".git").exists():
|
if not repo.exists() or not repo.joinpath(".git").exists():
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ def get_selection_input(question: str, option_list: List | Dict, default=None) -
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Invalid option_list type")
|
raise ValueError("Invalid option_list type")
|
||||||
|
|
||||||
Logger.print_error(INVALID_CHOICE)
|
Logger.print_error("Invalid option! Please select a valid option.", False)
|
||||||
|
|
||||||
|
|
||||||
def format_question(question: str, default=None) -> str:
|
def format_question(question: str, default=None) -> str:
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function main_ui() {
|
|||||||
function get_kiauh_version() {
|
function get_kiauh_version() {
|
||||||
local version
|
local version
|
||||||
cd "${KIAUH_SRCDIR}"
|
cd "${KIAUH_SRCDIR}"
|
||||||
version="$(git describe HEAD --always --tags | cut -d "-" -f 1,2)"
|
version="$(git tag -l 'v5*' | tail -1)"
|
||||||
echo "${version}"
|
echo "${version}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user