ursus-ssg


Nameursus-ssg JSON
Version 1.3.0 PyPI version JSON
download
home_pagehttp://github.com/all-about-berlin/ursus
SummaryStatic site generator
upload_time2024-08-04 20:31:21
maintainerNone
docs_urlNone
authorNicolas Bouliane
requires_python>=3.11
licenseMIT
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 0.29723s