Source code for kadi.lib.conversion

# Copyright 2020 Karlsruhe Institute of Technology
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import timezone
from urllib.parse import urlparse

from flask import json
from markdown_it import MarkdownIt
from markupsafe import Markup
from marshmallow import ValidationError
from marshmallow.fields import DateTime
from mdit_py_plugins.texmath import texmath_plugin


[docs]def strip(value): """Strip all surrounding whitespaces in a string. :param value: The string to strip. :return: The stripped string or the original input if it was not a string. """ if isinstance(value, str): return value.strip() return value
[docs]def normalize(value): """Normalize all and strip surrounding whitespaces in a string. :param value: The string to normalize. :return: The normalized string or the original input if it was not a string. """ if isinstance(value, str): return " ".join(value.split()) return value
[docs]def normalize_uri(value): """Normalize a URI by lowercasing its domain name portion. :param value: The URI to normalize as string. :return: The normalized URI or the original input if it was not a string. """ if isinstance(value, str): uri = urlparse(value) if uri.scheme and uri.netloc: return f"{uri.scheme.lower()}://{uri.netloc.lower()}{uri.path}" return value
[docs]def lower(value): """Lowercase all characters in a string. :param value: The string to lowercase. :return: The lowercased string or the original input if it was not a string. """ if isinstance(value, str): return value.lower() return value
[docs]def truncate(value, length): """Truncate a string based on a given length. :param value: The string to truncate. :param length: The maximum length of the string. :return: The truncated string or the original input if it was not a string. """ if isinstance(value, str) and len(value) > length: return f"{value[:length]}..." return value
[docs]def recode(value, from_encoding="utf-8", to_encoding="utf-8"): """Change the encoding of a string. :param value: The string value. :param from_encoding: (optional) The original encoding of the string. :param to_encoding: (optional) The target encoding of the string. :return: The newly encoded string or the original input if it was not a string or the recoding failed. """ try: if isinstance(value, str): value = value.encode(from_encoding).decode(to_encoding) except UnicodeDecodeError: pass return value
[docs]def clamp(value, min_value, max_value): """Clamp a numeric value to the inclusive range of the given min and max values. :param min_value: The minumum value. :param max_value: The maximum value. :return: The clamped value or the original input if it was not a numeric value. """ if isinstance(value, (int, float)): return min(max(value, min_value), max_value) return value
[docs]def none(value): """Return ``None`` if a given value is falsy. :param value: A value to check for truthness. :return: The unmodified value or ``None`` if it is falsy. """ if not value: return None return value
[docs]def empty_str(value): """Return an empty string if a given value is ``None``. :param value: A value to check for being ``None``. :return: The unmodified value or an empty string if it is ``None``. """ if value is None: return "" return value
[docs]def markdown_to_html(value): """Render a markdown string as HTML. Note that manually entered HTML will be left intact, as it will be escaped accordingly. :param value: The string to render. :return: The rendered string or the original input if it was not a string or could not be rendered. """ if isinstance(value, str): md = MarkdownIt("js-default").use(texmath_plugin) try: value = md.render(value) except: pass return value
[docs]def strip_markdown(value): """Strip a string of its markdown directives and normalize its whitespaces. Note that not all directives may be stripped, since some may not be supported by the markdown renderer used in :func:`markdown_to_html`. :param value: The string to strip. :return: The stripped string or the original input if it was not a string. """ if isinstance(value, str): value = markdown_to_html(value) return Markup(value).striptags() return value
[docs]def parse_datetime_string(value): """Parse a datetime string. :param value: The datetime string to parse in ISO 8601 format. :return: A timezone aware datetime object in UTC as specified in Python's ``datetime`` module or ``None`` if the given string was not a valid datetime string. """ try: # Marshmallow's parsing is pretty robust, so we just make use of it here. value = DateTime().deserialize(value) except ValidationError: return None return value.astimezone(timezone.utc)
[docs]def parse_boolean_string(value): """Parse a boolean string. The given string is parsed based on typical values used for thruthness, including ``True``, ``"true"``, ``"t"``, ``"yes"``, ``"y"``, ``"on"`` and ``"1"`` (case insensitive for all string values), instead of using Python's ``bool`` conversion. All other values are considered false. :param value: The boolean string to parse. :return: ``True`` if the given string is considered truthy, ``False`` otherwise. """ if isinstance(value, str): value = value.lower() return value in {True, "true", "t", "yes", "y", "on", "1"}
[docs]def parse_json_object(value): """Parse a JSON object string as a dictionary. :param value: The JSON object string to parse. :return: The parsed dictionary or an empty dictionary if the given string was not a valid JSON object. """ try: value = json.loads(value) except: return {} return value if isinstance(value, dict) else {}