html_compose.base_attribute
1from typing import Iterable, Tuple 2 3from markupsafe import Markup 4 5from .base_types import Resolvable 6 7 8def unsafe_text(value) -> str: 9 return Markup(str(value)) 10 11 12class BaseAttribute: 13 """ 14 Base class for all HTML element attributes. It resolves to a string. 15 16 Attribute Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes 17 """ 18 19 __slots__ = ("name", "data", "delimiter") 20 21 def __init__( 22 self, name: str, data: Resolvable = None, delimiter: str = " " 23 ): 24 self.name = name 25 self.data = data 26 self.delimiter = delimiter 27 28 def resolve_join(self, input_data: Iterable): 29 """ 30 Join a list of strings 31 Split out for implementors to override 32 """ 33 return self.delimiter.join( 34 x if isinstance(x, str) else str(x) for x in input_data 35 ) 36 37 def list_string_generator(self, data): 38 """ 39 Resolve list into string list (generator) 40 """ 41 for data_provider in data: 42 # You're gonna be a string then 43 if not isinstance(data_provider, str): 44 raise ValueError( 45 f"Input data must be a string, got {type(data_provider)}" 46 ) 47 yield data_provider 48 49 def dict_string_generator(self, data): 50 """ 51 Resolve dictionary into string list (generator) 52 53 Keys are returned if value is truthy 54 The value only determines if the key should be included 55 """ 56 for key, value in data.items(): 57 if not value: 58 continue 59 yield key 60 61 def dict_style_string_generator(self, data): 62 """ 63 Resolve dictionary key, value into css statement pairs 64 65 66 The implementation is the simplest `<key>: <value>. 67 User is therefore responsible for quoting. 68 """ 69 for key, value in data.items(): 70 yield f"{key}: {value}" 71 72 def resolve_data(self) -> str | None: 73 """ 74 Resolve right half of attribute into a string 75 76 A resolved string is returned from the input or resolved list/dict 77 """ 78 79 data = self.data 80 81 if data is True: 82 return "true" 83 84 # Just a string 85 if isinstance(data, str): 86 return data 87 88 if isinstance(data, int): 89 return str(data) 90 91 if data is None: 92 return None 93 94 # a list which can be resolved via join 95 _resolved = None 96 97 # List of strings 98 if isinstance(data, list): 99 _resolved = self.list_string_generator(data) 100 # dictionary of key value pairs 101 elif isinstance(data, dict): 102 if self.name == "style": 103 # Magic: Style attribute with key-value pairs procudes 104 # basic css statements. 105 _resolved = self.dict_style_string_generator(data) 106 else: 107 _resolved = self.dict_string_generator(data) 108 else: 109 raise ValueError(f"Input data type {data} not supported") 110 111 return self.resolve_join(_resolved) 112 113 def evaluate(self) -> Tuple[str, str] | None: 114 """ 115 Evaluate attribute, return key, value as tuple 116 or None if attribute is falsey 117 """ 118 if self.data is None or self.data is False: 119 return None 120 121 resolved = self.resolve_data() 122 if resolved is None: 123 return None 124 125 return (self.name, resolved) 126 127 def __repr__(self): 128 if self.delimiter != " ": 129 return f"BaseAttribute{{name={repr(self.name)}, data={repr(self.data)}, delimiter={repr(self.data)}}}" 130 131 return ( 132 f"BaseAttribute{{name={repr(self.name)}, data={repr(self.data)}}}" 133 )
def
unsafe_text(value) -> str:
class
BaseAttribute:
13class BaseAttribute: 14 """ 15 Base class for all HTML element attributes. It resolves to a string. 16 17 Attribute Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes 18 """ 19 20 __slots__ = ("name", "data", "delimiter") 21 22 def __init__( 23 self, name: str, data: Resolvable = None, delimiter: str = " " 24 ): 25 self.name = name 26 self.data = data 27 self.delimiter = delimiter 28 29 def resolve_join(self, input_data: Iterable): 30 """ 31 Join a list of strings 32 Split out for implementors to override 33 """ 34 return self.delimiter.join( 35 x if isinstance(x, str) else str(x) for x in input_data 36 ) 37 38 def list_string_generator(self, data): 39 """ 40 Resolve list into string list (generator) 41 """ 42 for data_provider in data: 43 # You're gonna be a string then 44 if not isinstance(data_provider, str): 45 raise ValueError( 46 f"Input data must be a string, got {type(data_provider)}" 47 ) 48 yield data_provider 49 50 def dict_string_generator(self, data): 51 """ 52 Resolve dictionary into string list (generator) 53 54 Keys are returned if value is truthy 55 The value only determines if the key should be included 56 """ 57 for key, value in data.items(): 58 if not value: 59 continue 60 yield key 61 62 def dict_style_string_generator(self, data): 63 """ 64 Resolve dictionary key, value into css statement pairs 65 66 67 The implementation is the simplest `<key>: <value>. 68 User is therefore responsible for quoting. 69 """ 70 for key, value in data.items(): 71 yield f"{key}: {value}" 72 73 def resolve_data(self) -> str | None: 74 """ 75 Resolve right half of attribute into a string 76 77 A resolved string is returned from the input or resolved list/dict 78 """ 79 80 data = self.data 81 82 if data is True: 83 return "true" 84 85 # Just a string 86 if isinstance(data, str): 87 return data 88 89 if isinstance(data, int): 90 return str(data) 91 92 if data is None: 93 return None 94 95 # a list which can be resolved via join 96 _resolved = None 97 98 # List of strings 99 if isinstance(data, list): 100 _resolved = self.list_string_generator(data) 101 # dictionary of key value pairs 102 elif isinstance(data, dict): 103 if self.name == "style": 104 # Magic: Style attribute with key-value pairs procudes 105 # basic css statements. 106 _resolved = self.dict_style_string_generator(data) 107 else: 108 _resolved = self.dict_string_generator(data) 109 else: 110 raise ValueError(f"Input data type {data} not supported") 111 112 return self.resolve_join(_resolved) 113 114 def evaluate(self) -> Tuple[str, str] | None: 115 """ 116 Evaluate attribute, return key, value as tuple 117 or None if attribute is falsey 118 """ 119 if self.data is None or self.data is False: 120 return None 121 122 resolved = self.resolve_data() 123 if resolved is None: 124 return None 125 126 return (self.name, resolved) 127 128 def __repr__(self): 129 if self.delimiter != " ": 130 return f"BaseAttribute{{name={repr(self.name)}, data={repr(self.data)}, delimiter={repr(self.data)}}}" 131 132 return ( 133 f"BaseAttribute{{name={repr(self.name)}, data={repr(self.data)}}}" 134 )
Base class for all HTML element attributes. It resolves to a string.
Attribute Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes
BaseAttribute( name: str, data: Union[NoneType, str, float, int, bool, Iterable[str | float | int | bool], Mapping[str | float | int | bool, bool]] = None, delimiter: str = ' ')
def
resolve_join(self, input_data: Iterable):
29 def resolve_join(self, input_data: Iterable): 30 """ 31 Join a list of strings 32 Split out for implementors to override 33 """ 34 return self.delimiter.join( 35 x if isinstance(x, str) else str(x) for x in input_data 36 )
Join a list of strings Split out for implementors to override
def
list_string_generator(self, data):
38 def list_string_generator(self, data): 39 """ 40 Resolve list into string list (generator) 41 """ 42 for data_provider in data: 43 # You're gonna be a string then 44 if not isinstance(data_provider, str): 45 raise ValueError( 46 f"Input data must be a string, got {type(data_provider)}" 47 ) 48 yield data_provider
Resolve list into string list (generator)
def
dict_string_generator(self, data):
50 def dict_string_generator(self, data): 51 """ 52 Resolve dictionary into string list (generator) 53 54 Keys are returned if value is truthy 55 The value only determines if the key should be included 56 """ 57 for key, value in data.items(): 58 if not value: 59 continue 60 yield key
Resolve dictionary into string list (generator)
Keys are returned if value is truthy The value only determines if the key should be included
def
dict_style_string_generator(self, data):
62 def dict_style_string_generator(self, data): 63 """ 64 Resolve dictionary key, value into css statement pairs 65 66 67 The implementation is the simplest `<key>: <value>. 68 User is therefore responsible for quoting. 69 """ 70 for key, value in data.items(): 71 yield f"{key}: {value}"
Resolve dictionary key, value into css statement pairs
The implementation is the simplest `
def
resolve_data(self) -> str | None:
73 def resolve_data(self) -> str | None: 74 """ 75 Resolve right half of attribute into a string 76 77 A resolved string is returned from the input or resolved list/dict 78 """ 79 80 data = self.data 81 82 if data is True: 83 return "true" 84 85 # Just a string 86 if isinstance(data, str): 87 return data 88 89 if isinstance(data, int): 90 return str(data) 91 92 if data is None: 93 return None 94 95 # a list which can be resolved via join 96 _resolved = None 97 98 # List of strings 99 if isinstance(data, list): 100 _resolved = self.list_string_generator(data) 101 # dictionary of key value pairs 102 elif isinstance(data, dict): 103 if self.name == "style": 104 # Magic: Style attribute with key-value pairs procudes 105 # basic css statements. 106 _resolved = self.dict_style_string_generator(data) 107 else: 108 _resolved = self.dict_string_generator(data) 109 else: 110 raise ValueError(f"Input data type {data} not supported") 111 112 return self.resolve_join(_resolved)
Resolve right half of attribute into a string
A resolved string is returned from the input or resolved list/dict
def
evaluate(self) -> Optional[Tuple[str, str]]:
114 def evaluate(self) -> Tuple[str, str] | None: 115 """ 116 Evaluate attribute, return key, value as tuple 117 or None if attribute is falsey 118 """ 119 if self.data is None or self.data is False: 120 return None 121 122 resolved = self.resolve_data() 123 if resolved is None: 124 return None 125 126 return (self.name, resolved)
Evaluate attribute, return key, value as tuple or None if attribute is falsey