phml.utilities.misc.component

  1from pathlib import Path
  2from re import finditer, sub
  3
  4from phml.core.nodes import AST, NODE, Element
  5
  6__all__ = [
  7    "tokanize_name",
  8    "tag_from_file",
  9    "filename_from_path",
 10    "parse_component",
 11    "valid_component_dict",
 12    "cmpt_name_from_path",
 13]
 14
 15
 16def tokanize_name(name: str, normalize: bool = True) -> list[str]:
 17    """Generates name tokens `some name tokanized` from a filename.
 18    Assumes filenames is one of:
 19    * snakecase - some_file_name
 20    * camel case - someFileName
 21    * pascal case - SomeFileName
 22
 23    Args:
 24        name (str): File name without extension
 25        normalize (bool): Make all tokens fully lowercase. Defaults to True
 26
 27    Returns:
 28        list[str]: List of word tokens.
 29    """
 30    tokens = []
 31    for token in finditer(r"(\b|[A-Z]|_|-|\.)([a-z]+)|([0-9]+)|([A-Z]+)(?=[^a-z])", name):
 32        first, rest, nums, cap = token.groups()
 33
 34        if first is not None and first.isupper():
 35            rest = first + rest
 36        elif cap is not None and cap.isupper():
 37            rest = cap
 38        elif nums is not None and nums.isnumeric():
 39            rest = str(nums)
 40        tokens.append(rest.lower() if normalize else rest)
 41    return tokens
 42
 43
 44def tag_from_file(filename: str | Path) -> str:
 45    """Generates a tag name some-tag-name from a filename.
 46    Assumes filenames of:
 47    * snakecase - some_file_name
 48    * camel case - someFileName
 49    * pascal case - SomeFileName
 50    """
 51
 52    if isinstance(filename, Path):
 53        if filename.is_file():
 54            filename = filename.name.replace(filename.suffix, "")
 55        else:
 56            raise TypeError("If filename is a path it must also be a valid file.")
 57
 58    tokens = tokanize_name(filename)
 59
 60    return "-".join(tokens)
 61
 62
 63def cmpt_name_from_path(file: Path | str, strip: str = "") -> str:
 64    """Construct a component name given a path. This will include parent directories.
 65    it will also strip the root directory as this is most commonly not wanted.
 66
 67    Examples:
 68        `components/blog/header.phml`
 69
 70        yields
 71
 72        `blog-header`
 73    """
 74    file = Path(file)
 75    file = file.with_name(file.name.replace(file.suffix, ""))
 76    file = sub(strip.strip("/"), "", file.as_posix().lstrip("/")).strip("/")
 77    dirs = [
 78        "".join([
 79            n[0].upper() + n[1:]
 80            for n in tokanize_name(name, False)
 81        ])
 82        for name in file.split("/")
 83    ]
 84    return ".".join(dirs)
 85
 86
 87def filename_from_path(file: Path) -> str:
 88    """Get the filename without the suffix from a pathlib.Path."""
 89
 90    return file.name.replace(file.suffix, "")
 91
 92
 93def valid_component_dict(cmpt: dict) -> bool:
 94    """Check if a component dict is valid."""
 95    return bool(
 96        ("python" in cmpt and isinstance(cmpt["python"], list))
 97        and ("script" in cmpt and isinstance(cmpt["script"], list))
 98        and ("style" in cmpt and isinstance(cmpt["script"], list))
 99        and ("component" in cmpt and isinstance(cmpt["component"], NODE))
100    )
101
102
103def parse_component(ast: AST) -> dict[str, Element]:
104    """Helper function to parse the components elements."""
105    from phml.utilities import (  # pylint: disable=import-outside-toplevel
106        check,
107        is_css_style,
108        is_javascript,
109        visit_children,
110    )
111
112    result = {"python": [], "script": [], "style": [], "component": None}
113    for node in visit_children(ast.tree):
114        if check(node, ["element", {"tag": "python"}]):
115            result["python"].append(node)
116        elif check(node, ["element", {"tag": "script"}]) and is_javascript(node):
117            result["script"].append(node)
118        elif check(node, ["element", {"tag": "style"}]) and is_css_style(node):
119            result["style"].append(node)
120        elif check(node, "element"):
121            if result["component"] is None:
122                result["component"] = node
123            else:
124                raise Exception(
125                    """\
126Components may only have one wrapping element. All other element in the root must be either a \
127script, style, or python tag. The root wrapping element must be '<PHML>`\
128"""
129                )
130
131    if result["component"] is None:
132        result["component"] = Element("")
133
134    return result
def tokanize_name(name: str, normalize: bool = True) -> list[str]:
17def tokanize_name(name: str, normalize: bool = True) -> list[str]:
18    """Generates name tokens `some name tokanized` from a filename.
19    Assumes filenames is one of:
20    * snakecase - some_file_name
21    * camel case - someFileName
22    * pascal case - SomeFileName
23
24    Args:
25        name (str): File name without extension
26        normalize (bool): Make all tokens fully lowercase. Defaults to True
27
28    Returns:
29        list[str]: List of word tokens.
30    """
31    tokens = []
32    for token in finditer(r"(\b|[A-Z]|_|-|\.)([a-z]+)|([0-9]+)|([A-Z]+)(?=[^a-z])", name):
33        first, rest, nums, cap = token.groups()
34
35        if first is not None and first.isupper():
36            rest = first + rest
37        elif cap is not None and cap.isupper():
38            rest = cap
39        elif nums is not None and nums.isnumeric():
40            rest = str(nums)
41        tokens.append(rest.lower() if normalize else rest)
42    return tokens

Generates name tokens some name tokanized from a filename. Assumes filenames is one of:

  • snakecase - some_file_name
  • camel case - someFileName
  • pascal case - SomeFileName
Args
  • name (str): File name without extension
  • normalize (bool): Make all tokens fully lowercase. Defaults to True
Returns

list[str]: List of word tokens.

def tag_from_file(filename: str | pathlib.Path) -> str:
45def tag_from_file(filename: str | Path) -> str:
46    """Generates a tag name some-tag-name from a filename.
47    Assumes filenames of:
48    * snakecase - some_file_name
49    * camel case - someFileName
50    * pascal case - SomeFileName
51    """
52
53    if isinstance(filename, Path):
54        if filename.is_file():
55            filename = filename.name.replace(filename.suffix, "")
56        else:
57            raise TypeError("If filename is a path it must also be a valid file.")
58
59    tokens = tokanize_name(filename)
60
61    return "-".join(tokens)

Generates a tag name some-tag-name from a filename. Assumes filenames of:

  • snakecase - some_file_name
  • camel case - someFileName
  • pascal case - SomeFileName
def filename_from_path(file: pathlib.Path) -> str:
88def filename_from_path(file: Path) -> str:
89    """Get the filename without the suffix from a pathlib.Path."""
90
91    return file.name.replace(file.suffix, "")

Get the filename without the suffix from a pathlib.Path.

def parse_component(ast: phml.core.nodes.AST.AST) -> dict[str, phml.core.nodes.nodes.Element]:
104def parse_component(ast: AST) -> dict[str, Element]:
105    """Helper function to parse the components elements."""
106    from phml.utilities import (  # pylint: disable=import-outside-toplevel
107        check,
108        is_css_style,
109        is_javascript,
110        visit_children,
111    )
112
113    result = {"python": [], "script": [], "style": [], "component": None}
114    for node in visit_children(ast.tree):
115        if check(node, ["element", {"tag": "python"}]):
116            result["python"].append(node)
117        elif check(node, ["element", {"tag": "script"}]) and is_javascript(node):
118            result["script"].append(node)
119        elif check(node, ["element", {"tag": "style"}]) and is_css_style(node):
120            result["style"].append(node)
121        elif check(node, "element"):
122            if result["component"] is None:
123                result["component"] = node
124            else:
125                raise Exception(
126                    """\
127Components may only have one wrapping element. All other element in the root must be either a \
128script, style, or python tag. The root wrapping element must be '<PHML>`\
129"""
130                )
131
132    if result["component"] is None:
133        result["component"] = Element("")
134
135    return result

Helper function to parse the components elements.

def valid_component_dict(cmpt: dict) -> bool:
 94def valid_component_dict(cmpt: dict) -> bool:
 95    """Check if a component dict is valid."""
 96    return bool(
 97        ("python" in cmpt and isinstance(cmpt["python"], list))
 98        and ("script" in cmpt and isinstance(cmpt["script"], list))
 99        and ("style" in cmpt and isinstance(cmpt["script"], list))
100        and ("component" in cmpt and isinstance(cmpt["component"], NODE))
101    )

Check if a component dict is valid.

def cmpt_name_from_path(file: pathlib.Path | str, strip: str = '') -> str:
64def cmpt_name_from_path(file: Path | str, strip: str = "") -> str:
65    """Construct a component name given a path. This will include parent directories.
66    it will also strip the root directory as this is most commonly not wanted.
67
68    Examples:
69        `components/blog/header.phml`
70
71        yields
72
73        `blog-header`
74    """
75    file = Path(file)
76    file = file.with_name(file.name.replace(file.suffix, ""))
77    file = sub(strip.strip("/"), "", file.as_posix().lstrip("/")).strip("/")
78    dirs = [
79        "".join([
80            n[0].upper() + n[1:]
81            for n in tokanize_name(name, False)
82        ])
83        for name in file.split("/")
84    ]
85    return ".".join(dirs)

Construct a component name given a path. This will include parent directories. it will also strip the root directory as this is most commonly not wanted.

Examples

components/blog/header.phml

yields

blog-header