# Ursus
Ursus is the static site generator used by [All About Berlin](https://allaboutberlin.com) and my [personal website](https://nicolasbouliane.com). It turns Markdown files and [Jinja](https://jinja.palletsprojects.com/) templates into a static website.
It also renders images in different sizes, renders SCSS, minifies JS and generates Lunr.js search indexes.
This project is in active use and development.
## Setup
### Installation
Install Ursus with pip:
```bash
pip install ursus-ssg
```
### Getting started
Call `ursus` to generate a static website. Call `ursus --help` to see the command line options it supports.
By default, Ursus looks for 3 directories, relative to the current directory:
- It looks for content in `./content`
- It looks for page templates in `./templates`
- It generates a static website in `./output`
For example, create a markdown file and save it as `./content/posts/first-post.md`.
```markdown
---
title: Hello world!
description: This is an example page
date_created: 2022-10-10
---
## Hello beautiful world
*This* is a template. Pretty cool eh?
```
Then, create a page template and save it as `./templates/posts/entry.html.jinja`.
```
<!DOCTYPE html>
<html>
<head>
<title>{{ entry.title }}</title>
<meta name="description" content="{{ entry.description }}">
</head>
<body>
{{ entry.body }}
Created on {{ entry.date_created }}
</body>
</html>
```
Your project should now look like this:
```
my-website/ <- You are here
├─ content/
│ └─ posts/
│ └─ first-post.md
└─ templates/
└─ posts/
└─ entry.html.jinja
```
Call `ursus` to generate a statuc website. It will create `./output/posts/first-post.html`.
### Configuring Ursus
To configure Ursus, create a configuration file.
```python
# Example Ursus config file
# Find all configuration options in `ursus/config.py`.
from ursus.config import config
config.content_path = Path(__file__).parent / 'blog'
config.templates_path = Path(__file__).parent / 'templates'
config.output_path = Path(__file__).parent.parent / 'dist'
config.site_url = 'https://allaboutberlin.com'
config.minify_js = True
config.minify_css = True
```
If you call your configuration file `ursus_config.py`, Ursus loads it automatically.
```
my-website/
├─ ursus_config.py
├─ content/
└─ templates/
```
You can also load a configuration file with the `-w` argument.
```bash
ursus -c /path/to/config.py
```
### Watching for changes
Ursus can rebuild your website when the content or templates change.
```bash
# Rebuild when content or templates change
ursus -w
ursus --watch
```
It can only rebuild the pages that changed. This is much faster, but it does not work perfectly.
```bash
# Only rebuild the pages that changed
ursus -wf
ursus --watch --fast
```
### Serving the website
Ursus can serve the website it generates. This is useful for testing.
```bash
# Serve the static website on port 80
ursus -s
ursus --serve 80
```
This is not meant for production. Use nginx, Caddy or some other static file server for that.
## How Ursus works
1. **Context processors** generate the context used to render templates. The context is just a big dictionary.
2. **Renderers** use the context and the templates to render the parts of the final website: pages, thumbnails, static assets, etc.
### Content
**Content** is what fills your website: text, images, videos, PDFs. Content is usually *rendered* to create a working website. Some content (like Markdown files) is rendered with Templates, and other (like images) is converted to a different file format.
Ursus looks for content in `./content`, unless you change `config.content_path`.
### Entries
A single piece of content is called an **Entry**. This can be a single image, a single markdown file, etc.
Each Entry has a **URI**. This is the Entry's unique identifier. The URI is the Entry's path relative to the content directory. For example, the URI of `./content/posts/first-post.md` is `posts/first-post.md`.
### Context
The **Context** contains the information needed to render your website. It's just a big dictionary, and you can put anything in it.
`context['entries']` contains is a dictionary of all your entries. The key is the Entry URI.
**Context processors** each add specific data to the context. For example, `MarkdownProcessor` adds your `.md` content to `context.entries`.
```python
# Example context
{
'entries': {
'posts/first-post.md': {
'title': 'Hello world!',
'description': 'This is an example page',
'date_created': datetime(2022, 10, 10),
'body': '<h2>Hello beautiful world</h2><p>...',
},
'posts/second-post.md': {
# ...
},
},
# Context processors can add more things to the context
'blog_title': 'Example blog',
'site_url': 'https://example.com/blog',
}
```
### Templates
**Templates** are used to render your Content. They are the theme of your website. Jinja templates, Javascript, CSS and theme images belong in the templates directory.
Ursus looks for templates in `./templates`, unless you change `config.templates_path`.
### Renderers
**Renderers** use the Context and the Templates to generate parts of your static website. For example, `JinjaRenderer` renders Jinja templates, `ImageTransformRenderer` converts and resizes your images, and `StaticAssetRenderer` copies your static assets.
### Output
This is the final static website generated by Ursus. Ursus generates a static website in `./output`, unless you change `config.output_path`.
The content of the output directory is ready to be served by any static file server.
## How context processors work
Context processors transform the context, which is a dict with information about each of your Entries.
Context processors ignore file and directory names that start with `.` or `_`. For example, `./content/_drafts/hello.md` and `./content/posts/_post-draft.md` are ignored.
### MarkdownProcessor
The `MarkdownProcessor` creates context for all `.md` files in `content_path`. The markdown content is in the `body` attribute.
```python
{
'entries': {
'posts/first-post.md': {
'title': 'Hello world!',
'description': 'This is an example page',
'date_created': datetime(2022, 10, 10),
'body': '<h2>Hello beautiful world</h2><p>...',
},
# ...
},
}
```
It makes a few changes to the default markdown output:
- Put the front matter in the context
- `related_*` keys are replaced by a list of related entry dicts
- `date_` keys are converted to `datetime` objects
- Other attributes are added to the entry object.
- Use responsive images based on `config.image_transforms` settings.
- `<img>` are converted to `<figure>` or `<picture>` tags when appropriate.
- Images are lazy-loaded with the `loading=lazy` attribute.
- Jinja tags (`{{ ... }}` and `{% ... %}`) are rendered as-is. You can use `{% include %}` and `{{ variables }}` in your content.
### GetEntriesProcessor
The `GetEntriesProcessor` adds a `get_entries` method to the context. It's used to get a list of entries of a certain type, and sort it.
```jinja
{% set posts = get_entries('posts', filter_by=filter_function, sort_by='date_created', reverse=True) %}
{% for post in posts %}
...
```
### GitDateProcessor
Adds the `date_updated` attribute to all Entries. It uses the file's last commit date.
```python
{
'entries': {
'posts/first-post.md': {
'date_updated': datetime(2022, 10, 10),
# ...
},
# ...
},
}
```
### ImageProcessor
Adds images and PDFs Entries to the context. Dimensions and image transforms are added to each Entry. Use in combination with `config.image_transforms`.
```python
{
'entries': {
'images/hello.jpg': {
'width': 320,
'height': 240,
'image_transforms': [
{
'is_default': True,
'input_mimetype': 'image/jpeg',
'output_mimetype': 'image/webp',
# ...
},
# ...
]
},
# ...
},
}
```
## How renderers work
Renderers use context and templates to generate parts of the static website.
A **Generator** takes your Content and your Templates and produces an Output. It's a recipe to turn your content into a final result. The default **StaticSiteGenerator** generates a static website. You can write your own Generator to output an eBook, a PDF, or anything else.
### ImageTransformRenderer
Renders images in your content directory.
- Images are converted and resized according to `config.image_transforms`.
- Files that can't be transformed (PDF to PDF) are copied as-is to the output directory.
- Images that can't be resized (SVG to anything) are copied as-is to the output directory.
- Image EXIF data is removed.
This renderer does nothing unless `config.image_transforms` is set:
```python
from ursus.config import config
config.image_transforms = {
# ./content/images/test.jpg
# ---> ./output/images/test.jpg
# ./content/images/test.pdf
# ---> ./output/images/test.pdf
'': {
'include': ('images/*', 'documents/*'),
'output_types': ('original'),
},
# ./content/images/test.jpg
# ---> ./output/images/content2x/test.jpg
# ---> ./output/images/content2x/test.webp
'content2x': {
'include': ('images/*', 'illustrations/*'),
'exclude': ('*.pdf', '*.svg'),
'max_size': (800, 1200),
'output_types': ('webp', 'original'),
},
# ./content/documents/test.pdf
# ---> ./output/documents/pdfPreviews/test.png
# ---> ./output/documents/pdfPreviews/test.webp
'pdfPreviews': {
'include': 'documents/*',
'max_size': (300, 500),
'output_types': ('webp', 'png'),
},
}
```
### JinjaRenderer
Renders `*.jinja` files in the templates directory.
The output file has the same name and relative path as the template, but the `.jinja` extension is removed.
```
my-website/
├─ templates/
│ ├─ contact.html.jinja
│ ├─ sitemap.xml.jinja
│ └─ posts/
│ └─ index.html.jinja
└─ output/
├─ contact.html
├─ sitemap.xml
└─ posts/
└─ index.html
```
Files named `entry.*.jinja` will render every entry with the same relative path.
```
my-website/
├─ content/
│ └─ posts/
│ ├─ first-post.md
│ ├─ second-post.md
│ └─ _draft.md
├─ templates/
│ └─ posts/
│ └─ entry.html.jinja
└─ output/
└─ posts/
├─ first-post.html
└─ second-post.html
```
Files or directory names that start with `.` or `_` are not rendered.
```
my-website/
├─ content/
│ └─ posts/
│ ├─ hello-world.md
│ ├─ .hidden.md
│ └─ _drafts
│ └─ not-rendered.md
├─ templates/
│ └─ posts/
│ └─ entry.html.jinja
└─ output/
└─ posts/
└─ hello-world.html
```
### StaticAssetRenderer
Copies all files under `./templates` except `.jinja` files to the same subdirectory in `./output`. Files starting with `.` are ignored. Files and directories starting with `_` are ignored.
```
my-website/
├─ templates/
│ ├─ _ignored.jpg
│ ├─ styles.css
│ ├─ images/
│ │ └─ hello.png
│ └─ js/
│ └─ test.js
└─ output/
├─ styles.css
├─ images/
│ └─ hello.png
└─ js/
└─ test.js
```
It uses hard links instead of copying files, so it does not use extra disk space.
## How generators work
Generators bring it all together. A generator takes all of your files, and generates some final product. There is only `StaticSiteGenerator`, which generates a static website. Custom generators could generate a book or a slideshow from the same content and templates.
## How linters work
Ursus supports linter. They verify the content when `ursus lint` is called. You can find examples in `ursus/linters`.
Raw data
{
"_id": null,
"home_page": "http://github.com/all-about-berlin/ursus",
"name": "ursus-ssg",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.11",
"maintainer_email": null,
"keywords": null,
"author": "Nicolas Bouliane",
"author_email": "contact@nicolasbouliane.com",
"download_url": "https://files.pythonhosted.org/packages/63/ca/ba4fac30f5d04b1b73d1589f3f97e6bdef81b17aa69982260d72f03ff251/ursus_ssg-1.3.0.tar.gz",
"platform": null,
"description": "# Ursus\n\nUrsus is the static site generator used by [All About Berlin](https://allaboutberlin.com) and my [personal website](https://nicolasbouliane.com). It turns Markdown files and [Jinja](https://jinja.palletsprojects.com/) templates into a static website.\n\nIt also renders images in different sizes, renders SCSS, minifies JS and generates Lunr.js search indexes.\n\nThis project is in active use and development.\n\n## Setup\n\n### Installation\n\nInstall Ursus with pip:\n\n```bash\npip install ursus-ssg\n```\n\n### Getting started\n\nCall `ursus` to generate a static website. Call `ursus --help` to see the command line options it supports.\n\nBy default, Ursus looks for 3 directories, relative to the current directory:\n\n- It looks for content in `./content`\n- It looks for page templates in `./templates`\n- It generates a static website in `./output`\n\nFor example, create a markdown file and save it as `./content/posts/first-post.md`.\n\n```markdown\n---\ntitle: Hello world!\ndescription: This is an example page\ndate_created: 2022-10-10\n---\n\n## Hello beautiful world\n\n*This* is a template. Pretty cool eh?\n```\n\nThen, create a page template and save it as `./templates/posts/entry.html.jinja`. \n\n```\n<!DOCTYPE html>\n<html>\n<head>\n <title>{{ entry.title }}</title>\n <meta name=\"description\" content=\"{{ entry.description }}\">\n</head>\n<body>\n {{ entry.body }}\n\n Created on {{ entry.date_created }}\n</body>\n</html>\n```\n\nYour project should now look like this:\n\n```\nmy-website/ <- You are here\n\u251c\u2500 content/\n\u2502 \u2514\u2500 posts/\n\u2502 \u2514\u2500 first-post.md\n\u2514\u2500 templates/\n \u2514\u2500 posts/\n \u2514\u2500 entry.html.jinja\n```\n\nCall `ursus` to generate a statuc website. It will create `./output/posts/first-post.html`.\n\n### Configuring Ursus\n\nTo configure Ursus, create a configuration file.\n\n```python\n# Example Ursus config file\n# Find all configuration options in `ursus/config.py`.\nfrom ursus.config import config\n\nconfig.content_path = Path(__file__).parent / 'blog'\nconfig.templates_path = Path(__file__).parent / 'templates'\nconfig.output_path = Path(__file__).parent.parent / 'dist'\n\nconfig.site_url = 'https://allaboutberlin.com'\n\nconfig.minify_js = True\nconfig.minify_css = True\n```\n\nIf you call your configuration file `ursus_config.py`, Ursus loads it automatically.\n\n```\nmy-website/\n\u251c\u2500 ursus_config.py\n\u251c\u2500 content/\n\u2514\u2500 templates/\n```\n\nYou can also load a configuration file with the `-w` argument.\n\n```bash\nursus -c /path/to/config.py\n```\n\n### Watching for changes\n\nUrsus can rebuild your website when the content or templates change.\n\n```bash\n# Rebuild when content or templates change\nursus -w\nursus --watch\n```\n\nIt can only rebuild the pages that changed. This is much faster, but it does not work perfectly.\n\n```bash\n# Only rebuild the pages that changed\nursus -wf\nursus --watch --fast\n```\n\n### Serving the website\n\nUrsus can serve the website it generates. This is useful for testing.\n\n```bash\n# Serve the static website on port 80\nursus -s\nursus --serve 80\n```\n\nThis is not meant for production. Use nginx, Caddy or some other static file server for that.\n\n## How Ursus works\n\n1. **Context processors** generate the context used to render templates. The context is just a big dictionary.\n2. **Renderers** use the context and the templates to render the parts of the final website: pages, thumbnails, static assets, etc.\n\n### Content\n\n**Content** is what fills your website: text, images, videos, PDFs. Content is usually *rendered* to create a working website. Some content (like Markdown files) is rendered with Templates, and other (like images) is converted to a different file format.\n\nUrsus looks for content in `./content`, unless you change `config.content_path`.\n\n### Entries\n\nA single piece of content is called an **Entry**. This can be a single image, a single markdown file, etc.\n\nEach Entry has a **URI**. This is the Entry's unique identifier. The URI is the Entry's path relative to the content directory. For example, the URI of `./content/posts/first-post.md` is `posts/first-post.md`.\n\n### Context\n\nThe **Context** contains the information needed to render your website. It's just a big dictionary, and you can put anything in it.\n\n`context['entries']` contains is a dictionary of all your entries. The key is the Entry URI.\n\n**Context processors** each add specific data to the context. For example, `MarkdownProcessor` adds your `.md` content to `context.entries`.\n\n```python\n# Example context\n{\n 'entries': {\n 'posts/first-post.md': {\n 'title': 'Hello world!',\n 'description': 'This is an example page',\n 'date_created': datetime(2022, 10, 10),\n 'body': '<h2>Hello beautiful world</h2><p>...',\n },\n 'posts/second-post.md': {\n # ...\n },\n },\n # Context processors can add more things to the context\n 'blog_title': 'Example blog',\n 'site_url': 'https://example.com/blog',\n}\n```\n\n### Templates\n\n**Templates** are used to render your Content. They are the theme of your website. Jinja templates, Javascript, CSS and theme images belong in the templates directory.\n\nUrsus looks for templates in `./templates`, unless you change `config.templates_path`.\n\n### Renderers\n\n**Renderers** use the Context and the Templates to generate parts of your static website. For example, `JinjaRenderer` renders Jinja templates, `ImageTransformRenderer` converts and resizes your images, and `StaticAssetRenderer` copies your static assets.\n\n### Output\n\nThis is the final static website generated by Ursus. Ursus generates a static website in `./output`, unless you change `config.output_path`.\n\nThe content of the output directory is ready to be served by any static file server.\n\n## How context processors work\n\nContext processors transform the context, which is a dict with information about each of your Entries.\n\nContext processors ignore file and directory names that start with `.` or `_`. For example, `./content/_drafts/hello.md` and `./content/posts/_post-draft.md` are ignored.\n\n### MarkdownProcessor\n\nThe `MarkdownProcessor` creates context for all `.md` files in `content_path`. The markdown content is in the `body` attribute.\n\n```python\n{\n 'entries': {\n 'posts/first-post.md': {\n 'title': 'Hello world!',\n 'description': 'This is an example page',\n 'date_created': datetime(2022, 10, 10),\n 'body': '<h2>Hello beautiful world</h2><p>...',\n },\n # ...\n },\n}\n```\n\nIt makes a few changes to the default markdown output:\n\n- Put the front matter in the context\n - `related_*` keys are replaced by a list of related entry dicts\n - `date_` keys are converted to `datetime` objects\n - Other attributes are added to the entry object.\n- Use responsive images based on `config.image_transforms` settings.\n- `<img>` are converted to `<figure>` or `<picture>` tags when appropriate.\n- Images are lazy-loaded with the `loading=lazy` attribute.\n- Jinja tags (`{{ ... }}` and `{% ... %}`) are rendered as-is. You can use `{% include %}` and `{{ variables }}` in your content.\n\n### GetEntriesProcessor\n\nThe `GetEntriesProcessor` adds a `get_entries` method to the context. It's used to get a list of entries of a certain type, and sort it.\n\n```jinja\n{% set posts = get_entries('posts', filter_by=filter_function, sort_by='date_created', reverse=True) %}\n\n{% for post in posts %}\n...\n```\n\n### GitDateProcessor\n\nAdds the `date_updated` attribute to all Entries. It uses the file's last commit date.\n\n```python\n{\n 'entries': {\n 'posts/first-post.md': {\n 'date_updated': datetime(2022, 10, 10),\n # ...\n },\n # ...\n },\n}\n```\n\n### ImageProcessor\n\nAdds images and PDFs Entries to the context. Dimensions and image transforms are added to each Entry. Use in combination with `config.image_transforms`.\n\n```python\n{\n 'entries': {\n 'images/hello.jpg': {\n 'width': 320,\n 'height': 240,\n 'image_transforms': [\n {\n 'is_default': True,\n 'input_mimetype': 'image/jpeg',\n 'output_mimetype': 'image/webp',\n # ...\n },\n # ...\n ]\n },\n # ...\n },\n}\n```\n\n## How renderers work\n\nRenderers use context and templates to generate parts of the static website.\n\nA **Generator** takes your Content and your Templates and produces an Output. It's a recipe to turn your content into a final result. The default **StaticSiteGenerator** generates a static website. You can write your own Generator to output an eBook, a PDF, or anything else.\n\n### ImageTransformRenderer\n\nRenders images in your content directory.\n\n- Images are converted and resized according to `config.image_transforms`.\n- Files that can't be transformed (PDF to PDF) are copied as-is to the output directory.\n- Images that can't be resized (SVG to anything) are copied as-is to the output directory.\n- Image EXIF data is removed.\n\nThis renderer does nothing unless `config.image_transforms` is set:\n\n```python\nfrom ursus.config import config\n\nconfig.image_transforms = {\n # ./content/images/test.jpg\n # ---> ./output/images/test.jpg\n # ./content/images/test.pdf\n # ---> ./output/images/test.pdf\n '': {\n 'include': ('images/*', 'documents/*'),\n 'output_types': ('original'),\n },\n # ./content/images/test.jpg\n # ---> ./output/images/content2x/test.jpg\n # ---> ./output/images/content2x/test.webp\n 'content2x': {\n 'include': ('images/*', 'illustrations/*'),\n 'exclude': ('*.pdf', '*.svg'),\n 'max_size': (800, 1200),\n 'output_types': ('webp', 'original'),\n },\n # ./content/documents/test.pdf\n # ---> ./output/documents/pdfPreviews/test.png\n # ---> ./output/documents/pdfPreviews/test.webp\n 'pdfPreviews': {\n 'include': 'documents/*',\n 'max_size': (300, 500),\n 'output_types': ('webp', 'png'),\n },\n}\n```\n\n### JinjaRenderer\n\nRenders `*.jinja` files in the templates directory.\n\nThe output file has the same name and relative path as the template, but the `.jinja` extension is removed.\n\n```\nmy-website/\n\u251c\u2500 templates/\n\u2502 \u251c\u2500 contact.html.jinja\n\u2502 \u251c\u2500 sitemap.xml.jinja\n\u2502 \u2514\u2500 posts/\n\u2502 \u2514\u2500 index.html.jinja\n\u2514\u2500 output/\n \u251c\u2500 contact.html\n \u251c\u2500 sitemap.xml\n \u2514\u2500 posts/\n \u2514\u2500 index.html\n```\n\nFiles named `entry.*.jinja` will render every entry with the same relative path.\n\n```\nmy-website/\n\u251c\u2500 content/\n\u2502 \u2514\u2500 posts/\n\u2502 \u251c\u2500 first-post.md\n\u2502 \u251c\u2500 second-post.md\n\u2502 \u2514\u2500 _draft.md\n\u251c\u2500 templates/\n\u2502 \u2514\u2500 posts/\n\u2502 \u2514\u2500 entry.html.jinja\n\u2514\u2500 output/\n \u2514\u2500 posts/\n \u251c\u2500 first-post.html\n \u2514\u2500 second-post.html\n```\n\nFiles or directory names that start with `.` or `_` are not rendered.\n\n```\nmy-website/\n\u251c\u2500 content/\n\u2502 \u2514\u2500 posts/\n\u2502 \u251c\u2500 hello-world.md\n\u2502 \u251c\u2500 .hidden.md\n\u2502 \u2514\u2500 _drafts\n\u2502 \u2514\u2500 not-rendered.md\n\u251c\u2500 templates/\n\u2502 \u2514\u2500 posts/\n\u2502 \u2514\u2500 entry.html.jinja\n\u2514\u2500 output/\n \u2514\u2500 posts/\n \u2514\u2500 hello-world.html\n```\n\n### StaticAssetRenderer\n\nCopies all files under `./templates` except `.jinja` files to the same subdirectory in `./output`. Files starting with `.` are ignored. Files and directories starting with `_` are ignored.\n\n```\nmy-website/\n\u251c\u2500 templates/\n\u2502 \u251c\u2500 _ignored.jpg\n\u2502 \u251c\u2500 styles.css\n\u2502 \u251c\u2500 images/\n\u2502 \u2502 \u2514\u2500 hello.png\n\u2502 \u2514\u2500 js/\n\u2502 \u2514\u2500 test.js\n\u2514\u2500 output/\n \u251c\u2500 styles.css\n \u251c\u2500 images/\n \u2502 \u2514\u2500 hello.png\n \u2514\u2500 js/\n \u2514\u2500 test.js\n```\n\nIt uses hard links instead of copying files, so it does not use extra disk space.\n\n## How generators work\n\nGenerators bring it all together. A generator takes all of your files, and generates some final product. There is only `StaticSiteGenerator`, which generates a static website. Custom generators could generate a book or a slideshow from the same content and templates.\n\n## How linters work\n\nUrsus supports linter. They verify the content when `ursus lint` is called. You can find examples in `ursus/linters`.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Static site generator",
"version": "1.3.0",
"project_urls": {
"Homepage": "http://github.com/all-about-berlin/ursus"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f273ecf5bf0e79979dfd3dbee3247a9318cf7450331d8efb174355ad01c33068",
"md5": "491c90a6b1ae712f9f776622da9b71d9",
"sha256": "5fcb52e70f9cbb2bd5cc7949b4c3b2dd36ed53e5318d3547a3b7afbd70782ceb"
},
"downloads": -1,
"filename": "ursus_ssg-1.3.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "491c90a6b1ae712f9f776622da9b71d9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.11",
"size": 34766,
"upload_time": "2024-08-04T20:31:20",
"upload_time_iso_8601": "2024-08-04T20:31:20.247593Z",
"url": "https://files.pythonhosted.org/packages/f2/73/ecf5bf0e79979dfd3dbee3247a9318cf7450331d8efb174355ad01c33068/ursus_ssg-1.3.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "63caba4fac30f5d04b1b73d1589f3f97e6bdef81b17aa69982260d72f03ff251",
"md5": "933bd8e2d88ab179fd3bffac29f46ff2",
"sha256": "35cac8f8b261f190bbea0635931dc3cbe39ae73ab3573e6c981beffffdd6ee0d"
},
"downloads": -1,
"filename": "ursus_ssg-1.3.0.tar.gz",
"has_sig": false,
"md5_digest": "933bd8e2d88ab179fd3bffac29f46ff2",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.11",
"size": 31349,
"upload_time": "2024-08-04T20:31:21",
"upload_time_iso_8601": "2024-08-04T20:31:21.642658Z",
"url": "https://files.pythonhosted.org/packages/63/ca/ba4fac30f5d04b1b73d1589f3f97e6bdef81b17aa69982260d72f03ff251/ursus_ssg-1.3.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-08-04 20:31:21",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "all-about-berlin",
"github_project": "ursus",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "ursus-ssg"
}