summaryrefslogtreecommitdiffhomepage
path: root/website/defs.bzl
diff options
context:
space:
mode:
Diffstat (limited to 'website/defs.bzl')
-rw-r--r--website/defs.bzl178
1 files changed, 178 insertions, 0 deletions
diff --git a/website/defs.bzl b/website/defs.bzl
new file mode 100644
index 000000000..f52946c15
--- /dev/null
+++ b/website/defs.bzl
@@ -0,0 +1,178 @@
+"""Wrappers for website documentation."""
+
+load("//tools:defs.bzl", "short_path")
+
+# DocInfo is a provider which simple adds sufficient metadata to the source
+# files (and additional data files) so that a jeyll header can be constructed
+# dynamically. This is done the via BUILD system so that the plain
+# documentation files can be viewable without non-compliant markdown headers.
+DocInfo = provider(
+ fields = [
+ "layout",
+ "description",
+ "permalink",
+ "category",
+ "subcategory",
+ "weight",
+ "editpath",
+ "authors",
+ ],
+)
+
+def _doc_impl(ctx):
+ return [
+ DefaultInfo(
+ files = depset(ctx.files.src + ctx.files.data),
+ ),
+ DocInfo(
+ layout = ctx.attr.layout,
+ description = ctx.attr.description,
+ permalink = ctx.attr.permalink,
+ category = ctx.attr.category,
+ subcategory = ctx.attr.subcategory,
+ weight = ctx.attr.weight,
+ editpath = short_path(ctx.files.src[0].short_path),
+ authors = ctx.attr.authors,
+ ),
+ ]
+
+doc = rule(
+ implementation = _doc_impl,
+ doc = "Annotate a document for jekyll headers.",
+ attrs = {
+ "src": attr.label(
+ doc = "The markdown source file.",
+ mandatory = True,
+ allow_single_file = True,
+ ),
+ "data": attr.label_list(
+ doc = "Additional data files (e.g. images).",
+ allow_files = True,
+ ),
+ "layout": attr.string(
+ doc = "The document layout.",
+ default = "docs",
+ ),
+ "description": attr.string(
+ doc = "The document description.",
+ default = "",
+ ),
+ "permalink": attr.string(
+ doc = "The document permalink.",
+ mandatory = True,
+ ),
+ "category": attr.string(
+ doc = "The document category.",
+ default = "",
+ ),
+ "subcategory": attr.string(
+ doc = "The document subcategory.",
+ default = "",
+ ),
+ "weight": attr.string(
+ doc = "The document weight.",
+ default = "50",
+ ),
+ "authors": attr.string_list(),
+ },
+)
+
+def _docs_impl(ctx):
+ # Tarball is the actual output.
+ tarball = ctx.actions.declare_file(ctx.label.name + ".tgz")
+
+ # But we need an intermediate builder to translate the files.
+ builder = ctx.actions.declare_file("%s-builder" % ctx.label.name)
+ builder_content = [
+ "#!/bin/bash",
+ "set -euo pipefail",
+ "declare -r T=$(mktemp -d)",
+ "function cleanup {",
+ " rm -rf $T",
+ "}",
+ "trap cleanup EXIT",
+ ]
+ for dep in ctx.attr.deps:
+ doc = dep[DocInfo]
+
+ # Sanity check the permalink.
+ if not doc.permalink.endswith("/"):
+ fail("permalink %s for target %s should end with /" % (
+ doc.permalink,
+ ctx.label.name,
+ ))
+
+ # Construct the header.
+ header = """\
+description: {description}
+permalink: {permalink}
+category: {category}
+subcategory: {subcategory}
+weight: {weight}
+editpath: {editpath}
+authors: {authors}
+layout: {layout}"""
+
+ for f in dep.files.to_list():
+ # Is this a markdown file? If not, then we ensure that it ends up
+ # in the same path as the permalink for relative addressing.
+ if not f.basename.endswith(".md"):
+ builder_content.append("mkdir -p $T/%s" % doc.permalink)
+ builder_content.append("cp %s $T/%s" % (f.path, doc.permalink))
+ continue
+
+ # Is this a post? If yes, then we must put this in the _posts
+ # directory. This directory is treated specially with respect to
+ # pagination and page generation.
+ dest = f.short_path
+ if doc.layout == "post":
+ dest = "_posts/" + f.basename
+ builder_content.append("echo Processing %s... >&2" % f.short_path)
+ builder_content.append("mkdir -p $T/$(dirname %s)" % dest)
+
+ # Construct the header dynamically. We include the title field from
+ # the markdown itself, as this is the g3doc format required. The
+ # title will be injected by the web layout however, so we don't
+ # want this to appear in the document.
+ args = dict([(k, getattr(doc, k)) for k in dir(doc)])
+ builder_content.append("title=\"$(grep -E '^# ' %s | head -n 1 | cut -d'#' -f2- || true)\"" % f.path)
+ builder_content.append("cat >$T/%s <<EOF" % dest)
+ builder_content.append("---")
+ builder_content.append("title: $title")
+ builder_content.append(header.format(**args))
+ builder_content.append("---")
+ builder_content.append("EOF")
+
+ # To generate the final page, we need to strip out the title (which
+ # was pulled above to generate the annotation in the frontmatter,
+ # and substitute the [TOC] tag with the {% toc %} plugin tag. Note
+ # that the pipeline here is almost important, as the grep will
+ # return non-zero if the file is empty, but we ignore that within
+ # the pipeline.
+ builder_content.append("grep -v -E '^# ' %s | sed -e 's|^\\[TOC\\]$|- TOC\\n{:toc}|' >>$T/%s" %
+ (f.path, dest))
+
+ builder_content.append("declare -r filename=$(readlink -m %s)" % tarball.path)
+ builder_content.append("(cd $T && tar -zcf \"${filename}\" .)\n")
+ ctx.actions.write(builder, "\n".join(builder_content), is_executable = True)
+
+ # Generate the tarball.
+ ctx.actions.run(
+ inputs = depset(ctx.files.deps),
+ outputs = [tarball],
+ progress_message = "Generating %s" % ctx.label,
+ executable = builder,
+ )
+ return [DefaultInfo(
+ files = depset([tarball]),
+ )]
+
+docs = rule(
+ implementation = _docs_impl,
+ doc = "Construct a site tarball from doc dependencies.",
+ attrs = {
+ "deps": attr.label_list(
+ doc = "All document dependencies.",
+ ),
+ },
+)