diff --git a/.coveragerc b/.coveragerc index 8c253ec..8190f9f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,4 +1,9 @@ [report] exclude_lines = pragma: no cover - if TYPE_CHECKING: \ No newline at end of file + if TYPE_CHECKING: + def __.+__ + @overload + keys + values + items diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 0b909e3..1f8498f 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.10"] + python-version: ["3.11"] steps: - uses: actions/checkout@v2 @@ -27,9 +27,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install pytest-cov - pip install coverage - pip install . + pip install .[tests,markdown] - name: Run tests with coverage run: | make test diff --git a/COMLETED.md b/COMLETED.md deleted file mode 100644 index 5f62a0a..0000000 --- a/COMLETED.md +++ /dev/null @@ -1,116 +0,0 @@ -# TODO -- [x] Write Tests - - [x] Utils - - [x] find - - [x] misc - - [x] transform - - [x] travel - - [x] validate - - [x] AST - - [x] Correct AST Generation (parsing) - - [x] To json - - [x] To phml - - [x] To html - - [x] Correct ast conversion (compiling) - - [x] From phml/html - - [x] From json -- [x] All utilities - - [x] unist - - [x] hast -- [x] Ensure python conditions are immediate siblings -- [x] Optimize - - [x] Parsing files - - [x] Compiling files - - [x] AST linking from parser and compiler - - [x] Nodes to dataclasses? - - [x] Utilities -- [x] Component based system -- [x] Layout for components - - [x] Can provide script, python, styles elements - - [x] Parent element or single element and then script, style, python, etc? -- [x] re-write inspect to be external util function - - [x] Pretty lines - - [x] # children - - [x] Positional - - [x] Type and/or tag - - [x] values of literals - - [x] properties of elements - - [x] data of entire tree -- [x] from-python: create temp python file for `python` tag and evaluate and import all *`from temp_python import *`* -- [x] evaluate-python: evaluate a string containing python -- [x] Node conversion external from nodes -- [x] Validate ast is proper format - - [x] doctype is only in root - - [x] if no doctype add doctype - - [x] root nodes are at the root of the tree -- [x] Convert From Formats - - [x] Json - - [x] html - - [x] phml -- [x] Convert To Formats - - [x] Json - - [x] phml - - [x] html - -## Utils from [`unist`](https://github.com/syntax-tree/unist#list-of-utilities) and [`hast`](https://github.com/syntax-tree/hast#list-of-utilities) to implement - -### unist -- [x] position: Positional information on node -- [x] inspect: Node inspector -- [x] find: Find node by condition -- [x] find-after: Find first node after certain node -- [x] find-all-after: Find all nodes after certain node -- [x] size: Calc number of nodes in a tree -- [x] test: Check if node passes test -- [x] find-before: Find first node before certain node -- [x] find-all-before: Find all nodes before certain node -- [x] find-all-between: Find all nodes between two nodes -- [x] ancestor: Get common ancestor of one or more nodes - -- [x] walk: recursively walk over nodes -- [x] visit-children: visit direct children of a parent -- [x] visit-all-after: visit all nodes after another node - -- [x] filter: Create a new tree with only nodes that pass a condition -- [x] remove: Remove nodes from trees -- [x] map: Create a new tree by mapping nodes - -- [x] assert: Asserts that a node is valid -- [x] generated: Check if node is generated - -- [x] builder: Helper for creating ast's (Own module) -- [x] index: Index the tree given conditions. Helps with traversal -- [x] modify-children: modify direct children of a parent - -- [ ] ? replace-all-between: replace nodes between two nodes or positions -- [ ] ? reduce: recursively reduce tree -- [ ] ? flatmap: Flat version of tree -- [ ] ? flat-filter: flat map of `filter` -- [ ] ? source: get the source of a value - -## hast -- [x] class-list: Mimic browser's classList API -- [x] classnames: merge class names together -- [x] has-property: Check if element has a certain property -- [x] heading: check if node is a heading -- [x] heading-rank: get the rank(depth/level) of headings -- [x] is-css-link: check if node is a CSS link -- [x] is-css-style: check if node is a CSS style -- [x] is-element: check if node is a certain element -- [x] is-event-handler: check if property is an event handler -- [x] is-javascript: check if node is a javascript `script` [ref](https://html.spec.whatwg.org/#category-label) -- [x] shift-heading: change heading rank (depth/level) -- [x] find-and-replace: find and replace text in tree -- [x] to-string: Get textContent of element - -- [x] embedded: check if a node is an embedded element -- [x] interactive: check if the node is an interactive element -- [x] phrasing: check if node is phrasing content -- [ ] ? menu-state: check the state of a menu element - -- [x] select: `querySelector`, `querySelectorAll`, and `matches` -- [x] sanitize: sanitize nodes -- [ ] ? to-text: inner-text of element - Rendered text - -- [ ] ? to-markdown: Converts phml ast tree to markdown -- [ ] ? from-markdown: Uses python markdown and gets ast from rendered markdown diff --git a/README.md b/README.md index 51a894c..0ba4104 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,32 @@ -![version](assets/badges/version.svg) [![License](https://img.shields.io/badge/License-MIT-9cf)](https://github.com/Tired-Fox/phml/blob/main/LICENSE) [![tired-fox - phml](https://img.shields.io/static/v1?label=tired-fox&message=phml&color=9cf&logo=github)](https://github.com/tired-fox/phml "Go to GitHub repo") -[![stars - phml](https://img.shields.io/github/stars/tired-fox/phml?style=social)](https://github.com/tired-fox/phml) -[![forks - phml](https://img.shields.io/github/forks/tired-fox/phml?style=social)](https://github.com/tired-fox/phml) - # Python Hypertext Markup Language (phml) -[![Deploy Docs](https://github.com/Tired-Fox/phml/actions/workflows/deploy_docs.yml/badge.svg)](https://github.com/Tired-Fox/phml/actions/workflows/deploy_docs.yml) [![GitHub release](https://img.shields.io/github/release/tired-fox/phml?include_prereleases=&sort=semver&color=brightgreen)](https://github.com/tired-fox/phml/releases/) -[![issues - phml](https://img.shields.io/github/issues/tired-fox/phml)](https://github.com/tired-fox/phml/issues) ![quality](assets/badges/quality.svg) ![testing](assets/badges/testing.svg) ![test coverage](assets/badges/test_cov.svg) - -**TOC** -- [Python Hypertext Markup Language (phml)](#python-hypertext-markup-language-phml) - - [Overview](#overview) - - [How to use](#how-to-use) - +
+ +![version](assets/badges/version.svg) +[![License](assets/badges/license.svg)](https://github.com/Tired-Fox/phml/blob/main/LICENSE) +[![Release](https://img.shields.io/github/v/release/tired-fox/phml.svg?style=flat-square&color=9cf)](https://github.com/Tired-Fox/phml/releases) +![Maintained](assets/badges/maintained.svg) -[![view - Documentation](https://img.shields.io/badge/view-Documentation-blue?style=for-the-badge)](https://tired-fox.github.io/phml/phml.html "Go to project documentation") +![testing](assets/badges/tests.svg) +![test coverage](assets/badges/coverage.svg)
+ + ## Overview The 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. +### Python Element + Let's start with the new `python` element. Python is a whitespace language. As such, phml has 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. ```html - - message = "hello world" - if "hello" in message: - print(message) - + message = "hello world" if "hello" in message: print(message) ``` The resulting python code would look like this. @@ -47,29 +42,24 @@ how you normally would and they are now available to the scope of the entire fil ```html -def URL(link: str) -> str: - links = { - "youtube": "https://youtube.com" - } - if link in links: - return links[link] - else: - return "" + def URL(link: str) -> str: links = { "youtube": "https://youtube.com" } if + link in links: return links[link] else: return "" ... Youtube - ``` phml 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. +### Inline Python and Python Attributes + Next 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` and it contains `Hello World!`. Now this variable can be used like this, `

{{ message }}

`, which renders to, `

Hello World!

`. -> Note: Inline python blocks are only rendered in a Text element or inside an html attribute. +> Note: Inline python blocks are only rendered in a Text element or inside an html attribute. Conditional rendering with `@if`, `@elif`, and `@else` is an extremely helpful tool in phml. `@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`. @@ -89,34 +79,32 @@ The compiled html will be: ```html ``` Python 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 ` -
- # content goes here -
+
# content goes here
- -# python code goes here - + # python code goes here ``` -Components can be added to the compiler by using `PHML.add('path/to/component.phml')`. You can define a components name when adding it to the compiler like this `PHML.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 ``. 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. +Components 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 ``. 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. Great 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 `<>` element which is a placeholder element. All children are treated as they are at the root of the component. @@ -129,8 +117,8 @@ Great now you have components. But what if you have a few components that are si ... <> -

Hello

-

World

+

Hello

+

World

<> ``` @@ -150,11 +138,7 @@ Now how do you pass information to component to use in rendering? That is where ```html - -Props = { - message: "" -} - + Props = { message: "" }

{{ message }}

@@ -167,15 +151,11 @@ Props = { Both 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. ```html - -Props = { - message: "" -} - + Props = { message: "" }

{{ message }}

- +
``` @@ -184,17 +164,17 @@ The `Slot` element must be capitalized. When a `Slot` element is present any chi ```html
- - - + + +
... -

Bottom

-

Top

-Middle +

Bottom

+

Top

+ Middle
... @@ -206,40 +186,51 @@ Middle ... ``` -PHML also has very basic markdown support. You may use the `Markdown` element to render markdown in place of the element itself. The element has 3 main uses: using the `src`/`:src` attribute to pass a string, the `file`/`:file` attribute to load the markdown from a file, and finally to just write markdown text inside as children to the element. The text as children is adjusted to have a normalized indent similar to the `python` element. If all of these methods are used, they are combined. The are combined in the order of `src`, then `file`, then the children. +### Markdown (Optional) + +PHML 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. + +The 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. + +Users 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 `{ '': { `