A renderer for Saga that uses Stencil to turn a RenderingContext into a String


A renderer for Saga that uses Stencil to turn a RenderingContext into a String.

It comes with a free function named stencil which takes an HTML template and a Stencil Environment, and returns a function that goes from RenderingContext to String, which can then be plugged into Saga's writers.



// swift-tools-version:5.5

import PackageDescription

let package = Package(
  name: "Example",
  platforms: [
  dependencies: [
    .package(url: "", from: "1.0.0"),
    .package(url: "", from: "0.5.0"),
    .package(url: "", from: "0.4.0")
  targets: [
      name: "Example",
      dependencies: [


import Foundation
import Saga
import PathKit
import SagaParsleyMarkdownReader
import SagaStencilRenderer
import Stencil

// SiteMetadata is given to every template.
// You can put whatever you want in here, as long as it's Decodable.
struct SiteMetadata: Metadata {
  let url: URL
  let name: String

let siteMetadata = SiteMetadata(
  url: URL(string: "")!,
  name: "Example website"

struct Run {
  static func main() async throws {
    let saga = try Saga(input: "content", output: "deploy", siteMetadata: siteMetadata)

    try await saga
      // All the Markdown files will be parsed to html,
      // using the default EmptyMetadata as the Item's metadata type.
        metadata: EmptyMetadata.self,
        readers: [.parsleyMarkdownReader()],
        itemWriteMode: .keepAsFile,
        writers: [
          .itemWriter(stencil("page.html", environment: getEnvironment(root: saga.rootPath)))

      // Run the steps we registered above

      // All the remaining files that were not parsed to markdown, so for example images, raw html files and css,
      // are copied as-is to the output folder.

func getEnvironment(root: Path) -> Environment {
  Environment(loader: FileSystemLoader(paths: [root + "templates"]))

Please check out the Example app to play around.

Extending the Stencil Environment

You can extend the Environment with your own tags and filters, see the official Stencil docs. This is why you need to pass in an Environment, instead of SagaStencilRenderer creating one for you.

For example:

func getEnvironment(root: Path) -> Environment {
  let ext = Extension()
  ext.registerFilter("url") { (value: Any?) in
    guard let item = value as? AnyItem else {
      return ""
    var url = "/" + item.relativeDestination.string
    if url.hasSuffix("/index.html") {
    return url

  return Environment(loader: FileSystemLoader(paths: [root + "templates"]), extensions: [ext])


  • Swift Tools 5.5.0
View More Packages from this Author


Last updated: Tue Nov 08 2022 07:26:54 GMT-0500 (GMT-05:00)