Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .claude/skills/winged/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
name: winged
description: Write or extend Winged-Python, a dependency-free Python DSL that builds HTML strings. Use when working with winged, Winged-Python, `from winged import`, Element/Document/Fragment/RawHtml/RenderOptions, the `winged` CLI (new/build/serve), or any task that generates HTML, a static site, a sitemap, or an RSS feed with this library.
---

# Winged-Python

A DSL that builds an HTML **string**. No DOM, no browser, no server, no runtime
dependencies.

## Which workflow are you in?

**A. Using the library** — building pages, a site, a feed. Read the rules below, then
`references/recipes.md` for the shape you need.

**B. Extending the library** — changing `src/winged/`. Read `AGENTS.md` at the repository
root; it has the element-adding contract and the "before you say you are done" gate.

## Rules

1. **Children are positional, attributes are keyword.** `Div(P("a"), cls="card")`.
2. **Text is escaped.** Pass a plain `str`. Never pre-escape — you get `<`.
3. **`RawHtml` is the only opt-out**, and never for anything from data.
4. **Void elements take no children** and raise if given one: `Br`, `Img`, `Input`,
`Link`, `Meta`, `Hr`, `Col`, `Source`, `Track`, `Wbr`, `Base`, `Embed`.
5. **`Fragment` groups; `RawHtml` injects.** `Fragment` keeps the tree and its
indentation, `RawHtml` loses both.
6. **Boolean attributes are `True`**, not `"true"`. `False`/`None` omit the attribute.
7. **Names that collide take a trailing underscore**: `cls`, `for_`, `type_`, `id_`.
The rendered attribute keeps the HTML spelling.
8. **`Document` owns the doctype and `lang`.** Never write `<!DOCTYPE html>` by hand.
9. **`render(node, options)` is a function and returns the string.** It does not print.
Options are a value; there is no global.
10. **Iterables are flattened and `None` children are dropped** — that is how loops and
conditionals work: `Ul(Li(x) for x in items)`, `Div(x if cond else None)`.
11. **`Img` requires `alt`; `Iframe` requires `title`.** Deliberate. Do not work around it.
12. **Never invent an element name.** Check `references/tag-catalog.md`.

## Read it when

| File | Read it when |
| --- | --- |
| `references/tag-catalog.md` | You need an element name or its signature. **Generated from the source, so it cannot drift.** |
| `references/recipes.md` | You need the shape for a page, layout, component, table, form, media, SEO head, sitemap, feed, or writing to disk. |
| `references/pitfalls.md` | Output is wrong, something is double-escaped, or a 0.1.0 name is missing. |

## Minimal example

```python
from winged import Body, Document, H1, Head, P, Title, render

page = Document(Head(Title("Home")), Body(H1("Hi"), P("<ok>")), lang="pt-BR")
print(render(page))
# <!DOCTYPE html>
# <html lang="pt-BR"><head><title>Home</title></head><body><h1>Hi</h1><p>&lt;ok&gt;</p></body></html>
```
1 change: 1 addition & 0 deletions .claude/skills/winged/references/pitfalls.md
1 change: 1 addition & 0 deletions .claude/skills/winged/references/recipes.md
1 change: 1 addition & 0 deletions .claude/skills/winged/references/tag-catalog.md
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @micheltlutz
45 changes: 17 additions & 28 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,27 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

about: Markup that is wrong, or an error you did not expect
labels: bug
---

**Describe the bug**
A clear and concise description of what the bug is.
## What happened

<!-- The markup produced, or the traceback. -->

## What you expected

**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
<!-- The markup you expected instead. -->

**Expected behavior**
A clear and concise description of what you expected to happen.
## Minimal reproduction

**Screenshots**
If applicable, add screenshots to help explain your problem.
```python
from winged import Div, render

**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
print(render(Div("...")))
```

**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
## Environment

**Additional context**
Add any other context about the problem here.
- Winged-Python version: <!-- python -c "import winged; print(winged.__version__)" -->
- Python version: <!-- python --version -->
- OS:
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Question
url: https://github.com/micheltlutz/Winged-Python/discussions
about: Ask how to do something with the library.
10 changes: 0 additions & 10 deletions .github/ISSUE_TEMPLATE/custom.md

This file was deleted.

26 changes: 13 additions & 13 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

about: Something the library cannot express, or expresses badly
labels: enhancement
---

**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
## The problem

<!-- What are you trying to build, and what stops you? Describe the problem before the
solution — ROADMAP.md is written the same way. -->

## What you do today

**Describe the solution you'd like**
A clear and concise description of what you want to happen.
<!-- The workaround, if there is one. -->

**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
## What you would like to write

**Additional context**
Add any other context or screenshots about the feature request here.
```python
# The call site you wish existed.
```
17 changes: 17 additions & 0 deletions .github/ISSUE_TEMPLATE/parity_gap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: Parity gap with Winged-Swift
about: Winged-Swift does something this port does not, or does differently
labels: parity
---

## The Winged-Swift behaviour

<!-- Name the file: Sources/WingedSwift/... -->

## What Winged-Python does instead

<!-- Rendered output from both, if you have it. -->

## Is the difference deliberate?

<!-- Check PORTING.md first — some differences are decisions, and it lists them. -->
18 changes: 18 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## What changed

<!-- One or two sentences. What does this do that the previous code did not? -->

## Why

<!-- The problem, not the patch. Link the issue if there is one: Closes #123 -->

## Markup impact

<!-- If rendered output changes, paste before and after. If it does not, say "none". -->

## Checklist

- [ ] `./scripts/verify.sh` passes
- [ ] Tests cover the change, and assert on rendered strings rather than tree shape
- [ ] `CHANGELOG.md` has an entry under `## [Unreleased]`
- [ ] If an element was added: a row in `winged/_tagtable.py`, and both generators re-run
14 changes: 11 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
- package-ecosystem: pip
directory: /
schedule:
interval: "weekly"
interval: weekly
open-pull-requests-limit: 10

# Without this, the workflows stay pinned wherever they were written. That is exactly
# how Winged-Python ended up on actions/checkout@v2 three years after v4 shipped.
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 10
98 changes: 98 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: CI

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

# A new push supersedes the run already in flight. Winged-Swift's workflows have no
# concurrency group, so its queue fills with runs nobody will read.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: Test (${{ matrix.os }}, ${{ matrix.python }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
# Every version pyproject.toml carries a classifier for. A claim nothing runs
# is a claim, not support.
python: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
cache: pip
- run: pip install -e . pytest pytest-cov
- run: pytest -q

lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install ruff
# Gating, deliberately. Winged-Swift marks its SwiftLint job continue-on-error, so
# its badge can be green while lint is failing.
- run: ruff check --output-format=github .
- run: ruff format --check .

types:
name: Types
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -e . mypy pytest
- run: mypy

generated:
name: Generated files are current
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -e .
# A tag added without regenerating fails here rather than drifting silently.
- run: python scripts/generate_elements.py --check
- run: python scripts/generate_tag_catalog.py --check

verify:
name: verify.sh
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -e . pytest pytest-cov ruff mypy build
- run: ./scripts/verify.sh

coverage:
name: Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -e . pytest pytest-cov
- run: pytest -q
- uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.xml
fail_ci_if_error: false
34 changes: 0 additions & 34 deletions .github/workflows/python-package.yml

This file was deleted.

Loading
Loading