Compare commits

...

10 Commits

Author SHA1 Message Date
CODeRUS
fb460ab687 Merge 81f409c5ca into 4978f22101 2025-02-09 03:13:36 +07:00
Andrey Kozhevnikov
81f409c5ca feature: save and select kconfig
Signed-off-by: Andrey Kozhevnikov <coderusinbox@gmail.com>
2025-02-09 03:13:27 +07:00
Mathijs Groothuis
4978f22101 fix(typo): Successfull > Successful
Co-authored-by: dw-0 <th33xitus@gmail.com>
Co-authored-by: dw-0 <domwil1091+github@gmail.com>
2025-02-08 13:30:37 +01:00
Mathijs Groothuis
8330f90b56 Fix typo: tyoing > typing
Fix typo: tyoing > typing

Co-authored-by: dw-0 <th33xitus@gmail.com>
Co-authored-by: dw-0 <domwil1091+github@gmail.com>
2025-02-05 19:07:19 +01:00
dw-0
2a08e3eb15 refactor: omit port 80 for IP in success message after webclient installation (#618)
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2025-01-18 17:38:40 +01:00
dw-0
a2a3e92b50 refactor: remove BASE_USER argument from crowsnest install command (#617)
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2025-01-18 17:38:00 +01:00
dw-0
3852464ab7 fix: use raw strings for regex parameter in get_string_input (#612)
fixes #602

Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2025-01-03 22:10:39 +01:00
dw-0
4ae5a37ec6 fix: most recent tag not shown correctly in main menu
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-11-24 21:43:10 +01:00
dw-0
935f81aab6 fix: backup fails in case of dangling symlink
Signed-off-by: Dominik Willner <th33xitus@gmail.com>
2024-11-24 21:26:12 +01:00
nlef
dbbc87f18e fix: use correct telegram bot config path (#600)
* fix telegram bot config path

* use _post)init_value

---------

Co-authored-by: dw-0 <th33xitus@gmail.com>
Co-authored-by: dw-0 <domwil1091+github@gmail.com>
2024-11-24 15:53:49 +01:00
15 changed files with 202 additions and 32 deletions

View File

@@ -27,7 +27,6 @@ from components.crowsnest import (
) )
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from core.backup_manager.backup_manager import BackupManager from core.backup_manager.backup_manager import BackupManager
from core.constants import CURRENT_USER
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.settings.kiauh_settings import KiauhSettings from core.settings.kiauh_settings import KiauhSettings
from core.types.component_status import ComponentStatus from core.types.component_status import ComponentStatus
@@ -73,7 +72,7 @@ def install_crowsnest() -> None:
Logger.print_info("Installer will prompt you for sudo password!") Logger.print_info("Installer will prompt you for sudo password!")
try: try:
run( run(
f"sudo make install BASE_USER={CURRENT_USER}", f"sudo make install",
cwd=CROWSNEST_DIR, cwd=CROWSNEST_DIR,
shell=True, shell=True,
check=True, check=True,

View File

@@ -25,6 +25,7 @@ KLIPPER_SERVICE_NAME = "klipper.service"
# directories # directories
KLIPPER_DIR = Path.home().joinpath("klipper") KLIPPER_DIR = Path.home().joinpath("klipper")
KLIPPER_KCONFIGS_DIR = Path.home().joinpath("klipper-kconfigs")
KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env") KLIPPER_ENV_DIR = Path.home().joinpath("klippy-env")
KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups") KLIPPER_BACKUP_DIR = BACKUP_ROOT_DIR.joinpath("klipper-backups")

View File

@@ -138,6 +138,7 @@ def start_flash_process(flash_options: FlashOptions) -> None:
if flash_options.flash_method is FlashMethod.REGULAR: if flash_options.flash_method is FlashMethod.REGULAR:
cmd = [ cmd = [
"make", "make",
f"KCONFIG_CONFIG={flash_options.selected_kconfig}",
flash_options.flash_command.value, flash_options.flash_command.value,
f"FLASH_DEVICE={flash_options.selected_mcu}", f"FLASH_DEVICE={flash_options.selected_mcu}",
] ]
@@ -165,17 +166,17 @@ def start_flash_process(flash_options: FlashOptions) -> None:
if rc != 0: if rc != 0:
raise Exception(f"Flashing failed with returncode: {rc}") raise Exception(f"Flashing failed with returncode: {rc}")
else: else:
Logger.print_ok("Flashing successfull!", start="\n", end="\n\n") Logger.print_ok("Flashing successful!", start="\n", end="\n\n")
except (Exception, CalledProcessError): except (Exception, CalledProcessError):
Logger.print_error("Flashing failed!", start="\n") Logger.print_error("Flashing failed!", start="\n")
Logger.print_error("See the console output above!", end="\n\n") Logger.print_error("See the console output above!", end="\n\n")
def run_make_clean() -> None: def run_make_clean(kconfig = '.config') -> None:
try: try:
run( run(
"make clean", f"make KCONFIG_CONFIG={kconfig} clean",
cwd=KLIPPER_DIR, cwd=KLIPPER_DIR,
shell=True, shell=True,
check=True, check=True,
@@ -185,10 +186,10 @@ def run_make_clean() -> None:
raise raise
def run_make_menuconfig() -> None: def run_make_menuconfig(kconfig = '.config') -> None:
try: try:
run( run(
"make PYTHON=python3 menuconfig", f"make PYTHON=python3 KCONFIG_CONFIG={kconfig} menuconfig",
cwd=KLIPPER_DIR, cwd=KLIPPER_DIR,
shell=True, shell=True,
check=True, check=True,
@@ -198,10 +199,10 @@ def run_make_menuconfig() -> None:
raise raise
def run_make() -> None: def run_make(kconfig = '.config') -> None:
try: try:
run( run(
"make PYTHON=python3", f"make PYTHON=python3 KCONFIG_CONFIG={kconfig}",
cwd=KLIPPER_DIR, cwd=KLIPPER_DIR,
shell=True, shell=True,
check=True, check=True,

View File

@@ -39,6 +39,7 @@ class FlashOptions:
_selected_mcu: str = "" _selected_mcu: str = ""
_selected_board: str = "" _selected_board: str = ""
_selected_baudrate: int = 250000 _selected_baudrate: int = 250000
_selected_kconfig: str = ".config"
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if not cls._instance: if not cls._instance:
@@ -104,3 +105,11 @@ class FlashOptions:
@selected_baudrate.setter @selected_baudrate.setter
def selected_baudrate(self, value: int) -> None: def selected_baudrate(self, value: int) -> None:
self._selected_baudrate = value self._selected_baudrate = value
@property
def selected_kconfig(self) -> str:
return self._selected_kconfig
@selected_kconfig.setter
def selected_kconfig(self, value: str) -> None:
self._selected_kconfig = value

View File

@@ -10,34 +10,121 @@ from __future__ import annotations
import textwrap import textwrap
from typing import List, Set, Type from typing import List, Set, Type
from os import path, listdir, mkdir
from shutil import copyfile
from components.klipper import KLIPPER_DIR from components.klipper import KLIPPER_DIR, KLIPPER_KCONFIGS_DIR
from components.klipper_firmware.firmware_utils import ( from components.klipper_firmware.firmware_utils import (
run_make, run_make,
run_make_clean, run_make_clean,
run_make_menuconfig, run_make_menuconfig,
) )
from core.logger import Logger from components.klipper_firmware.flash_options import FlashOptions
from core.logger import DialogType, Logger
from core.menus import Option from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu, print_back_footer
from core.types.color import Color from core.types.color import Color
from utils.sys_utils import ( from utils.sys_utils import (
check_package_install, check_package_install,
install_system_packages, install_system_packages,
update_system_package_lists, update_system_package_lists,
) )
from utils.input_utils import get_confirm, get_string_input, get_selection_input
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic # noinspection PyMethodMayBeStatic
class KlipperBuildFirmwareMenu(BaseMenu): class KlipperKConfigMenu(BaseMenu):
def __init__(self, previous_menu: Type[BaseMenu] | None = None): def __init__(self, previous_menu: Type[BaseMenu] | None = None):
super().__init__()
self.title = "Firmware Config Menu"
self.title_color = Color.CYAN
self.previous_menu: Type[BaseMenu] | None = previous_menu
self.flash_options = FlashOptions()
self.kconfigs_dirname = KLIPPER_KCONFIGS_DIR
self.kconfig_default = path.join(KLIPPER_DIR, ".config")
self.kconfig = self.kconfig_default if not path.isdir(self.kconfigs_dirname) else None
def run(self) -> None:
if not self.kconfig:
super().run()
else:
self.flash_options.selected_kconfig = self.kconfig
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
from core.menus.advanced_menu import AdvancedMenu
self.previous_menu = (
previous_menu if previous_menu is not None else AdvancedMenu
)
def set_options(self) -> None:
if not path.isdir(self.kconfigs_dirname):
return
self.input_label_txt = "Select config or action to continue (default=n)"
self.default_option = Option(method=self.select_config, opt_data=self.kconfig_default)
self.configs = []
option_index = 1
for kconfig in listdir(self.kconfigs_dirname):
if not kconfig.endswith(".config"):
continue
kconfig_path = path.join(self.kconfigs_dirname, kconfig)
if path.isfile(kconfig_path):
self.configs += [kconfig]
self.options[str(option_index)] = Option(method=self.select_config, opt_data=kconfig_path)
option_index += 1
self.options['n'] = Option(method=self.select_config, opt_data=self.kconfig_default)
def print_menu(self) -> None:
menu = textwrap.dedent(
"""
╟───────────────────────────────────────────────────────╢
║ Found previously saved firmware configs ║
║ ║
║ You can select existing firmware config or create a ║
║ new one. ║
║ ║
"""
)[1:]
start_index = 1
for i, s in enumerate(self.configs):
line = f"{start_index + i}) {s}"
menu += f"{line:<54}\n"
new_config = Color.apply("n) New firmware config", Color.YELLOW)
menu += f"{new_config:<63}\n"
menu += "║ ║\n"
menu += "╟───────────────────────────────────────────────────────╢\n"
print(menu, end="")
def select_config(self, **kwargs) -> None:
selection: str | None = kwargs.get("opt_data", None)
if selection is None:
raise Exception("opt_data is None")
if not path.isfile(selection) and selection != self.kconfig_default:
raise Exception("opt_data does not exists")
self.kconfig = selection
# noinspection PyUnusedLocal
# noinspection PyMethodMayBeStatic
class KlipperBuildFirmwareMenu(BaseMenu):
def __init__(self, kconfig: str | None = None, previous_menu: Type[BaseMenu] | None = None):
super().__init__() super().__init__()
self.title = "Build Firmware Menu" self.title = "Build Firmware Menu"
self.title_color = Color.CYAN self.title_color = Color.CYAN
self.previous_menu: Type[BaseMenu] | None = previous_menu self.previous_menu: Type[BaseMenu] | None = previous_menu
self.deps: Set[str] = {"build-essential", "dpkg-dev", "make"} self.deps: Set[str] = {"build-essential", "dpkg-dev", "make"}
self.missing_deps: List[str] = check_package_install(self.deps) self.missing_deps: List[str] = check_package_install(self.deps)
self.flash_options = FlashOptions()
self.kconfigs_dirname = KLIPPER_KCONFIGS_DIR
self.kconfig_default = path.join(KLIPPER_DIR, ".config")
self.kconfig = self.flash_options.selected_kconfig
def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None: def set_previous_menu(self, previous_menu: Type[BaseMenu] | None) -> None:
from core.menus.advanced_menu import AdvancedMenu from core.menus.advanced_menu import AdvancedMenu
@@ -67,7 +154,7 @@ class KlipperBuildFirmwareMenu(BaseMenu):
status_ok = Color.apply("*INSTALLED*", Color.GREEN) status_ok = Color.apply("*INSTALLED*", Color.GREEN)
status_missing = Color.apply("*MISSING*", Color.RED) status_missing = Color.apply("*MISSING*", Color.RED)
status = status_missing if d in self.missing_deps else status_ok status = status_missing if d in self.missing_deps else status_ok
padding = 39 - len(d) + len(status) + (len(status_ok) - len(status)) padding = 40 - len(d) + len(status) + (len(status_ok) - len(status))
d = Color.apply(f"{d}", Color.CYAN) d = Color.apply(f"{d}", Color.CYAN)
menu += f"{d}{status:>{padding}}\n" menu += f"{d}{status:>{padding}}\n"
menu += "║ ║\n" menu += "║ ║\n"
@@ -98,13 +185,16 @@ class KlipperBuildFirmwareMenu(BaseMenu):
def start_build_process(self, **kwargs) -> None: def start_build_process(self, **kwargs) -> None:
try: try:
run_make_clean() run_make_clean(self.kconfig)
run_make_menuconfig() run_make_menuconfig(self.kconfig)
run_make() run_make(self.kconfig)
Logger.print_ok("Firmware successfully built!") Logger.print_ok("Firmware successfully built!")
Logger.print_ok(f"Firmware file located in '{KLIPPER_DIR}/out'!") Logger.print_ok(f"Firmware file located in '{KLIPPER_DIR}/out'!")
if self.kconfig == self.kconfig_default:
self.save_firmware_config()
except Exception as e: except Exception as e:
Logger.print_error(e) Logger.print_error(e)
Logger.print_error("Building Klipper Firmware failed!") Logger.print_error("Building Klipper Firmware failed!")
@@ -112,3 +202,57 @@ class KlipperBuildFirmwareMenu(BaseMenu):
finally: finally:
if self.previous_menu is not None: if self.previous_menu is not None:
self.previous_menu().run() self.previous_menu().run()
def save_firmware_config(self) -> None:
Logger.print_dialog(
DialogType.CUSTOM,
[
"You can save the firmware build configs for multiple MCUs,"
" and use them to update the firmware after a Klipper version upgrade"
],
custom_title="Save firmware config",
)
if not get_confirm("Do you want to save firmware config?", default_choice=False):
return
filename = self.kconfig_default
while True:
Logger.print_dialog(
DialogType.CUSTOM,
[
"Allowed characters: a-z, 0-9 and '-'",
"The name must not contain the following:",
"\n\n",
"● Any special characters",
"● No leading or trailing '-'",
],
)
input_name = get_string_input(
"Enter the new firmware config name",
regex=r"^[a-z0-9]+([a-z0-9-]*[a-z0-9])?$",
)
filename = path.join(self.kconfigs_dirname, f"{input_name}.config")
if path.isfile(filename):
if get_confirm(f"Firmware config {input_name} already exists, overwrite?", default_choice=False):
break
if path.isdir(filename):
Logger.print_error(f"Path {filename} exists and it's a directory")
if not path.exists(filename):
break
if not get_confirm(f"Save firmware config to '{filename}'?", default_choice=True):
Logger.print_info("Aborted saving firmware config ...")
return
if not path.exists(self.kconfigs_dirname):
mkdir(self.kconfigs_dirname)
copyfile(self.kconfig_default, filename)
Logger.print_ok()
Logger.print_ok(f"Firmware config successfully saved to {filename}")

View File

@@ -11,6 +11,7 @@ from __future__ import annotations
import textwrap import textwrap
import time import time
from typing import Type from typing import Type
from os.path import basename
from components.klipper_firmware.firmware_utils import ( from components.klipper_firmware.firmware_utils import (
find_firmware_file, find_firmware_file,
@@ -36,6 +37,7 @@ from components.klipper_firmware.menus.klipper_flash_help_menu import (
KlipperFlashMethodHelpMenu, KlipperFlashMethodHelpMenu,
KlipperMcuConnectionHelpMenu, KlipperMcuConnectionHelpMenu,
) )
from components.klipper_firmware.menus.klipper_build_menu import KlipperKConfigMenu
from core.logger import DialogType, Logger from core.logger import DialogType, Logger
from core.menus import FooterType, Option from core.menus import FooterType, Option
from core.menus.base_menu import BaseMenu, MenuTitleStyle from core.menus.base_menu import BaseMenu, MenuTitleStyle
@@ -420,6 +422,7 @@ class KlipperFlashOverviewMenu(BaseMenu):
mcu = self.flash_options.selected_mcu.split("/")[-1] 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
kconfig = basename(self.flash_options.selected_kconfig)
color = Color.CYAN color = Color.CYAN
subheader = f"[{Color.apply('Overview', color)}]" subheader = f"[{Color.apply('Overview', color)}]"
menu = textwrap.dedent( menu = textwrap.dedent(
@@ -452,6 +455,13 @@ class KlipperFlashOverviewMenu(BaseMenu):
""" """
)[1:] )[1:]
if self.flash_options.flash_method is FlashMethod.REGULAR:
menu += textwrap.dedent(
f"""
║ Firmware config: {Color.apply(f"{kconfig:<36}", color)}
"""
)[1:]
menu += textwrap.dedent( menu += textwrap.dedent(
""" """
║ ║ ║ ║

View File

@@ -144,7 +144,7 @@ def install_client(
custom_color=Color.GREEN, custom_color=Color.GREEN,
center_content=True, center_content=True,
content=[ content=[
f"Open {client.display_name} now on: http://{get_ipv4_addr()}:{port}", f"Open {client.display_name} now on: http://{get_ipv4_addr()}{'' if port == 80 else f':{port}'}",
], ],
) )

View File

@@ -79,14 +79,14 @@ class BackupManager:
if source is None or not Path(source).exists(): if source is None or not Path(source).exists():
Logger.print_info("Source directory does not exist! Skipping ...") Logger.print_info("Source directory does not exist! Skipping ...")
return return None
target = self.backup_root_dir if target is None else target target = self.backup_root_dir if target is None else target
try: try:
date = get_current_date().get("date") date = get_current_date().get("date")
time = get_current_date().get("time") time = get_current_date().get("time")
backup_target = target.joinpath(f"{name.lower()}-{date}-{time}") backup_target = target.joinpath(f"{name.lower()}-{date}-{time}")
shutil.copytree(source, backup_target, ignore=self.ignore_folders_func) shutil.copytree(source, backup_target, ignore=self.ignore_folders_func, ignore_dangling_symlinks=True)
Logger.print_ok("Backup successful!") Logger.print_ok("Backup successful!")
return backup_target return backup_target

View File

@@ -14,6 +14,7 @@ from typing import Type
from components.klipper import KLIPPER_DIR from components.klipper import KLIPPER_DIR
from components.klipper.klipper import Klipper from components.klipper.klipper import Klipper
from components.klipper_firmware.menus.klipper_build_menu import ( from components.klipper_firmware.menus.klipper_build_menu import (
KlipperKConfigMenu,
KlipperBuildFirmwareMenu, KlipperBuildFirmwareMenu,
) )
from components.klipper_firmware.menus.klipper_flash_menu import ( from components.klipper_firmware.menus.klipper_flash_menu import (
@@ -76,12 +77,15 @@ class AdvancedMenu(BaseMenu):
rollback_repository(MOONRAKER_DIR, Moonraker) rollback_repository(MOONRAKER_DIR, Moonraker)
def build(self, **kwargs) -> None: def build(self, **kwargs) -> None:
KlipperKConfigMenu().run()
KlipperBuildFirmwareMenu(previous_menu=self.__class__).run() KlipperBuildFirmwareMenu(previous_menu=self.__class__).run()
def flash(self, **kwargs) -> None: def flash(self, **kwargs) -> None:
KlipperKConfigMenu().run()
KlipperFlashMethodMenu(previous_menu=self.__class__).run() KlipperFlashMethodMenu(previous_menu=self.__class__).run()
def build_flash(self, **kwargs) -> None: def build_flash(self, **kwargs) -> None:
KlipperKConfigMenu().run()
KlipperBuildFirmwareMenu(previous_menu=KlipperFlashMethodMenu).run() KlipperBuildFirmwareMenu(previous_menu=KlipperFlashMethodMenu).run()
KlipperFlashMethodMenu(previous_menu=self.__class__).run() KlipperFlashMethodMenu(previous_menu=self.__class__).run()

View File

@@ -21,9 +21,7 @@ from core.menus import Option
from core.menus.base_menu import BaseMenu from core.menus.base_menu import BaseMenu
from core.settings.kiauh_settings import KiauhSettings, RepoSettings from core.settings.kiauh_settings import KiauhSettings, RepoSettings
from core.types.color import Color from core.types.color import Color
from core.types.component_status import ComponentStatus
from procedures.switch_repo import run_switch_repo_routine from procedures.switch_repo import run_switch_repo_routine
from utils.git_utils import get_repo_name
from utils.input_utils import get_confirm, get_string_input from utils.input_utils import get_confirm, get_string_input
@@ -141,12 +139,12 @@ class SettingsMenu(BaseMenu):
repo = get_string_input( repo = get_string_input(
"Enter new repository URL", "Enter new repository URL",
regex="^[\w/.:-]+$", regex=r"^[\w/.:-]+$",
default=KLIPPER_REPO_URL if repo_name == "klipper" else MOONRAKER_REPO_URL, default=KLIPPER_REPO_URL if repo_name == "klipper" else MOONRAKER_REPO_URL,
) )
branch = get_string_input( branch = get_string_input(
"Enter new branch name", "Enter new branch name",
regex="^.+$", regex=r"^.+$",
default="master" default="master"
) )

View File

@@ -118,7 +118,7 @@ class MoonrakerTelegramBot:
) )
env_file_content = env_file_content.replace( env_file_content = env_file_content.replace(
"%CFG%", "%CFG%",
f"{self.base.cfg_dir}/printer.cfg", self.cfg_file.as_posix()
) )
env_file_content = env_file_content.replace( env_file_content = env_file_content.replace(
"%LOG%", "%LOG%",

View File

@@ -31,7 +31,7 @@ def change_system_hostname() -> None:
"http://<hostname>.local", "http://<hostname>.local",
"\n\n", "\n\n",
"Example: If you set your hostname to 'my-printer', you can access an " "Example: If you set your hostname to 'my-printer', you can access an "
"installed webinterface by tyoing 'http://my-printer.local' in the " "installed webinterface by typing 'http://my-printer.local' in the "
"browser.", "browser.",
], ],
custom_title="CHANGE SYSTEM HOSTNAME", custom_title="CHANGE SYSTEM HOSTNAME",
@@ -51,7 +51,7 @@ def change_system_hostname() -> None:
) )
hostname = get_string_input( hostname = get_string_input(
"Enter the new hostname", "Enter the new hostname",
regex="^[a-z0-9]+([a-z0-9-]*[a-z0-9])?$", regex=r"^[a-z0-9]+([a-z0-9-]*[a-z0-9])?$",
) )
if not get_confirm(f"Change the hostname to '{hostname}'?", default_choice=False): if not get_confirm(f"Change the hostname to '{hostname}'?", default_choice=False):
Logger.print_info("Aborting hostname change ...") Logger.print_info("Aborting hostname change ...")

View File

@@ -43,7 +43,8 @@ def get_kiauh_version() -> str:
Helper method to get the current KIAUH version by reading the latest tag Helper method to get the current KIAUH version by reading the latest tag
:return: string of the latest tag :return: string of the latest tag
""" """
return get_local_tags(Path(__file__).parent.parent)[-1] lastest_tag: str = get_local_tags(Path(__file__).parent.parent)[-1]
return lastest_tag
def convert_camelcase_to_kebabcase(name: str) -> str: def convert_camelcase_to_kebabcase(name: str) -> str:

View File

@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import json import json
import re
import shutil import shutil
import urllib.request import urllib.request
from http.client import HTTPResponse from http.client import HTTPResponse
@@ -118,7 +119,7 @@ def get_local_tags(repo_path: Path, _filter: str | None = None) -> List[str]:
:return: List of tags :return: List of tags
""" """
try: try:
cmd = ["git", "tag", "-l"] cmd: List[str] = ["git", "tag", "-l"]
if _filter is not None: if _filter is not None:
cmd.append(f"'${_filter}'") cmd.append(f"'${_filter}'")
@@ -129,8 +130,10 @@ def get_local_tags(repo_path: Path, _filter: str | None = None) -> List[str]:
cwd=repo_path.as_posix(), cwd=repo_path.as_posix(),
).decode(encoding="utf-8") ).decode(encoding="utf-8")
tags = result.split("\n") tags: List[str] = result.split("\n")[:-1]
return tags[:-1]
return sorted(tags, key=lambda x: [int(i) if i.isdigit() else i for i in
re.split(r'(\d+)', x)])
except CalledProcessError: except CalledProcessError:
return [] return []

View File

@@ -105,7 +105,7 @@ function install_crowsnest(){
pushd "${HOME}/crowsnest" &> /dev/null || exit 1 pushd "${HOME}/crowsnest" &> /dev/null || exit 1
title_msg "Installer will prompt you for sudo password!" title_msg "Installer will prompt you for sudo password!"
status_msg "Launching crowsnest installer ..." status_msg "Launching crowsnest installer ..."
if ! sudo make install BASE_USER=$USER; then if ! sudo make install; then
error_msg "Something went wrong! Please try again..." error_msg "Something went wrong! Please try again..."
exit 1 exit 1
fi fi