Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions source/_ext/contributors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
from dataclasses import dataclass
import enum
from pathlib import Path
from typing import NamedTuple

import docutils.nodes as nodes
from docutils.parsers.rst import directives
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
from sphinx.util.docutils import SphinxDirective
import yaml

Handle = str
Docname = str


class Role(enum.Enum):
author = "authors"
editor = "editors"


class ArticleCredit(NamedTuple):
role: Role
docname: Docname


ContributorRoles = dict[Handle, list[ArticleCredit]]


@dataclass
class Contributor:
github: str
name: str | None = None

@classmethod
def from_dict(cls, handle, data):
if not data:
# a contributor may be only known by handle and nothing else
data = {}
return cls(github=handle, name=data.get("name"))

def as_reference(self) -> list[nodes.Node]:
ref = nodes.reference(
"", f"@{self.github}", refuri=f"https://github.com/{self.github}"
)
if self.name:
return [nodes.Text(f"{self.name} "), ref]
return [ref]


# FIXME(@fricklerhandwerk): Get up-to-date contributor information from Nixpkgs' `maintainers.nix`
with open(Path(__file__).parent.parent / "contributors.yaml") as f:
_registry = {
handle: Contributor.from_dict(handle, data)
for handle, data in (yaml.safe_load(f) or {}).items()
}


def resolve(handles: list[str]) -> list[Contributor]:
result = []
for handle in handles:
if handle not in _registry:
raise ValueError(f"unknown contributor '{handle}'")
result.append(_registry[handle])
return result


def contributors_field(label: str, people: list[Contributor]) -> nodes.field:
para = nodes.paragraph()
first, *rest = [p.as_reference() for p in people]
para += first
for ref in rest:
para += nodes.Text(", ")
para += ref
return nodes.field("", nodes.field_name("", label), nodes.field_body("", para))


class ContributorsDirective(SphinxDirective):
option_spec = {role.value: directives.unchanged for role in Role}

def run(self) -> list[nodes.Node]:
if not hasattr(self.env, "contributors_data"):
self.env.contributors_data = {}

field_list = nodes.field_list(classes=["contributors"])
for role, label in [(Role.author, "Author"), (Role.editor, "Editor")]:
raw = self.options.get(role.value, "")
handles = [h.strip() for h in raw.split(",") if h.strip()]
people = resolve(handles)
if len(people) > 1:
label += "s"
if people:
field_list += contributors_field(label, people)

# record the contributor's role on the current document
for handle in handles:
self.env.contributors_data.setdefault(handle, []).append(
ArticleCredit(role, self.env.docname)
)

return [field_list] if field_list.children else []


def position_in_toc(env: BuildEnvironment) -> dict[str, int]:
"""
Annotate items in the table of contents with their depth-first linearisation order
"""
order = {}
stack = [env.config.root_doc]
while stack:
docname = stack.pop()
if docname in order:
continue
order[docname] = len(order)
stack.extend(reversed(env.toctree_includes.get(docname, [])))
return order


class ContributorsIndex(nodes.General, nodes.Element):
def render(
self, app: Sphinx, fromdocname: Docname, data: ContributorRoles
) -> nodes.definition_list:
env = app.builder.env
toc_position = position_in_toc(env)
dl = nodes.definition_list()
for handle in sorted(
(h for h in _registry if data.get(h)),
key=lambda h: -len(data[h]),
):
entries = data[handle]
item = nodes.definition_list_item()
dt = nodes.term()
dt += _registry[handle].as_reference()
item += dt
dd = nodes.definition()
field_list = nodes.field_list()
for role, label in [(Role.author, "Author"), (Role.editor, "Editor")]:
# sort by order of occurrence
docs = sorted(
(doc for r, doc in entries if r == role),
key=lambda d: toc_position.get(d, float("inf")),
)
if not docs:
continue
p = nodes.paragraph()
for i, docname in enumerate(docs):
if i > 0:
p += nodes.Text(", ")
title = env.titles.get(docname)
uri = app.builder.get_relative_uri(fromdocname, docname)
p += nodes.reference(
"",
title.astext() if title else docname,
internal=True,
refuri=uri,
)
field_list += nodes.field(
"", nodes.field_name("", label), nodes.field_body("", p)
)
dd += field_list
item += dd
dl += item
return dl


class ContributorsIndexDirective(SphinxDirective):
def run(self) -> list[nodes.Node]:
return [ContributorsIndex()]


def process_contributors_index(
app: Sphinx, doctree: nodes.document, fromdocname: str
) -> None:
env = app.builder.env
contributors = getattr(env, "contributors_data", {})
for node in doctree.findall(ContributorsIndex):
node.replace_self([node.render(app, fromdocname, contributors)])


def setup(app: Sphinx) -> dict:
app.add_directive("contributors", ContributorsDirective)
app.add_directive("contributors-index", ContributorsIndexDirective)
app.add_node(ContributorsIndex)
app.connect("doctree-resolved", process_contributors_index)
return {}
8 changes: 8 additions & 0 deletions source/_static/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,11 @@ html[data-theme="dark"] .highlight .gd {
html[data-theme="dark"] iframe[title="GitHub"] {
filter: invert(0.93) hue-rotate(100deg);
}

.contributors {
color: var(--pst-color-text-muted);
background: var(--pst-color-surface);
border-color: var(--pst-color-info);
border-radius: 0.25rem;
padding: 0.5rem;
}
5 changes: 5 additions & 0 deletions source/acknowledgements/contributors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(contributors-list)=
# Article authors and editors

```{contributors-index}
```
6 changes: 6 additions & 0 deletions source/acknowledgements/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
(acknowledgements)=
# Acknowledgements

```{toctree}
:hidden:

contributors.md
```

## Sponsoring

The following people and organisations have contributed to make this effort possible:
Expand Down
4 changes: 4 additions & 0 deletions source/concepts/flakes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
(flakes-definition)=
# Flakes

```{contributors}
:authors: kiara
```

## What are flakes?

Flakes offer an entrypoint file `flake.nix` aimed at sharing Nix code.
Expand Down
1 change: 1 addition & 0 deletions source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"sphinx_copybutton",
"sphinx_design",
"extractable_code_block",
"contributors",
"sphinx_sitemap",
"notfound.extension",
]
Expand Down
26 changes: 26 additions & 0 deletions source/contributors.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
djacu:
name: Daniel Baker
domenkozar:
name: Domen Kožar
fricklerhandwerk:
name: Valentin Gagarin
grahamc:
name: Graham Christensen
infinisil:
name: Silvan Mosberger
kiara:
name: Kiara Grouwstra
github: KiaraGrouwstra
mmesch:
name: Matthias Meschede
NobbZ:
name: Norbert Melzer
olafklingt:
proofconstruction:
name: Alexander Groleau
rapenne-s:
name: Solène Rapenne
tfc:
name: Jacek Galowicz
zmitchell:
name: Zach Mitchell
5 changes: 5 additions & 0 deletions source/guides/best-practices.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Best practices

```{contributors}
:authors: domenkozar
:editors: fricklerhandwerk, infinisil
```

## URLs

The Nix language syntax supports bare URLs, so one could write `https://example.com` instead of `"https://example.com"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ myst:

# Continuous integration with GitHub Actions

```{contributors}
:authors: domenkozar
```

Set up [GitHub Actions](https://github.com/features/actions) as your continuous integration (CI) workflow for commits and pull requests.

Nix lets CI build and cache developer environments for every project on every branch using binary caches.
Expand Down
4 changes: 4 additions & 0 deletions source/guides/recipes/post-build-hook.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
(post-build-hooks)=
# Setting up post-build hooks

```{contributors}
:authors: grahamc
```

This guide shows how to use the Nix [`post-build-hook`](https://nix.dev/manual/nix/2.22/command-ref/conf-file#conf-post-build-hook) configuration option to automatically upload build results to an [S3-compatible binary cache](https://nix.dev/manual/nix/2.22/store/types/s3-binary-cache-store).

## Implementation caveats
Expand Down
9 changes: 5 additions & 4 deletions source/tutorials/callpackage.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
---
date: 2022-09-08
authors:
- Norbert Melzer
- Valentin Gagarin
- Matthias Meschede
myst:
html_meta:
"keywords": "tutorial, callPackage, override, package, customise, parameters, nix, nixpkgs"
Expand All @@ -12,6 +8,11 @@ myst:
(callpackage-tutorial)=
# Package parameters and overrides with `callPackage`

```{contributors}
:authors: NobbZ
:editors: mmesch, fricklerhandwerk
```

Nix ships with a special-purpose programming language for creating packages and configurations: the Nix language.
It is used to build the Nix package collection, known as {term}`Nixpkgs`.

Expand Down
5 changes: 5 additions & 0 deletions source/tutorials/first-steps/ad-hoc-shell-environments.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

# Ad hoc shell environments

```{contributors}
:authors: domenkozar
:editors: fricklerhandwerk
```

In a Nix shell environment, you can immediately use any program packaged with Nix, without installing it permanently.

You can also share the command invoking such a shell with others, and it will work on all Linux distributions, WSL, and macOS[^1].
Expand Down
4 changes: 4 additions & 0 deletions source/tutorials/first-steps/declarative-shell.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ myst:
(declarative-reproducible-envs)=
# Declarative shell environments with `shell.nix`

```{contributors}
:authors: domenkozar, zmitchell
:editors: fricklerhandwerk
```
## Overview

Declarative shell environments allow you to:
Expand Down
5 changes: 5 additions & 0 deletions source/tutorials/first-steps/reproducible-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

# Reproducible interpreted scripts

```{contributors}
:authors: rapenne-s
:editors: fricklerhandwerk
```

In this tutorial, you will learn how to use Nix to create and run reproducible interpreted scripts, also known as [shebang] scripts.

## Requirements
Expand Down
5 changes: 5 additions & 0 deletions source/tutorials/module-system/a-basic-module/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# A basic module

```{contributors}
:authors: djacu
:editors: fricklerhandwerk
```

What is a module?

* A module is a function that takes an attribute set and returns an attribute set.
Expand Down
5 changes: 5 additions & 0 deletions source/tutorials/module-system/deep-dive.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

Or: *Wrapping the world in modules*

```{contributors}
:authors: infinisil
:editors: fricklerhandwerk, proofconstruction
```

In this tutorial you will follow an extensive demonstration of how to wrap an existing API with Nix modules.

## Overview
Expand Down
6 changes: 6 additions & 0 deletions source/tutorials/nix-language.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
(reading-nix-language)=


# Nix language basics

```{contributors}
:authors: fricklerhandwerk
:editors: infinisil
```

The Nix language is designed for conveniently creating and composing *derivations* – precise descriptions of how contents of existing files are used to derive new files.
It is a domain-specific, purely functional, lazily evaluated, dynamically typed programming language.

Expand Down
Loading
Loading