jamstack 2.0.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,186 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ --text: #1f1c19;
4
+ --muted: #756e66;
5
+ --background: #fbf9f6;
6
+ --accent: #0f5e59;
7
+ --rule: #e3ddd4;
8
+ }
9
+
10
+ @media (prefers-color-scheme: dark) {
11
+ :root {
12
+ --text: #ece7e0;
13
+ --muted: #a49c92;
14
+ --background: #16130f;
15
+ --accent: #7cc4bd;
16
+ --rule: #322c25;
17
+ }
18
+ }
19
+
20
+ * {
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ body {
25
+ margin: 0;
26
+ background: var(--background);
27
+ color: var(--text);
28
+ font: 1.125rem/1.75 "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
29
+ text-rendering: optimizeLegibility;
30
+ }
31
+
32
+ .masthead,
33
+ .content,
34
+ .colophon {
35
+ max-width: 40rem;
36
+ margin: 0 auto;
37
+ padding: 0 1.25rem;
38
+ }
39
+
40
+ .masthead {
41
+ padding-top: 4rem;
42
+ padding-bottom: 2.5rem;
43
+ text-align: center;
44
+ }
45
+
46
+ .logo {
47
+ color: var(--text);
48
+ font-size: 1.75rem;
49
+ font-weight: 600;
50
+ letter-spacing: -0.02em;
51
+ text-decoration: none;
52
+ }
53
+
54
+ .tagline {
55
+ margin: 0.5rem 0 0;
56
+ color: var(--muted);
57
+ font-size: 0.95rem;
58
+ font-style: italic;
59
+ }
60
+
61
+ .content {
62
+ padding-bottom: 4rem;
63
+ }
64
+
65
+ a {
66
+ color: var(--accent);
67
+ }
68
+
69
+ h1,
70
+ h2,
71
+ h3 {
72
+ line-height: 1.2;
73
+ letter-spacing: -0.02em;
74
+ }
75
+
76
+ h1 {
77
+ margin: 0 0 0.5rem;
78
+ font-size: 2.25rem;
79
+ }
80
+
81
+ time {
82
+ color: var(--muted);
83
+ font-size: 0.8rem;
84
+ letter-spacing: 0.06em;
85
+ text-transform: uppercase;
86
+ }
87
+
88
+ .post-list {
89
+ margin: 0;
90
+ padding: 0;
91
+ list-style: none;
92
+ }
93
+
94
+ .post-list li {
95
+ padding: 1.75rem 0;
96
+ border-top: 1px solid var(--rule);
97
+ }
98
+
99
+ .post-list li:first-child {
100
+ border-top: none;
101
+ }
102
+
103
+ .post-title {
104
+ display: block;
105
+ color: var(--text);
106
+ font-size: 1.4rem;
107
+ font-weight: 600;
108
+ text-decoration: none;
109
+ }
110
+
111
+ .post-title:hover {
112
+ color: var(--accent);
113
+ }
114
+
115
+ .summary {
116
+ margin: 0.4rem 0 0;
117
+ color: var(--muted);
118
+ font-size: 0.95rem;
119
+ }
120
+
121
+ .post-header {
122
+ padding-bottom: 1.5rem;
123
+ border-bottom: 1px solid var(--rule);
124
+ }
125
+
126
+ .prose {
127
+ padding-top: 1.5rem;
128
+ }
129
+
130
+ .prose p {
131
+ margin: 0 0 1.25em;
132
+ }
133
+
134
+ .prose blockquote {
135
+ margin: 1.5rem 0;
136
+ padding-left: 1.25rem;
137
+ border-left: 2px solid var(--rule);
138
+ color: var(--muted);
139
+ font-style: italic;
140
+ }
141
+
142
+ pre,
143
+ code {
144
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
145
+ font-size: 0.85em;
146
+ }
147
+
148
+ pre {
149
+ padding: 1rem;
150
+ overflow-x: auto;
151
+ background: color-mix(in srgb, var(--text) 6%, transparent);
152
+ border-radius: 6px;
153
+ }
154
+
155
+ code {
156
+ padding: 0.1em 0.3em;
157
+ background: color-mix(in srgb, var(--text) 7%, transparent);
158
+ border-radius: 3px;
159
+ }
160
+
161
+ pre code {
162
+ padding: 0;
163
+ background: none;
164
+ }
165
+
166
+ img {
167
+ max-width: 100%;
168
+ height: auto;
169
+ }
170
+
171
+ .back {
172
+ margin-top: 3rem;
173
+ font-size: 0.9rem;
174
+ }
175
+
176
+ .empty {
177
+ color: var(--muted);
178
+ }
179
+
180
+ .colophon {
181
+ padding-top: 1.25rem;
182
+ padding-bottom: 3rem;
183
+ border-top: 1px solid var(--rule);
184
+ color: var(--muted);
185
+ font-size: 0.85rem;
186
+ }
@@ -0,0 +1,15 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Page not found · {{ site.title }}{% endblock %}
4
+
5
+ {% block content %}
6
+ <article class="post">
7
+ <header class="post-header">
8
+ <h1>Page not found</h1>
9
+ </header>
10
+ <div class="prose">
11
+ <p>There is nothing at this address.</p>
12
+ <p><a href="/">Back to the home page</a></p>
13
+ </div>
14
+ </article>
15
+ {% endblock %}
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <html lang="{{ site.language }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{% block title %}{{ site.title }}{% endblock %}</title>
7
+ {% if site.description %}<meta name="description" content="{{ site.description }}">{% endif %}
8
+ <link rel="stylesheet" href="/style.css">
9
+ </head>
10
+ <body>
11
+ <header class="masthead">
12
+ <a class="logo" href="/">{{ site.title }}</a>
13
+ {% if site.description %}<p class="tagline">{{ site.description }}</p>{% endif %}
14
+ </header>
15
+ <main class="content">
16
+ {% block content %}{% endblock %}
17
+ </main>
18
+ <footer class="colophon">
19
+ <p>{{ site.title }}</p>
20
+ </footer>
21
+ </body>
22
+ </html>
@@ -0,0 +1,21 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ {% if page.content %}
5
+ <div class="intro">{{ page.content }}</div>
6
+ {% endif %}
7
+ {% set posts = pages | rejectattr("url", "equalto", "/") | selectattr("date") | list %}
8
+ {% if posts %}
9
+ <ol class="post-list">
10
+ {% for item in posts %}
11
+ <li>
12
+ <a class="post-title" href="{{ item.url }}">{{ item.title }}</a>
13
+ <time datetime="{{ item.date.isoformat() }}">{{ item.date.strftime("%d-%m-%Y") }}</time>
14
+ {% if item.meta.get("summary") %}<p class="summary">{{ item.meta.get("summary") }}</p>{% endif %}
15
+ </li>
16
+ {% endfor %}
17
+ </ol>
18
+ {% else %}
19
+ <p class="empty">No posts yet. Create one in <code>content/posts/</code>.</p>
20
+ {% endif %}
21
+ {% endblock %}
@@ -0,0 +1,3 @@
1
+ {% extends "post.html" %}
2
+
3
+ {% block post_footer %}{% endblock %}
@@ -0,0 +1,16 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}{{ page.title }} · {{ site.title }}{% endblock %}
4
+
5
+ {% block content %}
6
+ <article class="post">
7
+ <header class="post-header">
8
+ <h1>{{ page.title }}</h1>
9
+ {% if page.date %}<time datetime="{{ page.date.isoformat() }}">{{ page.date.strftime("%d-%m-%Y") }}</time>{% endif %}
10
+ </header>
11
+ <div class="prose">{{ page.content }}</div>
12
+ {% block post_footer %}
13
+ <p class="back"><a href="/">← All posts</a></p>
14
+ {% endblock %}
15
+ </article>
16
+ {% endblock %}
@@ -0,0 +1,2 @@
1
+ name = "blog"
2
+ description = "A reading-first blog theme with post listings and dates."
@@ -0,0 +1,137 @@
1
+ :root {
2
+ color-scheme: light dark;
3
+ --text: #1a1a1a;
4
+ --muted: #6b6b6b;
5
+ --background: #fdfdfc;
6
+ --accent: #2563eb;
7
+ --rule: #e5e5e2;
8
+ }
9
+
10
+ @media (prefers-color-scheme: dark) {
11
+ :root {
12
+ --text: #e8e8e6;
13
+ --muted: #a0a0a0;
14
+ --background: #131313;
15
+ --accent: #7aa2f7;
16
+ --rule: #2a2a2a;
17
+ }
18
+ }
19
+
20
+ * {
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ body {
25
+ margin: 0;
26
+ background: var(--background);
27
+ color: var(--text);
28
+ font: 1.0625rem/1.7 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
29
+ }
30
+
31
+ .site-header,
32
+ .site-main,
33
+ .site-footer {
34
+ max-width: 42rem;
35
+ margin: 0 auto;
36
+ padding: 0 1.25rem;
37
+ }
38
+
39
+ .site-header {
40
+ padding-top: 2.5rem;
41
+ padding-bottom: 1rem;
42
+ }
43
+
44
+ .site-title {
45
+ color: var(--text);
46
+ font-size: 1.125rem;
47
+ font-weight: 700;
48
+ letter-spacing: -0.01em;
49
+ text-decoration: none;
50
+ }
51
+
52
+ .site-main {
53
+ padding-bottom: 4rem;
54
+ }
55
+
56
+ h1,
57
+ h2,
58
+ h3 {
59
+ line-height: 1.25;
60
+ letter-spacing: -0.02em;
61
+ }
62
+
63
+ h1 {
64
+ font-size: 2rem;
65
+ margin: 1.5rem 0 0.75rem;
66
+ }
67
+
68
+ a {
69
+ color: var(--accent);
70
+ }
71
+
72
+ time {
73
+ color: var(--muted);
74
+ font-size: 0.875rem;
75
+ }
76
+
77
+ .page-list {
78
+ list-style: none;
79
+ margin: 2rem 0 0;
80
+ padding: 0;
81
+ }
82
+
83
+ .page-list li {
84
+ display: flex;
85
+ justify-content: space-between;
86
+ gap: 1rem;
87
+ padding: 0.6rem 0;
88
+ border-top: 1px solid var(--rule);
89
+ }
90
+
91
+ .page-list a {
92
+ text-decoration: none;
93
+ }
94
+
95
+ pre,
96
+ code {
97
+ font-family: ui-monospace, "SF Mono", Menlo, monospace;
98
+ font-size: 0.9em;
99
+ }
100
+
101
+ pre {
102
+ padding: 1rem;
103
+ overflow-x: auto;
104
+ background: color-mix(in srgb, var(--text) 6%, transparent);
105
+ border-radius: 8px;
106
+ }
107
+
108
+ code {
109
+ padding: 0.1em 0.3em;
110
+ background: color-mix(in srgb, var(--text) 7%, transparent);
111
+ border-radius: 4px;
112
+ }
113
+
114
+ pre code {
115
+ padding: 0;
116
+ background: none;
117
+ }
118
+
119
+ blockquote {
120
+ margin: 1.5rem 0;
121
+ padding-left: 1rem;
122
+ border-left: 3px solid var(--rule);
123
+ color: var(--muted);
124
+ }
125
+
126
+ img {
127
+ max-width: 100%;
128
+ height: auto;
129
+ }
130
+
131
+ .site-footer {
132
+ padding-top: 1rem;
133
+ padding-bottom: 2.5rem;
134
+ border-top: 1px solid var(--rule);
135
+ color: var(--muted);
136
+ font-size: 0.875rem;
137
+ }
@@ -0,0 +1,11 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}Page not found · {{ site.title }}{% endblock %}
4
+
5
+ {% block content %}
6
+ <article>
7
+ <h1>Page not found</h1>
8
+ <p>There is nothing at this address.</p>
9
+ <p><a href="/">Back to the home page</a></p>
10
+ </article>
11
+ {% endblock %}
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="{{ site.language }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{% block title %}{{ site.title }}{% endblock %}</title>
7
+ {% if site.description %}<meta name="description" content="{{ site.description }}">{% endif %}
8
+ <link rel="stylesheet" href="/style.css">
9
+ </head>
10
+ <body>
11
+ <header class="site-header">
12
+ <a class="site-title" href="/">{{ site.title }}</a>
13
+ </header>
14
+ <main class="site-main">
15
+ {% block content %}{% endblock %}
16
+ </main>
17
+ <footer class="site-footer">
18
+ <p>{{ site.title }}{% if site.description %} — {{ site.description }}{% endif %}</p>
19
+ </footer>
20
+ </body>
21
+ </html>
@@ -0,0 +1,17 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block content %}
4
+ <h1>{{ page.title }}</h1>
5
+ {{ page.content }}
6
+ {% set listed = pages | rejectattr("url", "equalto", "/") | list %}
7
+ {% if listed %}
8
+ <ul class="page-list">
9
+ {% for item in listed %}
10
+ <li>
11
+ <a href="{{ item.url }}">{{ item.title }}</a>
12
+ {% if item.date %}<time datetime="{{ item.date.isoformat() }}">{{ item.date.strftime("%d-%m-%Y") }}</time>{% endif %}
13
+ </li>
14
+ {% endfor %}
15
+ </ul>
16
+ {% endif %}
17
+ {% endblock %}
@@ -0,0 +1,11 @@
1
+ {% extends "base.html" %}
2
+
3
+ {% block title %}{{ page.title }} · {{ site.title }}{% endblock %}
4
+
5
+ {% block content %}
6
+ <article>
7
+ <h1>{{ page.title }}</h1>
8
+ {% if page.date %}<time datetime="{{ page.date.isoformat() }}">{{ page.date.strftime("%d-%m-%Y") }}</time>{% endif %}
9
+ {{ page.content }}
10
+ </article>
11
+ {% endblock %}
@@ -0,0 +1,2 @@
1
+ name = "minimal"
2
+ description = "A clean, responsive starting point for small sites."
@@ -0,0 +1,226 @@
1
+ Metadata-Version: 2.5
2
+ Name: jamstack
3
+ Version: 2.0.0rc1
4
+ Summary: Static site generator built on Python and Jinja
5
+ Project-URL: Homepage, https://github.com/jamstackpy/jamstack
6
+ Project-URL: Source, https://github.com/jamstackpy/jamstack
7
+ Project-URL: Issues, https://github.com/jamstackpy/jamstack/issues
8
+ Project-URL: Changelog, https://github.com/jamstackpy/jamstack/blob/stable/CHANGELOG.md
9
+ Author-email: Abdur-Rahmaan Janhangeer <arj.python@gmail.com>, Pedro Torcatt <pedrotorcattsoto@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: jinja,python,ssg,static site generator
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
23
+ Requires-Python: >=3.11
24
+ Requires-Dist: click>=8.1
25
+ Requires-Dist: jinja2>=3.1
26
+ Requires-Dist: markdown-it-py>=3.0
27
+ Provides-Extra: dev
28
+ Requires-Dist: commitizen>=4.0; extra == 'dev'
29
+ Requires-Dist: pre-commit>=3.7; extra == 'dev'
30
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
31
+ Requires-Dist: pytest>=8.0; extra == 'dev'
32
+ Requires-Dist: ruff>=0.17; extra == 'dev'
33
+ Requires-Dist: zensical>=0.1; extra == 'dev'
34
+ Description-Content-Type: text/markdown
35
+
36
+ <div align="center">
37
+ <img alt="Jamstack logo" src="https://i.imgur.com/sXUAdYJ.png" height="217" />
38
+
39
+ <p>A small static site generator built on Python and Jinja.</p>
40
+ </div>
41
+
42
+ [![CI](https://github.com/jamstackpy/jamstack/actions/workflows/ci.yml/badge.svg?branch=2.0)](https://github.com/jamstackpy/jamstack/actions/workflows/ci.yml)
43
+ ![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)
44
+
45
+ > **2.0 is under development.** This branch contains the rewrite and is not
46
+ > published on PyPI yet. The `stable` branch holds the current 1.x release.
47
+
48
+ ## Documentation
49
+
50
+ The full documentation lives in [`docs/`](docs/) and is built with Zensical.
51
+ Preview it locally with `zensical serve`; it is published at
52
+ <https://jamstackpy.github.io/>.
53
+
54
+ Jamstack turns a folder of Markdown and Jinja templates into a static site.
55
+ There is no database, no runtime, and no network access during a build: you
56
+ write content, run one command, and deploy the generated `dist/` folder
57
+ anywhere.
58
+
59
+ ## Features
60
+
61
+ - Markdown pages with TOML front matter, or raw Jinja templates when you need
62
+ full control.
63
+ - Themes as plain folders: bundled ones, your own, or a per-project override.
64
+ - A development server that rebuilds on change and keeps the last good build
65
+ when something fails.
66
+ - `sitemap.xml`, an Atom feed, and a themed `404.html` generated for you.
67
+ - Deterministic output: the same sources always produce the same bytes.
68
+ - Three runtime dependencies: `click`, `jinja2`, `markdown-it-py`.
69
+
70
+ ## Installation
71
+
72
+ Until 2.0 is published:
73
+
74
+ ```bash
75
+ python -m pip install "jamstack @ git+https://github.com/jamstackpy/jamstack@2.0"
76
+ ```
77
+
78
+ After the release:
79
+
80
+ ```bash
81
+ python -m pip install jamstack
82
+ ```
83
+
84
+ Python 3.11 or newer is required.
85
+
86
+ ## Quick start
87
+
88
+ ```bash
89
+ jamstack init my-site --theme blog
90
+ cd my-site
91
+ jamstack build
92
+ jamstack serve
93
+ ```
94
+
95
+ `jamstack init` scaffolds `site.toml`, a starter page, and the files the chosen
96
+ theme needs. `jamstack build` writes the site to `dist/`. `jamstack serve`
97
+ serves it at `http://127.0.0.1:8000` and rebuilds when a source changes.
98
+
99
+ ## Project layout
100
+
101
+ ```
102
+ my-site/
103
+ site.toml # site configuration
104
+ content/ # pages (.md or .html)
105
+ static/ # files copied as-is to the output root
106
+ templates/ # optional overrides of the theme's templates
107
+ themes/ # optional local themes
108
+ dist/ # build output (do not edit)
109
+ ```
110
+
111
+ ## Writing content
112
+
113
+ A page is a file under `content/`. Its path becomes its URL:
114
+
115
+ | Source | URL | Output |
116
+ | --- | --- | --- |
117
+ | `content/index.md` | `/` | `dist/index.html` |
118
+ | `content/about.md` | `/about/` | `dist/about/index.html` |
119
+ | `content/posts/hello.md` | `/posts/hello/` | `dist/posts/hello/index.html` |
120
+
121
+ Markdown files may start with TOML front matter delimited by `+++`:
122
+
123
+ ```markdown
124
+ +++
125
+ title = "Hello, world"
126
+ date = 2026-11-06
127
+ summary = "The first post."
128
+ +++
129
+
130
+ This is **Markdown**.
131
+ ```
132
+
133
+ Recognised keys:
134
+
135
+ | Key | Type | Meaning |
136
+ | --- | --- | --- |
137
+ | `title` | string | Page title. Defaults to the file name. |
138
+ | `date` | ISO date | Publication date. Feeds and listings order by it. |
139
+ | `template` | string | Template to render the page with. |
140
+ | `draft` | boolean | Excluded from builds unless `--drafts` is passed. |
141
+ | `summary` | string | Short description used by feeds and the blog theme. |
142
+
143
+ Any other key is available to templates as `page.meta`. Files ending in
144
+ `.html` are rendered as Jinja templates instead of Markdown, which makes it
145
+ easy to build landing pages with custom markup.
146
+
147
+ ## Configuration
148
+
149
+ ```toml
150
+ [site]
151
+ title = "My Site" # required
152
+ description = ""
153
+ author = ""
154
+ base_url = "https://example.com" # required for sitemap.xml and feed.xml
155
+ language = "en"
156
+
157
+ [build]
158
+ output = "dist"
159
+
160
+ [theme]
161
+ name = "blog"
162
+ ```
163
+
164
+ `base_url` must be the public, absolute URL of the site. When it is set,
165
+ Jamstack writes `sitemap.xml` with every page and `feed.xml` with every dated
166
+ page. When it is empty, both files are skipped rather than emitting relative
167
+ URLs.
168
+
169
+ ## Themes
170
+
171
+ List the available themes with:
172
+
173
+ ```bash
174
+ jamstack themes
175
+ ```
176
+
177
+ Two themes ship with the package: `minimal` for small sites and `blog` for
178
+ dated posts. A project can override a bundled theme by creating
179
+ `themes/<name>/` inside the project; local themes take precedence.
180
+
181
+ Themes are plain folders, so a custom theme is just a directory with a
182
+ `theme.toml`, `templates/`, and optionally `static/` and `scaffold/`. See
183
+ [docs/themes.md](docs/themes.md) for the full specification.
184
+
185
+ ## Command reference
186
+
187
+ | Command | Description |
188
+ | --- | --- |
189
+ | `jamstack init [PATH] [--theme NAME] [--force]` | Create a new site. |
190
+ | `jamstack build [--clean] [--drafts]` | Build the site into `dist/`. |
191
+ | `jamstack serve [--port PORT] [--drafts]` | Build, serve, and rebuild on change. |
192
+ | `jamstack themes` | List available themes. |
193
+ | `jamstack --version` | Print the installed version. |
194
+
195
+ `python -m jamstack` behaves like the `jamstack` command.
196
+
197
+ ## Migrating from 1.x
198
+
199
+ The 1.x engine (`settings.py`, `posts.json`, `static.py`, html5up themes) is
200
+ replaced entirely in 2.0. See [docs/migration.md](docs/migration.md) for a
201
+ step-by-step conversion guide, or keep using 1.x with:
202
+
203
+ ```bash
204
+ python -m pip install "jamstack<2"
205
+ ```
206
+
207
+ ## Development
208
+
209
+ ```bash
210
+ git clone git@github.com:jamstackpy/jamstack.git
211
+ cd jamstack
212
+ git switch 2.0
213
+ python -m venv .venv
214
+ . .venv/bin/activate
215
+ pip install -e ".[dev]"
216
+ pre-commit install --hook-type pre-commit --hook-type commit-msg
217
+ pytest
218
+ ```
219
+
220
+ Commits follow [Conventional Commits](https://www.conventionalcommits.org/);
221
+ see [CONTRIBUTING.md](CONTRIBUTING.md).
222
+
223
+ ## License
224
+
225
+ MIT. The 1.x html5up templates were distributed under CC BY 3.0 and are no
226
+ longer part of this branch.