Merge branch 'blender-v4.2-release'
This commit is contained in:
@@ -828,6 +828,9 @@ def extensions_panel_draw_impl(
|
||||
|
||||
if is_addon:
|
||||
col_a.label(text="Permissions")
|
||||
# WARNING: while this is documented to be a dict, old packages may contain a list of strings.
|
||||
# As it happens dictionary keys & list values both iterate over string,
|
||||
# however we will want to show the dictionary values eventually.
|
||||
if (value := item_remote.get("permissions")):
|
||||
col_b.label(text=", ".join([iface_(x.title()) for x in value]), translate=False)
|
||||
else:
|
||||
|
||||
@@ -101,6 +101,11 @@ RE_CONTROL_CHARS = re.compile(r'[\x00-\x1f\x7f-\x9f]')
|
||||
# 16kb to be responsive even on slow connections.
|
||||
CHUNK_SIZE_DEFAULT = 1 << 14
|
||||
|
||||
# Short descriptions for the UI:
|
||||
# Used for project tag-line & permissions values.
|
||||
TERSE_DESCRIPTION_MAX_LENGTH = 64
|
||||
|
||||
|
||||
# Standard out may be communicating with a parent process,
|
||||
# arbitrary prints are NOT acceptable.
|
||||
|
||||
@@ -1167,6 +1172,25 @@ def pkg_idname_is_valid_or_error(pkg_idname: str) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def pkg_manifest_validate_terse_description_or_error(value: str) -> Optional[str]:
|
||||
# Could be an argument.
|
||||
length_limit = TERSE_DESCRIPTION_MAX_LENGTH
|
||||
if (length_limit != -1) and (len(value) > length_limit):
|
||||
return "a value no longer than {:d} characters expected, found {:d}".format(length_limit, len(value))
|
||||
|
||||
if (error := pkg_manifest_validate_field_any_non_empty_string_stripped_no_control_chars(value, True)) is not None:
|
||||
return error
|
||||
|
||||
# As we don't have a reliable (unicode aware) punctuation check, just check the last character is alpha/numeric.
|
||||
if value[-1].isalnum():
|
||||
pass # OK.
|
||||
elif value[-1] in {")", "]", "}"}:
|
||||
pass # Allow closing brackets (sometimes used to mention formats).
|
||||
else:
|
||||
return "alpha-numeric suffix expected, the string must not end with punctuation"
|
||||
return None
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Manifest Validation (Generic Callbacks)
|
||||
#
|
||||
@@ -1282,19 +1306,7 @@ def pkg_manifest_validate_field_type(value: str, strict: bool) -> Optional[str]:
|
||||
|
||||
def pkg_manifest_validate_field_tagline(value: str, strict: bool) -> Optional[str]:
|
||||
if strict:
|
||||
if (error := pkg_manifest_validate_field_any_non_empty_string_stripped_no_control_chars(value, strict)) is not None:
|
||||
return error
|
||||
|
||||
# Additional requirements.
|
||||
if len(value) > 64:
|
||||
return "a value no longer than 64 characters expected, found {:d}".format(len(value))
|
||||
# As we don't have a reliable (unicode aware) punctuation check, just check the last character is alpha/numeric.
|
||||
if value[-1].isalnum():
|
||||
pass # OK.
|
||||
elif value[-1] in {")", "]", "}"}:
|
||||
pass # Allow closing brackets (sometimes used to mention formats).
|
||||
else:
|
||||
return "alpha-numeric suffix expected, the string must not end with punctuation"
|
||||
return pkg_manifest_validate_terse_description_or_error(value)
|
||||
else:
|
||||
if (error := pkg_manifest_validate_field_any_non_empty_string(value, strict)) is not None:
|
||||
return error
|
||||
@@ -1303,33 +1315,57 @@ def pkg_manifest_validate_field_tagline(value: str, strict: bool) -> Optional[st
|
||||
|
||||
|
||||
def pkg_manifest_validate_field_permissions(
|
||||
# `Dict[str, str]` is expected but at this point it's only guaranteed to be a dict.
|
||||
value: Dict[Any, Any],
|
||||
value: Union[
|
||||
# `Dict[str, str]` is expected but at this point it's only guaranteed to be a dict.
|
||||
Dict[Any, Any],
|
||||
# Kept for old files.
|
||||
List[Any],
|
||||
],
|
||||
strict: bool,
|
||||
) -> Optional[str]:
|
||||
_ = strict
|
||||
# Always strict for now as it doesn't seem as there are repositories using invalid values.
|
||||
strict = True
|
||||
|
||||
keys_valid = {
|
||||
"files",
|
||||
"network",
|
||||
"clipboard",
|
||||
"camera",
|
||||
"microphone",
|
||||
}
|
||||
|
||||
if strict:
|
||||
keys_valid = {
|
||||
"files",
|
||||
"network",
|
||||
"clipboard",
|
||||
"camera",
|
||||
"microphone",
|
||||
}
|
||||
# A list may be passed in when not-strict.
|
||||
if not isinstance(value, dict):
|
||||
return "permissions must be a table of strings, not a {:s}".format(str(type(value)))
|
||||
|
||||
for item_key, item_value in value.items():
|
||||
# Validate the key.
|
||||
if not isinstance(item_key, str):
|
||||
return "key \"{:s}\" must be a string not a {:s}".format(str(item_key), str(type(item_key)))
|
||||
if not isinstance(item_value, str):
|
||||
return "value of \"{:s}\" must be a string not a {:s}".format(item_key, str(type(item_value)))
|
||||
if item_key not in keys_valid:
|
||||
return "value of \"{:s}\" must be a value in {!r}".format(item_key, tuple(keys_valid))
|
||||
|
||||
# Validate the value.
|
||||
if not isinstance(item_value, str):
|
||||
return "value of \"{:s}\" must be a string not a {:s}".format(item_key, str(type(item_value)))
|
||||
|
||||
if (error := pkg_manifest_validate_terse_description_or_error(item_value)) is not None:
|
||||
return "value of \"{:s}\": {:s}".format(item_key, error)
|
||||
|
||||
else:
|
||||
# TODO: basic validation.
|
||||
# if (error := pkg_manifest_validate_field_any_dict_of_non_empty_strings(value, strict)) is not None:
|
||||
# return error
|
||||
pass
|
||||
if isinstance(value, dict):
|
||||
for item_key, item_value in value.items():
|
||||
if not isinstance(item_key, str):
|
||||
return "key \"{:s}\" must be a string not a {:s}".format(str(item_key), str(type(item_key)))
|
||||
if not isinstance(item_value, str):
|
||||
return "value of \"{:s}\" must be a string not a {:s}".format(item_key, str(type(item_value)))
|
||||
elif isinstance(value, list):
|
||||
# Historic beta convention, keep for compatibility.
|
||||
for i, item in enumerate(value):
|
||||
if not isinstance(item_key, str):
|
||||
return "Expected item at index {:d} to be an int not a {:s}".format(i, str(type(item)))
|
||||
else:
|
||||
# The caller doesn't allow this.
|
||||
assert False, "internal error, disallowed type"
|
||||
|
||||
return None
|
||||
|
||||
@@ -1434,7 +1470,7 @@ def pkg_manifest_validate_field_archive_hash(
|
||||
# Keep in sync with `PkgManifest`.
|
||||
# key, type, check_fn.
|
||||
pkg_manifest_known_keys_and_types: Tuple[
|
||||
Tuple[str, type, Callable[[Any, bool], Optional[str]]],
|
||||
Tuple[str, Union[type, Tuple[type, ...]], Callable[[Any, bool], Optional[str]]],
|
||||
...,
|
||||
] = (
|
||||
("id", str, pkg_manifest_validate_field_idname),
|
||||
@@ -1451,7 +1487,8 @@ pkg_manifest_known_keys_and_types: Tuple[
|
||||
("blender_version_max", str, pkg_manifest_validate_field_any_version_primitive_or_empty),
|
||||
("website", str, pkg_manifest_validate_field_any_non_empty_string_stripped_no_control_chars),
|
||||
("copyright", list, pkg_manifest_validate_field_any_non_empty_list_of_non_empty_strings),
|
||||
("permissions", dict, pkg_manifest_validate_field_permissions),
|
||||
# Type should be `dict` eventually, some existing packages will have a list of strings instead.
|
||||
("permissions", (dict, list), pkg_manifest_validate_field_permissions),
|
||||
("tags", list, pkg_manifest_validate_field_any_non_empty_list_of_non_empty_strings),
|
||||
("wheels", list, pkg_manifest_validate_field_wheels),
|
||||
)
|
||||
@@ -1514,8 +1551,11 @@ def pkg_manifest_is_valid_or_error_impl(
|
||||
pass
|
||||
else:
|
||||
error_list.append("\"{:s}\" must be a {:s}, not a {:s}".format(
|
||||
x_key,
|
||||
x_ty.__name__,
|
||||
x_key, (
|
||||
"[{:s}]".format(", ".join(x_ty_elem.__name__ for x_ty_elem in x_ty))
|
||||
if isinstance(x_ty, tuple) else
|
||||
x_ty.__name__
|
||||
),
|
||||
type(x_val).__name__,
|
||||
))
|
||||
if not all_errors:
|
||||
@@ -3044,6 +3084,9 @@ class subcmd_dummy:
|
||||
fh.write("""version = "1.0.0"\n""")
|
||||
fh.write("""tagline = "This is a tagline"\n""")
|
||||
fh.write("""blender_version_min = "0.0.0"\n""")
|
||||
fh.write("""[permissions]\n""")
|
||||
for value in ("files", "network", "clipboard", "camera", "microphone"):
|
||||
fh.write("""{:s} = "Example text"\n""".format(value))
|
||||
|
||||
with open(os.path.join(pkg_src_dir, "__init__.py"), "w", encoding="utf-8") as fh:
|
||||
fh.write("""
|
||||
|
||||
@@ -10,12 +10,19 @@ version = "0.2.0"
|
||||
blender_version_min = "2.80.0"
|
||||
maintainer = "Maintainer Name"
|
||||
license = [
|
||||
"SPDX:CC-0"
|
||||
"SPDX:CC-0",
|
||||
]
|
||||
copyright = [
|
||||
"Developer Name"
|
||||
"Developer Name",
|
||||
]
|
||||
|
||||
[permissions]
|
||||
network = "Need to sync data to server"
|
||||
files = "Import/export files from/to disk"
|
||||
clipboard = "Copy rotations"
|
||||
camera = "Reason for connecting to a camera"
|
||||
microphone = "An add-on could do this in theory"
|
||||
|
||||
[build]
|
||||
paths_exclude_pattern = [
|
||||
# Exclude all Python cache.
|
||||
|
||||
Reference in New Issue
Block a user