diff --git a/scripts/addons_core/bl_pkg/cli/blender_ext.py b/scripts/addons_core/bl_pkg/cli/blender_ext.py index 61dbf29a6db..6d84385eef6 100755 --- a/scripts/addons_core/bl_pkg/cli/blender_ext.py +++ b/scripts/addons_core/bl_pkg/cli/blender_ext.py @@ -187,54 +187,35 @@ def debug_stack_trace_to_file() -> None: )) -def message_done(msg_fn: MessageFn) -> bool: - """ - Print a non-fatal warning. - """ - return msg_fn("DONE", "") +class MessageLogger: + __slots__ = ( + "msg_fn", + ) + def __init__(self, msg_fn: MessageFn) -> None: + self.msg_fn = msg_fn -def message_warn(msg_fn: MessageFn, s: str) -> bool: - """ - Print a non-fatal warning. - """ - return msg_fn("WARN", s) + def done(self) -> bool: + return self.msg_fn("DONE", "") + def warn(self, s: str) -> bool: + return self.msg_fn("WARN", s) -def message_error(msg_fn: MessageFn, s: str) -> bool: - """ - Print an error. - """ - return msg_fn("ERROR", s) + def error(self, s: str) -> bool: + return self.msg_fn("ERROR", s) + def fatal_error(self, s: str) -> bool: + return self.msg_fn("FATAL_ERROR", s) -def message_fatal_error(msg_fn: MessageFn, s: str) -> bool: - """ - Print a fatal error. - """ - return msg_fn("FATAL_ERROR", s) + def status(self, s: str) -> bool: + return self.msg_fn("STATUS", s) + def path(self, s: str) -> bool: + return self.msg_fn("PATH", s) -def message_status(msg_fn: MessageFn, s: str) -> bool: - """ - Print a status message. - """ - return msg_fn("STATUS", s) - - -def message_path(msg_fn: MessageFn, s: str) -> bool: - """ - Print a path. - """ - return msg_fn("PATH", s) - - -def message_progress(msg_fn: MessageFn, s: str, progress: int, progress_range: int, unit: str) -> bool: - """ - Print a progress update. - """ - assert unit == 'BYTE' - return msg_fn("PROGRESS", (s, unit, progress, progress_range)) + def progress(self, s: str, progress: int, progress_range: int, unit: str) -> bool: + assert unit == 'BYTE' + return self.msg_fn("PROGRESS", (s, unit, progress, progress_range)) def force_exit_ok_enable() -> None: @@ -2374,7 +2355,7 @@ def repo_local_private_dir_ensure_with_subdir(*, local_dir: str, subdir: str) -> def repo_sync_from_remote( *, - msg_fn: MessageFn, + msglog: MessageLogger, remote_name: str, remote_url: str, local_dir: str, @@ -2390,11 +2371,11 @@ def repo_sync_from_remote( # Validate arguments. if (error := remote_url_validate_or_error(remote_url)) is not None: - message_fatal_error(msg_fn, error) + msglog.fatal_error(error) return False request_exit = False - request_exit |= message_status(msg_fn, "Checking repository \"{:s}\" for updates...".format(remote_name)) + request_exit |= msglog.status("Checking repository \"{:s}\" for updates...".format(remote_name)) if request_exit: return False @@ -2413,7 +2394,7 @@ def repo_sync_from_remote( with CleanupPathsContext(files=(local_json_path_temp,), directories=()): # TODO: time-out. - request_exit |= message_status(msg_fn, "Refreshing extensions list for \"{:s}\"...".format(remote_name)) + request_exit |= msglog.status("Refreshing extensions list for \"{:s}\"...".format(remote_name)) if request_exit: return False @@ -2430,7 +2411,7 @@ def repo_sync_from_remote( chunk_size=CHUNK_SIZE_DEFAULT, timeout_in_seconds=timeout_in_seconds, ): - request_exit |= message_progress(msg_fn, "Downloading...", read_total, size, 'BYTE') + request_exit |= msglog.progress("Downloading...", read_total, size, 'BYTE') if request_exit: break read_total += read @@ -2438,9 +2419,9 @@ def repo_sync_from_remote( except (Exception, KeyboardInterrupt) as ex: msg = url_retrieve_exception_as_message(ex, prefix="sync", url=remote_url) if demote_connection_errors_to_status and url_retrieve_exception_is_connectivity(ex): - message_status(msg_fn, msg) + msglog.status(msg) else: - message_fatal_error(msg_fn, msg) + msglog.fatal_error(msg) return False if request_exit: @@ -2448,14 +2429,13 @@ def repo_sync_from_remote( error_msg = repo_json_is_valid_or_error(local_json_path_temp) if error_msg is not None: - message_fatal_error( - msg_fn, + msglog.fatal_error( "Repository error: invalid manifest ({:s}) for repository \"{:s}\"!".format(error_msg, remote_name), ) return False del error_msg - request_exit |= message_status(msg_fn, "Extensions list for \"{:s}\" updated".format(remote_name)) + request_exit |= msglog.status("Extensions list for \"{:s}\" updated".format(remote_name)) if request_exit: return False @@ -2466,7 +2446,7 @@ def repo_sync_from_remote( os.rename(local_json_path_temp, local_json_path) if extension_override: - request_exit |= message_path(msg_fn, os.path.relpath(local_json_path, local_dir)) + request_exit |= msglog.path(os.path.relpath(local_json_path, local_dir)) return True @@ -2919,7 +2899,7 @@ class subcmd_server: @staticmethod def _generate_html( - msg_fn: MessageFn, + msglog: MessageLogger, *, repo_dir: str, repo_data: List[Dict[str, Any]], @@ -3030,7 +3010,7 @@ class subcmd_server: with open(html_template_filepath, "r", encoding="utf-8") as fh_html: html_template_text = fh_html.read() except Exception as ex: - message_fatal_error(msg_fn, "HTML template failed to read: {:s}".format(str(ex))) + msglog.fatal_error("HTML template failed to read: {:s}".format(str(ex))) return False else: html_template_text = HTML_TEMPLATE @@ -3044,7 +3024,7 @@ class subcmd_server: date=html.escape(datetime.datetime.now(tz=datetime.timezone.utc).strftime("%Y-%m-%d, %H:%M")), ) except KeyError as ex: - message_fatal_error(msg_fn, "HTML template error: {:s}".format(str(ex))) + msglog.fatal_error("HTML template error: {:s}".format(str(ex))) return False del template @@ -3054,19 +3034,18 @@ class subcmd_server: @staticmethod def generate( - msg_fn: MessageFn, + msglog: MessageLogger, *, repo_dir: str, html: bool, html_template: str, ) -> bool: - if url_has_known_prefix(repo_dir): - message_fatal_error(msg_fn, "Directory: {!r} must be a local path, not a URL!".format(repo_dir)) + msglog.fatal_error("Directory: {!r} must be a local path, not a URL!".format(repo_dir)) return False if not os.path.isdir(repo_dir): - message_fatal_error(msg_fn, "Directory: {!r} not found!".format(repo_dir)) + msglog.fatal_error("Directory: {!r} not found!".format(repo_dir)) return False repo_data_idname_map: Dict[str, List[PkgManifest]] = {} @@ -3086,14 +3065,14 @@ class subcmd_server: # Harmless, but skip directories. if entry.is_dir(): - message_warn(msg_fn, "found unexpected directory {!r}".format(entry.name)) + msglog.warn("found unexpected directory {!r}".format(entry.name)) continue filename = entry.name filepath = os.path.join(repo_dir, filename) manifest = pkg_manifest_from_archive_and_validate(filepath, strict=False) if isinstance(manifest, str): - message_error(msg_fn, "archive validation failed {!r}, error: {:s}".format(filepath, manifest)) + msglog.error("archive validation failed {!r}, error: {:s}".format(filepath, manifest)) continue manifest_dict = manifest._asdict() @@ -3112,10 +3091,7 @@ class subcmd_server: for key in ("archive_url", "archive_size", "archive_hash"): if key not in manifest_dict: continue - message_error( - msg_fn, - "malformed meta-data from {!r}, contains key it shouldn't: {:s}".format(filepath, key), - ) + msglog.error("malformed meta-data from {!r}, contains key it shouldn't: {:s}".format(filepath, key)) has_key_error = True if has_key_error: continue @@ -3137,11 +3113,11 @@ class subcmd_server: if len(pkg_items) == 1: continue if (error := pkg_manifest_detect_duplicates(pkg_idname, pkg_items)) is not None: - message_warn(msg_fn, "archive found with duplicates for id {:s}: {:s}".format(pkg_idname, error)) + msglog.warn("archive found with duplicates for id {:s}: {:s}".format(pkg_idname, error)) if html: if not subcmd_server._generate_html( - msg_fn, + msglog, repo_dir=repo_dir, repo_data=repo_data, html_template_filepath=html_template, @@ -3154,7 +3130,7 @@ class subcmd_server: with open(filepath_repo_json, "w", encoding="utf-8") as fh: json.dump(repo_gen_dict, fh, indent=2) - message_status(msg_fn, "found {:d} packages.".format(len(repo_data))) + msglog.status("found {:d} packages.".format(len(repo_data))) return True @@ -3166,7 +3142,7 @@ class subcmd_client: @staticmethod def list_packages( - msg_fn: MessageFn, + msglog: MessageLogger, remote_url: str, online_user_agent: str, access_token: str, @@ -3176,7 +3152,7 @@ class subcmd_client: # Validate arguments. if (error := remote_url_validate_or_error(remote_url)) is not None: - message_fatal_error(msg_fn, error) + msglog.fatal_error(error) return False remote_json_url = remote_url_get(remote_url) @@ -3199,9 +3175,9 @@ class subcmd_client: except (Exception, KeyboardInterrupt) as ex: msg = url_retrieve_exception_as_message(ex, prefix="list", url=remote_url) if demote_connection_errors_to_status and url_retrieve_exception_is_connectivity(ex): - message_status(msg_fn, msg) + msglog.status(msg) else: - message_fatal_error(msg_fn, msg) + msglog.fatal_error(msg) return False result_str = result.getvalue().decode("utf-8") @@ -3214,8 +3190,7 @@ class subcmd_client: request_exit = False for elem in items: - request_exit |= message_status( - msg_fn, + request_exit |= msglog.status( "{:s}({:s}): {:s}".format(elem.get("id"), elem.get("version"), elem.get("name")), ) if request_exit: @@ -3225,7 +3200,7 @@ class subcmd_client: @staticmethod def sync( - msg_fn: MessageFn, + msglog: MessageLogger, *, remote_url: str, remote_name: str, @@ -3241,7 +3216,7 @@ class subcmd_client: force_exit_ok_enable() success = repo_sync_from_remote( - msg_fn=msg_fn, + msglog=msglog, remote_name=remote_name, remote_url=remote_url, local_dir=local_dir, @@ -3255,7 +3230,7 @@ class subcmd_client: @staticmethod def _install_package_from_file_impl( - msg_fn: MessageFn, + msglog: MessageLogger, *, local_dir: str, filepath_archive: str, @@ -3273,27 +3248,18 @@ class subcmd_client: try: zip_fh_context = zipfile.ZipFile(filepath_archive, mode="r") except Exception as ex: - message_error( - msg_fn, - "Error extracting archive: {:s}".format(str(ex)), - ) + msglog.error("Error extracting archive: {:s}".format(str(ex))) return False with contextlib.closing(zip_fh_context) as zip_fh: archive_subdir = pkg_zipfile_detect_subdir_or_none(zip_fh) if archive_subdir is None: - message_error( - msg_fn, - "Missing manifest from: {:s}".format(filepath_archive), - ) + msglog.error("Missing manifest from: {:s}".format(filepath_archive)) return False manifest = pkg_manifest_from_zipfile_and_validate(zip_fh, archive_subdir, strict=False) if isinstance(manifest, str): - message_error( - msg_fn, - "Failed to load manifest from: {:s}".format(manifest), - ) + msglog.error("Failed to load manifest from: {:s}".format(manifest)) return False if manifest_compare is not None: @@ -3301,8 +3267,7 @@ class subcmd_client: # otherwise the package will install but not be able to collate # the installed package with the remote ID. if manifest_compare.id != manifest.id: - message_error( - msg_fn, + msglog.error( "Package ID mismatch (remote: \"{:s}\", archive: \"{:s}\")".format( manifest_compare.id, manifest.id, @@ -3310,8 +3275,7 @@ class subcmd_client: ) return False if manifest_compare.version != manifest.version: - message_error( - msg_fn, + msglog.error( "Package version mismatch (remote: \"{:s}\", archive: \"{:s}\")".format( manifest_compare.version, manifest.version, @@ -3326,9 +3290,9 @@ class subcmd_client: filter_blender_version=blender_version_tuple, filter_platform=platform_from_this_system(), skip_message_fn=lambda message: - any_as_none(message_error(msg_fn, "{:s}: {:s}".format(manifest.id, message))), + any_as_none(msglog.error("{:s}: {:s}".format(manifest.id, message))), error_fn=lambda ex: - any_as_none(message_error(msg_fn, "{:s}: {:s}".format(manifest.id, str(ex)))), + any_as_none(msglog.error("{:s}: {:s}".format(manifest.id, str(ex)))), ): return False @@ -3343,8 +3307,7 @@ class subcmd_client: # It's unlikely this exist, nevertheless if it does - it must be removed. if os.path.exists(filepath_local_pkg_temp): if (error := rmtree_with_fallback_or_error(filepath_local_pkg_temp)) is not None: - message_error( - msg_fn, + msglog.error( "Failed to remove temporary directory for \"{:s}\": {:s}".format(manifest.id, error), ) return False @@ -3359,19 +3322,13 @@ class subcmd_client: for member in zip_fh.infolist(): zip_fh.extract(member, filepath_local_pkg_temp) except Exception as ex: - message_error( - msg_fn, - "Failed to extract files for \"{:s}\": {:s}".format(manifest.id, str(ex)), - ) + msglog.error("Failed to extract files for \"{:s}\": {:s}".format(manifest.id, str(ex))) return False is_reinstall = False if os.path.isdir(filepath_local_pkg): if (error := rmtree_with_fallback_or_error(filepath_local_pkg)) is not None: - message_error( - msg_fn, - "Failed to remove existing directory for \"{:s}\": {:s}".format(manifest.id, error), - ) + msglog.error("Failed to remove existing directory for \"{:s}\": {:s}".format(manifest.id, error)) return False is_reinstall = True @@ -3380,26 +3337,26 @@ class subcmd_client: directories_to_clean.remove(filepath_local_pkg_temp) if is_reinstall: - message_status(msg_fn, "Reinstalled \"{:s}\"".format(manifest.id)) + msglog.status("Reinstalled \"{:s}\"".format(manifest.id)) else: - message_status(msg_fn, "Installed \"{:s}\"".format(manifest.id)) + msglog.status("Installed \"{:s}\"".format(manifest.id)) return True @staticmethod def install_packages_from_files( - msg_fn: MessageFn, + msglog: MessageLogger, *, local_dir: str, package_files: Sequence[str], blender_version: str, ) -> bool: if not os.path.exists(local_dir): - message_fatal_error(msg_fn, "destination directory \"{:s}\" does not exist".format(local_dir)) + msglog.fatal_error("destination directory \"{:s}\" does not exist".format(local_dir)) return False if isinstance(blender_version_tuple := blender_version_parse_or_error(blender_version), str): - message_fatal_error(msg_fn, blender_version_tuple) + msglog.fatal_error(blender_version_tuple) return False assert isinstance(blender_version_tuple, tuple) @@ -3408,7 +3365,7 @@ class subcmd_client: with CleanupPathsContext(files=(), directories=directories_to_clean): for filepath_archive in package_files: if not subcmd_client._install_package_from_file_impl( - msg_fn, + msglog, local_dir=local_dir, filepath_archive=filepath_archive, blender_version_tuple=blender_version_tuple, @@ -3422,7 +3379,7 @@ class subcmd_client: @staticmethod def install_packages( - msg_fn: MessageFn, + msglog: MessageLogger, *, remote_url: str, local_dir: str, @@ -3436,11 +3393,11 @@ class subcmd_client: # Validate arguments. if (error := remote_url_validate_or_error(remote_url)) is not None: - message_fatal_error(msg_fn, error) + msglog.fatal_error(error) return False if isinstance(blender_version_tuple := blender_version_parse_or_error(blender_version), str): - message_fatal_error(msg_fn, blender_version_tuple) + msglog.fatal_error(blender_version_tuple) return False assert isinstance(blender_version_tuple, tuple) @@ -3478,12 +3435,12 @@ class subcmd_client: packages_info: List[PkgManifest_Archive] = [] for pkg_idname, pkg_info_list in json_data_pkg_info_map.items(): if not pkg_info_list: - message_fatal_error(msg_fn, "Package \"{:s}\", not found".format(pkg_idname)) + msglog.fatal_error("Package \"{:s}\", not found".format(pkg_idname)) has_fatal_error = True continue def error_handle(ex: Exception) -> None: - message_error(msg_fn, "{:s}: {:s}".format(pkg_idname, str(ex))) + msglog.error("{:s}: {:s}".format(pkg_idname, str(ex))) pkg_info_list = [ pkg_info for pkg_info in pkg_info_list @@ -3497,8 +3454,7 @@ class subcmd_client: ] if not pkg_info_list: - message_fatal_error( - msg_fn, + msglog.fatal_error( "Package \"{:s}\", found but not compatible with this system".format(pkg_idname), ) has_fatal_error = True @@ -3509,7 +3465,7 @@ class subcmd_client: manifest_archive = pkg_manifest_archive_from_dict_and_validate(pkg_info, strict=False) if isinstance(manifest_archive, str): - message_fatal_error(msg_fn, "Package malformed meta-data for \"{:s}\", error: {:s}".format( + msglog.fatal_error("Package malformed meta-data for \"{:s}\", error: {:s}".format( pkg_idname, manifest_archive, )) @@ -3581,8 +3537,7 @@ class subcmd_client: chunk_size=CHUNK_SIZE_DEFAULT, timeout_in_seconds=timeout_in_seconds, ): - request_exit |= message_progress( - msg_fn, + request_exit |= msglog.progress( "Downloading \"{:s}\"".format(pkg_idname), filename_archive_size_test, archive_size_expected, @@ -3598,8 +3553,8 @@ class subcmd_client: # NOTE: don't support `demote_connection_errors_to_status` here because a connection # failure on installing *is* an error by definition. # Unlike querying information which might reasonably be skipped. - message_fatal_error( - msg_fn, url_retrieve_exception_as_message( + msglog.fatal_error( + url_retrieve_exception_as_message( ex, prefix="install", url=remote_url)) return False @@ -3608,7 +3563,7 @@ class subcmd_client: # Validate: if filename_archive_size_test != archive_size_expected: - message_error(msg_fn, "Archive size mismatch \"{:s}\", expected {:d}, was {:d}".format( + msglog.error("Archive size mismatch \"{:s}\", expected {:d}, was {:d}".format( pkg_idname, archive_size_expected, filename_archive_size_test, @@ -3616,7 +3571,7 @@ class subcmd_client: return False filename_archive_hash_test = "sha256:" + sha256.hexdigest() if filename_archive_hash_test != archive_hash_expected: - message_error(msg_fn, "Archive checksum mismatch \"{:s}\", expected {:s}, was {:s}".format( + msglog.error("Archive checksum mismatch \"{:s}\", expected {:s}, was {:s}".format( pkg_idname, archive_hash_expected, filename_archive_hash_test, @@ -3632,7 +3587,7 @@ class subcmd_client: filepath_local_cache_archive = os.path.join(local_cache_dir, manifest_archive.manifest.id + PKG_EXT) if not subcmd_client._install_package_from_file_impl( - msg_fn, + msglog, local_dir=local_dir, filepath_archive=filepath_local_cache_archive, blender_version_tuple=blender_version_tuple, @@ -3645,14 +3600,14 @@ class subcmd_client: @staticmethod def uninstall_packages( - msg_fn: MessageFn, + msglog: MessageLogger, *, local_dir: str, user_dir: str, packages: Sequence[str], ) -> bool: if not os.path.isdir(local_dir): - message_fatal_error(msg_fn, "Missing local \"{:s}\"".format(local_dir)) + msglog.fatal_error("Missing local \"{:s}\"".format(local_dir)) return False # Most likely this doesn't have duplicates,but any errors procured by duplicates @@ -3667,14 +3622,14 @@ class subcmd_client: # validate this path cannot be used for an unexpected outcome, # or using `../../` to remove directories that shouldn't. if (pkg_idname in {"", ".", ".."}) or ("\\" in pkg_idname or "/" in pkg_idname): - message_fatal_error(msg_fn, "Package name invalid \"{:s}\"".format(pkg_idname)) + msglog.fatal_error("Package name invalid \"{:s}\"".format(pkg_idname)) has_fatal_error = True continue # This will be a directory. filepath_local_pkg = os.path.join(local_dir, pkg_idname) if not os.path.isdir(filepath_local_pkg): - message_fatal_error(msg_fn, "Package not found \"{:s}\"".format(pkg_idname)) + msglog.fatal_error("Package not found \"{:s}\"".format(pkg_idname)) has_fatal_error = True continue @@ -3694,10 +3649,10 @@ class subcmd_client: filepath_local_pkg = os.path.join(local_dir, pkg_idname) if (error := rmtree_with_fallback_or_error(filepath_local_pkg)) is not None: - message_error(msg_fn, "Failure to remove \"{:s}\" with error ({:s})".format(pkg_idname, error)) + msglog.error("Failure to remove \"{:s}\" with error ({:s})".format(pkg_idname, error)) continue - message_status(msg_fn, "Removed \"{:s}\"".format(pkg_idname)) + msglog.status("Removed \"{:s}\"".format(pkg_idname)) filepath_local_cache_archive = os.path.join(local_cache_dir, pkg_idname + PKG_EXT) if os.path.exists(filepath_local_cache_archive): @@ -3707,8 +3662,7 @@ class subcmd_client: filepath_user_pkg = os.path.join(user_dir, pkg_idname) if os.path.isdir(filepath_user_pkg): if (error := rmtree_with_fallback_or_error(filepath_user_pkg)) is not None: - message_error( - msg_fn, + msglog.error( "Failure to remove \"{:s}\" user files with error ({:s})".format(pkg_idname, error), ) continue @@ -3720,7 +3674,7 @@ class subcmd_author: @staticmethod def build( - msg_fn: MessageFn, + msglog: MessageLogger, *, pkg_source_dir: str, pkg_output_dir: str, @@ -3730,17 +3684,17 @@ class subcmd_author: verbose: bool, ) -> bool: if not os.path.isdir(pkg_source_dir): - message_fatal_error(msg_fn, "Missing local \"{:s}\"".format(pkg_source_dir)) + msglog.fatal_error("Missing local \"{:s}\"".format(pkg_source_dir)) return False if pkg_output_dir != "." and pkg_output_filepath != "": - message_fatal_error(msg_fn, "Both output directory & output filepath set, set one or the other") + msglog.fatal_error("Both output directory & output filepath set, set one or the other") return False pkg_manifest_filepath = os.path.join(pkg_source_dir, PKG_MANIFEST_FILENAME_TOML) if not os.path.exists(pkg_manifest_filepath): - message_fatal_error(msg_fn, "File \"{:s}\" not found!".format(pkg_manifest_filepath)) + msglog.fatal_error("File \"{:s}\" not found!".format(pkg_manifest_filepath)) return False # TODO: don't use this line, because the build information needs to be extracted too. @@ -3750,13 +3704,13 @@ class subcmd_author: with open(pkg_manifest_filepath, "rb") as fh: manifest_data = tomllib.load(fh) except Exception as ex: - message_fatal_error(msg_fn, "Error parsing TOML \"{:s}\" {:s}".format(pkg_manifest_filepath, str(ex))) + msglog.fatal_error("Error parsing TOML \"{:s}\" {:s}".format(pkg_manifest_filepath, str(ex))) return False manifest = pkg_manifest_from_dict_and_validate_all_errros(manifest_data, from_repo=False, strict=True) if isinstance(manifest, list): for error_msg in manifest: - message_fatal_error(msg_fn, "Error parsing TOML \"{:s}\" {:s}".format(pkg_manifest_filepath, error_msg)) + msglog.fatal_error("Error parsing TOML \"{:s}\" {:s}".format(pkg_manifest_filepath, error_msg)) return False if split_platforms: @@ -3764,15 +3718,14 @@ class subcmd_author: # this could result in further problems for automated tasks which operate on the output # where they would expect a platform suffix on each archive. So consider this an error. if not manifest.platforms: - message_fatal_error( - msg_fn, + msglog.fatal_error( "Error in arguments \"--split-platforms\" with a manifest that does not declare \"platforms\"", ) return False if valid_tags_filepath: if subcmd_author._validate_tags( - msg_fn, + msglog, manifest=manifest, pkg_manifest_filepath=pkg_manifest_filepath, valid_tags_filepath=valid_tags_filepath, @@ -3781,8 +3734,7 @@ class subcmd_author: if (manifest_build_data := manifest_data.get("build")) is not None: if "generated" in manifest_build_data: - message_fatal_error( - msg_fn, + msglog.fatal_error( "Error in TOML \"{:s}\" contains reserved value: [build.generated]".format(pkg_manifest_filepath), ) return False @@ -3803,9 +3755,9 @@ class subcmd_author: ) if isinstance(manifest_build_test, list): for error_msg in manifest_build_test: - message_fatal_error( - msg_fn, "Error parsing TOML \"{:s}\" {:s}".format( - pkg_manifest_filepath, error_msg)) + msglog.fatal_error( + "Error parsing TOML \"{:s}\" {:s}".format(pkg_manifest_filepath, error_msg) + ) return False manifest_build = manifest_build_test del manifest_build_test @@ -3893,7 +3845,7 @@ class subcmd_author: del build_paths_extra_canonical except Exception as ex: - message_status(msg_fn, "Error building path list \"{:s}\"".format(str(ex))) + msglog.status("Error building path list \"{:s}\"".format(str(ex))) return False request_exit = False @@ -3939,7 +3891,7 @@ class subcmd_author: outfile = os.path.join(pkg_output_dir, pkg_filename) outfile_temp = os.path.join(pkg_output_dir, "." + pkg_filename) - request_exit |= message_status(msg_fn, "building: {:s}".format(pkg_filename)) + request_exit |= msglog.status("building: {:s}".format(pkg_filename)) if request_exit: return False @@ -3947,7 +3899,7 @@ class subcmd_author: try: zip_fh_context = zipfile.ZipFile(outfile_temp, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) except Exception as ex: - message_status(msg_fn, "Error creating archive \"{:s}\"".format(str(ex))) + msglog.status("Error creating archive \"{:s}\"".format(str(ex))) return False with contextlib.closing(zip_fh_context) as zip_fh: @@ -3976,13 +3928,13 @@ class subcmd_author: else: zip_fh.write(filepath_abs, filepath_rel, compress_type=compress_type) except Exception as ex: - message_status(msg_fn, "Error adding to archive \"{:s}\"".format(str(ex))) + msglog.status("Error adding to archive \"{:s}\"".format(str(ex))) return False if verbose: - message_status(msg_fn, "add: {:s}".format(filepath_rel)) + msglog.status("add: {:s}".format(filepath_rel)) - request_exit |= message_status(msg_fn, "complete") + request_exit |= msglog.status("complete") if request_exit: return False @@ -3990,12 +3942,12 @@ class subcmd_author: os.unlink(outfile) os.rename(outfile_temp, outfile) - message_status(msg_fn, "created: \"{:s}\", {:d}".format(outfile, os.path.getsize(outfile))) + msglog.status("created: \"{:s}\", {:d}".format(outfile, os.path.getsize(outfile))) return True @staticmethod def _validate_tags( - msg_fn: MessageFn, + msglog: MessageLogger, *, manifest: PkgManifest, # NOTE: This path is only for inclusion in the error message, @@ -4006,26 +3958,22 @@ class subcmd_author: assert valid_tags_filepath if manifest.tags is not None: if isinstance(valid_tags_data := pkg_manifest_tags_load_valid_map(valid_tags_filepath), str): - message_fatal_error( - msg_fn, + msglog.fatal_error( "Error in TAGS \"{:s}\" loading tags: {:s}".format(valid_tags_filepath, valid_tags_data), ) return False if (error := pkg_manifest_tags_valid_or_error(valid_tags_data, manifest.type, manifest.tags)) is not None: - message_fatal_error( - msg_fn, - ( - "Error in TOML \"{:s}\" loading tags: {:s}\n" - "Either correct the tag or disable validation using an empty tags argument --valid-tags=\"\", " - "see --help text for details." - ).format(pkg_manifest_filepath, error), - ) + msglog.fatal_error(( + "Error in TOML \"{:s}\" loading tags: {:s}\n" + "Either correct the tag or disable validation using an empty tags argument --valid-tags=\"\", " + "see --help text for details." + ).format(pkg_manifest_filepath, error)) return False return True @staticmethod def _validate_directory( - msg_fn: MessageFn, + msglog: MessageLogger, *, pkg_source_dir: str, valid_tags_filepath: str, @@ -4033,15 +3981,15 @@ class subcmd_author: pkg_manifest_filepath = os.path.join(pkg_source_dir, PKG_MANIFEST_FILENAME_TOML) if not os.path.exists(pkg_manifest_filepath): - message_fatal_error(msg_fn, "Error, file \"{:s}\" not found!".format(pkg_manifest_filepath)) + msglog.fatal_error("Error, file \"{:s}\" not found!".format(pkg_manifest_filepath)) return False # Demote errors to status as the function of this action is to check the manifest is stable. manifest = pkg_manifest_from_toml_and_validate_all_errors(pkg_manifest_filepath, strict=True) if isinstance(manifest, list): - message_status(msg_fn, "Error parsing TOML \"{:s}\"".format(pkg_manifest_filepath)) + msglog.status("Error parsing TOML \"{:s}\"".format(pkg_manifest_filepath)) for error_msg in manifest: - message_status(msg_fn, error_msg) + msglog.status(error_msg) return False expected_files = [] @@ -4050,7 +3998,7 @@ class subcmd_author: ok = True for filepath in expected_files: if not os.path.exists(os.path.join(pkg_source_dir, filepath)): - message_status(msg_fn, "Error, file missing from {:s}: \"{:s}\"".format( + msglog.status("Error, file missing from {:s}: \"{:s}\"".format( manifest.type, filepath, )) @@ -4060,19 +4008,19 @@ class subcmd_author: if valid_tags_filepath: if subcmd_author._validate_tags( - msg_fn, + msglog, manifest=manifest, pkg_manifest_filepath=pkg_manifest_filepath, valid_tags_filepath=valid_tags_filepath, ) is False: return False - message_status(msg_fn, "Success parsing TOML in \"{:s}\"".format(pkg_source_dir)) + msglog.status("Success parsing TOML in \"{:s}\"".format(pkg_source_dir)) return True @staticmethod def _validate_archive( - msg_fn: MessageFn, + msglog: MessageLogger, *, pkg_source_archive: str, valid_tags_filepath: str, @@ -4093,24 +4041,24 @@ class subcmd_author: try: zip_fh_context = zipfile.ZipFile(pkg_source_archive, mode="r") except Exception as ex: - message_status(msg_fn, "Error extracting archive \"{:s}\"".format(str(ex))) + msglog.status("Error extracting archive \"{:s}\"".format(str(ex))) return False with contextlib.closing(zip_fh_context) as zip_fh: if (archive_subdir := pkg_zipfile_detect_subdir_or_none(zip_fh)) is None: - message_status(msg_fn, "Error, archive has no manifest: \"{:s}\"".format(PKG_MANIFEST_FILENAME_TOML)) + msglog.status("Error, archive has no manifest: \"{:s}\"".format(PKG_MANIFEST_FILENAME_TOML)) return False # Demote errors to status as the function of this action is to check the manifest is stable. manifest = pkg_manifest_from_zipfile_and_validate_all_errors(zip_fh, archive_subdir, strict=True) if isinstance(manifest, list): - message_status(msg_fn, "Error parsing TOML in \"{:s}\"".format(pkg_source_archive)) + msglog.status("Error parsing TOML in \"{:s}\"".format(pkg_source_archive)) for error_msg in manifest: - message_status(msg_fn, error_msg) + msglog.status(error_msg) return False if valid_tags_filepath: if subcmd_author._validate_tags( - msg_fn, + msglog, manifest=manifest, # Only for the error message, use the ZIP relative path. pkg_manifest_filepath=( @@ -4133,7 +4081,7 @@ class subcmd_author: ok = True for filepath in expected_files: if zip_fh.NameToInfo.get(filepath) is None: - message_status(msg_fn, "Error, file missing from {:s}: \"{:s}\"".format( + msglog.status("Error, file missing from {:s}: \"{:s}\"".format( manifest.type, filepath, )) @@ -4141,25 +4089,25 @@ class subcmd_author: if not ok: return False - message_status(msg_fn, "Success parsing TOML in \"{:s}\"".format(pkg_source_archive)) + msglog.status("Success parsing TOML in \"{:s}\"".format(pkg_source_archive)) return True @staticmethod def validate( - msg_fn: MessageFn, + msglog: MessageLogger, *, source_path: str, valid_tags_filepath: str, ) -> bool: if os.path.isdir(source_path): result = subcmd_author._validate_directory( - msg_fn, + msglog, pkg_source_dir=source_path, valid_tags_filepath=valid_tags_filepath, ) else: result = subcmd_author._validate_archive( - msg_fn, + msglog, pkg_source_archive=source_path, valid_tags_filepath=valid_tags_filepath, ) @@ -4170,7 +4118,7 @@ class subcmd_dummy: @staticmethod def repo( - msg_fn: MessageFn, + msglog: MessageLogger, *, repo_dir: str, package_names: Sequence[str], @@ -4179,21 +4127,20 @@ class subcmd_dummy: def msg_fn_no_done(ty: str, data: PrimTypeOrSeq) -> bool: if ty == 'DONE': return False - return msg_fn(ty, data) + return msglog.msg_fn(ty, data) # Ensure package names are valid. package_names = tuple(set(package_names)) for pkg_idname in package_names: if (error_msg := pkg_idname_is_valid_or_error(pkg_idname)) is None: continue - message_fatal_error( - msg_fn, + msglog.fatal_error( "key \"id\", \"{:s}\" doesn't match expected format, \"{:s}\"".format(pkg_idname, error_msg), ) return False if url_has_known_prefix(repo_dir): - message_fatal_error(msg_fn, "Generating a repository on a remote path is not supported") + msglog.fatal_error("Generating a repository on a remote path is not supported") return False # Unlike most other commands, create the repo_dir it doesn't already exist. @@ -4201,7 +4148,7 @@ class subcmd_dummy: try: os.makedirs(repo_dir) except Exception as ex: - message_fatal_error(msg_fn, "Failed to create \"{:s}\" with error: {!r}".format(repo_dir, ex)) + msglog.fatal_error("Failed to create \"{:s}\" with error: {!r}".format(repo_dir, ex)) return False import tempfile @@ -4250,7 +4197,7 @@ def unregister(): # `{cmd} build --pkg-source-dir {pkg_src_dir} --pkg-output-dir {repo_dir}`. if not subcmd_author.build( - msg_fn_no_done, + MessageLogger(msg_fn_no_done), pkg_source_dir=pkg_src_dir, pkg_output_dir=repo_dir, pkg_output_filepath="", @@ -4263,7 +4210,7 @@ def unregister(): # `{cmd} server-generate --repo-dir {repo_dir}`. if not subcmd_server.generate( - msg_fn_no_done, + MessageLogger(msg_fn_no_done), repo_dir=repo_dir, html=True, html_template="", @@ -4271,12 +4218,12 @@ def unregister(): # Error running command. return False - message_done(msg_fn) + msglog.done() return True @staticmethod def progress( - msg_fn: MessageFn, + msglog: MessageLogger, *, time_duration: float, time_delay: float, @@ -4287,7 +4234,7 @@ def unregister(): size_beg = 0 size_end = 100 while time_duration == 0.0 or (time.time() - time_start < time_duration): - request_exit |= message_progress(msg_fn, "Demo", size_beg, size_end, 'BYTE') + request_exit |= msglog.progress("Demo", size_beg, size_end, 'BYTE') if request_exit: break size_beg += 1 @@ -4295,10 +4242,10 @@ def unregister(): size_beg = 0 time.sleep(time_delay) if request_exit: - message_done(msg_fn) + msglog.done() return False - message_done(msg_fn) + msglog.done() return True @@ -4328,7 +4275,7 @@ def argparse_create_server_generate( subparse.set_defaults( func=lambda args: subcmd_server.generate( - msg_fn_from_args(args), + msglog_from_args(args), repo_dir=args.repo_dir, html=args.html, html_template=args.html_template, @@ -4358,7 +4305,7 @@ def argparse_create_client_list(subparsers: "argparse._SubParsersAction[argparse subparse.set_defaults( func=lambda args: subcmd_client.list_packages( - msg_fn_from_args(args), + msglog_from_args(args), args.remote_url, online_user_agent=args.online_user_agent, access_token=args.access_token, @@ -4393,7 +4340,7 @@ def argparse_create_client_sync(subparsers: "argparse._SubParsersAction[argparse subparse.set_defaults( func=lambda args: subcmd_client.sync( - msg_fn_from_args(args), + msglog_from_args(args), remote_url=args.remote_url, remote_name=args.remote_name if args.remote_name else remote_url_params_strip(args.remote_url), local_dir=args.local_dir, @@ -4423,7 +4370,7 @@ def argparse_create_client_install_files(subparsers: "argparse._SubParsersAction subparse.set_defaults( func=lambda args: subcmd_client.install_packages_from_files( - msg_fn_from_args(args), + msglog_from_args(args), local_dir=args.local_dir, package_files=args.files, blender_version=args.blender_version, @@ -4452,7 +4399,7 @@ def argparse_create_client_install(subparsers: "argparse._SubParsersAction[argpa subparse.set_defaults( func=lambda args: subcmd_client.install_packages( - msg_fn_from_args(args), + msglog_from_args(args), remote_url=args.remote_url, local_dir=args.local_dir, local_cache=args.local_cache, @@ -4480,7 +4427,7 @@ def argparse_create_client_uninstall(subparsers: "argparse._SubParsersAction[arg subparse.set_defaults( func=lambda args: subcmd_client.uninstall_packages( - msg_fn_from_args(args), + msglog_from_args(args), local_dir=args.local_dir, user_dir=args.user_dir, packages=args.packages.split(","), @@ -4514,7 +4461,7 @@ def argparse_create_author_build( subparse.set_defaults( func=lambda args: subcmd_author.build( - msg_fn_from_args(args), + msglog_from_args(args), pkg_source_dir=args.source_dir, pkg_output_dir=args.output_dir, pkg_output_filepath=args.output_filepath, @@ -4543,7 +4490,7 @@ def argparse_create_author_validate( subparse.set_defaults( func=lambda args: subcmd_author.validate( - msg_fn_from_args(args), + msglog_from_args(args), source_path=args.source_path, valid_tags_filepath=args.valid_tags_filepath, ), @@ -4577,7 +4524,7 @@ def argparse_create_dummy_repo(subparsers: "argparse._SubParsersAction[argparse. subparse.set_defaults( func=lambda args: subcmd_dummy.repo( - msg_fn_from_args(args), + msglog_from_args(args), repo_dir=args.repo_dir, package_names=args.package_names, ), @@ -4618,7 +4565,7 @@ def argparse_create_dummy_progress(subparsers: "argparse._SubParsersAction[argpa subparse.set_defaults( func=lambda args: subcmd_dummy.progress( - msg_fn_from_args(args), + msglog_from_args(args), time_duration=args.time_duration, time_delay=args.time_delay, ), @@ -4710,17 +4657,17 @@ def msg_print_json_0(ty: str, data: PrimTypeOrSeq) -> bool: return REQUEST_EXIT -def msg_fn_from_args(args: argparse.Namespace) -> MessageFn: +def msglog_from_args(args: argparse.Namespace) -> MessageLogger: # Will be None when running form Blender. output_type = getattr(args, "output_type", 'TEXT') match output_type: case 'JSON': - return msg_print_json + return MessageLogger(msg_print_json) case 'JSON_0': - return msg_print_json_0 + return MessageLogger(msg_print_json_0) case 'TEXT': - return msg_print_text + return MessageLogger(msg_print_text) raise Exception("Unknown output!")