Addon Manager: pylint cleanup of devmode
This commit is contained in:
committed by
Chris Hennes
parent
efdbccd0b2
commit
0a8037a27d
@@ -25,16 +25,16 @@
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
from time import sleep
|
||||
from typing import List
|
||||
|
||||
import FreeCAD
|
||||
import addonmanager_freecad_interface as fci
|
||||
|
||||
from PySide import QtCore
|
||||
import addonmanager_utilities as utils
|
||||
from addonmanager_installer import AddonInstaller, MacroInstaller
|
||||
from Addon import Addon
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
translate = fci.translate
|
||||
|
||||
|
||||
class DependencyInstaller(QtCore.QObject):
|
||||
@@ -48,7 +48,7 @@ class DependencyInstaller(QtCore.QObject):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
addons: List[object],
|
||||
addons: List[Addon],
|
||||
python_requires: List[str],
|
||||
python_optional: List[str],
|
||||
location: os.PathLike = None,
|
||||
@@ -56,7 +56,8 @@ class DependencyInstaller(QtCore.QObject):
|
||||
"""Install the various types of dependencies that might be specified. If an optional
|
||||
dependency fails this is non-fatal, but other failures are considered fatal. If location
|
||||
is specified it overrides the FreeCAD user base directory setting: this is used mostly
|
||||
for testing purposes and shouldn't be set by normal code in most circumstances."""
|
||||
for testing purposes and shouldn't be set by normal code in most circumstances.
|
||||
"""
|
||||
super().__init__()
|
||||
self.addons = addons
|
||||
self.python_requires = python_requires
|
||||
@@ -95,18 +96,19 @@ class DependencyInstaller(QtCore.QObject):
|
||||
return False
|
||||
try:
|
||||
proc = self._run_pip(["--version"])
|
||||
FreeCAD.Console.PrintMessage(proc.stdout + "\n")
|
||||
fci.Console.PrintMessage(proc.stdout + "\n")
|
||||
except subprocess.CalledProcessError:
|
||||
self.no_pip.emit(f"{python_exe} -m pip --version")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _install_required(self, vendor_path: os.PathLike) -> bool:
|
||||
"""Install the required Python package dependencies. If any fail a failure signal is
|
||||
emitted and the function exits without proceeding with any additional installs."""
|
||||
def _install_required(self, vendor_path: str) -> bool:
|
||||
"""Install the required Python package dependencies. If any fail a failure
|
||||
signal is emitted and the function exits without proceeding with any additional
|
||||
installations."""
|
||||
for pymod in self.python_requires:
|
||||
if QtCore.QThread.currentThread().isInterruptionRequested():
|
||||
return
|
||||
return False
|
||||
try:
|
||||
proc = self._run_pip(
|
||||
[
|
||||
@@ -117,9 +119,9 @@ class DependencyInstaller(QtCore.QObject):
|
||||
pymod,
|
||||
]
|
||||
)
|
||||
FreeCAD.Console.PrintMessage(proc.stdout + "\n")
|
||||
fci.Console.PrintMessage(proc.stdout + "\n")
|
||||
except subprocess.CalledProcessError as e:
|
||||
FreeCAD.Console.PrintError(str(e) + "\n")
|
||||
fci.Console.PrintError(str(e) + "\n")
|
||||
self.failure.emit(
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
@@ -127,9 +129,10 @@ class DependencyInstaller(QtCore.QObject):
|
||||
).format(pymod),
|
||||
str(e),
|
||||
)
|
||||
return
|
||||
return False
|
||||
return True
|
||||
|
||||
def _install_optional(self, vendor_path: os.PathLike):
|
||||
def _install_optional(self, vendor_path: str):
|
||||
"""Install the optional Python package dependencies. If any fail a message is printed to
|
||||
the console, but installation of the others continues."""
|
||||
for pymod in self.python_optional:
|
||||
@@ -145,9 +148,9 @@ class DependencyInstaller(QtCore.QObject):
|
||||
pymod,
|
||||
]
|
||||
)
|
||||
FreeCAD.Console.PrintMessage(proc.stdout + "\n")
|
||||
fci.Console.PrintMessage(proc.stdout + "\n")
|
||||
except subprocess.CalledProcessError as e:
|
||||
FreeCAD.Console.PrintError(
|
||||
fci.Console.PrintError(
|
||||
translate(
|
||||
"AddonsInstaller", "Installation of optional package failed"
|
||||
)
|
||||
@@ -162,7 +165,8 @@ class DependencyInstaller(QtCore.QObject):
|
||||
final_args.extend(args)
|
||||
return self._subprocess_wrapper(final_args)
|
||||
|
||||
def _subprocess_wrapper(self, args) -> object:
|
||||
@staticmethod
|
||||
def _subprocess_wrapper(args) -> subprocess.CompletedProcess:
|
||||
"""Wrap subprocess call so test code can mock it."""
|
||||
return utils.run_interruptable_subprocess(args)
|
||||
|
||||
@@ -177,7 +181,7 @@ class DependencyInstaller(QtCore.QObject):
|
||||
for addon in self.addons:
|
||||
if QtCore.QThread.currentThread().isInterruptionRequested():
|
||||
return
|
||||
FreeCAD.Console.PrintMessage(
|
||||
fci.Console.PrintMessage(
|
||||
translate(
|
||||
"AddonsInstaller", "Installing required dependency {}"
|
||||
).format(addon.name)
|
||||
|
||||
@@ -110,7 +110,6 @@ class DeveloperMode:
|
||||
"""The main Developer Mode dialog, for editing package.xml metadata graphically."""
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# In the UI we want to show a translated string for the person type, but the underlying
|
||||
# string must be the one expected by the metadata parser, in English
|
||||
self.person_type_translation = {
|
||||
@@ -153,7 +152,7 @@ class DeveloperMode:
|
||||
QIcon.fromTheme("remove", QIcon(":/icons/list-remove.svg"))
|
||||
)
|
||||
|
||||
def show(self, parent=None, path=None):
|
||||
def show(self, parent=None, path: str = None):
|
||||
"""Show the main dev mode dialog"""
|
||||
if parent:
|
||||
self.dialog.setParent(parent)
|
||||
@@ -178,7 +177,7 @@ class DeveloperMode:
|
||||
self.metadata.Date = str(now)
|
||||
self.metadata.write(os.path.join(self.current_mod, "package.xml"))
|
||||
|
||||
def _populate_dialog(self, path_to_repo):
|
||||
def _populate_dialog(self, path_to_repo: str):
|
||||
"""Populate this dialog using the best available parsing of the contents of the repo at
|
||||
path_to_repo. This is a multi-layered process that starts with any existing package.xml
|
||||
file or other known metadata files, and proceeds through examining the contents of the
|
||||
@@ -330,7 +329,7 @@ class DeveloperMode:
|
||||
predictor = Predictor()
|
||||
self.metadata = predictor.predict_metadata(self.current_mod)
|
||||
|
||||
def _scan_for_git_info(self, path):
|
||||
def _scan_for_git_info(self, path: str):
|
||||
"""Look for branch availability"""
|
||||
self.git_interface = AddonGitInterface(path)
|
||||
if self.git_interface.git_exists:
|
||||
@@ -383,7 +382,7 @@ class DeveloperMode:
|
||||
# Finally, populate the combo boxes, etc.
|
||||
self._populate_combo()
|
||||
|
||||
# Disable all of the "Remove" buttons until something is selected
|
||||
# Disable all the "Remove" buttons until something is selected
|
||||
self.dialog.removeContentItemToolButton.setDisabled(True)
|
||||
|
||||
def _sync_metadata_to_ui(self):
|
||||
@@ -445,7 +444,7 @@ class DeveloperMode:
|
||||
else:
|
||||
self.metadata.PythonMin = "0.0.0" # Code for "unset"
|
||||
|
||||
# Content, people, and licenses should already be sync'ed
|
||||
# Content, people, and licenses should already be synchronized
|
||||
|
||||
###############################################################################################
|
||||
# DIALOG SLOTS
|
||||
@@ -516,7 +515,7 @@ class DeveloperMode:
|
||||
if entry and entry not in recent_mod_paths and os.path.exists(entry):
|
||||
recent_mod_paths.append(entry)
|
||||
|
||||
# Remove the whole thing so we can recreate it from scratch
|
||||
# Remove the whole thing, so we can recreate it from scratch
|
||||
self.pref.RemGroup("recentModsList")
|
||||
|
||||
if recent_mod_paths:
|
||||
@@ -604,11 +603,10 @@ class DeveloperMode:
|
||||
import vermin
|
||||
|
||||
required_minor_version = 0
|
||||
for dirpath, _, filenames in os.walk(self.current_mod):
|
||||
for dir_path, _, filenames in os.walk(self.current_mod):
|
||||
for filename in filenames:
|
||||
if filename.endswith(".py"):
|
||||
|
||||
with open(os.path.join(dirpath, filename), encoding="utf-8") as f:
|
||||
with open(os.path.join(dir_path, filename), encoding="utf-8") as f:
|
||||
contents = f.read()
|
||||
version_strings = vermin.version_strings(
|
||||
vermin.detect(contents)
|
||||
@@ -649,7 +647,7 @@ class DeveloperMode:
|
||||
translate("AddonsInstaller", "Install Vermin?"),
|
||||
translate(
|
||||
"AddonsInstaller",
|
||||
"Autodetecting the required version of Python for this Addon requires Vermin (https://pypi.org/project/vermin/). OK to install?",
|
||||
"Auto-detecting the required version of Python for this Addon requires Vermin (https://pypi.org/project/vermin/). OK to install?",
|
||||
),
|
||||
QMessageBox.Yes | QMessageBox.Cancel,
|
||||
)
|
||||
@@ -682,7 +680,7 @@ class DeveloperMode:
|
||||
)
|
||||
FreeCAD.Console.PrintMessage(proc.stdout.decode())
|
||||
if proc.returncode != 0:
|
||||
response = QMessageBox.critical(
|
||||
QMessageBox.critical(
|
||||
self.dialog,
|
||||
translate("AddonsInstaller", "Installation failed"),
|
||||
translate(
|
||||
@@ -697,7 +695,7 @@ class DeveloperMode:
|
||||
# pylint: disable=import-outside-toplevel
|
||||
import vermin
|
||||
except ImportError:
|
||||
response = QMessageBox.critical(
|
||||
QMessageBox.critical(
|
||||
self.dialog,
|
||||
translate("AddonsInstaller", "Installation failed"),
|
||||
translate(
|
||||
|
||||
@@ -58,7 +58,7 @@ translate = FreeCAD.Qt.translate
|
||||
class AddContent:
|
||||
"""A dialog for adding a single content item to the package metadata."""
|
||||
|
||||
def __init__(self, path_to_addon: os.PathLike, toplevel_metadata: FreeCAD.Metadata):
|
||||
def __init__(self, path_to_addon: str, toplevel_metadata: FreeCAD.Metadata):
|
||||
"""path_to_addon is the full path to the toplevel directory of this Addon, and
|
||||
toplevel_metadata is to overall package.xml Metadata object for this Addon. This
|
||||
information is used to assist the use in filling out the dialog by providing
|
||||
@@ -147,7 +147,7 @@ class AddContent:
|
||||
result = self.dialog.exec()
|
||||
if result == QDialog.Accepted:
|
||||
return self._generate_metadata()
|
||||
return None, None
|
||||
return None
|
||||
|
||||
def _populate_dialog(self, metadata: FreeCAD.Metadata) -> None:
|
||||
"""Fill in the dialog with the details from the passed metadata object"""
|
||||
@@ -231,7 +231,7 @@ class AddContent:
|
||||
|
||||
# Early return if this is the only addon
|
||||
if self.dialog.singletonCheckBox.isChecked():
|
||||
return (current_data, self.metadata)
|
||||
return current_data, self.metadata
|
||||
|
||||
# Otherwise, process the rest of the metadata (display name is already done)
|
||||
self.metadata.Description = (
|
||||
@@ -254,13 +254,14 @@ class AddContent:
|
||||
|
||||
licenses = []
|
||||
for row in range(self.dialog.licensesTableWidget.rowCount()):
|
||||
new_license = {}
|
||||
new_license["name"] = self.dialog.licensesTableWidget.item(row, 0).text
|
||||
new_license["file"] = self.dialog.licensesTableWidget.item(row, 1).text()
|
||||
new_license = {
|
||||
"name": self.dialog.licensesTableWidget.item(row, 0).text,
|
||||
"file": self.dialog.licensesTableWidget.item(row, 1).text(),
|
||||
}
|
||||
licenses.append(new_license)
|
||||
self.metadata.License = licenses
|
||||
|
||||
return (self.dialog.addonKindComboBox.currentData(), self.metadata)
|
||||
return self.dialog.addonKindComboBox.currentData(), self.metadata
|
||||
|
||||
###############################################################################################
|
||||
# DIALOG SLOTS
|
||||
@@ -381,7 +382,8 @@ class EditTags:
|
||||
|
||||
def exec(self):
|
||||
"""Execute the dialog, returning a list of tags (which may be empty, but still represents
|
||||
the expected list of tags to be set, e.g. the user may have removed them all)."""
|
||||
the expected list of tags to be set, e.g. the user may have removed them all).
|
||||
"""
|
||||
result = self.dialog.exec()
|
||||
if result == QDialog.Accepted:
|
||||
new_tags: List[str] = self.dialog.lineEdit.text().split(",")
|
||||
@@ -541,7 +543,8 @@ class EditDependency:
|
||||
self, dep_type="", dep_name="", dep_optional=False
|
||||
) -> Tuple[str, str, bool]:
|
||||
"""Execute the dialog, returning a tuple of the type of dependency (workbench, addon, or
|
||||
python), the name of the dependency, and a boolean indicating whether this is optional."""
|
||||
python), the name of the dependency, and a boolean indicating whether this is optional.
|
||||
"""
|
||||
|
||||
# If we are editing an existing row, set up the dialog:
|
||||
if dep_type and dep_name:
|
||||
@@ -564,8 +567,8 @@ class EditDependency:
|
||||
dep_name = self.dialog.dependencyComboBox.currentData()
|
||||
if dep_name == "other":
|
||||
dep_name = self.dialog.lineEdit.text()
|
||||
return (dep_type, dep_name, dep_optional)
|
||||
return ("", "", False)
|
||||
return dep_type, dep_name, dep_optional
|
||||
return "", "", False
|
||||
|
||||
def _populate_internal_workbenches(self):
|
||||
"""Add all known internal FreeCAD Workbenches to the list"""
|
||||
|
||||
@@ -43,6 +43,8 @@ try:
|
||||
RegexWrapper = QRegularExpression
|
||||
RegexValidatorWrapper = QRegularExpressionValidator
|
||||
except ImportError:
|
||||
QRegularExpressionValidator = None
|
||||
QRegularExpression = None
|
||||
from PySide.QtGui import (
|
||||
QRegExpValidator,
|
||||
)
|
||||
@@ -150,7 +152,7 @@ class LicenseSelector:
|
||||
new_short_code = self.dialog.otherLineEdit.text()
|
||||
self.pref.SetString("devModeLastSelectedLicense", new_short_code)
|
||||
return new_short_code, new_license_path
|
||||
return None, None
|
||||
return None
|
||||
|
||||
def set_license(self, short_code):
|
||||
"""Set the currently-selected license."""
|
||||
|
||||
@@ -72,9 +72,9 @@ class LicensesTable:
|
||||
"""Use the passed metadata object to populate the maintainers and authors"""
|
||||
self.widget.tableWidget.setRowCount(0)
|
||||
row = 0
|
||||
for l in self.metadata.License:
|
||||
shortcode = l["name"]
|
||||
path = l["file"]
|
||||
for lic in self.metadata.License:
|
||||
shortcode = lic["name"]
|
||||
path = lic["file"]
|
||||
self._add_row(row, shortcode, path)
|
||||
row += 1
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ class MetadataValidators:
|
||||
return
|
||||
|
||||
# The package.xml standard has some required elements that the basic XML reader is not
|
||||
# actually checking for. In developer mode, actually make sure that all of the rules are
|
||||
# actually checking for. In developer mode, actually make sure that all the rules are
|
||||
# being followed for each element.
|
||||
|
||||
errors = []
|
||||
@@ -141,7 +141,8 @@ class MetadataValidators:
|
||||
errors.extend(self.validate_urls(urls))
|
||||
return errors
|
||||
|
||||
def validate_urls(self, urls) -> List[str]:
|
||||
@staticmethod
|
||||
def validate_urls(urls) -> List[str]:
|
||||
"""Check the URLs provided by the addon"""
|
||||
errors = []
|
||||
if len(urls) == 0:
|
||||
@@ -183,14 +184,16 @@ class MetadataValidators:
|
||||
)
|
||||
return errors
|
||||
|
||||
def validate_workbench_metadata(self, workbench) -> List[str]:
|
||||
@staticmethod
|
||||
def validate_workbench_metadata(workbench) -> List[str]:
|
||||
"""Validate the required element(s) for a workbench"""
|
||||
errors = []
|
||||
if not workbench.Classname or len(workbench.Classname) == 0:
|
||||
errors.append("No <classname> specified for workbench")
|
||||
return errors
|
||||
|
||||
def validate_preference_pack_metadata(self, pack) -> List[str]:
|
||||
@staticmethod
|
||||
def validate_preference_pack_metadata(pack) -> List[str]:
|
||||
"""Validate the required element(s) for a preference pack"""
|
||||
errors = []
|
||||
if not pack.Name or len(pack.Name) == 0:
|
||||
|
||||
@@ -61,7 +61,7 @@ class PersonEditor:
|
||||
self.dialog.nameLineEdit.text(),
|
||||
self.dialog.emailLineEdit.text(),
|
||||
)
|
||||
return (None, None, None)
|
||||
return "", "", ""
|
||||
|
||||
def setup(
|
||||
self, person_type: str = "maintainer", name: str = "", email: str = ""
|
||||
|
||||
@@ -63,7 +63,7 @@ class Predictor:
|
||||
if not self.git_manager:
|
||||
raise Exception("Cannot use Developer Mode without git installed")
|
||||
|
||||
def predict_metadata(self, path: os.PathLike) -> FreeCAD.Metadata:
|
||||
def predict_metadata(self, path: str) -> FreeCAD.Metadata:
|
||||
"""Create a predicted Metadata object based on the contents of the passed-in directory"""
|
||||
if not os.path.isdir(path):
|
||||
return None
|
||||
@@ -85,7 +85,7 @@ class Predictor:
|
||||
|
||||
committers = self.git_manager.get_last_committers(self.path)
|
||||
|
||||
# This is a dictionary keyed to the author's name (which can be many different
|
||||
# This is a dictionary keyed to the author's name (which can be many
|
||||
# things, depending on the author) containing two fields, "email" and "count". It
|
||||
# is common for there to be multiple entries representing the same human being,
|
||||
# so a passing attempt is made to reconcile:
|
||||
|
||||
@@ -40,6 +40,8 @@ try:
|
||||
RegexWrapper = QRegularExpression
|
||||
RegexValidatorWrapper = QRegularExpressionValidator
|
||||
except ImportError:
|
||||
QRegularExpressionValidator = None
|
||||
QRegularExpression = None
|
||||
from PySide.QtGui import (
|
||||
QRegExpValidator,
|
||||
)
|
||||
@@ -163,4 +165,4 @@ class VersionValidator(QValidator):
|
||||
return semver_result
|
||||
if calver_result[0] == QValidator.Intermediate:
|
||||
return calver_result
|
||||
return (QValidator.Invalid, value, position)
|
||||
return QValidator.Invalid, value, position
|
||||
|
||||
@@ -29,34 +29,15 @@ import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
import time
|
||||
import FreeCAD
|
||||
|
||||
from PySide import QtCore # Needed to detect thread interruption
|
||||
|
||||
import addonmanager_utilities as utils
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
def initialize_git() -> object:
|
||||
"""If git is enabled, locate the git executable if necessary and return a new
|
||||
GitManager object. The executable location is saved in user preferences for reuse,
|
||||
and git can be disabled by setting the disableGit parameter in the Addons
|
||||
preference group. Returns None if for any of those reasons we aren't using git."""
|
||||
|
||||
git_manager = None
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
disable_git = pref.GetBool("disableGit", False)
|
||||
if not disable_git:
|
||||
try:
|
||||
git_manager = GitManager()
|
||||
except NoGitFound:
|
||||
pass
|
||||
return git_manager
|
||||
|
||||
|
||||
class NoGitFound(RuntimeError):
|
||||
"""Could not locate the git executable on this system."""
|
||||
|
||||
@@ -219,7 +200,7 @@ class GitManager:
|
||||
|
||||
original_cwd = os.getcwd()
|
||||
|
||||
# Make sure we are not currently in that directory, otherwise on Windows the rename
|
||||
# Make sure we are not currently in that directory, otherwise on Windows the "rename"
|
||||
# will fail. To guarantee we aren't in it, change to it, then shift up one.
|
||||
os.chdir(local_path)
|
||||
os.chdir("..")
|
||||
@@ -284,7 +265,7 @@ class GitManager:
|
||||
return branches
|
||||
|
||||
def get_last_committers(self, local_path, n=10):
|
||||
"""Examine the last n entries of the commit history, and return a list of all of the
|
||||
"""Examine the last n entries of the commit history, and return a list of all the
|
||||
committers, their email addresses, and how many commits each one is responsible for."""
|
||||
old_dir = os.getcwd()
|
||||
os.chdir(local_path)
|
||||
@@ -313,7 +294,7 @@ class GitManager:
|
||||
return result_dict
|
||||
|
||||
def get_last_authors(self, local_path, n=10):
|
||||
"""Examine the last n entries of the commit history, and return a list of all of the
|
||||
"""Examine the last n entries of the commit history, and return a list of all the
|
||||
authors, their email addresses, and how many commits each one is responsible for."""
|
||||
old_dir = os.getcwd()
|
||||
os.chdir(local_path)
|
||||
@@ -338,7 +319,7 @@ class GitManager:
|
||||
# Find git. In preference order
|
||||
# A) The value of the GitExecutable user preference
|
||||
# B) The executable located in the same bin directory as FreeCAD and called "git"
|
||||
# C) The result of an shutil search for your system's "git" executable
|
||||
# C) The result of a shutil search for your system's "git" executable
|
||||
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
git_exe = prefs.GetString("GitExecutable", "Not set")
|
||||
if not git_exe or git_exe == "Not set" or not os.path.exists(git_exe):
|
||||
@@ -371,3 +352,20 @@ class GitManager:
|
||||
)
|
||||
|
||||
return proc.stdout
|
||||
|
||||
|
||||
def initialize_git() -> Optional[GitManager]:
|
||||
"""If git is enabled, locate the git executable if necessary and return a new
|
||||
GitManager object. The executable location is saved in user preferences for reuse,
|
||||
and git can be disabled by setting the disableGit parameter in the Addons
|
||||
preference group. Returns None if for any of those reasons we aren't using git."""
|
||||
|
||||
git_manager = None
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
disable_git = pref.GetBool("disableGit", False)
|
||||
if not disable_git:
|
||||
try:
|
||||
git_manager = GitManager()
|
||||
except NoGitFound:
|
||||
pass
|
||||
return git_manager
|
||||
|
||||
@@ -47,17 +47,13 @@ if FreeCAD.GuiUp:
|
||||
# Python urllib.request (if requests is not available).
|
||||
import NetworkManager # Requires an event loop, so is only available with the GUI
|
||||
else:
|
||||
has_requests = False
|
||||
try:
|
||||
import requests
|
||||
|
||||
has_requests = True
|
||||
except ImportError:
|
||||
has_requests = False
|
||||
requests = None
|
||||
import urllib.request
|
||||
import ssl
|
||||
|
||||
|
||||
# @package AddonManager_utilities
|
||||
# \ingroup ADDONMANAGER
|
||||
# \brief Utilities to work across different platforms, providers and python versions
|
||||
@@ -99,7 +95,7 @@ def symlink(source, link_name):
|
||||
def rmdir(path: os.PathLike) -> bool:
|
||||
try:
|
||||
shutil.rmtree(path, onerror=remove_readonly)
|
||||
except Exception:
|
||||
except (WindowsError, PermissionError, OSError):
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -214,10 +210,10 @@ def get_desc_regex(repo):
|
||||
"""Returns a regex string that extracts a WB description to be displayed in the description
|
||||
panel of the Addon manager, if the README could not be found"""
|
||||
|
||||
parsedUrl = urlparse(repo.url)
|
||||
if parsedUrl.netloc == "github.com":
|
||||
parsed_url = urlparse(repo.url)
|
||||
if parsed_url.netloc == "github.com":
|
||||
return r'<meta property="og:description" content="(.*?)"'
|
||||
if parsedUrl.netloc in ["gitlab.com", "salsa.debian.org", "framagit.org"]:
|
||||
if parsed_url.netloc in ["gitlab.com", "salsa.debian.org", "framagit.org"]:
|
||||
return r'<meta.*?content="(.*?)".*?og:description.*?>'
|
||||
FreeCAD.Console.PrintLog(
|
||||
"Debug: addonmanager_utilities.get_desc_regex: Unknown git host:",
|
||||
@@ -230,10 +226,10 @@ def get_desc_regex(repo):
|
||||
def get_readme_html_url(repo):
|
||||
"""Returns the location of a html file containing readme"""
|
||||
|
||||
parsedUrl = urlparse(repo.url)
|
||||
if parsedUrl.netloc == "github.com":
|
||||
parsed_url = urlparse(repo.url)
|
||||
if parsed_url.netloc == "github.com":
|
||||
return f"{repo.url}/blob/{repo.branch}/README.md"
|
||||
if parsedUrl.netloc in ["gitlab.com", "salsa.debian.org", "framagit.org"]:
|
||||
if parsed_url.netloc in ["gitlab.com", "salsa.debian.org", "framagit.org"]:
|
||||
return f"{repo.url}/-/blob/{repo.branch}/README.md"
|
||||
FreeCAD.Console.PrintLog(
|
||||
"Unrecognized git repo location '' -- guessing it is a GitLab instance..."
|
||||
@@ -358,8 +354,8 @@ def get_python_exe() -> str:
|
||||
E) The result of an shutil search for your system's "python" executable"""
|
||||
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
|
||||
python_exe = prefs.GetString("PythonExecutableForPip", "Not set")
|
||||
fc_dir = FreeCAD.getHomePath()
|
||||
if not python_exe or python_exe == "Not set" or not os.path.exists(python_exe):
|
||||
fc_dir = FreeCAD.getHomePath()
|
||||
python_exe = os.path.join(fc_dir, "bin", "python3")
|
||||
if "Windows" in platform.system():
|
||||
python_exe += ".exe"
|
||||
@@ -409,7 +405,7 @@ def blocking_get(url: str, method=None) -> str:
|
||||
if FreeCAD.GuiUp and method is None or method == "networkmanager":
|
||||
NetworkManager.InitializeNetworkManager()
|
||||
p = NetworkManager.AM_NETWORK_MANAGER.blocking_get(url)
|
||||
elif has_requests and method is None or method == "requests":
|
||||
elif requests and method is None or method == "requests":
|
||||
response = requests.get(url)
|
||||
if response.status_code == 200:
|
||||
p = response.text
|
||||
@@ -420,18 +416,18 @@ def blocking_get(url: str, method=None) -> str:
|
||||
return p
|
||||
|
||||
|
||||
def run_interruptable_subprocess(args) -> object:
|
||||
def run_interruptable_subprocess(args) -> subprocess.CompletedProcess:
|
||||
"""Wrap subprocess call so it can be interrupted gracefully."""
|
||||
creationflags = 0
|
||||
creation_flags = 0
|
||||
if hasattr(subprocess, "CREATE_NO_WINDOW"):
|
||||
# Added in Python 3.7 -- only used on Windows
|
||||
creationflags = subprocess.CREATE_NO_WINDOW
|
||||
creation_flags = subprocess.CREATE_NO_WINDOW
|
||||
try:
|
||||
p = subprocess.Popen(
|
||||
args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
creationflags=creationflags,
|
||||
creationflags=creation_flags,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
@@ -447,13 +443,12 @@ def run_interruptable_subprocess(args) -> object:
|
||||
except subprocess.TimeoutExpired:
|
||||
if QtCore.QThread.currentThread().isInterruptionRequested():
|
||||
p.kill()
|
||||
stdout, stderr = p.communicate()
|
||||
return_code = -1
|
||||
raise ProcessInterrupted()
|
||||
if return_code is None or return_code != 0:
|
||||
raise subprocess.CalledProcessError(return_code, args, stdout, stderr)
|
||||
return subprocess.CompletedProcess(args, return_code, stdout, stderr)
|
||||
|
||||
|
||||
def get_main_am_window():
|
||||
windows = QtWidgets.QApplication.topLevelWidgets()
|
||||
for widget in windows:
|
||||
|
||||
Reference in New Issue
Block a user