Files
obsidian-metadata/src/obsidian_metadata/_utils/utilities.py
2023-03-03 21:10:43 -05:00

187 lines
5.3 KiB
Python

"""Utility functions."""
import re
from os import name, system
from typing import Any
import typer
from obsidian_metadata.__version__ import __version__
from obsidian_metadata._utils.console import console
def clean_dictionary(dictionary: dict[str, Any]) -> dict[str, Any]:
"""Clean up a dictionary by markdown formatting from keys and values.
Args:
dictionary (dict): Dictionary to clean
Returns:
dict: Cleaned dictionary
"""
new_dict = {key.strip(): value for key, value in dictionary.items()}
new_dict = {key.strip("*[]#"): value for key, value in new_dict.items()}
for key, value in new_dict.items():
new_dict[key] = [s.strip("*[]#") for s in value if isinstance(value, list)]
return new_dict
def clear_screen() -> None: # pragma: no cover
"""Clear the screen."""
# for windows
_ = system("cls") if name == "nt" else system("clear")
def dict_contains(
dictionary: dict[str, list[str]], key: str, value: str = None, is_regex: bool = False
) -> bool:
"""Check if a dictionary contains a key.
Args:
dictionary (dict): Dictionary to check
key (str): Key to check for
value (str, optional): Value to check for. Defaults to None.
is_regex (bool, optional): Whether the key is a regex. Defaults to False.
Returns:
bool: Whether the dictionary contains the key
"""
if value is None:
if is_regex:
return any(re.search(key, str(_key)) for _key in dictionary)
return key in dictionary
if is_regex:
found_keys = []
for _key in dictionary:
if re.search(key, str(_key)):
found_keys.append(
any(re.search(value, _v) for _v in dictionary[_key]),
)
return any(found_keys)
return key in dictionary and value in dictionary[key]
def dict_values_to_lists_strings(dictionary: dict, strip_null_values: bool = False) -> dict:
"""Convert all values in a dictionary to lists of strings.
Args:
dictionary (dict): Dictionary to convert
strip_null (bool): Whether to strip null values
Returns:
dict: Dictionary with all values converted to lists of strings
{key: sorted(new_dict[key]) for key in sorted(new_dict)}
"""
new_dict = {}
if strip_null_values:
for key, value in dictionary.items():
if isinstance(value, list):
new_dict[key] = sorted([str(item) for item in value if item is not None])
elif isinstance(value, dict):
new_dict[key] = dict_values_to_lists_strings(value) # type: ignore[assignment]
elif value is None or value == "None" or value == "":
new_dict[key] = []
else:
new_dict[key] = [str(value)]
return new_dict
for key, value in dictionary.items():
if isinstance(value, list):
new_dict[key] = sorted([str(item) for item in value])
elif isinstance(value, dict):
new_dict[key] = dict_values_to_lists_strings(value) # type: ignore[assignment]
else:
new_dict[key] = [str(value)]
return new_dict
def docstring_parameter(*sub: Any) -> Any:
"""Replace variables within docstrings.
Args:
sub (Any): Replacement variables
Usage:
@docstring_parameter("foo", "bar")
def foo():
'''This is a {0} docstring with {1} variables.'''
"""
def dec(obj: Any) -> Any:
"""Format object."""
obj.__doc__ = obj.__doc__.format(*sub)
return obj
return dec
def merge_dictionaries(dict1: dict, dict2: dict) -> dict:
"""Merge two dictionaries. When the values are lists, they are merged and sorted.
Args:
dict1 (dict): First dictionary.
dict2 (dict): Second dictionary.
Returns:
dict: Merged dictionary.
"""
for k, v in dict2.items():
if k in dict1:
if isinstance(v, list):
dict1[k].extend(v)
else:
dict1[k] = v
for k, v in dict1.items():
if isinstance(v, list):
dict1[k] = sorted(set(v))
elif isinstance(v, dict): # pragma: no cover
for kk, vv in v.items():
if isinstance(vv, list):
v[kk] = sorted(set(vv))
return dict(sorted(dict1.items()))
def remove_markdown_sections(
text: str,
strip_codeblocks: bool = False,
strip_inlinecode: bool = False,
strip_frontmatter: bool = False,
) -> str:
"""Strip markdown sections from text.
Args:
text (str): Text to remove code blocks from
strip_codeblocks (bool, optional): Strip code blocks. Defaults to False.
strip_inlinecode (bool, optional): Strip inline code. Defaults to False.
strip_frontmatter (bool, optional): Strip frontmatter. Defaults to False.
Returns:
str: Text without code blocks
"""
if strip_codeblocks:
text = re.sub(r"`{3}.*?`{3}", "", text, flags=re.DOTALL)
if strip_inlinecode:
text = re.sub(r"`.*?`", "", text)
if strip_frontmatter:
text = re.sub(r"^\s*---.*?---", "", text, flags=re.DOTALL)
return text # noqa: RET504
def version_callback(value: bool) -> None:
"""Print version and exit."""
if value:
console.print(f"{__package__.split('.')[0]}: v{__version__}")
raise typer.Exit()