jamstack 2.0.0__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,216 @@
1
+ Metadata-Version: 2.5
2
+ Name: jamstack
3
+ Version: 2.0.0
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=stable)](https://github.com/jamstackpy/jamstack/actions/workflows/ci.yml)
43
+ ![PyPI](https://img.shields.io/pypi/v/jamstack)
44
+ ![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)
45
+
46
+ ## Documentation
47
+
48
+ The full documentation lives in [`docs/`](docs/) and is built with Zensical.
49
+ Preview it locally with `zensical serve`; it is published at
50
+ <https://jamstackpy.github.io/>.
51
+
52
+ Jamstack turns a folder of Markdown and Jinja templates into a static site.
53
+ There is no database, no runtime, and no network access during a build: you
54
+ write content, run one command, and deploy the generated `dist/` folder
55
+ anywhere.
56
+
57
+ ## Features
58
+
59
+ - Markdown pages with TOML front matter, or raw Jinja templates when you need
60
+ full control.
61
+ - Themes as plain folders: bundled ones, your own, or a per-project override.
62
+ - A development server that rebuilds on change and keeps the last good build
63
+ when something fails.
64
+ - `sitemap.xml`, an Atom feed, and a themed `404.html` generated for you.
65
+ - Deterministic output: the same sources always produce the same bytes.
66
+ - Three runtime dependencies: `click`, `jinja2`, `markdown-it-py`.
67
+
68
+ ## Installation
69
+
70
+ ```bash
71
+ python -m pip install jamstack
72
+ ```
73
+
74
+ Python 3.11 or newer is required.
75
+
76
+ ## Quick start
77
+
78
+ ```bash
79
+ jamstack init my-site --theme blog
80
+ cd my-site
81
+ jamstack build
82
+ jamstack serve
83
+ ```
84
+
85
+ `jamstack init` scaffolds `site.toml`, a starter page, and the files the chosen
86
+ theme needs. `jamstack build` writes the site to `dist/`. `jamstack serve`
87
+ serves it at `http://127.0.0.1:8000` and rebuilds when a source changes.
88
+
89
+ ## Project layout
90
+
91
+ ```
92
+ my-site/
93
+ site.toml # site configuration
94
+ content/ # pages (.md or .html)
95
+ static/ # files copied as-is to the output root
96
+ templates/ # optional overrides of the theme's templates
97
+ themes/ # optional local themes
98
+ dist/ # build output (do not edit)
99
+ ```
100
+
101
+ ## Writing content
102
+
103
+ A page is a file under `content/`. Its path becomes its URL:
104
+
105
+ | Source | URL | Output |
106
+ | --- | --- | --- |
107
+ | `content/index.md` | `/` | `dist/index.html` |
108
+ | `content/about.md` | `/about/` | `dist/about/index.html` |
109
+ | `content/posts/hello.md` | `/posts/hello/` | `dist/posts/hello/index.html` |
110
+
111
+ Markdown files may start with TOML front matter delimited by `+++`:
112
+
113
+ ```markdown
114
+ +++
115
+ title = "Hello, world"
116
+ date = 2026-11-06
117
+ summary = "The first post."
118
+ +++
119
+
120
+ This is **Markdown**.
121
+ ```
122
+
123
+ Recognised keys:
124
+
125
+ | Key | Type | Meaning |
126
+ | --- | --- | --- |
127
+ | `title` | string | Page title. Defaults to the file name. |
128
+ | `date` | ISO date | Publication date. Feeds and listings order by it. |
129
+ | `template` | string | Template to render the page with. |
130
+ | `draft` | boolean | Excluded from builds unless `--drafts` is passed. |
131
+ | `summary` | string | Short description used by feeds and the blog theme. |
132
+
133
+ Any other key is available to templates as `page.meta`. Files ending in
134
+ `.html` are rendered as Jinja templates instead of Markdown, which makes it
135
+ easy to build landing pages with custom markup.
136
+
137
+ ## Configuration
138
+
139
+ ```toml
140
+ [site]
141
+ title = "My Site" # required
142
+ description = ""
143
+ author = ""
144
+ base_url = "https://example.com" # required for sitemap.xml and feed.xml
145
+ language = "en"
146
+
147
+ [build]
148
+ output = "dist"
149
+
150
+ [theme]
151
+ name = "blog"
152
+ ```
153
+
154
+ `base_url` must be the public, absolute URL of the site. When it is set,
155
+ Jamstack writes `sitemap.xml` with every page and `feed.xml` with every dated
156
+ page. When it is empty, both files are skipped rather than emitting relative
157
+ URLs.
158
+
159
+ ## Themes
160
+
161
+ List the available themes with:
162
+
163
+ ```bash
164
+ jamstack themes
165
+ ```
166
+
167
+ Two themes ship with the package: `minimal` for small sites and `blog` for
168
+ dated posts. A project can override a bundled theme by creating
169
+ `themes/<name>/` inside the project; local themes take precedence.
170
+
171
+ Themes are plain folders, so a custom theme is just a directory with a
172
+ `theme.toml`, `templates/`, and optionally `static/` and `scaffold/`. See
173
+ [docs/themes.md](docs/themes.md) for the full specification.
174
+
175
+ ## Command reference
176
+
177
+ | Command | Description |
178
+ | --- | --- |
179
+ | `jamstack init [PATH] [--theme NAME] [--force]` | Create a new site. |
180
+ | `jamstack build [--clean] [--drafts]` | Build the site into `dist/`. |
181
+ | `jamstack serve [--port PORT] [--drafts]` | Build, serve, and rebuild on change. |
182
+ | `jamstack themes` | List available themes. |
183
+ | `jamstack --version` | Print the installed version. |
184
+
185
+ `python -m jamstack` behaves like the `jamstack` command.
186
+
187
+ ## Migrating from 1.x
188
+
189
+ The 1.x engine (`settings.py`, `posts.json`, `static.py`, html5up themes) is
190
+ replaced entirely in 2.0. See [docs/migration.md](docs/migration.md) for a
191
+ step-by-step conversion guide, or keep using 1.x with:
192
+
193
+ ```bash
194
+ python -m pip install "jamstack<2"
195
+ ```
196
+
197
+ ## Development
198
+
199
+ ```bash
200
+ git clone git@github.com:jamstackpy/jamstack.git
201
+ cd jamstack
202
+ git switch stable
203
+ python -m venv .venv
204
+ . .venv/bin/activate
205
+ pip install -e ".[dev]"
206
+ pre-commit install --hook-type pre-commit --hook-type commit-msg
207
+ pytest
208
+ ```
209
+
210
+ Commits follow [Conventional Commits](https://www.conventionalcommits.org/);
211
+ see [CONTRIBUTING.md](CONTRIBUTING.md).
212
+
213
+ ## License
214
+
215
+ MIT. The 1.x html5up templates were distributed under CC BY 3.0 and are no
216
+ longer part of this branch.