Skip to content

Commit

Permalink
Enhancement: rename shortcut module and improve docstring/type hints …
Browse files Browse the repository at this point in the history
…consistency (#543)
  • Loading branch information
Guts authored Sep 2, 2024
2 parents 6b942b9 + cb77ca9 commit 92a8472
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 86 deletions.
4 changes: 4 additions & 0 deletions qgis_deployment_toolbelt/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@
# defaults
DEFAULT_QDT_WORKING_FOLDER = Path.home().joinpath(".cache/qgis-deployment-toolbelt")

# QGIS executable filenames
QGIS_BIN_WINDOWS_FILENAME: str = "qgis-bin.exe"
QGIS_LTR_BIN_WINDOWS_FILENAME: str = "qgis-ltr-bin.exe"

# Operating systems
SUPPORTED_OPERATING_SYSTEMS_CODENAMES: tuple[str, ...] = ("darwin", "linux", "win32")

Expand Down
2 changes: 1 addition & 1 deletion qgis_deployment_toolbelt/jobs/job_shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from qgis_deployment_toolbelt.__about__ import __title__, __version__
from qgis_deployment_toolbelt.jobs.generic_job import GenericJob
from qgis_deployment_toolbelt.profiles.qdt_profile import QdtProfile
from qgis_deployment_toolbelt.shortcuts import ApplicationShortcut
from qgis_deployment_toolbelt.shortcuts.shortcuts_handler import ApplicationShortcut

# #############################################################################
# ########## Globals ###############
Expand Down
4 changes: 0 additions & 4 deletions qgis_deployment_toolbelt/shortcuts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +0,0 @@
#! python3 # noqa: E265

# submodules
from .shortcuts import ApplicationShortcut # noqa: F401
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@
import pythoncom
import win32com.client
from win32comext.shell import shell, shellcon
elif opersys == "linux":
import distro
else:
pass


# package
from qgis_deployment_toolbelt.__about__ import __title__, __version__
from qgis_deployment_toolbelt.constants import OSConfiguration
from qgis_deployment_toolbelt.constants import (
QGIS_BIN_WINDOWS_FILENAME,
QGIS_LTR_BIN_WINDOWS_FILENAME,
OSConfiguration,
)
from qgis_deployment_toolbelt.utils.check_path import check_path
from qgis_deployment_toolbelt.utils.slugger import sluggy

Expand All @@ -56,19 +57,26 @@ def __init__(
self,
name: str,
exec_path: str | Path,
exec_arguments: Iterable[str] = None,
description: str = None,
icon_path: str | Path = None,
work_dir: str | Path = None,
exec_arguments: Iterable[str] | None = None,
description: str | None = None,
icon_path: str | Path | None = None,
work_dir: str | Path | None = None,
):
"""Initialize a shortcut object.
:param str name: name of the shortcut that will be created
:param Union[str, Path] exec_path: path to the executable (which should exist)
:param Iterable[str] exec_arguments: list of arguments and options to pass to the executable, defaults to None
:param str description: shortcut description, defaults to None
:param Union[str, Path] icon_path: path to icon file, defaults to None
:param Union[str, Path] work_dir: current folder where to start the executable, defaults to None. In QDT, it's the profile folder.
Args:
name (str): name of the shortcut that will be created
exec_path (str | Path): path to the executable (which should exist)
exec_arguments (Iterable[str] | None, optional): list of arguments and \
options to pass to the executable. Defaults to None.
description (str | None, optional): shortcut description. Defaults to None.
icon_path (str | Path | None, optional): path to icon file. Defaults to None.
work_dir (str | Path | None, optional): current folder where to start the \
executable. Defaults to None. In QDT, it's the profile folder.
Raises:
ValueError: if shortcut name contains invalid characters (depending on \
operating system)
"""
# retrieve operating system specific configuration
self.os_config = OSConfiguration.from_opersys()
Expand All @@ -87,25 +95,27 @@ def __init__(
if not self.exec_path.exists():
# helper to handle common typo error on executable name on Windows
if (
self.exec_path.name.endswith("qgis-bin.exe")
and self.exec_path.with_name("qgis-ltr-bin.exe").exists()
self.exec_path.name.endswith(QGIS_BIN_WINDOWS_FILENAME)
and self.exec_path.with_name(QGIS_LTR_BIN_WINDOWS_FILENAME).exists()
):
logger.warning(
f"Executable set does not exist: {self.exec_path} "
f"but {self.exec_path.with_name('qgis-ltr-bin.exe')} does, so "
"this one will be used instead. Check and fix your scenario."
)
self.exec_path = self.exec_path.with_name("qgis-ltr-bin.exe")
self.exec_path = self.exec_path.with_name(
QGIS_LTR_BIN_WINDOWS_FILENAME
)
elif (
self.exec_path.name.endswith("qgis-ltr-bin.exe")
and self.exec_path.with_name("qgis-bin.exe").exists()
self.exec_path.name.endswith(QGIS_LTR_BIN_WINDOWS_FILENAME)
and self.exec_path.with_name(QGIS_BIN_WINDOWS_FILENAME).exists()
):
logger.warning(
f"Executable set does not exist: {self.exec_path} "
f"but {self.exec_path.with_name('qgis-bin.exe')} does, so "
"this one will be used instead. Check and fix your scenario."
)
self.exec_path = self.exec_path.with_name("qgis-bin.exe")
self.exec_path = self.exec_path.with_name(QGIS_BIN_WINDOWS_FILENAME)
else:
logger.warning(
f"Executable does not exist: {self.exec_path}. "
Expand Down Expand Up @@ -146,13 +156,20 @@ def create(
self,
desktop: bool = False,
start_menu: bool = False,
) -> tuple[str, str]:
"""Creates Shortcut
) -> tuple[Path | None, Path | None]:
"""Creates shortcuts.
:param bool desktop: True to generate a Desktop shortcut, defaults to False
:param bool start_menu: True to generate a 'Start Menu' shortcut, defaults to False
Args:
desktop (bool, optional): True to generate a Desktop shortcut. \
Defaults to False.
start_menu (bool, optional): True to generate a 'Start Menu' shortcut. \
Defaults to False.
:return Tuple[str, str]: desktop and startmenu path
Raises:
TypeError: if options are not booleans
Returns:
tuple[Path, Path]: desktop and startmenu path
"""
if isinstance(desktop, bool):
self.desktop = desktop
Expand All @@ -177,28 +194,31 @@ def create(
elif opersys == "linux":
return self.freedesktop_create()
else:
pass
return (None, None)

def check_exec_arguments(
self, exec_arguments: Iterable[str] | None
) -> tuple[str] | None:
def check_exec_arguments(self, exec_arguments: Iterable[str] | None) -> str | None:
"""Check if exec_arguments are valid.
:param Union[Iterable[str], None] exec_arguments: input executable arguments to check
Args:
exec_arguments (Iterable[str] | None): input executable arguments to check
:return Union[Tuple[str], None]: tuple of arguments
Returns:
str | None: str of arguments
"""
if not exec_arguments:
return None
# store as path

# iterate and separate with spaces
return " ".join(exec_arguments)

def check_icon_path(self, icon_path: str | Path | None) -> Path | None:
"""Check icon path and return full path if it exists.
:param Union[str, Path] icon_path: input icon path to check
Args:
icon_path (str | Path | None): input icon path to check
:return Union[Path, None]: icon path as Path if str or Path, else None
Returns:
Path | None: icon path as Path if str or Path, else None
"""
if icon_path is None:
logger.debug(
Expand All @@ -223,9 +243,11 @@ def check_icon_path(self, icon_path: str | Path | None) -> Path | None:
def check_work_dir(self, work_dir: str | Path | None) -> Path | None:
"""Check work dir and return full path if it exists.
:param Union[str, Path] work_dir: input work dir to check
Args:
work_dir (str | Path | None): input work dir to check
:return Union[Path, None]: work dir as Path if str or Path, else None
Returns:
Path | None: work dir as Path if str or Path, else None
"""
if not work_dir:
return None
Expand Down Expand Up @@ -281,15 +303,16 @@ def desktop_path(self) -> Path:
return default_value

@property
def homedir_path(self) -> Path:
def homedir_path(self) -> Path | None:
"""Return home directory.
For Windows, note that we return `CSIDL_PROFILE`, not `CSIDL_APPDATA`,
`CSIDL_LOCAL_APPDATA` or `CSIDL_COMMON_APPDATA`.
See: https://www.nirsoft.net/articles/find_special_folder_location.html
TODO: evaluate use of platformdirs
:return Path: path to the user home
Returns:
Path | None: path to the user home
"""
if opersys == "win32":
return Path(shell.SHGetFolderPath(0, shellcon.CSIDL_PROFILE, 0, 0))
Expand All @@ -312,30 +335,30 @@ def homedir_path(self) -> Path:
return None

@property
def startmenu_path(self) -> Path:
def startmenu_path(self) -> Path | None:
"""Return user Start Menu Programs folder.
For Windows, note that we return `CSIDL_PROGRAMS` not `CSIDL_COMMON_PROGRAMS`.
:return Path: path to the Start Menu Programs folder
Returns:
Path: path to the Start Menu Programs folder
"""
if opersys == "win32":
return Path(shell.SHGetFolderPath(0, shellcon.CSIDL_PROGRAMS, None, 0))
elif opersys == "linux":
return self.homedir_path / ".local/share/applications"
elif opersys == "darwin":
return self.homedir_path / ".local/share/applications"
elif opersys in ("darwin", "linux"):
if isinstance(self.homedir_path, Path):
return self.homedir_path / ".local/share/applications"
return None
else:
logger.error(f"Unrecognized operating system: {opersys}.")
return None

# -- PRIVATE --------------------------------------------------------------

def freedesktop_create(self) -> tuple[Path | None, Path | None]:
"""Creates shortcut on distributions using FreeDesktop.
:return: desktop and startmenu path
:rtype: Tuple[Union[Path, None], Union[Path, None]]
Returns:
tuple[Path | None, Path | None]: desktop and startmenu path
"""
# grab shortcut template depending if we are in frozen mode
# (typically PyInstaller) or as "normal" Python
Expand Down Expand Up @@ -406,7 +429,7 @@ def freedesktop_create(self) -> tuple[Path | None, Path | None]:
shortcut_desktop_path = None

# create required shortcut
if self.start_menu:
if self.start_menu and isinstance(self.startmenu_path, Path):
self.startmenu_path.mkdir(parents=True, exist_ok=True)
# create shortcut
shortcut_start_menu_path = Path(
Expand All @@ -427,8 +450,8 @@ def freedesktop_create(self) -> tuple[Path | None, Path | None]:
def win32_create(self) -> tuple[Path | None, Path | None]:
"""Creates shortcut on Windows.
:return: desktop and startmenu path
:rtype: Tuple[Union[Path, None], Union[Path, None]]
Returns:
tuple[Path | None, Path | None]: desktop and startmenu path
"""
# variable
_WSHELL = win32com.client.Dispatch("Wscript.Shell", pythoncom.CoInitialize())
Expand Down Expand Up @@ -464,7 +487,7 @@ def win32_create(self) -> tuple[Path | None, Path | None]:
shortcut_desktop_path = None

# start menu shortcut
if self.start_menu:
if self.start_menu and isinstance(self.startmenu_path, Path):
shortcut_start_menu_path = (
self.startmenu_path / f"{self.name}{self.os_config.shortcut_extension}"
)
Expand All @@ -487,31 +510,3 @@ def win32_create(self) -> tuple[Path | None, Path | None]:
shortcut_start_menu_path = None

return (shortcut_desktop_path, shortcut_start_menu_path)


# #############################################################################
# ##### Stand alone program ########
# ##################################

if __name__ == "__main__":
"""Standalone execution."""
if opersys == "linux" and distro.name() in ("Ubuntu", "Kubuntu"):
qgis_shortcut = ApplicationShortcut(
"QDT",
exec_path="/home/jmo/.local/bin/qdeploy-toolbelt",
exec_arguments=[
"-v",
],
description="Launch QGIS Deployment Toolbelt with the scenario located "
"into the local repository.",
work_dir=Path(__file__).parent.parent,
)

print(
type(qgis_shortcut.homedir_path),
qgis_shortcut.homedir_path,
)
print(type(qgis_shortcut.desktop_path), qgis_shortcut.desktop_path)
print(type(qgis_shortcut.startmenu_path), qgis_shortcut.startmenu_path)

qgis_shortcut.create()
3 changes: 2 additions & 1 deletion tests/test_shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
python -m unittest tests.test_shortcuts.TestShortcut.test_shortcut_creation
"""

# standard lib
import unittest
from pathlib import Path
from sys import platform as opersys

# project
from qgis_deployment_toolbelt.shortcuts import ApplicationShortcut
from qgis_deployment_toolbelt.shortcuts.shortcuts_handler import ApplicationShortcut

# ############################################################################
# ########## Classes #############
Expand Down

0 comments on commit 92a8472

Please sign in to comment.