Package: ursus-ssg
UrsusUrsus is the static site generator used byAll About Berlinand mypersonal website. It turns Markdown files andJinjatemplates 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.SetupInstallationInstall Ursus with pip:pipinstallursus-ssgGetting startedCallursusto generate a static website. Callursus --helpto see the command line options it supports.By default, Ursus looks for 3 directories, relative to the current directory:It looks for content in./contentIt looks for page templates in./templatesIt generates a static website in./outputFor example, create a markdown file and save it as./content/posts/first-post.md.---
title: Hello world!
description: This is an example pagedate_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.jinjaCallursusto generate a statuc website. It will create./output/posts/first-post.html.Configuring UrsusTo configure Ursus, create a configuration file.# Example Ursus config file# Find all configuration options in `ursus/config.py`.fromursus.configimportconfigconfig.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=Trueconfig.minify_css=TrueIf you call your configuration fileursus_config.py, Ursus loads it automatically.my-website/
├─ ursus_config.py
├─ content/
└─ templates/You can also load a configuration file with the-wargument.ursus-c/path/to/config.pyWatching for changesUrsus can rebuild your website when the content or templates change.# Rebuild when content or templates changeursus-w
ursus--watchIt can only rebuild the pages that changed. This is much faster, but it does not work perfectly.# Only rebuild the pages that changedursus-wf
ursus--watch--fastServing the websiteUrsus can serve the website it generates. This is useful for testing.# Serve the static website on port 80ursus-s
ursus--serve80This is not meant for production. Use nginx, Caddy or some other static file server for that.How Ursus worksContext processorsgenerate the context used to render templates. The context is just a big dictionary.Renderersuse the context and the templates to render the parts of the final website: pages, thumbnails, static assets, etc.ContentContentis what fills your website: text, images, videos, PDFs. Content is usuallyrenderedto 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 changeconfig.content_path.EntriesA single piece of content is called anEntry. This can be a single image, a single markdown file, etc.Each Entry has aURI. 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.mdisposts/first-post.md.ContextTheContextcontains 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 processorseach add specific data to the context. For example,MarkdownProcessoradds your.mdcontent tocontext.entries.# 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',}TemplatesTemplatesare 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 changeconfig.templates_path.RenderersRenderersuse the Context and the Templates to generate parts of your static website. For example,JinjaRendererrenders Jinja templates,ImageTransformRendererconverts and resizes your images, andStaticAssetRenderercopies your static assets.OutputThis is the final static website generated by Ursus. Ursus generates a static website in./output, unless you changeconfig.output_path.The content of the output directory is ready to be served by any static file server.How context processors workContext 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.mdand./content/posts/_post-draft.mdare ignored.MarkdownProcessorTheMarkdownProcessorcreates context for all.mdfiles incontent_path. The markdown content is in thebodyattribute.{'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 contextrelated_*keys are replaced by a list of related entry dictsdate_keys are converted todatetimeobjectsOther attributes are added to the entry object.Use responsive images based onconfig.image_transformssettings.<img>are converted to<figure>or<picture>tags when appropriate.Images are lazy-loaded with theloading=lazyattribute.Jinja tags ({{ ... }}and{% ... %}) are rendered as-is. You can use{% include %}and{{ variables }}in your content.GetEntriesProcessorTheGetEntriesProcessoradds aget_entriesmethod to the context. It's used to get a list of entries of a certain type, and sort it.{%setposts=get_entries('posts',filter_by=filter_function,sort_by='date_created',reverse=True)%}{%forpostinposts%}...GitDateProcessorAdds thedate_updatedattribute to all Entries. It uses the file's last commit date.{'entries':{'posts/first-post.md':{'date_updated':datetime(2022,10,10),# ...},# ...},}ImageProcessorAdds images and PDFs Entries to the context. Dimensions and image transforms are added to each Entry. Use in combination withconfig.image_transforms.{'entries':{'images/hello.jpg':{'width':320,'height':240,'image_transforms':[{'is_default':True,'input_mimetype':'image/jpeg','output_mimetype':'image/webp',# ...},# ...]},# ...},}How renderers workRenderers use context and templates to generate parts of the static website.AGeneratortakes your Content and your Templates and produces an Output. It's a recipe to turn your content into a final result. The defaultStaticSiteGeneratorgenerates a static website. You can write your own Generator to output an eBook, a PDF, or anything else.ImageTransformRendererRenders images in your content directory.Images are converted and resized according toconfig.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 unlessconfig.image_transformsis set:fromursus.configimportconfigconfig.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'),},}JinjaRendererRenders*.jinjafiles in the templates directory.The output file has the same name and relative path as the template, but the.jinjaextension is removed.my-website/
├─ templates/
│  ├─ contact.html.jinja
│  ├─ sitemap.xml.jinja
│  └─ posts/
│     └─ index.html.jinja
└─ output/
   ├─ contact.html
   ├─ sitemap.xml
   └─ posts/
      └─ index.htmlFiles namedentry.*.jinjawill 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.htmlFiles 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.htmlStaticAssetRendererCopies all files under./templatesexcept.jinjafiles 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.jsIt uses hard links instead of copying files, so it does not use extra disk space.How generators workGenerators bring it all together. A generator takes all of your files, and generates some final product. There is onlyStaticSiteGenerator, which generates a static website. Custom generators could generate a book or a slideshow from the same content and templates.How linters workUrsus supports linter. They verify the content whenursus lintis called. You can find examples inursus/linters.