Lightweight

module lightweight

Lightweight is a "Code over configuration" static site generator.

from lightweight import Site, markdown, paths, jinja, template, rss, atom, sass


def blog_posts(source):
    post_template = template('posts/_template.html')
    # Use globs to select files. # source = 'posts/**.md'
    return (markdown(path, post_template) for path in paths(source))


site = Site(url='https://example.org/')

# Render an index page from Jinja2 template.
site.include('index.html', jinja('pages/index.html'))

# Render markdown blog posts.
[site.include(f'posts/{post.source_path.stem}.html', post) for post in blog_posts('posts/**.md')]
site.include('posts.html', jinja('pages/posts.html'))

# Syndicate RSS and Atom feeds.
site.include('posts.atom.xml', atom(site['posts']))
site.include('posts.rss.xml', rss(site['posts']))

# Render SASS to CSS.
site.include('css/style.css', sass('styles/style.scss'))

# Include a copy of a directory.
site.include('img')
site.include('js')

# Execute all included content.
site.generate()

module lightweight.content


module lightweight.content.content

class Content

An abstract content that can be included by a Site.

def write(self, path: GenPath, ctx: GenContext)

Write the content to the file at path.


module lightweight.content.copies

class DirectoryCopy

Site content which is a copy of a directory from the path provided as source.

source: Union[Path, str]

def write(self, path: GenPath, ctx: GenContext)

class FileCopy

Site content which is a copy of a file from the path provided as source.

source: Union[Path, str]

def write(self, path: GenPath, ctx: GenContext)

def copy(path: Union[str, Path])

Copy file or directory at path, ensuring their existence.


module lightweight.content.feeds

Generate RSS/Atom feeds from the content included by a site.

object atom

An instance of AtomGenerator.

object rss

An instance of RssGenerator.

module lightweight.content.jinja

Render Jinja templates in place of Lightweight Content.

class JinjaPage

Content rendered from a Jinja Template.

template: Template

source_path: Path

props: Dict[str, Any]

def render(self, ctx)

def write(self, path: GenPath, ctx: GenContext)

def from_ctx(func: Callable[GenContext, T]) ⟶ Callable[GenContext, T]

Evaluate the provided function lazily from context at the point of generation.

 from lightweight import jinja, from_ctx

 ...

 def post_tasks(ctx: GenContext):
     return [task for task in ctx.tasks if task.path.parts[0] == 'posts']

 ...

 site.include('posts', jinja('posts.html', posts=from_ctx(post_tasks)))

def jinja(template_path: Union[str, Path], **props) ⟶ JinjaPage

Renders the page at path with provided parameters.

Templates are resolved from the current directory (cwd).


module lightweight.content.lwmd

Lightweight Markdown toolkit.

LwRenderer is an implementation of the mistune.Renderer adding table of contents, and overriding some elements.

class LwRenderer

Renders Markdown overriding the following:

Also provides a way to compile a table of contents via LwRenderer.table_of_contents.

toc: TocBuilder

Rendering a given link or email address.

:param link: link content or email address. :param is_email: whether this is an email or not.

def block_code(self, code, lang = None)

Rendering block level code. pre > code.

:param code: text content of the code block. :param lang: language of the given code.

def block_html(self, html)

Rendering block level pure html content.

:param html: text content of the html snippet.

def block_quote(self, text)

Rendering <blockquote> with the given text.

:param text: text content of the blockquote.

def codespan(self, text)

Rendering inline code text.

:param text: text content for inline code.

def double_emphasis(self, text)

Rendering strong text.

:param text: text content for emphasis.

def emphasis(self, text)

Rendering emphasis text.

:param text: text content for emphasis.

def escape(self, text)

Rendering escape sequence.

:param text: text content.

def footnote_item(self, key, text)

Rendering a footnote item.

:param key: identity key for the footnote. :param text: text content of the footnote.

def footnote_ref(self, key, index)

Rendering the ref anchor of a footnote.

:param key: identity key for the footnote. :param index: the index count of current footnote.

def footnotes(self, text)

Wrapper for all footnotes.

:param text: contents of all footnotes.

def header(self, text, level, raw = None)

def hrule(self)

Rendering method for <hr> tag.

def image(self, src, title, text)

Rendering a image with title and text.

:param src: source link of the image. :param title: title text of the image. :param text: alt text of the image.

def inline_html(self, html)

Rendering span level pure html content.

:param html: text content of the html snippet.

def linebreak(self)

Rendering line break like <br>.

def list(self, body, ordered = True)

Rendering list tags like <ul> and <ol>.

:param body: body contents of the list. :param ordered: whether this list is ordered or not.

def list_item(self, text)

Rendering list item snippet. Like <li>.

def newline(self)

Rendering newline element.

def paragraph(self, text)

Rendering paragraph tags. Like <p>.

def placeholder(self)

Returns the default, empty output value for the renderer.

All renderer methods use the '+=' operator to append to this value. Default is a string so rendering HTML can build up a result string with the rendered Markdown.

Can be overridden by Renderer subclasses to be types like an empty list, allowing the renderer to create a tree-like structure to represent the document (which can then be reprocessed later into a separate format like docx or pdf).

def reset(self)

def strikethrough(self, text)

Rendering ~~strikethrough~~ text.

:param text: text content for strikethrough.

def table(self, header, body)

Rendering table element. Wrap header and body in it.

:param header: header part of the table. :param body: body part of the table.

def table_cell(self, content, **flags)

Rendering a table cell. Like <th> <td>.

:param content: content of current table cell. :param header: whether this is header or not. :param align: align of current table cell.

def table_of_contents(self, level) ⟶ TableOfContents

def table_row(self, content)

Rendering a table row. Like <tr>.

:param content: content of current table row.

def text(self, text)

Rendering unformatted text.

:param text: text content.

class Section

Table of contents item.

html: str

id: str

sections: List[Section]

title: str

slug: str

class TableOfContents

Table of contents of a Markdown document.

Composed from a list of sections.

The id property is set to the root <ul> element.

html: str

id: str

sections: List[Section]

class TocBuilder

Table of Contents builder used by TocMixin.

entries: List[TocEntry]

def append(self, entry: TocEntry)

Add an item to the table of contents.

def compile(self, level) ⟶ TableOfContents

Create a new Table of contents.

class TocEntry

An entry of the TocBuilder.

count: method_descriptor

index: method_descriptor

level: int

slug: str

title: str

class TocMixin

A mixin used by LwRenderer compiling a table of contents.

Note: requires calling reset after rendering.

toc: TocBuilder

def header(self, text, level, raw = None)

def reset(self)

def table_of_contents(self, level) ⟶ TableOfContents


module lightweight.content.markdown

Lightweight Markdown Content.

Usage:

from lightweight import markdown, template

...

site.include('hello.html', markdown('posts/hello.md', template('templates/post.html')))

class MarkdownPage

Content generated from rendering a markdown file to a Jinja template.

template: Template

source_path: Path

text: str

renderer: Type[LwRenderer]

title: Optional[str]

summary: Optional[str]

created: Optional[datetime]

updated: Optional[datetime]

front_matter: Dict[str, Any]

props: Dict[str, Any]

def render(self, ctx: GenContext) ⟶ RenderedMarkdown

Render Markdown to html, extracting the ToC.

def write(self, path: GenPath, ctx: GenContext)

Writes a rendered Jinja template with rendered Markdown, parameters from front-matter and code to the file provided path.

class RenderedMarkdown

The result of parsing and rendering Markdown.

html: str

preview_html: str

toc: TableOfContents

def markdown(md_path: Union[str, Path], template: Template, renderer = <class 'lightweight.content.lwmd.LwRenderer'>, **kwargs) ⟶ MarkdownPage

Create a markdown page that can be included by a Site. Markdown page is compiled from a markdown file at path (*.md) and a Jinja Template.

Provided key-word arguments are passed as props to the template on render. Such props can also be lazily evaluated from GenContext by using the from_ctx(func) decorator.


module lightweight.content.sass

SCSS/Sass Lightweight Content.

Allows rendering single file, style directories and corresponding sourcemaps.

Usage:

from lightweight import sass

...

site.include('css/style.css', sass('styles/style.scss', sourcemap=False))

class Sass

Content created by compiling Sass and SCSS.

path: Path

sourcemap: bool

def write(self, path: GenPath, ctx: GenContext)

def sass(location: str, sourcemap: bool = True) ⟶ Sass

Run Sass/SCSS compiler on files at location. Can be a file name or a directory.

Sourcemaps are written under "<location>.map".

 site.include('css/style.css', sass('styles/style.scss'))

Creates 2 files: css/styles.css and css/styles.css.map.


module lightweight.empty

empty value is useful for specifying actually missing. See Site.copy signature for an example.

empty is a singleton so it can always be checked as value is empty.

class Empty

An alternative to None, when None is a legitimate option for a function argument, but it also such an argument can be omitted. See Site.copy(...)

Empty is a singleton.

object empty

An instance of Empty.

module lightweight.errors

class AbsolutePathIncluded

args: getset_descriptor

with_traceback: method_descriptor

class IncludedDuplicate

args: getset_descriptor

with_traceback: method_descriptor

class InvalidCommand

An invalid CLI command.

args: getset_descriptor

with_traceback: method_descriptor


module lightweight.files

Lightweight utilities for working with files.

@contextmanager
def directory(location: Union[str, Path])

Execute following statements using the provided location as "cwd" (current working directory).

 from pathlib import Path

 project_location = Path(__file__).absolute().parent

 with directory(project_location):
     site.include('index.html')

def paths(pattern: Union[str, Path]) ⟶ List[Path]

List paths matching the provided glob pattern.

 >>> print(paths('lightweight/**/__init__.py'))
 [PosixPath('lightweight/__init__.py'), PosixPath('lightweight/content/__init__.py')]

 >>> print(paths('lightweight/*.typed'))
 [PosixPath('lightweight/py.typed')]

module lightweight.generation

class GenContext

A generation context. Contains the data useful during the generation: the site and the list of tasks to be executed in the process.

The context is created by a Site upon starting generation and provided to the Content.write(path, ctx) method as a second parameter.

site: Site

out: Path

tasks: Tuple[GenTask, ...]

generated: datetime

version: str

def path(self, p: Union[Path, str]) ⟶ GenPath

Create a new GenPath in this generation context from a regular path.

class GenPath

A path for writing content. It contains both, the relative path (as specified by site.include(relative_path, content)) and the real path (an absolute path which in site’s out).

File system operations performed on real_path; relative path is used for all other operations, e.g. __str__ returns a relative path representation.

Also, proper URL can be obtained from generation path

 site = Site('https://example.org/')
 resources = GenPath(Path('resources'), out='/tmp/out', url_factory=lambda location: site/location)

 teapot: GenPath = resources / 'teapot.txt'

 print(str(teapot))  # resources/teapot.txt
 print(teapot.absolute())  # '/tmp/out/resources/teapot.txt'
 print(teapot.url)  # https://example.org/resources/teapot.txt

 print(teapot.exists())  # False

 # Create a file with text.
 teapot.create('I am a teapot')

 print(teapot.exists())  # True

location: str

name: str

parent: GenPath

parts: Tuple[str, ...]

real_path: Path

suffix: str

url: str

relative_path: Path

out: Path

url_factory: Callable[str, str]

def absolute(self) ⟶ Path

An alias of GenPath.real_path.

def create(self, contents: Union[str, bytes]) ⟶ None

Create a file with provided contents. Contents can be str or bytes.

def exists(self) ⟶ bool

Checks if file exists

def mkdir(self, mode = 511, parents = True, exist_ok = True)

Create directory at path.

Differs from the defaults in other Python mkdir signatures: creates whole parent hierarchy of directories if they do not exist.

def open(self, mode = 'r', buffering = -1, encoding = None, errors = None, newline = None) ⟶ IO[Any]

Open the file. Same as Path.open(...)

def with_name(self, name: str) ⟶ GenPath

Create a new GenPath which differs from the current only by file name.

def with_suffix(self, suffix: str) ⟶ GenPath

Create a new GenPath with a different file suffix (extension).

class GenTask

A task executed by Site during generation.

All of site’s tasks can be accessed during generation via GenContext.

Generation Task objects differ from the Site’s IncludedContent by having the path of GenPath type (instead of regular Path). GenPath has knowledge of the generation out directory, and is passed directly to content.write(path, ctx).

Includes cwd (current working directory) in which the original content was created. Content write(...) operates from this directory.

path: GenPath

ctx: GenContext

content: Content

cwd: str


module lightweight.site

class Author

An author. Mostly used by RSS/Atom.

email: Optional[str]

name: Optional[str]

class Site

A static site for generation, which is basically a collection of Content.

Site is one of the few mutable Lightweight components. It is available to content during write, as a property of the provided ctx.

The only required parameter is the URL of the site. Other parameters may be useful for different content types.

The following code output to the out directory the following content:

 site = Site('https://example.org/')

 site.include('index.html', jinja('index.html'))
 site.include('about.html', jinja('about.html'))
 site.include('css/style.css', sass('styles/main.scss'))
 site.include('img')
 site.include('js')

 site.generate(out='out')

url: str

content: Includes

title: Optional[str]

icon_url: Optional[str]

logo_url: Optional[str]

description: Optional[str]

authors: Set[Author]

language: Optional[str]

copyright: Optional[str]

updated: Optional[datetime]

def copy(self, url: Union[str, Empty] = <lightweight.empty>, content: Union[Includes, Empty] = <lightweight.empty>, title: Union[str, None, Empty] = <lightweight.empty>, icon_url: Union[str, None, Empty] = <lightweight.empty>, description: Union[str, None, Empty] = <lightweight.empty>, author_name: Union[str, None, Empty] = <lightweight.empty>, author_email: Union[str, None, Empty] = <lightweight.empty>, language: Union[str, None, Empty] = <lightweight.empty>, copyright: Union[str, None, Empty] = <lightweight.empty>, updated: Union[datetime, None, Empty] = <lightweight.empty>) ⟶ Site

Creates a new Site which copies the attributes of the current one.

Some property values can be overriden, by providing them to this method.

def create_ctx(self, out: Path) ⟶ GenContext

Override for custom context types.

def generate(self, out: Union[str, Path] = 'out')

Generate the site in directory provided as out.

If the out directory does not exist — it will be created along with its whole hierarchy.

If the out directory already exists – it will be deleted with all of it contents.

def include(self, location: str, content: Union[Content, Site, str, None] = None)

Include the content at the location.

Note the content write is executed only upon calling Site.generate().

The location cannot be absolute. It cannot start with a forward slash.

During the include the cwd (current working directory) is recorded. The content’s write will be executed from this directory.

Check overloads for alternative signatures.

Overloads:
def include(self, location: str)

Include a file, a directory, or multiple files with a glob pattern.

def include(self, location: str, content: Content)

Include the content at the provided location.

def include(self, location: str, content: Site)

Include the content at the provided location.

def include(self, location: str, content: str)

Copy files from content to location.


module lightweight.template

This module configures a Jinja environment to use with the app (jinja_env). This environment is configured to locate templates and resolve their inner references according to the current working directory (cwd).

Also a strict undefined is enabled. This means that any operations with an undefined Jinja template parameter will result in an error. This is a safer approach in contrast to Jinja defaults.

template is a shortcut for loading templates using this environment.

object jinja_env

An instance of Environment.

def template(location: Union[str, Path]) ⟶ Template

A shorthand for loading a Jinja2 template from the current working directory.