{"id":16807602,"url":"https://github.com/tired-fox/phml","last_synced_at":"2025-04-11T01:12:44.730Z","repository":{"id":64029581,"uuid":"563520087","full_name":"Tired-Fox/phml","owner":"Tired-Fox","description":"A python based html parser that allows for adding additional functionality and custom nodes","archived":false,"fork":false,"pushed_at":"2023-07-05T19:17:09.000Z","size":15834,"stargazers_count":4,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-11T01:12:38.732Z","etag":null,"topics":["html","hypertext","language","markup","python","templating"],"latest_commit_sha":null,"homepage":"https://tired-fox.github.io/phml/phml.html","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Tired-Fox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-08T19:33:28.000Z","updated_at":"2024-12-30T14:51:24.000Z","dependencies_parsed_at":"2025-02-18T10:46:36.702Z","dependency_job_id":null,"html_url":"https://github.com/Tired-Fox/phml","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tired-Fox%2Fphml","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tired-Fox%2Fphml/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tired-Fox%2Fphml/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tired-Fox%2Fphml/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Tired-Fox","download_url":"https://codeload.github.com/Tired-Fox/phml/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248322571,"owners_count":21084337,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["html","hypertext","language","markup","python","templating"],"created_at":"2024-10-13T09:54:13.880Z","updated_at":"2025-04-11T01:12:44.714Z","avatar_url":"https://github.com/Tired-Fox.png","language":"Python","readme":"# Python Hypertext Markup Language (phml)\n\n\u003c!-- Header Badges --\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \n![version](assets/badges/version.svg)\n[![License](assets/badges/license.svg)](https://github.com/Tired-Fox/phml/blob/main/LICENSE)\n[![Release](https://img.shields.io/github/v/release/tired-fox/phml.svg?style=flat-square\u0026color=9cf)](https://github.com/Tired-Fox/phml/releases)\n![Maintained](assets/badges/maintained.svg)\n\n![testing](assets/badges/tests.svg)\n![test coverage](assets/badges/coverage.svg)\n\n\u003c/div\u003e\n\n\u003c!-- End Badges --\u003e\n\n## Overview\n\nThe idea behind the creation of Python in Hypertext Markup Language (phml), is to allow for web page generation with direct access to python. This language takes inspiration directly from frameworks like Vue.js, Astro.js, Solid.js, and SvelteKit. There is conditional rendering, components, python elements, inline/embedded python blocks, and slot, named slots, and much more. Now let's dive into more the language.\n\n### Python Element\n\nLet's start with the new `python` element. Python is a whitespace language. As such, phml\nhas the challenge of maintaining the indentation in an appropriate way as to preserve the intended whitespace. The key focus is the indended whitespace. While this can be tricky the first line with content serves as a reference. The amount of indentation for the first line is removed from each line and the remaining whitespace is left alone. For example if there is a python block that looks like this.\n\n```html\n\u003cpython\u003e message = \"hello world\" if \"hello\" in message: print(message) \u003c/python\u003e\n```\n\nThe resulting python code would look like this.\n\n```python\nmessage = \"hello world\"\nif \"hello\" in message:\n  print(message)\n```\n\nSo now we can write python code, now what? You can define functions and variables\nhow you normally would and they are now available to the scope of the entire file. Consider the following example; You can define function called `URL` in the `python` element and it can be accessed in any other part of the file. So the code would look like this:\n\n```html\n\u003cpython\u003e\n  def URL(link: str) -\u003e str: links = { \"youtube\": \"https://youtube.com\" } if\n  link in links: return links[link] else: return \"\"\n\u003c/python\u003e\n\n...\n\n\u003ca href=\"{URL('youtube')}\"\u003eYoutube\u003c/a\u003e\n```\n\nphml combines all `python` elements and treats them as one python file. This is of the likes of the `script` or `style` tags. With the fact that you can write any code in the python element and used it anywhere else in the file you of the full power of the python programming language at your desposal.\n\n### Inline Python and Python Attributes\n\nNext up is inline python blocks. These are represented with `{{}}` in text elements. Any text in-between the brackets will be processed as python. This is mostly useful when you want to inject a value from python. Assume that there is a variable defined in the `python` element called `message`\nand it contains `Hello World!`. Now this variable can be used like this, `\u003cp\u003e{{ message }}\u003c/p\u003e`,\nwhich renders to, `\u003cp\u003eHello World!\u003c/p\u003e`.\n\n\u003e Note: Inline python blocks are only rendered in a Text element or inside an html attribute.\n\nConditional rendering with `@if`, `@elif`, and `@else` is an extremely helpful tool in phml.\n`@if` can be used alone and the python inside it's value must be truthy for the element to be rendered. `@elif` requires an element with a `@if` or `@elif` attribute immediately before it, and it's condition is rendered the same as `@if` but only rendered if a `@if` or `@elif` first fails. `@else` requires there to be either a `@if` or a `@else` immediately before it. It only renders if the previous element's condition fails. If `@elif` or `@else` is on an element, but the previous element isn't a `@if` or `@elif` then an exception will occur. Most importantly, the first element in a chain of conditions must be a `@if`.\n\nOther than conditions, there is also a built in for loop element. The format looks something like `\u003cFor :each=\"item in collection\u003e\"` and it duplicates it's children at the node position of the `For` element. The `For` element requires there to be an `each` attribute for it to be rendered. You can consider the value of this element as pythons equivelent to `for item in collection:` as this is what the `each` attribute expands out to. The attributes defined in the `each` element, `item` from the previous example, is exposed to the children of the for loop. The attributes from the iteration are scoped recursively through the children. All conditionals work for the the `For` element. An added feature is when a `For` iteration has an error or iterates zero times, the `@elif` or `@else` following the `For` is used instead. This means that a `For` failing or generating zero is like a failed `@if` and can be treated as such. Below is an example of how a `For` element could be used.\n\n```html\n\u003cul\n  \u003cFor :each=\"i in range(3)\"\u003e\n    \u003cli\u003e{i}\u003c/li\u003e\n  \u003c/For\u003e\n  \u003cli @else\u003eNo items in range\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nThe compiled html will be:\n\n```html\n\u003cul\u003e\n  \u003cli\u003e1\u003c/li\u003e\n  \u003cli\u003e2\u003c/li\u003e\n  \u003cli\u003e3\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nPython attributes are shortcuts for using inline python blocks in html attributes. Normally, in phml, you would inject python logic into an attribute similar to this `src=\"{url('youtube')}\"`. If you would like to make the whole attribute value a python expression you may prefix any attribute with a `:`. This keeps the attribute name the same after the prefix, but tells the parser that the entire value should be processed as python. So the previous example with `URL` can also be expressed as `\u003ca :href=\"URL('youtube')\u003eYoutube\u003c/a\u003e\"`.\n\n### Components\n\nPHML includes a powerful component system. The components are partial phml files and are added to the core compiler. After adding the component whenever an element with the same name as the component is found, it is replaced. Components have scoped `python` elements, while all `style` and `script` elements are global to the file they are injected into. Components require that there is only one element, that isn't a `python`, `script`, or `style` tag, to be present. A sample component can look something like the example below.\n\n```html\n\u003c!-- Component.phml --\u003e\n\u003cdiv\u003e# content goes here\u003c/div\u003e\n\n\u003cpython\u003e # python code goes here \u003c/python\u003e\n\u003cstyle\u003e\n  /* styles go here */\n\u003c/style\u003e\n\u003cscript\u003e\n  // js goes here\n\u003c/script\u003e\n```\n\nComponents can be added to the compiler by using `HypertextManager.add('path/to/component.phml')`. You can define a components name when adding it to the compiler like this `HypertextManager.add(('Component', 'path/to/component.phml'))`, or you can just let the compiler figure it out for you. Each directory in the path given along with the file name are combine to create the components name. So if you pass a component path that is `path/to/component.phml` it will create a components name of `Path.To.Component` which is then used as `\u003cPath.To.Component /\u003e`. The compiler will try to parse and understand the component name and make it Pascal case. So if you have a file name of `CoMP_onEnt.phml` it will result in `CoMPOnEnt`. It uses `_` as a seperator between words along with capital letters. It will also recognize an all caps word bordering a new word with a capital letter.\n\nGreat now you have components. But what if you have a few components that are siblings and you don't want them to be nested in a parent element. PHML provides a `\u003c\u003e` element which is a placeholder element. All children are treated as they are at the root of the component.\n\n```html\n\u003c!-- file.phml --\u003e\n...\n\u003cbody\u003e\n  \u003cComponent /\u003e\n\u003c/body\u003e\n...\n\u003c!-- Component.phml --\u003e\n\u003c\u003e\n\u003cp\u003eHello\u003c/p\u003e\n\u003cp\u003eWorld\u003c/p\u003e\n\u003c\u003e\n```\n\nwill result in the following rendered html\n\n```html\n\u003c!-- file.html --\u003e\n...\n\u003cbody\u003e\n  \u003cp\u003eHello\u003c/p\u003e\n  \u003cp\u003eWorld\u003c/p\u003e\n\u003c/body\u003e\n...\n```\n\nNow how do you pass information to component to use in rendering? That is where the `Props` variable comes in. The `Props` variable is a dictionary defined in the components `python` element. This defines what attributes on the component are props along with their default values.\n\n```html\n\u003c!-- component.phml --\u003e\n\u003cpython\u003e Props = { message: \"\" } \u003c/python\u003e\n\n\u003cp\u003e{{ message }}\u003c/p\u003e\n\n\u003c!-- file.phml --\u003e\n...\n\u003cComponent message=\"Hello World!\" /\u003e\n...\n```\n\nBoth normal attribute values and python attributes can be used for props. The above example really only works for self closing components. What if you want to pass children to the component? That is where slots come in.\n\n```html\n\u003cpython\u003e Props = { message: \"\" } \u003c/python\u003e\n\n\u003cdiv class=\"callout\"\u003e\n  \u003cp @if=\"message is not None\"\u003e{{ message }}\u003c/p\u003e\n  \u003cslot /\u003e\n\u003c/div\u003e\n```\n\nThe `Slot` element must be capitalized. When a `Slot` element is present any children inside of a component are inserted in place of it. If no children exist then the slot is just removed. What about having multiple slots and having certain components go to certain slot. PHML covers this with the `slot` and `name` attribute. The slot attribute holds the name of the slot that the child element should be placed in. The name attribute goes on the `Slot` element itself giving it it's name. There may only be one `Slot` of every name including the default `Slot` with no name attribute. An example of this will look something like this.\n\n```html\n\u003c!-- component.phml --\u003e\n\u003cdiv\u003e\n  \u003cslot name=\"top\" /\u003e\n  \u003cslot /\u003e\n  \u003cslot name=\"bottom\" /\u003e\n\u003c/div\u003e\n\n\u003c!-- file.phml --\u003e\n...\n\u003cComponent\u003e\n  \u003cp slot=\"bottom\"\u003eBottom\u003c/p\u003e\n  \u003cp slot=\"top\"\u003eTop\u003c/p\u003e\n  Middle\n\u003c/Component\u003e\n...\n\n\u003c!-- file.html --\u003e\n...\n\u003cp slot=\"top\"\u003eTop\u003c/p\u003e\nMiddle\n\u003cp slot=\"bottom\"\u003eBottom\u003c/p\u003e\n...\n```\n\n### Markdown (Optional)\n\nPHML also has very basic markdown support. The `Markdown` element can be used to render markdown in place of the element itself. The markdown component is an optional feature of phml. To enable the feature you can run `pip3 install phml[markdown]` or `pip3 install markdown`, then use the component in a phml file. The markdown component can only reference/render a markdown file. To do so, use the `:src`/`src` attribute to specify the path to the markdown file, relative to the current file. If phml is rendering from a parsed dict or str, then the current working directory is used.\n\nThe markdown component uses a few default extensions while parsing. First it uses `codehilite` and `fenced_code` for highlighting code blocks, this also requires a css file generated with `pygmentize`. Lastly, it will use `tables` to add the ability to parse markdown tables. This makes the markdown close to a github flavor. \n\nUsers may need to add additional markdown extension or to configure them. That is where the `:extras`/`extras` and `:configs` attributes come in. The `:extras` attribute is a list of string names of the markdown extensions to add. The `extras` attribute, also `:extras`, is a space seperated string of the extension names. To configure the extensions the python attribute `:configs` is used. The attribute must be a dict of the format `{ '\u003cextension\u003e': { `\u003coption\u003e`: \u003cvalue\u003e } }`. The options and available values are found in the `markdown` modules [documentation](https://python-markdown.github.io/extensions/).\n  \nWhen the markdown is compiled, the resulting html elements are nested in an `article` element. All attributes that are not `src`, `extras`, or `:config` on the markdown component are added to the parent `article` element and are left unprocessed. This means that python attributes other than `:config` or `:extras` on the markdown component are left \"as is\".\n\n```html\n\u003c!-- file.phml --\u003e\n\u003cMarkdown\n  src=\"../markdown/file.md\"\n  extras=\"smarty footnotes\"\n  :configs=\"{\n     \"footnotes\": {\n       \"BACKLINK_TEXT\": \"$\"           \n     }\n  }\"\n\u003e\n```\n___\n  \n\u003e :warning: This language is in early development stages. Everything is currently subject to change. All forms of feedback are encouraged.\n\n\u003cbr\u003e\nFor more information, check out the [API Docs](https://tired-fox.github.io/phml/phml.html)\n\n## Basic Usage\n\nThe current version is able to parse phml using an html parser. This creates a phml ast which then can be converted back to phml or to json.\n\n**Use**\n\nFirst import the core parser and compiler, `from phml import HypertextManager`. Then you can do the following:\n\n```python\nphml = HypertextManager().load(\"path/to/file.phml\")\nprint(phml.render())\n```\n\nThere is method chaining so most if not all methods can be chained. The obvious exception being any method that returns a value.\n\nBy default `HypertextManager.render()` will return the `html` string. If you want to get a `json` string you may pass `Formats.JSON`. `HypertextManager.render(file_type=Formats.JSON)`.\n\nIf you want to write to a file you can call `phml.write(\"path/to/output/file.phml\")`. Same with `render` it defaults to html. You can change this the same way as `render`. `core.write(\"path/to/otuput/file.json\", file_type=Formats.JSON)`.\n\nFor both `render` and `write` you will first need to call `phml.load(\"path/to/source/file.phml\")`. This parses the source file and stores the ast in the parser. `render` and `write` then use that ast to create the desired output. Optionally if you already have a phml or html string or a properly formatted dict you can call `core.parse(data)` which will parse that information similar to `load`.\n\nEvery time `phml.parse` or `phml.load` is called it will overwrite the stored ast variable.\n\nThere are many more features such as globally exposed variables, components, slots, exposing python files to be used in phml files, etc...\n\nFor more information check out the [API Docs](https://tired-fox.github.io/phml/phml.html)\n\n\u003c!-- Footer Badges --\u003e\n\n\u003cbr\u003e\n\u003cdiv align=\"center\"\u003e\n  \n![Made with Python](assets/badges/made_with_python.svg)\n![Built with love](assets/badges/built_with_love.svg)\n\n\u003c/div\u003e\n\n\u003c!-- End Badges --\u003e\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftired-fox%2Fphml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftired-fox%2Fphml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftired-fox%2Fphml/lists"}