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.
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