phml.core.core

  1from io import TextIOWrapper
  2from pathlib import Path
  3from typing import Any, Optional
  4from phml.core.defaults.config import EnableDefault
  5
  6from phml.core.formats import Format, Formats
  7from phml.core.nodes import AST, NODE
  8from phml.utilities import cmpt_name_from_path, parse_component
  9
 10from phml.types import Component, Components, PathLike
 11from phml.types.config import EnableKeys
 12
 13from .compiler import Compiler
 14from .parser import Parser
 15
 16__all__ = ["PHML", "Compiler"]
 17
 18class PHML:
 19    """A helper class that bundles the functionality
 20    of the parser and compiler together. Allows for loading source files,
 21    parsing strings and dicts, rendering to a different format, and finally
 22    writing the results of a render to a file.
 23    """
 24
 25    @property
 26    def ast(self) -> AST:
 27        """The parsed ast value."""
 28        return self._parser.ast
 29
 30    @ast.setter
 31    def ast(self, _ast: AST):
 32        self._parser.ast = _ast
 33        
 34    @property
 35    def components(self) -> dict:
 36        """The components currently stored in the compiler."""
 37        return self._compiler.components
 38
 39    def __init__(
 40        self,
 41        *,
 42        scopes: list[str] | None = None,
 43        components: Components | None = None,
 44        enable: dict[EnableKeys, bool] = EnableDefault,
 45        **contexts: Any,
 46    ):
 47        self._parser = Parser()
 48        self._compiler = Compiler(components=components, enable=enable)
 49        self._scopes = scopes or []
 50        self._context = dict(contexts)
 51
 52    def expose(self, **kwargs: Any):
 53        """Add additional data to the compilers global values. These values are exposed for every
 54        call to render or write.
 55        """
 56        self._context.update(kwargs)
 57
 58    def redact(self, key: str):
 59        """Remove a value from the compilers globally exposed values."""
 60        self._context.pop(key, None)
 61
 62    def expand(self, *args: str):
 63        """Add relative paths to a directory, that you want added to the python path
 64        for every time render or write is called.
 65        """
 66        self._scopes.extend([arg for arg in args if arg not in self._scopes])
 67
 68    def restrict(self, *args: str):
 69        """Remove relative paths to a directory, that are in the compilers globally added scopes.
 70        This prevents them from being added to the python path.
 71        """
 72        for arg in args:
 73            if arg in self._scopes:
 74                self._scopes.remove(arg)
 75
 76    def add(
 77        self,
 78        *components: Component,
 79        strip: str = "",
 80    ):
 81        """Add a component to the compiler's component list.
 82
 83        Components passed in can be of a few types. The first type it can be is a
 84        pathlib.Path type. This will allow for automatic parsing of the file at the
 85        path and then the filename and parsed ast are passed to the compiler. It can
 86        also be a dictionary of str being the name of the element to be replaced.
 87        The name can be snake case, camel case, or pascal cased. The value can either
 88        be the parsed result of the component from phml.utilities.parse_component() or the
 89        parsed ast of the component. Lastely, the component can be a tuple. The first
 90        value is the name of the element to be replaced; with the second value being
 91        either the parsed result of the component or the component's ast.
 92
 93        Note:
 94            Any duplicate components will be replaced.
 95
 96        Args:
 97            components: Any number values indicating
 98            name of the component and the the component. The name is used
 99            to replace a element with the tag==name.
100        """
101
102        for component in components:
103            if isinstance(component, list):
104                if not all(isinstance(path, PathLike) for path in component):
105                    raise TypeError("If a component argument is a list all values must be either a \
106str or pathlib.Path pointing to the file.")
107                for path in component:
108                    self._parser.load(Path(path))
109                    self._compiler.add(
110                        (
111                            cmpt_name_from_path(Path(path), strip),
112                            parse_component(self._parser.ast),
113                        )
114                    )
115            elif isinstance(component, PathLike):
116                self._parser.load(Path(component))
117                self._compiler.add(
118                    (
119                        cmpt_name_from_path(Path(component), strip),
120                        parse_component(self._parser.ast),
121                    )
122                )
123            elif isinstance(component, tuple) and isinstance(component[1], PathLike):
124                self._parser.load(Path(component[1]))
125                self._compiler.add((component[0], parse_component(self._parser.ast)))
126            else:
127                self._compiler.add(component)
128        return self
129
130    def remove(self, *components: str | NODE):
131        """Remove an element from the list of element replacements.
132
133        Takes any number of strings or node objects. If a string is passed
134        it is used as the key that will be removed. If a node object is passed
135        it will attempt to find a matching node and remove it.
136        """
137        self._compiler.remove(*components)
138        return self
139
140    def load(self, file_path: str | Path, from_format: Optional[Format] = None, auto_close: bool = True):
141        """Load a source files data and parse it to phml.
142
143        Args:
144            file_path (str | Path): The file path to the source file.
145        """
146        self._parser.load(file_path, from_format, auto_close)
147        return self
148
149    def parse(self, data: str | dict, from_format: Format = Formats.PHML, auto_close: bool = True):
150        """Parse a str or dict object into phml.
151
152        Args:
153            data (str | dict): Object to parse to phml
154        """
155        self._parser.parse(data, from_format, auto_close)
156        return self
157    
158    def compile(
159        self,
160        file_type: Format = Formats.HTML,
161        scopes: Optional[list[str]] = None,
162        components: Optional[dict] = None,
163        **kwargs,
164    ) -> AST:
165        """Compile the parsed ast into it's fully processed form.
166
167        Args:
168            file_type (str): The format to render to. Currently support html, phml, and json.
169            indent (Optional[int], optional): The number of spaces per indent. By default it will
170            use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
171            has 2 spaces.
172
173        Returns:
174            AST: The processed ast. Ast is in the final format of the passed in file_type
175        """
176
177        scopes = scopes or []
178        for scope in self._scopes:
179            if scope not in scopes:
180                scopes.append(scope)
181
182        return self._compiler.compile(
183            self._parser.ast,
184            to_format=file_type,
185            scopes=scopes,
186            components=components,
187            **{**self._context, **kwargs},
188        )
189
190    def render(
191        self,
192        file_type: Format = Formats.HTML,
193        indent: Optional[int] = None,
194        scopes: Optional[list[str]] = None,
195        components: Optional[dict] = None,
196        **kwargs,
197    ) -> str:
198        """Render the parsed ast to a different format. Defaults to rendering to html.
199
200        Args:
201            file_type (str): The format to render to. Currently support html, phml, and json.
202            indent (Optional[int], optional): The number of spaces per indent. By default it will
203            use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
204            has 2 spaces.
205
206        Returns:
207            str: The rendered content in the appropriate format.
208        """
209
210        scopes = scopes or []
211        for scope in self._scopes:
212            if scope not in scopes:
213                scopes.append(scope)
214
215        return self._compiler.render(
216            self._parser.ast,
217            to_format=file_type,
218            indent=indent,
219            scopes=scopes,
220            components=components,
221            **{**self._context, **kwargs},
222        )
223
224    def write(
225        self,
226        file: str | Path | TextIOWrapper,
227        file_type: Format = Formats.HTML,
228        indent: Optional[int] = None,
229        scopes: Optional[list[str]] = None,
230        replace_suffix: bool = False,
231        components: Optional[dict] = None,
232        **kwargs,
233    ):
234        """Renders the parsed ast to a different format, then writes
235        it to a given file. Defaults to rendering and writing out as html.
236
237        Args:
238            file (str | Path | TextIOWrapper): The path to the file to be written to, or the opened
239            file to write to.
240
241            file_type (str): The format to render the ast as.
242
243            indent (Optional[int], optional): The number of spaces per indent. By default it will
244            use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
245            has 2 spaces.
246
247            scopes (list[str], None): The relative paths from the cwd to the directory that will
248            be inserted into the python path.
249
250            replace_suffix (bool): Override to use the preferred file suffix no matter what.
251            Defaults to False, as the preferred suffix will only be used if no suffix is provided.
252
253            kwargs: Any additional data to pass to the compiler that will be exposed to the
254            phml files.
255        """
256        if isinstance(file, (str | Path)):
257            file = Path(file)
258            
259            file.parent.mkdir(parents=True, exist_ok=True)
260
261            if file.suffix == "" or replace_suffix:
262                file = file.with_suffix(file_type.suffix())
263
264            with open(file, "+w", encoding="utf-8") as dest_file:
265                dest_file.write(
266                    self.render(
267                        file_type=file_type,
268                        indent=indent,
269                        scopes=scopes,
270                        components=components,
271                        **kwargs,
272                    )
273                )
274        elif isinstance(file, TextIOWrapper):
275            file.write(
276                self.render(
277                    file_type=file_type,
278                    indent=indent,
279                    scopes=scopes,
280                    components=components,
281                    **kwargs,
282                )
283            )
284        return self
class PHML:
 19class PHML:
 20    """A helper class that bundles the functionality
 21    of the parser and compiler together. Allows for loading source files,
 22    parsing strings and dicts, rendering to a different format, and finally
 23    writing the results of a render to a file.
 24    """
 25
 26    @property
 27    def ast(self) -> AST:
 28        """The parsed ast value."""
 29        return self._parser.ast
 30
 31    @ast.setter
 32    def ast(self, _ast: AST):
 33        self._parser.ast = _ast
 34        
 35    @property
 36    def components(self) -> dict:
 37        """The components currently stored in the compiler."""
 38        return self._compiler.components
 39
 40    def __init__(
 41        self,
 42        *,
 43        scopes: list[str] | None = None,
 44        components: Components | None = None,
 45        enable: dict[EnableKeys, bool] = EnableDefault,
 46        **contexts: Any,
 47    ):
 48        self._parser = Parser()
 49        self._compiler = Compiler(components=components, enable=enable)
 50        self._scopes = scopes or []
 51        self._context = dict(contexts)
 52
 53    def expose(self, **kwargs: Any):
 54        """Add additional data to the compilers global values. These values are exposed for every
 55        call to render or write.
 56        """
 57        self._context.update(kwargs)
 58
 59    def redact(self, key: str):
 60        """Remove a value from the compilers globally exposed values."""
 61        self._context.pop(key, None)
 62
 63    def expand(self, *args: str):
 64        """Add relative paths to a directory, that you want added to the python path
 65        for every time render or write is called.
 66        """
 67        self._scopes.extend([arg for arg in args if arg not in self._scopes])
 68
 69    def restrict(self, *args: str):
 70        """Remove relative paths to a directory, that are in the compilers globally added scopes.
 71        This prevents them from being added to the python path.
 72        """
 73        for arg in args:
 74            if arg in self._scopes:
 75                self._scopes.remove(arg)
 76
 77    def add(
 78        self,
 79        *components: Component,
 80        strip: str = "",
 81    ):
 82        """Add a component to the compiler's component list.
 83
 84        Components passed in can be of a few types. The first type it can be is a
 85        pathlib.Path type. This will allow for automatic parsing of the file at the
 86        path and then the filename and parsed ast are passed to the compiler. It can
 87        also be a dictionary of str being the name of the element to be replaced.
 88        The name can be snake case, camel case, or pascal cased. The value can either
 89        be the parsed result of the component from phml.utilities.parse_component() or the
 90        parsed ast of the component. Lastely, the component can be a tuple. The first
 91        value is the name of the element to be replaced; with the second value being
 92        either the parsed result of the component or the component's ast.
 93
 94        Note:
 95            Any duplicate components will be replaced.
 96
 97        Args:
 98            components: Any number values indicating
 99            name of the component and the the component. The name is used
100            to replace a element with the tag==name.
101        """
102
103        for component in components:
104            if isinstance(component, list):
105                if not all(isinstance(path, PathLike) for path in component):
106                    raise TypeError("If a component argument is a list all values must be either a \
107str or pathlib.Path pointing to the file.")
108                for path in component:
109                    self._parser.load(Path(path))
110                    self._compiler.add(
111                        (
112                            cmpt_name_from_path(Path(path), strip),
113                            parse_component(self._parser.ast),
114                        )
115                    )
116            elif isinstance(component, PathLike):
117                self._parser.load(Path(component))
118                self._compiler.add(
119                    (
120                        cmpt_name_from_path(Path(component), strip),
121                        parse_component(self._parser.ast),
122                    )
123                )
124            elif isinstance(component, tuple) and isinstance(component[1], PathLike):
125                self._parser.load(Path(component[1]))
126                self._compiler.add((component[0], parse_component(self._parser.ast)))
127            else:
128                self._compiler.add(component)
129        return self
130
131    def remove(self, *components: str | NODE):
132        """Remove an element from the list of element replacements.
133
134        Takes any number of strings or node objects. If a string is passed
135        it is used as the key that will be removed. If a node object is passed
136        it will attempt to find a matching node and remove it.
137        """
138        self._compiler.remove(*components)
139        return self
140
141    def load(self, file_path: str | Path, from_format: Optional[Format] = None, auto_close: bool = True):
142        """Load a source files data and parse it to phml.
143
144        Args:
145            file_path (str | Path): The file path to the source file.
146        """
147        self._parser.load(file_path, from_format, auto_close)
148        return self
149
150    def parse(self, data: str | dict, from_format: Format = Formats.PHML, auto_close: bool = True):
151        """Parse a str or dict object into phml.
152
153        Args:
154            data (str | dict): Object to parse to phml
155        """
156        self._parser.parse(data, from_format, auto_close)
157        return self
158    
159    def compile(
160        self,
161        file_type: Format = Formats.HTML,
162        scopes: Optional[list[str]] = None,
163        components: Optional[dict] = None,
164        **kwargs,
165    ) -> AST:
166        """Compile the parsed ast into it's fully processed form.
167
168        Args:
169            file_type (str): The format to render to. Currently support html, phml, and json.
170            indent (Optional[int], optional): The number of spaces per indent. By default it will
171            use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
172            has 2 spaces.
173
174        Returns:
175            AST: The processed ast. Ast is in the final format of the passed in file_type
176        """
177
178        scopes = scopes or []
179        for scope in self._scopes:
180            if scope not in scopes:
181                scopes.append(scope)
182
183        return self._compiler.compile(
184            self._parser.ast,
185            to_format=file_type,
186            scopes=scopes,
187            components=components,
188            **{**self._context, **kwargs},
189        )
190
191    def render(
192        self,
193        file_type: Format = Formats.HTML,
194        indent: Optional[int] = None,
195        scopes: Optional[list[str]] = None,
196        components: Optional[dict] = None,
197        **kwargs,
198    ) -> str:
199        """Render the parsed ast to a different format. Defaults to rendering to html.
200
201        Args:
202            file_type (str): The format to render to. Currently support html, phml, and json.
203            indent (Optional[int], optional): The number of spaces per indent. By default it will
204            use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
205            has 2 spaces.
206
207        Returns:
208            str: The rendered content in the appropriate format.
209        """
210
211        scopes = scopes or []
212        for scope in self._scopes:
213            if scope not in scopes:
214                scopes.append(scope)
215
216        return self._compiler.render(
217            self._parser.ast,
218            to_format=file_type,
219            indent=indent,
220            scopes=scopes,
221            components=components,
222            **{**self._context, **kwargs},
223        )
224
225    def write(
226        self,
227        file: str | Path | TextIOWrapper,
228        file_type: Format = Formats.HTML,
229        indent: Optional[int] = None,
230        scopes: Optional[list[str]] = None,
231        replace_suffix: bool = False,
232        components: Optional[dict] = None,
233        **kwargs,
234    ):
235        """Renders the parsed ast to a different format, then writes
236        it to a given file. Defaults to rendering and writing out as html.
237
238        Args:
239            file (str | Path | TextIOWrapper): The path to the file to be written to, or the opened
240            file to write to.
241
242            file_type (str): The format to render the ast as.
243
244            indent (Optional[int], optional): The number of spaces per indent. By default it will
245            use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
246            has 2 spaces.
247
248            scopes (list[str], None): The relative paths from the cwd to the directory that will
249            be inserted into the python path.
250
251            replace_suffix (bool): Override to use the preferred file suffix no matter what.
252            Defaults to False, as the preferred suffix will only be used if no suffix is provided.
253
254            kwargs: Any additional data to pass to the compiler that will be exposed to the
255            phml files.
256        """
257        if isinstance(file, (str | Path)):
258            file = Path(file)
259            
260            file.parent.mkdir(parents=True, exist_ok=True)
261
262            if file.suffix == "" or replace_suffix:
263                file = file.with_suffix(file_type.suffix())
264
265            with open(file, "+w", encoding="utf-8") as dest_file:
266                dest_file.write(
267                    self.render(
268                        file_type=file_type,
269                        indent=indent,
270                        scopes=scopes,
271                        components=components,
272                        **kwargs,
273                    )
274                )
275        elif isinstance(file, TextIOWrapper):
276            file.write(
277                self.render(
278                    file_type=file_type,
279                    indent=indent,
280                    scopes=scopes,
281                    components=components,
282                    **kwargs,
283                )
284            )
285        return self

A helper class that bundles the functionality of the parser and compiler together. Allows for loading source files, parsing strings and dicts, rendering to a different format, and finally writing the results of a render to a file.

PHML( *, scopes: list[str] | None = None, components: dict[str, dict[str, list | phml.core.nodes.nodes.Root | phml.core.nodes.nodes.Element | phml.core.nodes.nodes.Text | phml.core.nodes.nodes.Comment | phml.core.nodes.nodes.DocType | phml.core.nodes.nodes.Parent | phml.core.nodes.nodes.Node | phml.core.nodes.nodes.Literal] | phml.core.nodes.AST.AST | str | pathlib.Path] | tuple[str, dict[str, list | phml.core.nodes.nodes.Root | phml.core.nodes.nodes.Element | phml.core.nodes.nodes.Text | phml.core.nodes.nodes.Comment | phml.core.nodes.nodes.DocType | phml.core.nodes.nodes.Parent | phml.core.nodes.nodes.Node | phml.core.nodes.nodes.Literal] | phml.core.nodes.AST.AST | str | pathlib.Path] | list[str | pathlib.Path] | str | pathlib.Path | None = None, enable: dict[typing.Literal['html', 'markdown'], bool] = {'html': False, 'markdown': False, 'for': True}, **contexts: Any)
40    def __init__(
41        self,
42        *,
43        scopes: list[str] | None = None,
44        components: Components | None = None,
45        enable: dict[EnableKeys, bool] = EnableDefault,
46        **contexts: Any,
47    ):
48        self._parser = Parser()
49        self._compiler = Compiler(components=components, enable=enable)
50        self._scopes = scopes or []
51        self._context = dict(contexts)

The parsed ast value.

components: dict

The components currently stored in the compiler.

def expose(self, **kwargs: Any):
53    def expose(self, **kwargs: Any):
54        """Add additional data to the compilers global values. These values are exposed for every
55        call to render or write.
56        """
57        self._context.update(kwargs)

Add additional data to the compilers global values. These values are exposed for every call to render or write.

def redact(self, key: str):
59    def redact(self, key: str):
60        """Remove a value from the compilers globally exposed values."""
61        self._context.pop(key, None)

Remove a value from the compilers globally exposed values.

def expand(self, *args: str):
63    def expand(self, *args: str):
64        """Add relative paths to a directory, that you want added to the python path
65        for every time render or write is called.
66        """
67        self._scopes.extend([arg for arg in args if arg not in self._scopes])

Add relative paths to a directory, that you want added to the python path for every time render or write is called.

def restrict(self, *args: str):
69    def restrict(self, *args: str):
70        """Remove relative paths to a directory, that are in the compilers globally added scopes.
71        This prevents them from being added to the python path.
72        """
73        for arg in args:
74            if arg in self._scopes:
75                self._scopes.remove(arg)

Remove relative paths to a directory, that are in the compilers globally added scopes. This prevents them from being added to the python path.

 77    def add(
 78        self,
 79        *components: Component,
 80        strip: str = "",
 81    ):
 82        """Add a component to the compiler's component list.
 83
 84        Components passed in can be of a few types. The first type it can be is a
 85        pathlib.Path type. This will allow for automatic parsing of the file at the
 86        path and then the filename and parsed ast are passed to the compiler. It can
 87        also be a dictionary of str being the name of the element to be replaced.
 88        The name can be snake case, camel case, or pascal cased. The value can either
 89        be the parsed result of the component from phml.utilities.parse_component() or the
 90        parsed ast of the component. Lastely, the component can be a tuple. The first
 91        value is the name of the element to be replaced; with the second value being
 92        either the parsed result of the component or the component's ast.
 93
 94        Note:
 95            Any duplicate components will be replaced.
 96
 97        Args:
 98            components: Any number values indicating
 99            name of the component and the the component. The name is used
100            to replace a element with the tag==name.
101        """
102
103        for component in components:
104            if isinstance(component, list):
105                if not all(isinstance(path, PathLike) for path in component):
106                    raise TypeError("If a component argument is a list all values must be either a \
107str or pathlib.Path pointing to the file.")
108                for path in component:
109                    self._parser.load(Path(path))
110                    self._compiler.add(
111                        (
112                            cmpt_name_from_path(Path(path), strip),
113                            parse_component(self._parser.ast),
114                        )
115                    )
116            elif isinstance(component, PathLike):
117                self._parser.load(Path(component))
118                self._compiler.add(
119                    (
120                        cmpt_name_from_path(Path(component), strip),
121                        parse_component(self._parser.ast),
122                    )
123                )
124            elif isinstance(component, tuple) and isinstance(component[1], PathLike):
125                self._parser.load(Path(component[1]))
126                self._compiler.add((component[0], parse_component(self._parser.ast)))
127            else:
128                self._compiler.add(component)
129        return self

Add a component to the compiler's component list.

Components passed in can be of a few types. The first type it can be is a pathlib.Path type. This will allow for automatic parsing of the file at the path and then the filename and parsed ast are passed to the compiler. It can also be a dictionary of str being the name of the element to be replaced. The name can be snake case, camel case, or pascal cased. The value can either be the parsed result of the component from phml.utilities.parse_component() or the parsed ast of the component. Lastely, the component can be a tuple. The first value is the name of the element to be replaced; with the second value being either the parsed result of the component or the component's ast.

Note

Any duplicate components will be replaced.

Args
  • components: Any number values indicating
  • name of the component and the the component. The name is used
  • to replace a element with the tag==name.
131    def remove(self, *components: str | NODE):
132        """Remove an element from the list of element replacements.
133
134        Takes any number of strings or node objects. If a string is passed
135        it is used as the key that will be removed. If a node object is passed
136        it will attempt to find a matching node and remove it.
137        """
138        self._compiler.remove(*components)
139        return self

Remove an element from the list of element replacements.

Takes any number of strings or node objects. If a string is passed it is used as the key that will be removed. If a node object is passed it will attempt to find a matching node and remove it.

def load( self, file_path: str | pathlib.Path, from_format: Optional[phml.core.formats.format.Format] = None, auto_close: bool = True):
141    def load(self, file_path: str | Path, from_format: Optional[Format] = None, auto_close: bool = True):
142        """Load a source files data and parse it to phml.
143
144        Args:
145            file_path (str | Path): The file path to the source file.
146        """
147        self._parser.load(file_path, from_format, auto_close)
148        return self

Load a source files data and parse it to phml.

Args
  • file_path (str | Path): The file path to the source file.
def parse( self, data: str | dict, from_format: phml.core.formats.format.Format = <class 'phml.core.formats.phml_format.PHMLFormat'>, auto_close: bool = True):
150    def parse(self, data: str | dict, from_format: Format = Formats.PHML, auto_close: bool = True):
151        """Parse a str or dict object into phml.
152
153        Args:
154            data (str | dict): Object to parse to phml
155        """
156        self._parser.parse(data, from_format, auto_close)
157        return self

Parse a str or dict object into phml.

Args
  • data (str | dict): Object to parse to phml
def compile( self, file_type: phml.core.formats.format.Format = <class 'phml.core.formats.html_format.HTMLFormat'>, scopes: Optional[list[str]] = None, components: Optional[dict] = None, **kwargs) -> phml.core.nodes.AST.AST:
159    def compile(
160        self,
161        file_type: Format = Formats.HTML,
162        scopes: Optional[list[str]] = None,
163        components: Optional[dict] = None,
164        **kwargs,
165    ) -> AST:
166        """Compile the parsed ast into it's fully processed form.
167
168        Args:
169            file_type (str): The format to render to. Currently support html, phml, and json.
170            indent (Optional[int], optional): The number of spaces per indent. By default it will
171            use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
172            has 2 spaces.
173
174        Returns:
175            AST: The processed ast. Ast is in the final format of the passed in file_type
176        """
177
178        scopes = scopes or []
179        for scope in self._scopes:
180            if scope not in scopes:
181                scopes.append(scope)
182
183        return self._compiler.compile(
184            self._parser.ast,
185            to_format=file_type,
186            scopes=scopes,
187            components=components,
188            **{**self._context, **kwargs},
189        )

Compile the parsed ast into it's fully processed form.

Args
  • file_type (str): The format to render to. Currently support html, phml, and json.
  • indent (Optional[int], optional): The number of spaces per indent. By default it will
  • use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
  • has 2 spaces.
Returns

AST: The processed ast. Ast is in the final format of the passed in file_type

def render( self, file_type: phml.core.formats.format.Format = <class 'phml.core.formats.html_format.HTMLFormat'>, indent: Optional[int] = None, scopes: Optional[list[str]] = None, components: Optional[dict] = None, **kwargs) -> str:
191    def render(
192        self,
193        file_type: Format = Formats.HTML,
194        indent: Optional[int] = None,
195        scopes: Optional[list[str]] = None,
196        components: Optional[dict] = None,
197        **kwargs,
198    ) -> str:
199        """Render the parsed ast to a different format. Defaults to rendering to html.
200
201        Args:
202            file_type (str): The format to render to. Currently support html, phml, and json.
203            indent (Optional[int], optional): The number of spaces per indent. By default it will
204            use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
205            has 2 spaces.
206
207        Returns:
208            str: The rendered content in the appropriate format.
209        """
210
211        scopes = scopes or []
212        for scope in self._scopes:
213            if scope not in scopes:
214                scopes.append(scope)
215
216        return self._compiler.render(
217            self._parser.ast,
218            to_format=file_type,
219            indent=indent,
220            scopes=scopes,
221            components=components,
222            **{**self._context, **kwargs},
223        )

Render the parsed ast to a different format. Defaults to rendering to html.

Args
  • file_type (str): The format to render to. Currently support html, phml, and json.
  • indent (Optional[int], optional): The number of spaces per indent. By default it will
  • use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
  • has 2 spaces.
Returns

str: The rendered content in the appropriate format.

def write( self, file: str | pathlib.Path | _io.TextIOWrapper, file_type: phml.core.formats.format.Format = <class 'phml.core.formats.html_format.HTMLFormat'>, indent: Optional[int] = None, scopes: Optional[list[str]] = None, replace_suffix: bool = False, components: Optional[dict] = None, **kwargs):
225    def write(
226        self,
227        file: str | Path | TextIOWrapper,
228        file_type: Format = Formats.HTML,
229        indent: Optional[int] = None,
230        scopes: Optional[list[str]] = None,
231        replace_suffix: bool = False,
232        components: Optional[dict] = None,
233        **kwargs,
234    ):
235        """Renders the parsed ast to a different format, then writes
236        it to a given file. Defaults to rendering and writing out as html.
237
238        Args:
239            file (str | Path | TextIOWrapper): The path to the file to be written to, or the opened
240            file to write to.
241
242            file_type (str): The format to render the ast as.
243
244            indent (Optional[int], optional): The number of spaces per indent. By default it will
245            use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
246            has 2 spaces.
247
248            scopes (list[str], None): The relative paths from the cwd to the directory that will
249            be inserted into the python path.
250
251            replace_suffix (bool): Override to use the preferred file suffix no matter what.
252            Defaults to False, as the preferred suffix will only be used if no suffix is provided.
253
254            kwargs: Any additional data to pass to the compiler that will be exposed to the
255            phml files.
256        """
257        if isinstance(file, (str | Path)):
258            file = Path(file)
259            
260            file.parent.mkdir(parents=True, exist_ok=True)
261
262            if file.suffix == "" or replace_suffix:
263                file = file.with_suffix(file_type.suffix())
264
265            with open(file, "+w", encoding="utf-8") as dest_file:
266                dest_file.write(
267                    self.render(
268                        file_type=file_type,
269                        indent=indent,
270                        scopes=scopes,
271                        components=components,
272                        **kwargs,
273                    )
274                )
275        elif isinstance(file, TextIOWrapper):
276            file.write(
277                self.render(
278                    file_type=file_type,
279                    indent=indent,
280                    scopes=scopes,
281                    components=components,
282                    **kwargs,
283                )
284            )
285        return self

Renders the parsed ast to a different format, then writes it to a given file. Defaults to rendering and writing out as html.

Args
  • file (str | Path | TextIOWrapper): The path to the file to be written to, or the opened
  • file to write to.
  • file_type (str): The format to render the ast as.
  • indent (Optional[int], optional): The number of spaces per indent. By default it will
  • use the standard for the given format. HTML has 4 spaces, phml has 4 spaces, and json
  • has 2 spaces.
  • scopes (list[str], None): The relative paths from the cwd to the directory that will
  • be inserted into the python path.
  • replace_suffix (bool): Override to use the preferred file suffix no matter what.
  • Defaults to False, as the preferred suffix will only be used if no suffix is provided.
  • kwargs: Any additional data to pass to the compiler that will be exposed to the
  • phml files.
class Compiler:
 25class Compiler:
 26    """Used to compile phml into other formats. HTML, PHML,
 27    JSON, Markdown, etc...
 28    """
 29
 30    ast: AST
 31    """phml ast used by the compiler to generate a new format."""
 32
 33    def __init__(
 34        self,
 35        _ast: Optional[AST] = None,
 36        components: Optional[dict[str, dict[str, list | NODE]]] = None,
 37        enable: ConfigEnable | None = None,
 38    ):
 39        self.ast = _ast
 40        self.components = components or {}
 41        self.config: Config = {
 42            "enabled": enable or EnableDefault
 43        }
 44
 45    def add(
 46        self,
 47        *components: dict[str, dict[str, list | NODE] | AST]
 48        | tuple[str, dict[str, list | NODE] | AST],
 49    ):
 50        """Add a component to the compilers component list.
 51
 52        Components passed in can be of a few types. It can also be a
 53        dictionary of str being the name of the element to be replaced. The
 54        name can be snake case, camel case, or pascal cased. The value can
 55        either be the parsed result of the component from
 56        phml.utilities.parse_component() or the parsed ast of the component.
 57        Lastely, the component can be a tuple. The first value is the name of
 58        the element to be replaced; with the second value being either the
 59        parsed result of the component or the component's ast.
 60
 61        Note:
 62            Any duplicate components will be replaced.
 63
 64        Args:
 65            components: Any number values indicating
 66            name of the component and the the component. The name is used
 67            to replace a element with the tag==name.
 68        """
 69
 70        for component in components:
 71            if isinstance(component, dict):
 72                for key, value in component.items():
 73                    if isinstance(value, AST):
 74                        self.components[key] = {"data": parse_component(value), "cache": None}
 75                    elif isinstance(value, dict) and valid_component_dict(value):
 76                        self.components[key] = {"data": value, "cache": None}
 77            elif isinstance(component, tuple):
 78                if isinstance(component[0], str) and isinstance(component[1], AST):
 79                    self.components[component[0]] = {
 80                        "data": parse_component(component[1]),
 81                        "cache": None,
 82                    }
 83                elif isinstance(component[0], str) and valid_component_dict(component[1]):
 84                    self.components[component[0]] = {"data": component[1], "cache": None}
 85
 86        return self
 87
 88    def __construct_scope_path(self, scope: str) -> list[str]:
 89        """Get the individual sub directories to to the scopes path."""
 90        sub_dirs = sub(r"(\.+\/?)+", "", scope)
 91        sub_dirs = [sub_dir for sub_dir in os.path.split(sub_dirs) if sub_dir.strip() != ""]
 92        path = []
 93        for sub_dir in sub_dirs:
 94            if sub_dir in ["*", "**"]:
 95                raise Exception(f"Can not use wildcards in scopes: {scope}")
 96            path.append(sub_dir)
 97        return path
 98
 99    def remove(self, *components: str | NODE):
100        """Takes either component names or components and removes them
101        from the dictionary.
102
103        Args:
104            components (str | NODE): Any str name of components or
105            node value to remove from the components list in the compiler.
106        """
107        for component in components:
108            if isinstance(component, str):
109                if component in self.components:
110                    self.components.pop(component, None)
111                else:
112                    raise KeyError(f"Invalid component name '{component}'")
113            elif isinstance(component, NODE):
114                for key, value in self.components.items():
115                    if isinstance(value["data"], dict) and value["data"]["component"] == component:
116                        self.components.pop(key, None)
117                        break
118
119        return self
120
121    def compile(
122        self,
123        _ast: Optional[AST] = None,
124        to_format: Format = Formats.HTML,
125        scopes: Optional[list[str]] = None,
126        components: Optional[dict] = None,
127        safe_vars: bool = False,
128        **kwargs: Any,
129    ) -> AST:
130        """Execute compilation to a different format."""
131
132        _ast = _ast or self.ast
133
134        if _ast is None:
135            raise Exception("Must provide an ast to compile")
136
137        # Insert the scopes into the path
138        scopes = scopes or ["./"]
139        if scopes is not None:
140
141            for scope in scopes:
142                sys.path.append(
143                    os.path.join(
144                        sys.path[0],
145                        *self.__construct_scope_path(scope),
146                    )
147                )
148
149        # Depending on the format parse with the appropriate function
150        components = components or dict()
151        cmpts = {**self.components, **components}
152        return to_format.compile(
153            _ast,
154            self.config,
155            cmpts,
156            safe_vars=safe_vars,
157            **kwargs
158        )
159
160    def render(
161        self,
162        _ast: Optional[AST] = None,
163        to_format: Format = Formats.HTML,
164        indent: Optional[int] = None,
165        scopes: Optional[list[str]] = None,
166        components: Optional[dict] = None,
167        safe_vars: bool = False,
168        **kwargs: Any,
169    ) -> str:
170        """Execute compilation to a different format."""
171
172        _ast = _ast or self.ast
173
174        if _ast is None:
175            raise Exception("Must provide an ast to compile")
176
177        # Insert the scopes into the path
178        scopes = scopes or ["./"]
179        if scopes is not None:
180
181            for scope in scopes:
182                sys.path.append(
183                    os.path.join(
184                        sys.path[0],
185                        *self.__construct_scope_path(scope),
186                    )
187                )
188
189        # Depending on the format parse with the appropriate function
190        components = components or dict()
191        cmpts = {**self.components, **components}
192        return to_format.render(
193            _ast,
194            self.config,
195            cmpts,
196            indent,
197            safe_vars=safe_vars,
198            **kwargs
199        )

Used to compile phml into other formats. HTML, PHML, JSON, Markdown, etc...

Compiler( _ast: Optional[phml.core.nodes.AST.AST] = None, components: Optional[dict[str, dict[str, list | phml.core.nodes.nodes.Root | phml.core.nodes.nodes.Element | phml.core.nodes.nodes.Text | phml.core.nodes.nodes.Comment | phml.core.nodes.nodes.DocType | phml.core.nodes.nodes.Parent | phml.core.nodes.nodes.Node | phml.core.nodes.nodes.Literal]]] = None, enable: dict[typing.Literal['html', 'markdown'], bool] | None = None)
33    def __init__(
34        self,
35        _ast: Optional[AST] = None,
36        components: Optional[dict[str, dict[str, list | NODE]]] = None,
37        enable: ConfigEnable | None = None,
38    ):
39        self.ast = _ast
40        self.components = components or {}
41        self.config: Config = {
42            "enabled": enable or EnableDefault
43        }

phml ast used by the compiler to generate a new format.

45    def add(
46        self,
47        *components: dict[str, dict[str, list | NODE] | AST]
48        | tuple[str, dict[str, list | NODE] | AST],
49    ):
50        """Add a component to the compilers component list.
51
52        Components passed in can be of a few types. It can also be a
53        dictionary of str being the name of the element to be replaced. The
54        name can be snake case, camel case, or pascal cased. The value can
55        either be the parsed result of the component from
56        phml.utilities.parse_component() or the parsed ast of the component.
57        Lastely, the component can be a tuple. The first value is the name of
58        the element to be replaced; with the second value being either the
59        parsed result of the component or the component's ast.
60
61        Note:
62            Any duplicate components will be replaced.
63
64        Args:
65            components: Any number values indicating
66            name of the component and the the component. The name is used
67            to replace a element with the tag==name.
68        """
69
70        for component in components:
71            if isinstance(component, dict):
72                for key, value in component.items():
73                    if isinstance(value, AST):
74                        self.components[key] = {"data": parse_component(value), "cache": None}
75                    elif isinstance(value, dict) and valid_component_dict(value):
76                        self.components[key] = {"data": value, "cache": None}
77            elif isinstance(component, tuple):
78                if isinstance(component[0], str) and isinstance(component[1], AST):
79                    self.components[component[0]] = {
80                        "data": parse_component(component[1]),
81                        "cache": None,
82                    }
83                elif isinstance(component[0], str) and valid_component_dict(component[1]):
84                    self.components[component[0]] = {"data": component[1], "cache": None}
85
86        return self

Add a component to the compilers component list.

Components passed in can be of a few types. It can also be a dictionary of str being the name of the element to be replaced. The name can be snake case, camel case, or pascal cased. The value can either be the parsed result of the component from phml.utilities.parse_component() or the parsed ast of the component. Lastely, the component can be a tuple. The first value is the name of the element to be replaced; with the second value being either the parsed result of the component or the component's ast.

Note

Any duplicate components will be replaced.

Args
  • components: Any number values indicating
  • name of the component and the the component. The name is used
  • to replace a element with the tag==name.
 99    def remove(self, *components: str | NODE):
100        """Takes either component names or components and removes them
101        from the dictionary.
102
103        Args:
104            components (str | NODE): Any str name of components or
105            node value to remove from the components list in the compiler.
106        """
107        for component in components:
108            if isinstance(component, str):
109                if component in self.components:
110                    self.components.pop(component, None)
111                else:
112                    raise KeyError(f"Invalid component name '{component}'")
113            elif isinstance(component, NODE):
114                for key, value in self.components.items():
115                    if isinstance(value["data"], dict) and value["data"]["component"] == component:
116                        self.components.pop(key, None)
117                        break
118
119        return self

Takes either component names or components and removes them from the dictionary.

Args
  • components (str | NODE): Any str name of components or
  • node value to remove from the components list in the compiler.
def compile( self, _ast: Optional[phml.core.nodes.AST.AST] = None, to_format: phml.core.formats.format.Format = <class 'phml.core.formats.html_format.HTMLFormat'>, scopes: Optional[list[str]] = None, components: Optional[dict] = None, safe_vars: bool = False, **kwargs: Any) -> phml.core.nodes.AST.AST:
121    def compile(
122        self,
123        _ast: Optional[AST] = None,
124        to_format: Format = Formats.HTML,
125        scopes: Optional[list[str]] = None,
126        components: Optional[dict] = None,
127        safe_vars: bool = False,
128        **kwargs: Any,
129    ) -> AST:
130        """Execute compilation to a different format."""
131
132        _ast = _ast or self.ast
133
134        if _ast is None:
135            raise Exception("Must provide an ast to compile")
136
137        # Insert the scopes into the path
138        scopes = scopes or ["./"]
139        if scopes is not None:
140
141            for scope in scopes:
142                sys.path.append(
143                    os.path.join(
144                        sys.path[0],
145                        *self.__construct_scope_path(scope),
146                    )
147                )
148
149        # Depending on the format parse with the appropriate function
150        components = components or dict()
151        cmpts = {**self.components, **components}
152        return to_format.compile(
153            _ast,
154            self.config,
155            cmpts,
156            safe_vars=safe_vars,
157            **kwargs
158        )

Execute compilation to a different format.

def render( self, _ast: Optional[phml.core.nodes.AST.AST] = None, to_format: phml.core.formats.format.Format = <class 'phml.core.formats.html_format.HTMLFormat'>, indent: Optional[int] = None, scopes: Optional[list[str]] = None, components: Optional[dict] = None, safe_vars: bool = False, **kwargs: Any) -> str:
160    def render(
161        self,
162        _ast: Optional[AST] = None,
163        to_format: Format = Formats.HTML,
164        indent: Optional[int] = None,
165        scopes: Optional[list[str]] = None,
166        components: Optional[dict] = None,
167        safe_vars: bool = False,
168        **kwargs: Any,
169    ) -> str:
170        """Execute compilation to a different format."""
171
172        _ast = _ast or self.ast
173
174        if _ast is None:
175            raise Exception("Must provide an ast to compile")
176
177        # Insert the scopes into the path
178        scopes = scopes or ["./"]
179        if scopes is not None:
180
181            for scope in scopes:
182                sys.path.append(
183                    os.path.join(
184                        sys.path[0],
185                        *self.__construct_scope_path(scope),
186                    )
187                )
188
189        # Depending on the format parse with the appropriate function
190        components = components or dict()
191        cmpts = {**self.components, **components}
192        return to_format.render(
193            _ast,
194            self.config,
195            cmpts,
196            indent,
197            safe_vars=safe_vars,
198            **kwargs
199        )

Execute compilation to a different format.