├── .circleci └── config.yml ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── docker.yml │ └── template.yml ├── .gitignore ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE.md ├── README.md ├── action.yml ├── add-note.py ├── archive.mk ├── build-index.sh ├── build-targets.sh ├── config.mk ├── default-branch.py ├── deps.mk ├── doc ├── ADOPTING.md ├── FEATURES.md ├── REPO.md ├── SETUP.md ├── SUBMITTING.md ├── TEMPLATE.md ├── TIPS.md ├── TOOLS.md ├── UPDATE.md ├── WEB.md ├── WG-SETUP.md └── images │ ├── comment-line.png │ ├── commit-branch.png │ ├── commit-main.png │ ├── edit.png │ ├── pr-broken.png │ ├── pr-conflict.png │ ├── pr-edit.png │ ├── pr-files.png │ ├── pr-interstitial.png │ ├── pr-merge.png │ ├── pr-resolve-controls.png │ ├── pr-revert.png │ ├── pr-review.png │ ├── pr-update.png │ └── pr.png ├── docker ├── action │ ├── .dockerignore │ ├── Dockerfile │ ├── Gemfile │ ├── entrypoint.sh │ └── requirements.txt ├── circleci │ ├── .dockerignore │ └── Dockerfile └── math │ └── Dockerfile ├── example ├── draft-todo-yourname-protocol.md └── draft-todo-yourname-protocol.xml ├── extract-metadata.py ├── format-trace.sh ├── get-email.sh ├── ghpages.mk ├── id.mk ├── main.mk ├── pre-commit.sh ├── pre-push.sh ├── requirements.txt ├── setup-branch.sh ├── setup-codeowners.py ├── setup-note.sh ├── setup-readme.sh ├── setup.mk ├── style.css ├── targets.mk ├── template ├── .circleci │ └── config.yml ├── .editorconfig ├── .github │ └── workflows │ │ ├── archive.yml │ │ ├── ghpages.yml │ │ ├── publish.yml │ │ └── update.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── Makefile ├── issues.html └── issues.js ├── tests ├── build.feature ├── environment.py ├── git.feature ├── setup.feature ├── steps │ ├── build_commands.py │ ├── repo_setup.py │ └── results.py └── upload.feature ├── trace.sh ├── update-venue.sh ├── update.mk ├── upload.mk ├── v3.css ├── venv.mk └── wg-meta.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: cimg/base:stable 6 | auth: 7 | username: $DOCKERHUB_USERNAME 8 | password: $DOCKERHUB_PASSWORD 9 | environment: 10 | # Files that we use to determine whether to build. 11 | DOCKER_BUILD_PATTERN: '^\(docker\|.circleci\)/' 12 | 13 | steps: 14 | - checkout 15 | - setup_remote_docker: 16 | version: default 17 | 18 | - run: 19 | name: "Build Docker Image" 20 | command: | 21 | if ! git show --name-only --format='' HEAD | grep -q "$DOCKER_BUILD_PATTERN"; then 22 | exit 23 | fi 24 | if [ -n "$CIRCLE_TAG" ]; then 25 | tag="$CIRCLE_TAG" 26 | elif [ "$CIRCLE_BRANCH" = "main" ]; then 27 | tag=latest 28 | else 29 | tag="$CIRCLE_BRANCH" 30 | fi 31 | docker build -t docker.io/martinthomson/i-d-template-action:"$tag" docker/action 32 | docker build -t docker.io/martinthomson/i-d-template:"$tag" \ 33 | --build-arg REGISTRY=docker.io --build-arg VERSION="$tag" docker/circleci 34 | 35 | - run: 36 | name: "Run Tests" 37 | command: | 38 | if [ -n "$CIRCLE_TAG" ]; then 39 | exit 40 | fi 41 | if ! git show --name-only --format='' HEAD | grep -q "$DOCKER_BUILD_PATTERN"; then 42 | tag=latest 43 | elif [ "$CIRCLE_BRANCH" = "main" ]; then 44 | tag=latest 45 | else 46 | tag="$CIRCLE_BRANCH" 47 | fi 48 | set -x 49 | cleanup() { set +e; docker cp test:/tmp/artifacts /tmp/artifacts; docker rm -f test; } 50 | trap cleanup EXIT 51 | docker run -d --name test \ 52 | docker.io/martinthomson/i-d-template:"$tag" sleep 300 53 | docker cp . test:/test 54 | docker exec -u 0 test chown -R idci:idci /test 55 | docker exec -u 0 test apk add black 56 | docker exec -w /test test black --check . 57 | docker exec -u 0 test pip3 install --break-system-packages behave 58 | docker exec test mkdir /tmp/artifacts 59 | docker exec -w /test test behave --junit --junit-directory /tmp/artifacts /test/tests 60 | 61 | - run: 62 | name: "Push Docker Image" 63 | command: | 64 | if ! git show --name-only --format='' HEAD | grep -q "$DOCKER_BUILD_PATTERN"; then 65 | exit 66 | fi 67 | if [ -n "$CIRCLE_TAG" ]; then 68 | tag="$CIRCLE_TAG" 69 | elif [ "$CIRCLE_BRANCH" = "main" ]; then 70 | tag=latest 71 | else 72 | exit 73 | fi 74 | if [ -n "$DOCKER_PASSWORD" ]; then 75 | echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin 76 | docker push docker.io/martinthomson/i-d-template:"$tag" 77 | fi 78 | 79 | - store_artifacts: 80 | path: /tmp/artifacts 81 | 82 | - store_test_results: 83 | path: /tmp/artifacts 84 | 85 | 86 | workflows: 87 | version: 2 88 | build: 89 | jobs: 90 | - build: 91 | filters: 92 | tags: 93 | only: /.*?/ 94 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Force *.sed and *.sh files to use LF for EOL -- CRLF breaks 2 | *.sed text eol=lf 3 | *.sh text eol=lf 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Auto-update GitHub Actions 2 | version: 2 3 | updates: 4 | - package-ecosystem: 5 | - "Bundler" 6 | - "github-actions" 7 | - "pip" 8 | directory: "/" 9 | schedule: 10 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: "Update Docker Image for GitHub Action" 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - "docker/**" 8 | - ".github/workflows/docker.yml" 9 | schedule: 10 | # Once monthly at a randomly selected time. 11 | - cron: "24 2 3,18 * *" 12 | 13 | jobs: 14 | build: 15 | name: "Update Docker Image" 16 | runs-on: ubuntu-latest 17 | 18 | services: 19 | registry: 20 | image: registry:2 21 | ports: 22 | - 5000:5000 23 | 24 | steps: 25 | - name: "Checkout" 26 | uses: actions/checkout@v4 27 | 28 | - name: "Configure" 29 | id: config 30 | run: | 31 | ref="${{ github.ref }}" 32 | if [ "$ref" = "refs/heads/main" ]; then 33 | label=latest 34 | elif [ "${ref#refs/tags/}" != "$ref" ]; then 35 | label="${ref#refs/tags/}" 36 | else 37 | label=test 38 | fi 39 | tag() { 40 | echo "${1}/martinthomson/i-d-template${2}:${label}" 41 | } 42 | if [ "$label" = "test" ]; then 43 | registry=localhost:5000 44 | driver_opts="network=host" 45 | else 46 | registry=ghcr.io 47 | driver_opts= 48 | fi 49 | action_tags="$(tag "$registry" -action)" 50 | circle_tags="$(tag "$registry" "")" 51 | math_tags="$(tag "$registry" -math)" 52 | echo "registry=$registry" >>"$GITHUB_OUTPUT" 53 | echo "driver_opts=$driver_opts" >>"$GITHUB_OUTPUT" 54 | echo "label=$label" >>"$GITHUB_OUTPUT" 55 | echo "action_tags=$action_tags" >>"$GITHUB_OUTPUT" 56 | echo "circle_tags=$circle_tags" >>"$GITHUB_OUTPUT" 57 | echo "math_tags=$math_tags" >>"$GITHUB_OUTPUT" 58 | 59 | - name: "Setup Docker Buildx" 60 | uses: docker/setup-buildx-action@v3 61 | with: 62 | driver-opts: ${{ steps.config.outputs.driver_opts }} 63 | 64 | - name: "Login to GitHub Container Registry" 65 | if: ${{ steps.config.outputs.label != 'test' }} 66 | uses: docker/login-action@v3 67 | with: 68 | registry: ghcr.io 69 | username: ${{ secrets.GHCR_USERNAME }} 70 | password: ${{ secrets.GHCR_PASSWORD }} 71 | 72 | - name: "Build and Publish GitHub Actions Image" 73 | uses: docker/build-push-action@v6 74 | with: 75 | context: ./docker/action 76 | file: ./docker/action/Dockerfile 77 | push: true 78 | tags: ${{ steps.config.outputs.action_tags }} 79 | 80 | - name: "Build and Publish CircleCI Image" 81 | uses: docker/build-push-action@v6 82 | with: 83 | context: ./docker/circleci 84 | file: ./docker/circleci/Dockerfile 85 | build-args: | 86 | REGISTRY=${{ steps.config.outputs.registry }} 87 | VERSION=${{ steps.config.outputs.label }} 88 | push: true 89 | tags: ${{ steps.config.outputs.circle_tags }} 90 | 91 | - name: "Build and Publish Math Image" 92 | uses: docker/build-push-action@v6 93 | with: 94 | context: ./docker/math 95 | file: ./docker/math/Dockerfile 96 | build-args: | 97 | REGISTRY=${{ steps.config.outputs.registry }} 98 | VERSION=${{ steps.config.outputs.label }} 99 | push: true 100 | tags: ${{ steps.config.outputs.math_tags }} 101 | -------------------------------------------------------------------------------- /.github/workflows/template.yml: -------------------------------------------------------------------------------- 1 | name: "Validate Workflows in Template" 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | paths: 7 | - ".github/workflows/template.yml" 8 | - "template/.github/workflows/**" 9 | - "v3.css" 10 | - "Gemfile" 11 | - "action/docker/Gemfile" 12 | - "requirements.txt" 13 | - "action/docker/requirements.txt" 14 | 15 | 16 | jobs: 17 | build: 18 | name: "Validate Workflows" 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: "Checkout" 23 | uses: actions/checkout@v4 24 | 25 | - name: "Verify Gemfile and requirements.txt consistency" 26 | run: | 27 | e='' 28 | for src in Gemfile requirements.txt; do 29 | dst="docker/action/$src" 30 | [ -f "$src" ] || e="$e\n$src is missing" 31 | [ -f "$dst" ] || e="$e\n$dst is missing" 32 | diff -u "$dst" "$src" || e="$e\n$src and $dst differ" 33 | done 34 | if [ -n "$e" ]; then 35 | ! printf "$e" 36 | fi 37 | 38 | - name: "Compare workflow files in internet-draft-template" 39 | run: | 40 | git clone --depth 1 https://github.com/martinthomson/internet-draft-template idt 41 | e='' 42 | for src in template/.github/workflows/*.yml; do 43 | dst="idt/${src#*/}" 44 | [ -f "$dst" ] || e="$e\nworkflow ${src##*/} is missing from internet-draft-template" 45 | diff -u "$dst" "$src" || e="$e\nworkflow ${src##*/} differs" 46 | done 47 | if [ -n "$e" ]; then 48 | ! printf "$e" 49 | fi 50 | 51 | - name: "Compare CSS in rfc-css" 52 | run: | 53 | git clone --depth 1 https://github.com/martinthomson/rfc-css rfc-css 54 | diff -u <(./rfc-css/combine.sh) v3.css || ! echo "v3.css differs from rfc-css" 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | lib 4 | rfc2629xslt 5 | *.xslt 6 | .template-files.mk 7 | draft-* 8 | issues.json 9 | pulls.json 10 | *.DS_Store 11 | Gemfile.lock 12 | .gems 13 | .venv 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This project is in the public domain; your contributions need to be too. 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'kramdown-rfc' 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This project is in the public domain. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Internet Draft Template Repository 2 | 3 | The contents of this repository can be used to manage the editing of an 4 | [Internet Draft](https://authors.ietf.org/en/content-guidelines-overview). 5 | 6 | This tool provides many 7 | [features](https://github.com/martinthomson/i-d-template/blob/main/doc/FEATURES.md). 8 | 9 | # Getting Started 10 | 11 | You need a [GitHub account](https://github.com/join). 12 | 13 | The [template 14 | repository](https://github.com/martinthomson/i-d-template/blob/main/doc/TEMPLATE.md) 15 | is the easiest way to get started. You don't need any additional software. 16 | 17 | If you want to use command-line tools, make sure you have the [necessary 18 | software 19 | installed](https://github.com/martinthomson/i-d-template/blob/main/doc/SETUP.md). 20 | [Manual 21 | setup](https://github.com/martinthomson/i-d-template/blob/main/doc/REPO.md) is 22 | mostly automated. 23 | 24 | Work. 25 | 26 | [Submit the final 27 | product](https://github.com/martinthomson/i-d-template/blob/main/doc/SUBMITTING.md). 28 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Internet-Draft CI' 2 | description: 'Tool for building Internet-Drafts as GitHub Actions' 3 | author: 'Martin Thomson ' 4 | 5 | inputs: 6 | make: 7 | description: 'The make target to build' 8 | required: false 9 | default: 'all' 10 | 11 | token: 12 | description: 'Set this to secrets.GITHUB_TOKEN' 13 | required: false 14 | 15 | runs: 16 | using: 'docker' 17 | image: 'docker://ghcr.io/martinthomson/i-d-template-action:latest' 18 | args: 19 | - ${{ inputs.make }} 20 | env: 21 | GITHUB_TOKEN: ${{ inputs.token }} 22 | 23 | branding: 24 | icon: 'check-square' 25 | color: 'green' 26 | -------------------------------------------------------------------------------- /add-note.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import fileinput 4 | import re 5 | 6 | found = False 7 | frontEnd = re.compile(" *") 8 | for line in fileinput.input(): 9 | if not found: 10 | match = frontEnd.match(line) 11 | if match: 12 | found = True 13 | if match.start() > 0: 14 | print(line[: match.start()], end="") 15 | 16 | try: 17 | with fileinput.FileInput(files=(".note.xml")) as note: 18 | for n in note: 19 | print(n, end="") 20 | except: 21 | pass 22 | 23 | print(line[match.start() :], end="") 24 | continue 25 | 26 | # Don't add a note if there is one already. 27 | if line.find("") >= 0: 28 | found = True 29 | 30 | print(line, end="") 31 | -------------------------------------------------------------------------------- /archive.mk: -------------------------------------------------------------------------------- 1 | ARCHIVE_BRANCH := gh-pages 2 | .PHONY: fetch-archive 3 | fetch-archive: 4 | -git fetch -qf origin $(ARCHIVE_BRANCH):$(ARCHIVE_BRANCH) 5 | 6 | ## Aliases for old stuff for compatibility reasons (added 2020-03-17). 7 | .PHONY: issues ghissues gh-issues 8 | issues: archive 9 | gh-issues gh-issues: gh-archive 10 | DISABLE_ARCHIVE_FETCH ?= $(DISABLE_ISSUE_FETCH) 11 | 12 | ifneq (true,$(PUSH_GHPAGES)) 13 | DISABLE_ARCHIVE_FETCH ?= true 14 | endif 15 | 16 | # Can't load issues without authentication. 17 | ifeq (,$(GITHUB_API_TOKEN)) 18 | DISABLE_ARCHIVE_FETCH := true 19 | endif 20 | 21 | archive_script = $(trace) archive -s archive-repo \ 22 | $(python) -m archive-repo archive $(GITHUB_REPO_FULL) $(GITHUB_API_TOKEN) 23 | ifeq (true,$(ARCHIVE_FULL)) 24 | define archive_issues 25 | echo $(archive_script) $(1); \ 26 | $(archive_script) $(1) 27 | endef 28 | else 29 | define archive_issues 30 | old_archive=$$(mktemp $(TMPDIR)/archive-old.XXXXXX); \ 31 | trap 'rm -f $$old_archive' EXIT; \ 32 | git show $(ARCHIVE_BRANCH):$(1) > $$old_archive || true; \ 33 | echo $(archive_script) $(1) --reference $$old_archive; \ 34 | $(archive_script) $(1) --reference $$old_archive 35 | endef 36 | endif 37 | 38 | ## Store a copy of any GitHub issues and pull requests. 39 | .PHONY: archive 40 | archive: archive.json 41 | archive.json: fetch-archive $(drafts_source) $(DEPS_FILES) 42 | @if [ -f $@ ] && [ "$(call file_size,$@)" -gt 0 ] && \ 43 | [ "$(call last_modified,$@)" -gt "$(call last_commit,$(ARCHIVE_BRANCH),$@)" ] 2>/dev/null; then \ 44 | echo 'Skipping update of $@ (it is newer than the ones on the branch)'; exit; \ 45 | fi; \ 46 | skip=$(DISABLE_ARCHIVE_FETCH); \ 47 | if [ $(CI) = true -a "$$skip" != true -a \ 48 | $$(($$(date '+%s')-28800)) -lt "$$(git log -n 1 --pretty=format:%ct $(ARCHIVE_BRANCH) -- $@)" ] 2>/dev/null; then \ 49 | skip=true; echo 'Skipping update of $@ (most recent update was in the last 8 hours)'; \ 50 | fi; \ 51 | if [ "$$skip" = true ]; then \ 52 | echo 'Using existing copy of $@'; \ 53 | git show $(ARCHIVE_BRANCH):$@ > $@ || true; \ 54 | exit; \ 55 | fi; \ 56 | $(call archive_issues,$@) 57 | 58 | ARCHIVE_ROOT := $(TMPDIR)/gharchive$(PID) 59 | $(ARCHIVE_ROOT): fetch-archive 60 | @git show-ref refs/heads/$(ARCHIVE_BRANCH) >/dev/null 2>&1 || \ 61 | (git show-ref refs/remotes/origin/$(ARCHIVE_BRANCH) >/dev/null 2>&1 && \ 62 | git branch -t $(ARCHIVE_BRANCH) origin/$(ARCHIVE_BRANCH)) || \ 63 | ! echo 'Error: No $(ARCHIVE_BRANCH) branch, run `make -f $(LIBDIR)/setup.mk setup-ghpages` to initialize it.' 64 | git clone -q -b $(ARCHIVE_BRANCH) . $@ 65 | 66 | $(ARCHIVE_ROOT)/%.json: %.json $(ARCHIVE_ROOT) 67 | cp -f $< $@ 68 | 69 | ## Commit and push the changes to $(ARCHIVE_BRANCH) 70 | .PHONY: gh-archive 71 | ifneq (,$(MAKE_TRACE)) 72 | gh-archive: 73 | @$(call MAKE_TRACE,gh-archive) 74 | else 75 | gh-archive: $(ARCHIVE_ROOT)/archive.json 76 | cp -f $(LIBDIR)/template/issues.html $(LIBDIR)/template/issues.js $(ARCHIVE_ROOT) 77 | @-git -C $(ARCHIVE_ROOT) rm --ignore-unmatch -f issues.json pulls.json 78 | git -C $(ARCHIVE_ROOT) add -f archive.json issues.html issues.js 79 | if test `git -C $(ARCHIVE_ROOT) status --porcelain archive.json issues.js issues.html | wc -l` -gt 0; then \ 80 | git -C $(ARCHIVE_ROOT) $(CI_AUTHOR) commit -m "Script updating archive at $(shell date -u +%FT%TZ). [ci skip]"; fi 81 | ifeq (true,$(PUSH_GHPAGES)) 82 | ifneq (,$(if $(CI_HAS_WRITE_KEY),1,$(if $(GITHUB_PUSH_TOKEN),,1))) 83 | $(trace) archive -s archive-push git -C $(ARCHIVE_ROOT) push -f "$(shell git remote get-url --push $(GIT_REMOTE))" $(ARCHIVE_BRANCH) 84 | else 85 | @echo git -C $(ARCHIVE_ROOT) push -qf https://github.com/$(GITHUB_REPO_FULL) $(ARCHIVE_BRANCH) 86 | @git -C $(ARCHIVE_ROOT) push -qf https://$(GITHUB_PUSH_TOKEN)@github.com/$(GITHUB_REPO_FULL) $(ARCHIVE_BRANCH) >/dev/null 2>&1 \ 87 | || $(trace) all -s archive-push ! echo "git -C $(GHPAGES_ROOT) push -qf https://****@github.com/$(GITHUB_REPO_FULL) $(ARCHIVE_BRANCH)" 88 | endif 89 | else 90 | ifeq (true,$(CI)) 91 | @echo "*** Warning: pushing to the gh-pages branch is disabled." 92 | else 93 | $(trace) all -s archive-push git -C $(ARCHIVE_ROOT) push -f origin $(ARCHIVE_BRANCH) 94 | endif 95 | endif # PUSH_GHPAGES 96 | -rm -rf $(ARCHIVE_ROOT) 97 | endif # MAKE_TRACE 98 | 99 | ## Save archive.json to the CI_ARTIFACTS directory 100 | ifneq (,$(CI_ARTIFACTS)) 101 | .PHONY: artifacts 102 | artifacts: archive.json 103 | endif 104 | -------------------------------------------------------------------------------- /build-index.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Usage: $0 html [dir] [branch] github.com [gh-user] [gh-repo] [draft source...] > index.html 4 | # Usage: $0 md [dir] [branch] github.com [gh-user] [gh-repo] [draft source...] > index.md 5 | 6 | hash realpath 2>/dev/null || function realpath() { cd "$1"; pwd -P; } 7 | 8 | format="$1" 9 | root=$(realpath "${2:-.}") 10 | hoster="${4:-github.com}" 11 | user="${5:-}" 12 | repo="${6:-}" 13 | default_branch="${DEFAULT_BRANCH:-$("$(dirname "$0")/default-branch.py")}" 14 | branch="${3:-$default_branch}" 15 | libdir="${LIBDIR:-"$(realpath "$(dirname "$0")")"}" 16 | TMPDIR="${TMPDIR:-/tmp}" 17 | [[ -n "$VENV" ]] && python="${python:-"${VENV}/python"}" 18 | python="${python:-python3}" 19 | shift 6 20 | # Remaining arguments (now $@) are source files 21 | all_drafts=("$@") 22 | 23 | gh="https://${hoster}/${user}/${repo}" 24 | 25 | case "$hoster" in 26 | codeberg.org) 27 | hosterpages="codeberg.page" 28 | ;; 29 | github.com) 30 | hosterpages="github.io" 31 | ;; 32 | gitlab.com) 33 | hosterpages="gitlab.io" 34 | ;; 35 | *) 36 | hosterpages="pages.${hoster}" 37 | ;; 38 | esac 39 | 40 | function rfcdiff() { 41 | function arg() { 42 | if [[ "$1" != "${1#*:}" ]]; then 43 | echo "url_$2=$1" 44 | else 45 | echo "doc_$2=$1" 46 | fi 47 | } 48 | echo "https://author-tools.ietf.org/api/iddiff?$(arg "$1" 1)&$(arg "$2" 2)" 49 | } 50 | 51 | function reldot() { 52 | [[ "$1" = "$root" ]] && echo '.' || echo "${1/"$root"\//}" 53 | } 54 | 55 | function githubio() { 56 | d="${1%/}/" 57 | echo "https://${user}.${hosterpages}/${repo}/${d#"$default_branch"/}${2}.txt" 58 | } 59 | 60 | function githubcom() { 61 | echo "https://${hoster}/${user}/${repo}/${1}" 62 | } 63 | 64 | DATERE='[0-9]* [A-Z][a-z]* 20[0-9][0-9]' 65 | IGNOREDATE=( 66 | 'sed' '-e' "/^ This Internet-Draft will expire on ${DATERE}./d" 67 | '-e' "s/^Expires: $DATERE/Expires: DATEHERE/" 68 | '-e' 's/\(.\{56\}\).\{11\} 20[0-9][0-9]$/\1/' 69 | ) 70 | 71 | if [[ "$format" = "html" ]]; then 72 | indent='' 73 | function q() { 74 | v="${1//&/&}" 75 | echo "${v//$2" 93 | } 94 | function a() { 95 | url="$1" 96 | txt="$2" 97 | cls="$3" 98 | ttl="$4" 99 | [[ -n "$cls" ]] && cls=" class=$(qq "$cls")" 100 | [[ -n "$ttl" ]] && ttl=" title=$(qq "$ttl")" 101 | echo "$(q "$txt")" 102 | } 103 | 104 | function td() { 105 | e td "$@" 106 | } 107 | function th() { 108 | e th "$@" 109 | } 110 | function tr_i() { 111 | wi "" 112 | } 113 | function tr_o() { 114 | wo "" 115 | } 116 | function table_i() { 117 | wi "" 118 | } 119 | function table_o() { 120 | wo '
' 121 | } 122 | function h1() { 123 | e h1 "$@" 124 | } 125 | function h2() { 126 | e h2 "$@" 127 | } 128 | function p() { 129 | e p "$@" 130 | } 131 | elif [[ "$format" = "md" ]]; then 132 | function q() { 133 | v="$1" 134 | shift 135 | for c in "$@"; do 136 | v="${v//${c}/\\${c}}" 137 | done 138 | echo "$v" 139 | } 140 | function w() { 141 | echo "$@" 142 | } 143 | function wi() { 144 | : 145 | } 146 | function wo() { 147 | : 148 | } 149 | function a() { 150 | url="$1" 151 | txt="$2" 152 | ttl="$4" 153 | [[ -n "$ttl" ]] && ttl=" \"$(q "$ttl" '"')\"" 154 | echo "[$(q "$txt" "]")]($(q "$url" ")")$ttl)" 155 | } 156 | function td() { 157 | echo -n " $1 |" 158 | } 159 | function th() { 160 | echo -n " $1 |" 161 | } 162 | function tr_i() { 163 | echo -n "|" 164 | } 165 | function tr_o() { 166 | echo 167 | } 168 | function table_i() { 169 | extra="" 170 | if [[ "$1" == "$2" ]]; then 171 | extra=" --- | --- |" 172 | fi 173 | echo "| Draft | | | |${extra//-/ }" 174 | echo "| ----- | --- | --- | --- |${extra}" 175 | } 176 | function table_o() { 177 | echo 178 | } 179 | function h1() { 180 | p "# $1" 181 | } 182 | function h2() { 183 | p "## $1" 184 | } 185 | function p() { 186 | echo "$1" 187 | echo 188 | } 189 | else 190 | echo "Unknown format: $format" 2>&1 191 | exit 2 192 | fi 193 | 194 | # Mac versions of bash are old and terrible. 195 | if declare -A test >/dev/null 2>&1; then 196 | declare -A issue_labels=() 197 | else 198 | disable_cache=true 199 | fi 200 | function issue_label() { 201 | file="$1" 202 | if [[ -z "$disable_cache" && -n "${issue_labels[file]}" ]]; then 203 | echo "${issue_labels[file]:1}" 204 | return 205 | fi 206 | for i in "${all_drafts[@]}"; do 207 | if [[ "${i%.*}" == "$file" ]]; then 208 | label=$("$python" "${libdir}/extract-metadata.py" "$i" github-issue-label) 209 | [[ -z "$disable_cache" ]] && issue_labels[file]="x$label" 210 | echo "$label" 211 | return 212 | fi 213 | done 214 | } 215 | 216 | if [[ "$format" = "html" ]]; then 217 | w '' 218 | wi '' 219 | wi '' 220 | w ''"$user/$repo $branch"' preview' 221 | w '' 222 | wi '' 230 | wo '' 231 | wi '' 232 | fi 233 | 234 | tmpfiles=() 235 | trap 'rm -f "${tmpfiles[@]}"' EXIT 236 | function list_dir() { 237 | dir="$1" 238 | branch="$2" 239 | files=($(find "$dir" -maxdepth 1 \( -name 'draft-*.txt' -o -name 'rfc*.txt' \) -print)) 240 | if [[ "${#files[@]}" -eq 0 ]]; then 241 | return 242 | fi 243 | table_i "$branch" "$default_branch" 244 | for file in "${files[@]}"; do 245 | dir=$(dirname "$file") 246 | file=$(basename "$file" .txt) 247 | 248 | tr_i 249 | src="${branch}:$(git ls-tree --name-only "$branch" -- "$file".md "$file".xml 2>/dev/null | head -1)" 250 | [[ -n "${src##*:}" ]] || \ 251 | src="origin/${branch}:$(git ls-tree --name-only "origin/$branch" -- "$file".md "$file".xml 2>/dev/null | head -1)" 252 | if [[ -n "${src##*:}" ]]; then 253 | tmp="$(mktemp "${TMPDIR}/build-index$$-XXXXXX").${src##*.}" 254 | tmpfiles+=("$tmp") 255 | git show "$src" >"$tmp" 256 | src="$tmp" 257 | else 258 | # Fallback to the file in the current directory. 259 | src=$(ls "$file".{md,xml} 2>/dev/null | head -1) 260 | fi 261 | abbrev=$("$python" "${libdir}/extract-metadata.py" "$src" abbrev) 262 | title=$("$python" "${libdir}/extract-metadata.py" "$src" title) 263 | td "$(a "$(reldot "$dir")/${file}.html" "$abbrev" "html $file" "$title (HTML)")" 264 | td "$(a "$(reldot "$dir")/${file}.txt" "plain text" "txt $file" "$title (Text)")" 265 | this_githubio=$(githubio "$branch" "$file") 266 | if [[ "$2" == "$default_branch" ]]; then 267 | td "$(a "https://datatracker.ietf.org/doc/${file}" datatracker "dt $file" "Datatracker for $file")" 268 | diff=$(rfcdiff "$file" "$this_githubio") 269 | td "$(a "$diff" 'diff with last submission' "diff $file")" 270 | if [[ "${#files[@]}" -eq 1 ]]; then 271 | td "" 272 | else 273 | label=$(issue_label "$file") 274 | if [[ -n "$label" ]]; then 275 | td "$(a "$(githubcom labels/$label)" issues "issues $file")" 276 | else 277 | td "" 278 | fi 279 | fi 280 | elif diff -q <("${IGNOREDATE[@]}" "${root}/${file}.txt") <("${IGNOREDATE[@]}" "${dir}/${file}.txt") >/dev/null; then 281 | td "same as $default_branch" 282 | else 283 | diff=$(rfcdiff $(githubio "$default_branch/" "$file") "$this_githubio") 284 | td "$(a "$diff" 'diff with '"$default_branch" "diff $file")" 285 | fi 286 | tr_o 287 | done 288 | table_o 289 | } 290 | 291 | branchlink="$gh" 292 | [[ "$branch" = "$default_branch" ]] || branchlink="${branchlink}/tree/${branch}" 293 | h1 "Editor's drafts for $(q "$branch") branch of $(a "$branchlink" "${user}/${repo}")" 294 | [[ "$branch" = "$default_branch" ]] && \ 295 | p "View $(a "issues.html" "saved issues"), or the latest GitHub $(a "${gh}/issues" issues) and $(a "${gh}/pulls" "pull requests") in the $(a "${gh}" repo)." 296 | 297 | list_dir "${root}" "$branch" 298 | 299 | for dir in $(find "${root}" -mindepth 1 -type d \( -name '.*' -prune -o -print \)); do 300 | dir_branch="${dir#$root/}" 301 | h2 "Preview for branch $(a "$dir_branch" "$dir_branch")" 302 | list_dir "$dir" "$dir_branch" 303 | done 304 | 305 | if [ "$format" = "html" ]; then 306 | wi '' 328 | wo '' 329 | wo '' 330 | fi 331 | -------------------------------------------------------------------------------- /build-targets.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Build extra targets for make. This includes targets that are a little tricky 3 | # to build. This includes all versions of the draft other than the latest: all 4 | # tagged versions and the next version for submission. As a result, it also 5 | # includes diffs. 6 | 7 | # Usage: $0 [drafts ...] 8 | 9 | drafts=("$@") 10 | candidates=$((${#drafts[@]} * 5)) 11 | versioned="${VERSIONED:-versioned}" 12 | txt_html_warning="$(mktemp)" 13 | trap 'rm -f "$txt_html_warning"' EXIT 14 | 15 | next() { 16 | printf "${1%-*}-%2.2d" $((1${1##*-} - 99)) 17 | } 18 | 19 | print_sed() { 20 | noop_cmd="$1" 21 | sed_cmd="$2" 22 | shift 2 23 | if [ $# -gt 0 ]; then 24 | printf "$sed_cmd" 25 | else 26 | printf "$noop_cmd" 27 | fi 28 | for s in "$@"; do 29 | printf " -e '$s'" 30 | done 31 | } 32 | 33 | # This builds a make target for a specific tag. 34 | build_target() { 35 | tag="$1" 36 | target_name="$2" 37 | source_file= 38 | subst=() 39 | for file in $(git ls-tree --name-only "$tag" | grep '^draft-'); do 40 | if [ "${file##*.}" = "txt" -o "${file##*.}" = "html" ]; then 41 | echo "warning: $file is checked in at revision $tag" 1>&2 42 | rm -f "$txt_html_warning" 43 | continue 44 | fi 45 | if [ "${file%.*}" = "${target_name%-*}" ]; then 46 | source_file="$file" 47 | file_tag="$target_name" 48 | else 49 | # This is the last tag for the identified file at the tag we're 50 | # interested in. 51 | prev_file_tag=$(git describe --candidates="$candidates" --tags \ 52 | --match "${file%.*}-*" --abbrev=0 "$tag" 2>/dev/null) 53 | 54 | # No previous: -00, building for HEAD: next, otherwise use tag. 55 | if [ -z "$prev_file_tag" ]; then 56 | file_tag="${file%.*}-00" 57 | elif [ "$tag" = HEAD ]; then 58 | file_tag=$(next "$prev_file_tag") 59 | else 60 | file_tag="$prev_file_tag" 61 | fi 62 | fi 63 | subst+=("s/${file%.*}-latest/${file_tag}/g") 64 | done 65 | 66 | if [ -z "$source_file" ]; then 67 | echo "warning: No file found at revision $tag for $target_name" 1>&2 68 | return 69 | fi 70 | 71 | target="${target_name}.${source_file##*.}" 72 | if [ "${source_file##*.}" != "xml" ] || [ "$tag" = HEAD ]; then 73 | # Don't keep the temporary file (unless it is XML from a tag). 74 | printf ".INTERMEDIATE: ${versioned}/${target}\n" 75 | fi 76 | if [ "$tag" = HEAD ]; then 77 | printf "${versioned}/${target}: ${source_file} | ${versioned}\n" 78 | printf "\t" 79 | print_sed cat sed "${subst[@]}" 80 | printf " \$< >\$@\n" 81 | else 82 | # Keep the XML around for tagged builds (not HEAD). 83 | printf ".SECONDARY: ${versioned}/${target%.*}.xml\n" 84 | printf "${versioned}/${target}: | ${versioned}\n" 85 | printf "\tgit show \"$tag:$source_file\"" 86 | print_sed '' ' | sed' "${subst[@]}" 87 | printf " >\$@\n" 88 | fi 89 | } 90 | 91 | printf "${versioned}:\n" 92 | printf "\t@mkdir -p \$@\n" 93 | 94 | for draft in "${drafts[@]%.*}"; do 95 | if [ "${draft#draft-}" != "$draft" ]; then 96 | tags=($(git tag --list "${draft}-[0-9][0-9]")) 97 | else 98 | tags=($(git tag --list "$draft")) 99 | fi 100 | for i in "${tags[@]}"; do 101 | build_target "$i" "$i" 102 | done 103 | 104 | if [ "${#tags[@]}" -gt 0 ]; then 105 | next_draft=$(next "${tags[$((${#tags[@]}-1))]}") 106 | elif [ "${draft#draft-}" != "$draft" ]; then 107 | next_draft="${draft}-00" 108 | else 109 | next_draft="" 110 | fi 111 | if [ -n "$next_draft" ]; then 112 | build_target HEAD "$next_draft" 113 | 114 | if [ "${#tags[@]}" -gt 0 ]; then 115 | # Write out a diff target 116 | printf "diff-${draft}.html: ${versioned}/${tags[$((${#tags[@]}-1))]}.txt ${versioned}/${next_draft}.txt\n" 117 | printf "\t-\$(iddiff) \$^ > \$@\n" 118 | fi 119 | fi 120 | done 121 | 122 | if [ ! -e "$txt_html_warning" ]; then 123 | echo "warning: checked in txt or html files can cause issues" 1>&2 124 | echo "warning: remove these files with \`git rm\`" 1>&2 125 | fi 126 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # The following tools are used by this file. 2 | # All are assumed to be on the path, but you can override these 3 | # in the environment, or command line. 4 | 5 | # xml2rfc (when running locally, this is installed in a virtualenv for you) 6 | XML2RFC_RFC_BASE_URL := https://www.rfc-editor.org/rfc/ 7 | XML2RFC_ID_BASE_URL := https://datatracker.ietf.org/doc/html/ 8 | 9 | # Set sensible defaults for different xml2rfc targets. 10 | # Common options (which are added to $xml2rfc later) so that they can be tweaked further. 11 | XML2RFC_OPTS := -q --rfc-base-url $(XML2RFC_RFC_BASE_URL) --id-base-url $(XML2RFC_ID_BASE_URL) --allow-local-file-access 12 | # Target-specific options. 13 | XML2RFC_TEXT := --text 14 | ifeq (true,$(TEXT_PAGINATION)) 15 | XML2RFC_TEXT += --pagination 16 | else 17 | XML2RFC_TEXT += --no-pagination 18 | endif 19 | XML2RFC_CSS := $(LIBDIR)/v3.css 20 | XML2RFC_HTML := --html --css=$(XML2RFC_CSS) --metadata-js-url=/dev/null 21 | 22 | # If you are using markdown files use either kramdown-rfc or mmark 23 | # https://github.com/cabo/kramdown-rfc 24 | # (when running locally, kramdown-rfc is installed for you) 25 | kramdown-rfc ?= kramdown-rfc 26 | # Tell kramdown not to generate targets on references so the above takes effect. 27 | export KRAMDOWN_NO_TARGETS := true 28 | export KRAMDOWN_PERSISTENT := true 29 | ifneq (true,$(VERBOSE)) 30 | # Suppress messages about downloading references. 31 | export KRAMDOWN_REFCACHE_QUIET := true 32 | endif 33 | 34 | # Use an emoji for the favicon 35 | FAVICON_EMOJI ?= 36 | ifneq (,$(FAVICON_EMOJI)) 37 | FAVICON ?= 38 | endif 39 | 40 | # mmark (https://github.com/mmarkdown/mmark) 41 | mmark ?= mmark 42 | 43 | # If you are using outline files: 44 | # https://github.com/Juniper/libslax/tree/master/doc/oxtradoc 45 | oxtradoc ?= oxtradoc.in 46 | 47 | # When using rfc2629.xslt extensions: 48 | # https://greenbytes.de/tech/webdav/rfc2629xslt.html 49 | xsltproc ?= xsltproc 50 | 51 | # For sanity checkout your draft: 52 | # https://github.com/ietf-tools/idnits 53 | idnits ?= idnits 54 | 55 | # For diff: 56 | # https://github.com/ietf-tools/iddiff 57 | iddiff ?= iddiff -c 8 58 | 59 | # For generating PDF: 60 | # https://www.gnu.org/software/enscript/ 61 | enscript ?= enscript 62 | # http://www.ghostscript.com/ 63 | ps2pdf ?= ps2pdf 64 | 65 | # This is for people running macs 66 | SHELL := bash 67 | 68 | # For uploading draft "releases" to the datatracker. 69 | curl ?= curl -sS 70 | DATATRACKER_UPLOAD_URL ?= https://datatracker.ietf.org/api/submission 71 | 72 | # The type of index that is created for gh-pages. 73 | # Supported options are 'html' and 'md'. 74 | INDEX_FORMAT ?= html 75 | 76 | # For spellchecking: pip install --user codespell 77 | codespell ?= codespell 78 | 79 | # Tracing tool 80 | trace := $(LIBDIR)/trace.sh 81 | 82 | # Where versioned copies are stored. 83 | VERSIONED ?= versioned 84 | 85 | # We can blame this one on some weird configuration. 86 | TMPDIR ?= /tmp 87 | 88 | # Set this to "true" to disable caching where possible. 89 | DISABLE_CACHE ?= false 90 | ifeq (true,$(DISABLE_CACHE)) 91 | # Disable caching in kramdown-rfc. 92 | KRAMDOWN_REFCACHE_REFETCH := true 93 | export KRAMDOWN_REFCACHE_REFETCH 94 | # xml2rfc caches always, so point it at an empty directory. 95 | TEMP_CACHE := $(shell mktemp -d) 96 | XML2RFC_REFCACHEDIR := $(TEMP_CACHE) 97 | else 98 | # Enable caching for kramdown-rfc and xml2rfc. 99 | ifeq (,$(KRAMDOWN_REFCACHEDIR)) 100 | ifeq (true,$(CI)) 101 | XML2RFC_REFCACHEDIR := $(realpath .)/.refcache 102 | # In CI, only cache drafts for 5 minutes to pick up on recent updates. 103 | export KRAMDOWN_REFCACHETTL := 300 104 | else 105 | # When running locally, cache drafts for a week. 106 | export KRAMDOWN_REFCACHETTL_RFC := 23673600 107 | # Cache IANA and DOI for 3 months since they change rarely. 108 | export KRAMDOWN_REFCACHETTL := 604800 109 | # Cache RFCs for 9 months since they are immutable. 110 | export KRAMDOWN_REFCACHETTL_DOI_IANA := 7776000 111 | endif 112 | XML2RFC_REFCACHEDIR ?= $(HOME)/.cache/xml2rfc 113 | KRAMDOWN_REFCACHEDIR := $(XML2RFC_REFCACHEDIR) 114 | else 115 | XML2RFC_REFCACHEDIR ?= $(KRAMDOWN_REFCACHEDIR) 116 | endif 117 | ifneq (,$(shell mkdir -p -v $(KRAMDOWN_REFCACHEDIR))) 118 | $(info Created cache directory at $(KRAMDOWN_REFCACHEDIR)) 119 | endif 120 | endif # DISABLE_CACHE 121 | XML2RFC_OPTS += --cache=$(XML2RFC_REFCACHEDIR) 122 | export KRAMDOWN_REFCACHEDIR 123 | -------------------------------------------------------------------------------- /default-branch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Usage: $0 [gh-user] [gh-repo] [gh-token] 3 | 4 | import os 5 | import subprocess 6 | import sys 7 | 8 | if os.environ.get("DEFAULT_BRANCH") is not None: 9 | print(os.environ["DEFAULT_BRANCH"]) 10 | exit(0) 11 | 12 | 13 | def warn(m): 14 | print(f"warning: {sys.argv[0]}: {m}", file=sys.stderr) 15 | 16 | 17 | def get_branch(rev): 18 | try: 19 | revparse = ["git", "rev-parse", "--abbrev-ref", rev] 20 | v = subprocess.check_output(revparse, stderr=open(os.devnull, "wb")) 21 | 22 | print(v.decode("utf-8").strip(" \r\n").split("/")[-1]) 23 | exit(0) 24 | except subprocess.CalledProcessError: 25 | pass 26 | 27 | 28 | remote = os.environ.get("GIT_REMOTE", default="origin") 29 | get_branch(f"{remote}/HEAD") 30 | get_branch(f"refs/remotes/{remote}/HEAD") 31 | 32 | # We shouldn't get here... 33 | 34 | if ( 35 | len(sys.argv) < 3 36 | or os.environ.get("BRANCH_FETCH", default="true").lower() == "false" 37 | ): 38 | warn("unable to determine default branch") 39 | get_branch("HEAD") 40 | exit(1) 41 | 42 | try: 43 | import requests 44 | except ImportError: 45 | warn("need 'requests' to determine default branch") 46 | warn(" 'pip3 install [--user] requests' to install") 47 | get_branch("HEAD") 48 | exit(1) 49 | 50 | 51 | url = f"https://api.github.com/repos/{sys.argv[1]}/{sys.argv[2]}" 52 | headers = {} 53 | if len(sys.argv) >= 4: 54 | headers["Authorization"] = f"bearer {sys.argv[3]}" 55 | response = requests.get(url, headers=headers) 56 | response.raise_for_status() 57 | result = response.json() 58 | 59 | # Fix it 60 | head = f"refs/remotes/{remote}/HEAD" 61 | ref = f"refs/remotes/{remote}/{result['default_branch']}" 62 | warn("correcting the default branch locally:") 63 | warn(f" git symbolic-ref {head} {ref}") 64 | subprocess.check_output(["git", "symbolic-ref", head, ref]) 65 | 66 | # Report it 67 | print(result["default_branch"]) 68 | -------------------------------------------------------------------------------- /deps.mk: -------------------------------------------------------------------------------- 1 | ## Installed dependencies 2 | # 3 | # This framework will automatically install build-time dependencies as a 4 | # prerequisite for build targets. This installation uses local directories 5 | # (usually under lib/) to store all installed software. 6 | # 7 | # Some dependencies are defined in the framework and will always be installed. 8 | # This includes xml2rfc and kramdown-rfc. Other dependencies are specific to a 9 | # particular project and will be driven from files that are in the project 10 | # repository. 11 | # 12 | # Currently, this supports three different package installation frameworks: 13 | # * pip for python, specified in requirements.txt 14 | # * gem for ruby, specified in Gemfile 15 | # * npm for nodejs, specified in package.json 16 | # Each system has its own format for specifying dependencies. What you need to 17 | # know is that if you include any of the above files, you don't need to worry 18 | # about ensuring that these tools are available when a build runs. 19 | # 20 | # This also works in CI runs, with caching, so your builds won't run too slowly. 21 | # The prerequsites that are installed by default are installed globally in a CI 22 | # docker image, which avoids an expensive installation step in CI. This makes 23 | # CI runs slightly different than local runs. 24 | # 25 | ## Configuration 26 | # 27 | # For python, if you have some extra tools, just add them to requirements.txt 28 | # and they will be installed into a virtual environment. 29 | # 30 | # For ruby, listing tools in a `Gemfile` will ensure that files are installed. 31 | # You should add `Gemfile.lock` to your .gitignore file if you do this. 32 | # 33 | # For nodejs, new dependencies can be added to `package.json`. Use `npm install 34 | # -s ` to add files. You should add `package-lock.json` and 35 | # `node_modules/` to your `.gitignore` file if you do this. 36 | # 37 | # Tools are added to the path, so you should have no problem running them. 38 | # 39 | ## Using Tools 40 | # 41 | # Makefile rules can be written to use a new tool. Ensure that the variable 42 | # `$(DEPS_FILES)` is a dependency of any target that relies on tools being 43 | # available. For example, for a linter: 44 | # 45 | # lint:: example-lint 46 | # .PHONY: example-lint 47 | # example-lint: $(drafts_xml) $(DEPS_FILES) 48 | # $(example-linter) $(filter-out $(DEPS_FILES),$^) 49 | # 50 | # Note the filtering that is used here to avoid linting those files. 51 | # 52 | ## Manual Additions 53 | # 54 | # To manually add dependencies, edit your `Makefile` as follows. 55 | # 56 | # 1. Choose a file you will use as a marker to track installation and add that 57 | # to `$(DEPS_FILES)`: 58 | # DEPS_FILES := .example.dep 59 | # This change needs to appear *before* the `include $(LIBDIR)/main.mk` 60 | # line of the `Makefile`; subsequent changes can be put below. 61 | # 2. Add the marker file to your `.gitignore` 62 | # 3. Add a recipe for the marker file that installs the tool. If your 63 | # installation depends on local files, add those as dependencies. Make 64 | # sure to touch the marker file when you do this. 65 | # .example.dep: example.cfg 66 | # @install-example --config $^ 67 | # @touch $@ 68 | # 4. If necessary, add the tool to `$(PATH)`. 69 | # 5. (Optionally) Add a dependency to `update-deps` that updates the tool. 70 | # This allows people to update the tool periodically to catch changes in 71 | # the tool outside of the local repository (such as new releases). No 72 | # need to do this if you are only installing in CI builds (as below). 73 | # 6. (Optionally) Add a dependency to `clean-deps` to remove the tool. 74 | # 7. (Optionally) Add steps to the workflow files so that the tool is 75 | # cached between builds in CI. 76 | # 77 | # Generally, it is better to install tools in a subdirectory as that does 78 | # not require alterations to the system that might be disruptive. However, 79 | # that can mean that you can't use prebuilt binaries (such as those that 80 | # are included in an OS distribution). 81 | # 82 | ## Using OS Package Manager in CI 83 | # 84 | # If you use the OS packagage manager, you should only do that in CI by making 85 | # the installation conditional on `$(CI)`. This isn't simple as you won't have 86 | # access to this variable when you set `$(DEPS_FILES)`. So rather than making 87 | # that addition conditional, make it unconditional and change the recipe: 88 | # .example.dep: example.cfg 89 | # ifeq(true,$(CI)) 90 | # @install-example --config $^ 91 | # else 92 | # # maybe test if the tool is present 93 | # # then print a warning and fail if it isn't 94 | # endif 95 | # @touch $@ 96 | # 97 | # The additions to `update-deps` and `clean-deps` are unnecessary if you only 98 | # make changes in CI. 99 | # 100 | # CI images are based on Alpine Linux, which uses `apk`: 101 | # https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper 102 | # and the "main" and "community" package repositories (by default): 103 | # https://pkgs.alpinelinux.org/packages 104 | 105 | .PHONY: deps clean-deps update-deps 106 | 107 | # Make really doesn't handle spaces in filenames well. 108 | # Using $(realpath) exposes make to spaces in directory names above this one. 109 | # Though we might prefer to use $(realpath), this function operates a fallback 110 | # so that the full path is not used if there are spaces in directory names. 111 | ifeq ($(words $(realpath $(LIBDIR))),1) 112 | safe-realpath = $(realpath $(1)) 113 | relative-paths := false 114 | else 115 | ifneq (,$(DISABLE_SPACES_WARNING)) 116 | $(warning Your $$LIBDIR ($(LIBDIR)) contains spaces; some things might break.) 117 | endif 118 | safe-realpath = $(1) 119 | relative-paths := true 120 | endif 121 | 122 | ifeq (true,$(DISABLE_CACHE)) 123 | no-cache := --no-cache 124 | no-cache-dir := --no-cache-dir 125 | bundle-update-all := --all 126 | endif 127 | 128 | ## Python 129 | 130 | ifeq (true,$(CI)) 131 | # Override VENVDIR so we can use caching in CI. 132 | VENVDIR = $(call safe-realpath,.)/.venv 133 | endif 134 | 135 | VENVDIR ?= $(call safe-realpath,$(LIBDIR))/.venv 136 | REQUIREMENTS_TXT := $(wildcard requirements.txt) 137 | 138 | ifneq (,$(strip $(REQUIREMENTS_TXT))) 139 | # Need to maintain a local marker file in case the lib/ directory is shared. 140 | LOCAL_VENV := .requirements.txt 141 | DEPS_FILES += $(LOCAL_VENV) 142 | endif 143 | 144 | ifneq (true,$(CI)) 145 | # Don't install from lib/requirements.txt in CI; these are in the docker image. 146 | REQUIREMENTS_TXT += $(LIBDIR)/requirements.txt 147 | endif 148 | 149 | ## Install from requirements.txt. 150 | ifneq (,$(strip $(REQUIREMENTS_TXT))) 151 | ifeq (true,$(CI)) 152 | # Under CI, install from the local requirements.txt, but install globally (no venv). 153 | pip ?= pip3 154 | $(LOCAL_VENV): 155 | "$(pip)" install --no-user $(no-cache-dir) $(foreach path,$(REQUIREMENTS_TXT),-r $(path)) 156 | @touch $@ 157 | 158 | # No clean-deps target in CI.. 159 | 160 | else # CI 161 | # We have something to install in a venv, so include venv.mk. 162 | include $(LIBDIR)/venv.mk 163 | export VENV 164 | pip := $(VENV)/pip 165 | python := $(VENV)/python 166 | xml2rfc := $(VENV)/xml2rfc $(XML2RFC_OPTS) 167 | rfc-tidy := $(VENV)/rfc-tidy 168 | export PATH := $(VENV):$(PATH) 169 | 170 | ifneq (,$(LOCAL_VENV)) 171 | $(LOCAL_VENV): $(VENV)/$(MARKER) 172 | @touch $@ 173 | else 174 | DEPS_FILES += $(VENV)/$(MARKER) 175 | endif 176 | 177 | clean-deps:: clean-venv 178 | endif # CI 179 | update-deps:: 180 | "$(pip)" install --no-user $(no-cache-dir) --upgrade --upgrade-strategy eager \ 181 | $(foreach path,$(REQUIREMENTS_TXT),-r "$(path)") 182 | endif # -e requirements.txt 183 | 184 | # Variable defaults for CI 185 | python ?= python3 186 | xml2rfc ?= xml2rfc $(XML2RFC_OPTS) 187 | rfc-tidy ?= rfc-tidy 188 | 189 | 190 | ## Ruby 191 | ifeq (,$(shell which bundle)$(filter true,$(NO_RUBY))) 192 | $(warning ruby bundler not installed; skipping bundle install) 193 | NO_RUBY := true 194 | endif 195 | 196 | ifneq (true,$(NO_RUBY)) 197 | BUNDLE_IGNORE_MESSAGES := true 198 | export BUNDLE_IGNORE_MESSAGES 199 | ifeq (true,$(CI)) 200 | # Override BUNDLE_PATH so we can use caching in CI. 201 | BUNDLE_PATH := $(call safe-realpath,.)/.gems 202 | BUNDLE_DISABLE_VERSION_CHECK := true 203 | export BUNDLE_DISABLE_VERSION_CHECK 204 | endif 205 | BUNDLE_PATH ?= $(call safe-realpath,$(LIBDIR))/.gems 206 | # Install binaries to somewhere sensible instead of .../ruby/$v/bin where $v 207 | # doesn't even match the current ruby version. 208 | BUNDLE_BIN := $(BUNDLE_PATH)/bin 209 | export PATH := $(BUNDLE_BIN):$(PATH) 210 | ifeq (true,$(relative-paths)) 211 | # This means that BUNDLE_PATH is relative, which bundler will interpret 212 | # as being relative to the Gemfile, not the current directory, so tweak 213 | # the two path settings for bundler. After setting $PATH. 214 | # Thankfully, we don't install from $(LIBDIR)/Gemfile in CI, which 215 | # would mean that this would need to be "../.gems" there. 216 | $(warning Using a relative path for bundler) 217 | bundle-path-override-lib := BUNDLE_PATH=$(LIBDIR)/.gems BUNDLE_BIN=$(LIBDIR)/.gems/bin 218 | bundle-path-override := BUNDLE_PATH=.gems BUNDLE_BIN=.gems/bin 219 | endif 220 | export BUNDLE_PATH 221 | export BUNDLE_BIN 222 | 223 | 224 | ifneq (,$(wildcard Gemfile)) 225 | # A local Gemfile exists. 226 | DEPS_FILES += Gemfile.lock 227 | Gemfile.lock: Gemfile 228 | $(bundle-path-override-lib) bundle install $(no-cache) --gemfile="$(call safe-realpath,$<)" 229 | @touch $@ 230 | 231 | update-deps:: Gemfile 232 | $(bundle-path-override-lib) bundle update $(bundle-update-all) --gemfile="$(call safe-realpath,$<)" 233 | 234 | clean-deps:: 235 | -rm -rf "$(BUNDLE_PATH)" 236 | endif # Gemfile 237 | 238 | ifneq (true,$(CI)) 239 | # Install kramdown-rfc. 240 | DEPS_FILES += $(LIBDIR)/Gemfile.lock 241 | $(LIBDIR)/Gemfile.lock: $(LIBDIR)/Gemfile 242 | $(bundle-path-override) bundle install $(no-cache) --gemfile="$(call safe-realpath,$<)" 243 | @touch $@ 244 | 245 | update-deps:: $(LIBDIR)/Gemfile 246 | $(bundle-path-override) bundle update $(bundle-update-all) --gemfile="$(call safe-realpath,$<)" 247 | 248 | clean-deps:: 249 | -rm -rf "$(BUNDLE_PATH)" 250 | endif # !CI 251 | endif # !NO_RUBY 252 | 253 | 254 | ## Nodejs 255 | ifeq (,$(shell which npm)) 256 | ifneq (,$(wildcard package.json)) 257 | $(warning package.json exists, but npm not available; npm packages not installed) 258 | endif 259 | NO_NODEJS := true 260 | endif 261 | 262 | ifneq (true,$(NO_NODEJS)) 263 | export PATH := $(abspath node_modules/.bin):$(PATH) 264 | ifneq (,$(wildcard package.json)) 265 | DEPS_FILES += package-lock.json 266 | package-lock.json: package.json 267 | npm install 268 | @touch $@ 269 | 270 | update-deps:: 271 | npm update --no-save --dev 272 | 273 | clean-deps:: 274 | -rm -rf package-lock.json 275 | endif # package.json 276 | endif # !NO_NODEJS 277 | 278 | ## Link everything up 279 | deps:: $(DEPS_FILES) 280 | 281 | update-deps:: 282 | 283 | clean-deps:: 284 | @-rm -f $(DEPS_FILES) 285 | -------------------------------------------------------------------------------- /doc/ADOPTING.md: -------------------------------------------------------------------------------- 1 | # Adopting a Draft 2 | 3 | When a working group adopts an individual draft that was created with this 4 | template, the obvious ways of migrating a repository to a different organization 5 | are often not great. 6 | 7 | Firstly, **do not create a fork**. There are better options. 8 | 9 | You can rename the draft before or after transferring the repository. Don't 10 | forget to change both the filename and the name of the draft (in the docname field, 11 | for example). 12 | 13 | 14 | ## Best Option - Transfer the Repository 15 | 16 | A transfer means that all the pull requests, issues and any CI configuration are moved 17 | in addition to the code (the drafts), its history, and tags. 18 | 19 | To do this, make the owner of the repository part of the working group organization. 20 | You need to give them the ability to create repositories in order for this to be 21 | allowed. 22 | 23 | The owner of the repository can go to the settings tab for the repository and transfer. 24 | This can be found at the very bottom of the page, in the "Danger Zone". 25 | 26 | See the [GitHub instructions for transferring repositories](https://help.github.com/articles/about-repository-transfers/). 27 | 28 | 29 | ## Alternative - Copy History 30 | 31 | In case you can't transfer, you can copy the history of the old repository into 32 | a new repository. For this, you don't need any special roles, just the ability 33 | to push to the new repository. This won't copy or move issues or pull requests. 34 | 35 | Make a new repository. Make sure that it is empty when you make it (don't 36 | create a README when GitHub asks). Then make a new repository locally: 37 | 38 | ```sh 39 | $ git init new-repo 40 | $ cd new-repo 41 | $ git remote add origin https://github.com/new-owner/new-repo 42 | ``` 43 | 44 | Then pull the contents of the old repo in: 45 | 46 | ```sh 47 | $ git pull https://github.com/old-owner/old-repo $DEFAULT_BRANCH 48 | $ git push 49 | ``` 50 | 51 | You then need to setup the `gh-pages` and `gh-issues` 52 | branches: 53 | 54 | ```sh 55 | $ make 56 | $ make -f lib/setup.mk setup-ghpages setup-ghissues 57 | ``` 58 | 59 | 60 | ## Cleanup 61 | 62 | Any copies of the repository should have any clone that references the old 63 | location updated. The old location will continue to work, but it's good pratice 64 | to change: 65 | 66 | ```sh 67 | $ git remote set-url origin https://github.com/new/repo 68 | ``` 69 | 70 | After the draft is renamed and the origin is updated, you might want to rebuild 71 | the README and the "venue" information in the draft (markdown only): 72 | 73 | ```sh 74 | $ make update-readme 75 | $ make update-venue 76 | ``` 77 | 78 | This will overwrite the contents of `README.md` and update the header of any markdown draft. 79 | If you have made changes to the README, you can just update the intro text and the links instead. 80 | 81 | You should also update any links to your repo from other places. 82 | 83 | 84 | ## Submit 85 | 86 | Of course, you also need to [submit the draft as a -00](./SUBMITTING.md). 87 | -------------------------------------------------------------------------------- /doc/FEATURES.md: -------------------------------------------------------------------------------- 1 | # What This Project Can Do 2 | 3 | ```sh 4 | $ make 5 | ``` 6 | 7 | Turn internet-draft source into text and HTML. This supports XML files using 8 | [xml2rfc](https://xml2rfc.tools.ietf.org/), markdown files using either 9 | [kramdown-rfc](https://github.com/cabo/kramdown-rfc) or 10 | [mmark](https://github.com/miekg/mmark). 11 | 12 | Multiple files can be managed in the same repository. This only requires the 13 | addition of the source files for each draft, though you might choose to 14 | [update](https://github.com/martinthomson/i-d-template/blob/main/doc/UPDATE.md#automatically-generated-files) 15 | some files. 16 | 17 | ```sh 18 | $ make diff 19 | ``` 20 | 21 | Show changes since your last draft submission using 22 | [rfcdiff](https://www.ietf.org/rfcdiff). 23 | 24 | ```sh 25 | $ make gh-pages 26 | ``` 27 | 28 | Update the `gh-pages` branch with the latest edits. [GitHub 29 | pages](https://pages.github.com/) make your changes available on the web. 30 | 31 | This builds a HTML index that links to HTML and text copies of all drafts on all 32 | branches, plus links for showing changes with the most recent draft submission. 33 | Files for old branches are retained for one month after the branch is deleted, 34 | then cleaned up the next time this process runs. 35 | 36 | ```sh 37 | $ git tag -a draft-ietf-unicorn-protocol-02 38 | $ make upload 39 | ``` 40 | 41 | Upload tagged changes to the IETF datatracker using the 42 | [API](https://datatracker.ietf.org/api/submit). This will also manage draft 43 | renames by setting the "replaces" field. 44 | 45 | ```sh 46 | $ make lint 47 | $ make fix-lint 48 | ``` 49 | 50 | Check for common formatting errors like trailing whitespace and automatically 51 | fix them. 52 | 53 | The lint check and a check that drafts build correctly is installed as a git 54 | hook so that it runs before each commit. 55 | 56 | ```sh 57 | $ make idnits 58 | ``` 59 | 60 | Check for nits using the [idnits](https://www.ietf.org/tools/idnits) tool. 61 | 62 | ```sh 63 | $ make issues 64 | ``` 65 | 66 | Download a copy of GitHub issues and pull requests. 67 | 68 | If there are surprising errors, this may help: 69 | ```sh 70 | $ make clean 71 | ``` 72 | 73 | 74 | ## Setup a Repository 75 | 76 | When you [setup a repository](REPO.md), this tool installs a stub `Makefile`. 77 | It also creates the following files from a template: `README.md`, `.gitignore`, 78 | `CONTRIBUTING.md`, `LICENSE.md`, files for GitHub pages, and GitHub Action configuration 79 | under `.github/workflows/` (see below). 80 | 81 | 82 | ## Automation Features 83 | 84 | Using [GitHub Actions](https://github.com/features/actions) you get automated 85 | continuous integration that can: 86 | 87 | * Check that commits and pull requests for errors. 88 | * Automatically maintain a readable copy for display on GitHub Pages. 89 | * Automatically save a copy of GitHub issues and pull requests to the repository 90 | for offline access. This includes a simple HTML viewer. 91 | * Automatically [submit tagged versions of drafts](./SUBMITTING.md) to the IETF datatracker. 92 | 93 | Alternatively, you can also use [circleci](http://circleci.com/) or 94 | [travis](https://travis-ci.org/) [as described 95 | here](REPO.md#automatic-update-for-editors-copy). 96 | -------------------------------------------------------------------------------- /doc/REPO.md: -------------------------------------------------------------------------------- 1 | # Working Group Setup 2 | 3 | Make a [new organization](https://github.com/organizations/new) for your working 4 | group. This guide will use the name `unicorn-wg` for your working group. 5 | 6 | There is a [more detailed 7 | guide](https://github.com/martinthomson/i-d-template/blob/main/doc/WG-SETUP.md) 8 | for working groups. 9 | 10 | # New Draft Setup 11 | 12 | If you are more familiar with git and GitHub, try the [fast setup](#fast-setup). 13 | 14 | [Make a new repository](https://github.com/new). This guide will use the 15 | name `unicorn-protocol` in examples. 16 | 17 | When prompted, select the option to initialize the repository with a README. 18 | 19 | Clone that repository: 20 | ```sh 21 | $ git clone https://github.com/unicorn-wg/unicorn-protocol.git 22 | $ cd unicorn-protocol 23 | ``` 24 | 25 | Choose whether you want to use markdown, outline, or xml as your input format. 26 | If you already have a draft, then that decision is already made for you. 27 | 28 | Make a draft file in the root of the repo. The name of the file is important; make 29 | it match the name of your draft. Don't include a version number in the name. Our 30 | example would be `draft-ietf-unicorn-protocol.md` in markdown. 31 | 32 | You can copy of one of the examples files 33 | ([markdown](https://github.com/martinthomson/i-d-template/blob/main/example/draft-todo-yourname-protocol.md) or 34 | [XML](https://github.com/martinthomson/i-d-template/blob/main/example/draft-todo-yourname-protocol.xml)) 35 | if you are starting from scratch. 36 | 37 | Rename the draft. These tools rely on the draft being in the form 38 | `draft-$source-$name.{xml|md|...}`. The [official Internet-Draft naming 39 | guide](https://www.ietf.org/standards/ids/guidelines/#7) describes how the IETF 40 | (and related groups) name drafts. Here, you drop the trailing version number 41 | from the draft name and include an extension indicating the type of file. You 42 | will be using the same source file to produce multiple versions of the draft. 43 | 44 | You also need to include the name of your draft *inside* the document. This 45 | usually includes the version number, but in this tool, replace that the '-00' or 46 | '-07' with '-latest' instead. This allows the tool to generate a version number 47 | automatically. 48 | 49 | A complete draft isn't necessary at this point. In XML, you should have at 50 | least: 51 | ```xml 52 | 53 | 54 | The Unicorn Protocol 55 | ``` 56 | 57 | Markdown is similar: 58 | ```yaml 59 | --- 60 | docname: draft-ietf-unicorn-protocol-latest 61 | title: The Unicorn Protocol 62 | --- 63 | ``` 64 | _(If using `mmark`, replace `---` by `%%%`.)_ 65 | 66 | Add the draft, commit and push your changes: 67 | ```sh 68 | $ git add draft-ietf-unicorn-protocol.md 69 | $ git commit draft-ietf-unicorn-protocol.md 70 | $ git push 71 | ``` 72 | 73 | Clone a copy of this repository into place: 74 | 75 | ```sh 76 | $ git clone https://github.com/martinthomson/i-d-template lib 77 | ``` 78 | 79 | *Option:* If you prefer a stable version of this code, you can use `git submodule` 80 | instead. 81 | 82 | Run the setup command: 83 | 84 | ```sh 85 | $ make -f lib/setup.mk 86 | ``` 87 | 88 | *Option:* If you prefer to use [Julian Reschke's 89 | XSLT](https://github.com/reschke/xml2rfc) for generating HTML, add 90 | `USE_XSLT=true` to the setup command line. 91 | 92 | The setup removes adds some files, updates `README.md` with the details of 93 | your draft, sets up a `gh-pages` branch for your editor's copy. This pushes 94 | the `gh-pages` branch to `origin`. If you don't want that, run `make -f 95 | lib/setup.mk setup-default-branch` instead. 96 | 97 | Finally, push: 98 | 99 | ```sh 100 | $ git push 101 | ``` 102 | 103 | *Note:* The `gh-pages` branch will only contain empty files until you (or CI) 104 | updates the files there. 105 | 106 | 107 | # Fast Setup 108 | 109 | For the brave, or those who are more familiar with git. This is the process I 110 | follow. Make a new repository on GitHub, but don't initialize it with a 111 | README. Then: 112 | 113 | ```sh 114 | $ git init unicorn-protocol 115 | $ cd unicorn-protocol 116 | $ git checkout --orphan main 117 | $ git remote add origin https://github.com/unicorn-wg/unicorn-protocol 118 | # Copy a template in place, change the filename and title. 119 | $ git add draft-*.{md,xml} 120 | $ git commit -m "Initial version blah blah blah" 121 | $ git push -u origin main 122 | $ git clone https://github.com/martinthomson/i-d-template lib 123 | $ make -f lib/setup.mk 124 | $ git push 125 | ``` 126 | 127 | 128 | # Updating The Editor's Copy 129 | 130 | GitHub serves any HTML you check in on the `gh-pages` branch by default. This 131 | can be useful for ensuring that the latest version of your draft is available in 132 | a usable form. 133 | 134 | You can maintain `gh-pages` manually by running the following command 135 | occasionally. 136 | 137 | ```sh 138 | $ make ghpages 139 | ``` 140 | 141 | When you do that, you will need to push the `gh-pages` branch yourself. 142 | 143 | The default template includes files that will enable [GitHub 144 | Actions](https://github.com/features/actions). These will result in 145 | automatically enabling builds that update the editor's copy, publish tagged 146 | drafts to datatracker, and periodically save an archive of issues and pull 147 | requests. 148 | 149 | Or, you can disable GitHub Actions by deleting files under `.github/workflows` 150 | and use Circle CI, as described in the next section. 151 | 152 | 153 | # Automatic Update for Editor's Copy with Circle CI 154 | 155 | This requires that you sign in with [Circle](https://circleci.com/). 156 | 157 | First enable builds for the new repository in the [Circle 158 | Dashboard](https://app.circleci.com/). 159 | 160 | Then, you need to get yourself a [new GitHub application 161 | token](https://github.com/settings/tokens/new). The application token only 162 | needs the `public_repo` privilege. This will let it push updates to your 163 | `gh-pages` branch. 164 | 165 | You can add environment variables using the Circle interface. Make a variable 166 | with the name `GH_TOKEN` and the value of your newly-created application token. 167 | 168 | **WARNING**: You might want to use a dummy account for application tokens to 169 | minimize the consequences of accidental leaks of your key. 170 | 171 | Once you enable pushes, be very careful merging pull requests that alter 172 | `.circleci/config.yml` or `Makefile`. Changes to those files can cause the 173 | value of the token to be published for all to see. You don't want that to 174 | happen. Even though tokens can be revoked easily, discovering a leak might take 175 | some time. Only pushes to the main repository will be able to see the token, so 176 | there is no need to worry about running CI on malicious pull requests (just 177 | don't merge them). 178 | 179 | Circle will now check pull requests for errors, letting you know if things 180 | didn't work out so that you don't merge anything suspect. 181 | 182 | A `.travis.yml` file exists 183 | ([here](https://github.com/martinthomson/i-d-template/blob/main/template/.travis.yml)) 184 | that can be used to setup [Travis](https://travis-ci.org). However, that 185 | process is less well supported. 186 | 187 | 188 | # Regenerating README.md 189 | 190 | When you change things in the repository, it can help if you are able to use 191 | automatic regeneration for files. `README.md` is typically the file that needs 192 | this sort of updating as files are added, renamed, or removed fairly often. 193 | 194 | As long as your input files are newer than `README.md` (use `touch` if this 195 | isn't the case), then it can be rebuilt using `setup.mk` as follows: 196 | 197 | ```sh 198 | make -f lib/setup.mk README.md 199 | git commit -m "Update README" README.md 200 | ``` 201 | 202 | This will erase any customization you have added. 203 | -------------------------------------------------------------------------------- /doc/SETUP.md: -------------------------------------------------------------------------------- 1 | # Installation and Setup 2 | 3 | At a minimum, you need a POSIX environment with: 4 | 5 | * `make` 6 | * `python3` with `pip` and `venv` 7 | * `ruby` with `gem` and `bundler` 8 | 9 | Optionally, you might also want `mmark` or NodeJS and `npm`. 10 | 11 | When running locally, a python virtual environment is created under `lib/` and 12 | necessary tools are installed there. Similarly, ruby installations are created 13 | under `lib/`. The tools that are used can be updated with `make update-deps`. 14 | 15 | 16 | ## General 17 | 18 | These tools work well natively on Linux and Mac. 19 | 20 | Windows users should use [the Windows Subsystem for 21 | Linux](https://docs.microsoft.com/en-us/windows/wsl/install) with a Linux 22 | distribution like Ubuntu (`wsl --install -d Ubuntu` from an administrator 23 | prompt). 24 | 25 | From within Ubuntu, you can install the dependencies for this repository: 26 | 27 | ```sh 28 | sudo apt-get install -y git make python3-pip python3-venv 29 | ``` 30 | 31 | You can also add recommended packages, as follows: 32 | 33 | ```sh 34 | sudo apt-get install -y ruby-bundler npm libxml2-utils 35 | ``` 36 | 37 | It is also possible to use [cygwin](https://cygwin.org/) or an 38 | [MSYS2](https://www.msys2.org/)-based system (like 39 | [Mozilla-Build](https://wiki.mozilla.org/MozillaBuild)), but these can be more 40 | difficult to setup and use. 41 | 42 | 43 | ## make 44 | 45 | Mac users might need to install [Homebrew](https://brew.sh) to get a version of 46 | [`make`](https://www.gnu.org/software/make/) that fully supports all of the template 47 | features. Apple ships an old version of GNU make with XCode which can fail in 48 | mysterious ways. Some effort has been made to avoid this bustage, so most 49 | features work fine, but no warranty is made if something breaks. 50 | 51 | ```sh 52 | brew install make 53 | ``` 54 | 55 | Note that this might install as `gmake`. Follow the instructions to add this as 56 | `make` to your path. 57 | 58 | 59 | ## Python 60 | 61 | You need to provide [Python 3](https://www.python.org/). Be sure not to get 62 | Python 2 or anything older than Python 3.6, which are no longer supported. 63 | 64 | [`pip`](https://pip.pypa.io/en/stable/installing/) and 65 | [`venv`](https://docs.python.org/3/library/venv.html) are used to install 66 | packages into a temporary virtual environment. These are sometimes installed 67 | with python, but some Linux distributions can put these in separate packages 68 | (look for `python3-pip` and `python3-venv`). 69 | 70 | 71 | ## Ruby 72 | 73 | By default, [Ruby](https://www.ruby-lang.org/) is used to install 74 | [kramdown-rfc](https://github.com/cabo/kramdown-rfc). This is installed using 75 | the Ruby [bundler](https://bundler.io/), which also requires the Ruby package 76 | tool, [`gem`](https://rubygems.org/). 77 | 78 | The `gem` tool is often installed alongside Ruby, but you might need to install 79 | bundler separately (look for `ruby-bundler`). 80 | 81 | You can pass `NO_RUBY=true` as an argument to `make` or export an environment 82 | variable with that value to disable this feature. 83 | 84 | 85 | ## mmark 86 | 87 | If you use `mmark` for markdown (i.e., files starting with `%%%`), you will need 88 | to install and manage an `mmark` installation. 89 | 90 | Binaries for [`mmark`](https://github.com/mmarkdown/mmark) is available from 91 | their [releases](https://github.com/mmarkdown/mmark/releases) and Mac users can 92 | use homebrew (`brew install mmark`). 93 | 94 | 95 | ## npm 96 | 97 | If you use dependencies such as [aasvg](https://github.com/martinthomson/aasvg), 98 | you need to have NodeJS and npm installed. 99 | 100 | If `npm` is installed and your project has a `package.json`, then running `make` 101 | will automatically call npm to install the dependencies. 102 | 103 | You can create the `package.json` by running `npm`, for example: 104 | 105 | ```sh 106 | $ npm i -save aasvg 107 | $ git add package.json 108 | $ git commit 109 | ``` 110 | -------------------------------------------------------------------------------- /doc/SUBMITTING.md: -------------------------------------------------------------------------------- 1 | # Submitting Drafts 2 | 3 | Occasionally, you will want to submit versions of your draft to the official 4 | IETF repository. You can use GitHub Actions (or CircleCI) to manage the 5 | process in a mostly automated fashion by [pushing a tag](#ci) or [creating a 6 | release](#release). You can [tag and publish from the command line](#cli) 7 | or just [generate XML for the next version and submit manually](#manual). 8 | 9 | 10 | 11 | ## Using Continuous Integration 12 | 13 | If you have enabled continuous integration, the CI system can automatically 14 | upload a draft to the datatracker. All you have to do is tag the repository, 15 | push the tag, and await an email with further instructions. 16 | 17 | ```sh 18 | $ git push origin 19 | $ git tag -a draft-ietf-unicorn-protocol-00 20 | $ git push origin draft-ietf-unicorn-protocol-00 21 | ``` 22 | 23 | The tag you use has to include the full name of the draft without any file 24 | extension. That name must include a revision number. The tag also has to match 25 | the name of the source file. For the above example, the tag will submit a draft 26 | named `draft-ietf-unicorn-protocol.md` or `draft-ietf-unicorn-protocol.xml`. 27 | 28 | If you have multiple drafts that you are submitting together, tag them all then 29 | push the tags at the same time. Circle CI has a 30 | [bug](https://support.circleci.com/hc/en-us/articles/115013854347-Jobs-builds-not-triggered-when-pushing-tag) 31 | that prevents `git push --tags` from triggering builds if you have multiple 32 | drafts, but pushing individual tags in quick succession will work. 33 | 34 | For this feature to work best, use an annotated tag (that's the `-a` option 35 | above). Annotated tags require a comment (use `-m` to set this on the 36 | command line). An annotated tag associates your email address with the 37 | submission. Lightweight tags don't have an email address and the first author 38 | listed in the draft will be attributed instead. 39 | 40 | **Important**: Before you start, push the commit that you intend to tag. A CI 41 | build will check that the draft can be built correctly so that you don't tag a 42 | broken state. Pushing the tag won't also push the commit it references and so 43 | the build will not run, perhaps not ever. 44 | 45 | **Note**: The email address you use for making this submission needs to match a 46 | verified datatracker account email address ([create one 47 | here](https://datatracker.ietf.org/accounts/create/)). The email address that 48 | git uses can be found by calling `git config --get user.email`, if you aren't 49 | certain. 50 | 51 | Once the CI system has built the draft, it will publish it automatically and you 52 | will receive an email asking you to confirm the submission. You don't need to 53 | have a GitHub account token configured for this feature to be enabled. 54 | 55 | If you have renamed a draft, this will also set the "replaces" field for you 56 | automatically, based on the git history of the file. 57 | 58 | 59 | 60 | ## GitHub Release 61 | 62 | Creating a GitHub release using the intended draft name is an easy way to submit 63 | versions without using the command line. Simply publish a new release that uses 64 | a tag in the form `draft----`. GitHub Actions will take 65 | care of generating XML and submitting it to the datatracker; see above. 66 | 67 | This will attribute the submission to the first author listed in the draft, no 68 | matter who generated the release, just like with a lightweight tag. 69 | 70 | 71 | ## Picking an Email 72 | 73 | The IETF datatracker is quite picky about the email that is associated with a 74 | submission. To work around that, there are several ways email addresses are 75 | chosen for a submission. 76 | 77 | 1. An email address can be set when manually running the GitHub action. 78 | This isn't a common way of requesting submission, but you might manually run 79 | the action if a build fails. 80 | 81 | 3. Setting a variable called `UPLOAD_EMAIL` in your Makefile. Make sure to 82 | export the value: 83 | 84 | ```make 85 | export UPLOAD_EMAIL ?= my@email.example 86 | ``` 87 | 88 | 3. The email address you used to create the tag (annotated tags only). 89 | This will come from your git configuration. 90 | 91 | 4. Your GitHub account email address. 92 | 93 | 5. The email address of the first author in the draft. 94 | 95 | You can tell datatracker about your email address(es) 96 | [here](https://datatracker.ietf.org/accounts/profile/). You might need to 97 | ensure that the first email the above process finds is the primary address 98 | known to the datatracker. 99 | 100 | 101 | ## If the Build Fails 102 | 103 | Sometimes the build will fail. Some errors can be worked around by retrying the 104 | build. Both GitHub Actions and CircleCI offer options to restart the build from 105 | the status page. If you think that an error isn't your fault, try running the 106 | build again. 107 | 108 | If you need to fix a problem in the draft, you can delete the tag: 109 | 110 | ```sh 111 | $ git tag -d draft-ietf-unicorn-protocol-03 112 | $ git push origin :draft-ietf-unicorn-protocol-03 113 | ``` 114 | 115 | Fix the draft, push the changes, let the CI check them, then you can reapply the 116 | tag. 117 | 118 | 119 | 120 | ## Semi-automated Process 121 | 122 | You should only really do this if you don't have CI enabled or if the CI build 123 | fails. The `make publish` command can be used to upload a tagged draft to the 124 | datatracker. 125 | 126 | ```sh 127 | $ git tag -a draft-ietf-unicorn-protocol-03 128 | $ make publish 129 | ``` 130 | 131 | This uses the same process that the CI system uses. If you have multiple tags 132 | pointing to the current HEAD, this will attempt to publish all of those drafts. 133 | 134 | Push tags when you are happy with the submission. If CI is enabled, it might 135 | try to upload and fail, but that's OK. Datatracker will safely reject duplicate 136 | submissions. 137 | 138 | 139 | 140 | ## Manual Process 141 | 142 | If you don't want to use automation, you can make a submission version of your 143 | draft and upload it yourself. Or, in rare circumstances, you might have to 144 | email a draft for submission. You can generate the next numbered version of a 145 | draft using this process, or you can generate any previously tagged version of 146 | a draft. Numbered drafts will be created in a directory named `versioned`. 147 | 148 | This uses git tags to work out what versions exist already, so always use tags. 149 | `make next` will calculate the next version number based on what is tagged. When 150 | there are no tags, it starts at `-00`. 151 | 152 | ```sh 153 | $ make next 154 | ``` 155 | 156 | If you already have a tag in place or want to build a specific tag, you can 157 | identify the specific XML file directly. This works for any version you have 158 | submitted. 159 | 160 | ```sh 161 | $ make versioned/draft-ietf-unicorn-protocol-05.xml 162 | ``` 163 | 164 | **Note**: For older versions of `make`, you might have to run `make extra` 165 | before these targets become available. 166 | 167 | [Submit the .xml file](https://datatracker.ietf.org/submit/). Please don't 168 | submit a `.txt` file. 169 | 170 | If you submit manually, tag your repository and upload the tags so that the next 171 | version can be generated correctly. The tag you should use is the full draft 172 | name including a revision number. 173 | 174 | ```sh 175 | $ git tag -a draft-ietf-unicorn-protocol-05 176 | $ git push origin draft-ietf-unicorn-protocol-05 177 | ``` 178 | 179 | Don't worry if you have CI enabled. CI might try to build and publish the 180 | draft. This will fail as datatracker safely rejects duplicate submissions. 181 | -------------------------------------------------------------------------------- /doc/TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Using the Template Repository 2 | 3 | For creating and managing an [Internet-Draft](https://authors.ietf.org/en/content-guidelines-overview) (I-D). 4 | 5 | A separate [template 6 | repository](https://github.com/martinthomson/internet-draft-template) exists to 7 | help people get started with this tool. With that repository, setup is a very 8 | simple process. 9 | 10 | 1. [Create a new repository using the 11 | template](https://github.com/martinthomson/internet-draft-template/generate). 12 | Check "Include all branches", or you will need to enable GitHub Pages 13 | manually. 14 | 15 | 2. Rename your I-D and add a title. The newly created repository will contain 16 | a link to a page where you can do this using the GitHub editor. Setup will 17 | automatically run. Setup should be done in less than a minute. 18 | 19 | Now you are set to work on the document, using whatever process you choose. 20 | This uses all the same capabilities as the manual process, so contributors can 21 | use command-line tools if that suits them. 22 | 23 | To publish an I-D, [create a new 24 | release](https://github.com/martinthomson/i-d-template/blob/main/doc/SUBMITTING.md#github-release) 25 | and the draft will be submitted to the datatracker automatically. 26 | 27 | Note: The newly created repository will run a few actions during this process 28 | that might fail. That's OK. They will succeed once you edit the draft. If it 29 | bothers you, delete the runs in the UI. 30 | 31 |
32 | 33 | It is not possible to update workflows (the files GitHub Actions use) from an 34 | action unless you use custom personal access tokens. Rather than complicate the 35 | setup process by requiring a token, this template includes all the necessary 36 | workflow files from the beginning, plus a special setup workflow. Before the 37 | repository is properly setup, the other workflows will fail immediately (and 38 | safely). The setup workflow removes itself once it is successful. 39 | 40 |
41 | -------------------------------------------------------------------------------- /doc/TIPS.md: -------------------------------------------------------------------------------- 1 | ## When Something Goes Wrong 2 | 3 | Sometimes, something breaks. There are a few things you can try to reset and 4 | continue. 5 | 6 | Cleaning temporary files might help. 7 | 8 | ```sh 9 | $ make clean 10 | ``` 11 | 12 | You can also update the template (`update`), 13 | the tools that are automatically installed (`update-deps`), or 14 | the files that are copied to your repository (`update-files`). 15 | 16 | ```sh 17 | $ make update 18 | $ make update-deps 19 | $ make update-files 20 | ``` 21 | 22 | Sometimes, it can help to blow away the `lib` directory: 23 | 24 | ```sh 25 | $ rm -rf lib 26 | ``` 27 | 28 | 29 | ## Save Space 30 | 31 | The `lib` directory can get pretty big, because it contains copies of all 32 | of the tools that your draft needs. If you have multiple drafts, you can 33 | point all those drafts to the same checkout with a symlink, as long as you 34 | aren't using `git submodules`: 35 | 36 | ```sh 37 | $ rm -rf lib 38 | $ ln -s ../some-other-draft/lib lib 39 | ``` 40 | 41 | An advantage of this is that updates to `xml2rfc` and other tools that 42 | are triggered from one repository will be available to all others. 43 | 44 | This largely works even if you have different configurations for each 45 | repository. For instance, if you have a `Gemfile` or `requirements.txt`, 46 | any tool that one repository installs will be available to all others. 47 | You do need to remember to list tools in one of these files if you use 48 | them, or builds will fail in CI or for other people. 49 | 50 | Set `ID_TEMPLATE_HOME` in your environment to a common location 51 | (such as a checkout of this repository) and the `Makefile` will create 52 | a symlink for you[^old]. 53 | 54 | ```sh 55 | $ git clone https://github.com/martinthomson/i-d-template i-d-template 56 | $ echo 'export ID_TEMPLATE_HOME="'"$(pwd)"'/i-d-template"' >> ~/.profile 57 | ``` 58 | 59 | [^old]: Old versions of the `Makefile` don't, so you might need to update. 60 | 61 | 62 | ## When Creating Pull Requests on Another Repository 63 | 64 | Make the `origin` remote point to two different places. Pull from the "main" 65 | repository, and push to your own fork. Like this: 66 | 67 | ```sh 68 | $ git clone https://github.com/tlswg/tls13-spec tls13 69 | $ cd tls13 70 | $ git remote set-url --push origin https://github.com/martinthomson/tls13-spec 71 | $ git remote -v 72 | origin https://github.com/tlswg/tls13-spec (fetch) 73 | origin https://github.com/martinthomson/tls13-spec (push) 74 | ``` 75 | 76 | Now when you pull, you will pull from the "main" repository, but pushes go to 77 | your private branch. 78 | 79 | If you work on multiple machines and use push and pull to synchronize, you can 80 | setup another remote for your fork, or just pull or fetch directly by 81 | specifying the full remote name: 82 | 83 | ```sh 84 | $ git pull https://github.com/martinthomson/tls13-spec main 85 | $ git fetch https://github.com/martinthomson/tls13-spec 86 | ``` 87 | 88 | 89 | ## Cleaning Old Branches 90 | 91 | If you work on many pull requests over time, you will create many branches. 92 | Even if you delete the branch on GitHub, your local repository can have lots 93 | of dead branches. The following creates a command, `git trim` that prunes 94 | branches that have been merged. 95 | 96 | ```sh 97 | $ git config --global alias.trim '!f() { git branch --merged @ | sed -e '"'"'/^\*/d;s/^ //;/^\('"'"'$(git config --get trim.savebranch | sed -e '"'"'s/[, ]/\\|/g'"'"')'"'"'\)$/d'"'"' | xargs -r git branch -d; }; f' 98 | $ git config --global trim.savebranch main,gh-pages,gh-issues 99 | ``` 100 | 101 | The `trim.savebranch` config item includes the names of branches that you 102 | want to keep always. 103 | 104 | 105 | ## No Unnecessary Merges on Pull 106 | 107 | When you pull, git can decide to merge for you if your branch has diverged. 108 | This can be annoying to recover from. Setting the following configuration 109 | ensures that you don't create merge commits when you pull: 110 | 111 | ```sh 112 | $ git config --global pull.ff only 113 | ``` 114 | 115 | Now when you pull, your local changes will be rebased onto remote changes. 116 | 117 | 118 | ## When Using kramdown-rfc 119 | 120 | Set `KRAMDOWN_REFCACHEDIR` in your environment to `~/.cache/xml2rfc`. If you 121 | have multiple repositories, this means that you only have a single global 122 | cache. You will download the references for RFC 2119 far less often. Also, 123 | this is where `xml2rfc` caches references, so both tools will prime the cache 124 | for the other. 125 | 126 | Always include the following in the YAML header of the markdown file: 127 | 128 | ```yaml 129 | v: 3 130 | ``` 131 | -------------------------------------------------------------------------------- /doc/TOOLS.md: -------------------------------------------------------------------------------- 1 | # Additional Tools 2 | 3 | You have some custom process that you want to run to validate code, generate 4 | examples, or process content. You might use some specialized tools for this. 5 | 6 | 7 | ## Preprocessing Markdown 8 | 9 | For instance, if a building a draft depends on a preprocessing step where 10 | markdown is changed to include examples or other information, you can do 11 | something like this: 12 | 13 | ```make 14 | MD_PREPROCESSOR := python3 add-examples.py examples.bin 15 | draft-unicorn-protocol.xml: add-examples.py examples.bin 16 | ``` 17 | 18 | This results in the markdown being passed as stdin to the `add-examples.py` 19 | script. This also shows that the XML file depends on the script and the 20 | `examples.bin` file. If either of those files changes - in addition to the 21 | markdown source - then the draft will be rebuilt. 22 | 23 | Note that intermediate files, like the XML file here, are usually deleted 24 | automatically by `make`. If a preprocessing step is particularly expensive 25 | or you need to process the XML specially, you can instruct `make` to keep it 26 | around: 27 | 28 | ```make 29 | .SECONDARY: draft-unicorn-protocol.xml 30 | ``` 31 | 32 | There is no need to check this file in though. Intermediate files like this 33 | should be listed in `.gitignore`. 34 | 35 | 36 | ## Linting Tools 37 | 38 | To run a lint automatically, you can extend the `lint` make target with your 39 | own. Just add something like this to your `Makefile`: 40 | 41 | ```make 42 | # The wc lint validates that the source doesn't exceed 10000 lines. 43 | .PHONY: wc 44 | wc: $(drafts_source) 45 | [ $(wc -l $^) -lt 10000 ] 46 | 47 | lint:: wc 48 | ``` 49 | 50 | You can add multiple linters as you like. 51 | 52 | 53 | ## Installing Dependencies 54 | 55 | CI runs in a limited docker image, which means that you probably won't have 56 | access to your preferred tool in the minimal CI environment. You could fork 57 | the docker image for your build, but that also means creating your own fork 58 | of the GitHub Action as well. 59 | 60 | You can manage additional dependencies provided through common package manager 61 | tools by listing dependencies. Any dependencies listed in a`requirements.txt`, 62 | `Gemfile`, or `package.json` will be automatically installed for you as 63 | [described here](https://github.com/martinthomson/i-d-template/blob/main/deps.mk). 64 | 65 | * For python, create and add a file called 66 | [`requirements.txt`](https://pip.pypa.io/en/stable/reference/requirements-file-format/). 67 | 68 | * For ruby, create and add a file called 69 | [`Gemfile`](https://bundler.io/man/gemfile.5.html). Note that `Gemfile.lock` 70 | should be added to your `.gitignore`. 71 | 72 | * For nodejs, create and add a file called 73 | [`package.json`](https://docs.npmjs.com/cli/v8/configuring-npm/package-json) 74 | or just run `npm add --save ` then add the file. Note that 75 | `package-lock.json` should be added to your `.gitignore`. 76 | 77 | Tools listed in these files will be installed automatically when building, both 78 | locally or in CI. In CI, the files needed are cached, which means that the time 79 | needed to install larger packages is amortized over multiple builds. 80 | 81 | 82 | ## Manually Installing Dependencies 83 | 84 | Say you have a custom command that checks the XML in some way. You can 85 | have the build install this automatically by adding a dependency on a rule 86 | that installs the tool, like so: 87 | 88 | ```make 89 | checker-tool ?= checker-tool 90 | # Use a hidden marker file to indicate that the tool is installed. 91 | checker-marker ?= .checker-tool-installed.txt 92 | # Adding the marker to $(DEPS_FILES) is optional, but it will ensure 93 | # that your installation will be refreshed with `make update`. 94 | DEPS_FILES += $(checker-marker) 95 | .PHONY: run-checker 96 | run-checker: $(drafts_xml) $(checker-marker) 97 | $(checker-tool) $(filter-out $(checker-marker),$^) 98 | 99 | lint:: run-checker 100 | 101 | $(checker-marker): 102 | magically install checker-tool as $(checker-tool) 103 | @touch $@ 104 | ``` 105 | 106 | Note that you might need to specify a full path to the installed file if the 107 | installer puts the file in a place that isn't on your `$PATH`. Extra care is 108 | needed here when running in CI. 109 | 110 | One way to do this is to ensure that the binary is identified when running 111 | `make`. You can modify `.github/workflows/*.yml` or `.circleci/config.yml` 112 | to include additional arguments to make should do this. The following is a 113 | modified target in `.github/workflows/ghpages.yml` and 114 | `.github/workflows/publish.yml`: 115 | 116 | ```yaml 117 | - name: "Build Drafts" 118 | uses: martinthomson/i-d-template@v1 119 | with: 120 | make: latest checker-tool=/root/.local/bin/checker-tool 121 | ``` 122 | 123 | Alternatively, you can attempt to detect that you are running in CI and adjust 124 | the installation process accordingly. Then the target will be run 125 | automatically. 126 | 127 | ```make 128 | $(checker-marker): 129 | @hash $(notdir $(checker-tool)) 2>/dev/null || \ 130 | magically install checker-tool for $$USER as $(checker-tool) 131 | @touch $@ 132 | ``` 133 | 134 | Note that GitHub Actions builds run as root, but CircleCI builds use a special user, 135 | which can have a significant effect on how you install some packages. 136 | 137 | 138 | ## Using the Mega/Math image in GitHub Actions 139 | 140 | For GitHub Actions, the `martinthomson/i-d-template@v1m` tag identifies an 141 | alternative action that uses [a different Docker 142 | image](https://github.com/martinthomson/i-d-template/pkgs/container/i-d-template-math) 143 | than the default. This image includes additional tools, as shown in its 144 | [Dockerfile](https://github.com/martinthomson/i-d-template/blob/main/docker/math/Dockerfile). 145 | 146 | If you want to use the tools included in this image, use the 147 | `martinthomson/i-d-template@v1m` action. If you want to add tools to this 148 | image, please send a pull request that modifies this Dockerfile. Unlike the 149 | [core 150 | image](https://github.com/martinthomson/i-d-template/blob/main/docker/action/Dockerfile), 151 | which is kept deliberately lean, most requests to add content to this image will 152 | be accepted. The cost is that CI runs using this image will take quite a bit 153 | longer as fetching the image takes more time. 154 | -------------------------------------------------------------------------------- /doc/UPDATE.md: -------------------------------------------------------------------------------- 1 | # Updating 2 | 3 | Over time, this project will be improved by adding features or fixing bugs. 4 | 5 | Get a new copy of this code by running: 6 | 7 | ```sh 8 | $ make update 9 | ``` 10 | 11 | This will pull an updated copy of this repository and ensure that its 12 | dependencies are also updated. 13 | 14 | This will also install [git 15 | hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) that catch some 16 | common mistakes. 17 | 18 | If you run `make`, then `make update` will run automatically every 2 weeks. 19 | 20 | ## Automatically Generated Files 21 | 22 | The files that this repository generates sometimes get out of sync. This might 23 | be because of changes external to the repository (such as CI system updates) or 24 | changes to the draft (a new filename, author, or title). 25 | 26 | These files can be updated by running: 27 | 28 | ```sh 29 | $ make update-files 30 | ``` 31 | 32 | This updates all of the files below, plus `.gitignore`, `Makefile`, and 33 | `.note.xml` (if needed). 34 | 35 | This erases any customizations that have been made to those files. 36 | 37 | The `make update-files` rule can be manually run using the "Update Generated 38 | Files" GitHub Action. 39 | 40 | Special targets can be run individually: 41 | 42 | * `make update-readme` regenerates `README.md` (and `CONTRIBUTING.md`) 43 | * `make update-codeowners` regenerates `.github/CODEOWNERS` 44 | * `make update-venue` regenerates the `venue` section in a 45 | [kramdown-rfc](https://github.com/cabo/kramdown-rfc) draft 46 | 47 | -------------------------------------------------------------------------------- /doc/WG-SETUP.md: -------------------------------------------------------------------------------- 1 | # Setting up Github for an IETF WG 2 | 3 | * The unit of Github is a repository. 4 | * Repositories are owned by individuals or organizations 5 | * The mapping to IETF concepts is: 6 | * repository <-> draft(s) 7 | * organization <-> WG 8 | * There an [IETF organization](https://github.com/ietf), but it's just a shell that maintains pointers to WGs 9 | 10 | The starting point for setting up a WG / I-D is here: 11 | https://github.com/martinthomson/i-d-template 12 | 13 | # Prerequisites 14 | 15 | * A web browser 16 | * A Github account 17 | * A git client. Command-line is best, and we'll assume that below. You can probably accomplish the same thing with a GUI git client, but it will be much harder. You can get a client at 18 | * Tools to "compile" your draft, for which there are instructions here: 19 | 20 | # Setting up an organization 21 | 22 | As that document linked above says, the first thing to do is to click the link and set up a new organization. You will need to choose an organization name and provide a "billing" email (even though you'll use the free version). The normal pattern for organization names is $WGNAME-wg, e.g., "rtcweb-wg". For the billing email, it's simplest just to put in the email that goes with your Github account. 23 | 24 | Then you'll be prompted to invite other people to the organization. You should add your co-chair, and maybe the responsible AD. We'll talk about authors further down, when we talk about repositories. Click "Finish", and your organization will be created. 25 | 26 | Once your organization has been created, you can access it under the "Organizations" section of your Github home page, or at , for example . 27 | 28 | 29 | # Setting up a repository for a document 30 | 31 | If you go to your organization's page (i.e., the WG's page), then there's a big green button in the middle that says "New Repository". Click it! 32 | 33 | The repository name should be the draft name, without the "draft-ietf-wg" part; that's redundant with the rest of the URL. So for example, if you were creating draft-ietf-unicorn-tears, the repository name would simply be "tears". You can leave the "Description" field blank or fill in a brief description; it's just Github eye candy. Choose "Public" for the visibility level (transparency!). Finally, make sure that the "Initialize this repository with a README" check box is checked (this will simplify things down the road). 34 | 35 | Click "Create Repository", and now you have a repo! 36 | 37 | 38 | # Allowing people to contribute to a document 39 | 40 | In order to keep things orderly, only certain people can commit to a document's repository (or "push" in the git lingo). The owners of the organization have push privileges by default (that's the chairs and AD you added earlier). You will need to set the permissions to allow the document author to push to the repository. 41 | 42 | This is done by adding authors to a "team" that can push to this repository. Back on your organization page, find the tab that says "Teams". Hit the "New Team" button. Choose a name, such as "$REPOSITORY_NAME editors" (this name is not publicly visible to anyone but you). Select "Write Access" so that editors can push. Then, find the search box that says "Add a person" (top right) and add the Github user ids of the editors of the draft. This will send the authors an invitation that they will need to accept. 43 | 44 | Protip: If you have regular contributors to the working group, it might pay to create another group with "Read Access" for those contributors. This allows you to assign tasks to those people in the Github UI, and makes other user-related functions more accessible. 45 | 46 | 47 | # Initializing the repository with a document 48 | 49 | Since we're talking about a WG document here, we'll assume you've already got an XML or Markdown file that represents the draft. We will set up the repo to contain that document, along with some tooling that makes it easy to "compile" the document into RFC format. 50 | 51 | WARNING: THIS IS THE PART THAT REQUIRES A COMMAND LINE. 52 | 53 | Full instructions for initializing the repository are [here](./REPO.md). 54 | 55 | In essence, what you're doing is copying over the tooling from a template repository, and then creating the Internet-Draft either from your existing source file, or from a template. Both XML and Markdown are supported as the draft source. (There is also experimental support for the [outline2xml](https://github.com/martinthomson/i-d-template/pull/2) format, which has been used for netconf drafts.) 56 | 57 | Once you've got your draft populated, just use "make" to create HTML- and TXT-formatted versions Internet-Draft. 58 | -------------------------------------------------------------------------------- /doc/images/comment-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/comment-line.png -------------------------------------------------------------------------------- /doc/images/commit-branch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/commit-branch.png -------------------------------------------------------------------------------- /doc/images/commit-main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/commit-main.png -------------------------------------------------------------------------------- /doc/images/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/edit.png -------------------------------------------------------------------------------- /doc/images/pr-broken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr-broken.png -------------------------------------------------------------------------------- /doc/images/pr-conflict.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr-conflict.png -------------------------------------------------------------------------------- /doc/images/pr-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr-edit.png -------------------------------------------------------------------------------- /doc/images/pr-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr-files.png -------------------------------------------------------------------------------- /doc/images/pr-interstitial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr-interstitial.png -------------------------------------------------------------------------------- /doc/images/pr-merge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr-merge.png -------------------------------------------------------------------------------- /doc/images/pr-resolve-controls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr-resolve-controls.png -------------------------------------------------------------------------------- /doc/images/pr-revert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr-revert.png -------------------------------------------------------------------------------- /doc/images/pr-review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr-review.png -------------------------------------------------------------------------------- /doc/images/pr-update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr-update.png -------------------------------------------------------------------------------- /doc/images/pr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martinthomson/i-d-template/1e574ab5138b8dd11fb8b45c3129e4dc1021a57f/doc/images/pr.png -------------------------------------------------------------------------------- /docker/action/.dockerignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | -------------------------------------------------------------------------------- /docker/action/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | LABEL maintainer="Martin Thomson " 3 | 4 | RUN set -e; \ 5 | echo > /etc/apk/repositories; \ 6 | echo http://dl-cdn.alpinelinux.org/alpine/v$(cat /etc/alpine-release | cut -d'.' -f1,2)/main >> /etc/apk/repositories; \ 7 | echo http://dl-cdn.alpinelinux.org/alpine/v$(cat /etc/alpine-release | cut -d'.' -f1,2)/community >> /etc/apk/repositories; \ 8 | apk add --no-cache \ 9 | bash \ 10 | ca-certificates \ 11 | coreutils \ 12 | curl \ 13 | findutils \ 14 | git \ 15 | grep \ 16 | libintl \ 17 | libxml2-utils \ 18 | libxslt \ 19 | make \ 20 | musl-locales \ 21 | nodejs \ 22 | npm \ 23 | openssh \ 24 | py3-appdirs \ 25 | py3-configargparse \ 26 | py3-html5lib \ 27 | py3-jinja2 \ 28 | py3-lxml \ 29 | py3-magic \ 30 | py3-pip \ 31 | py3-pycountry \ 32 | py3-pyflakes \ 33 | py3-requests \ 34 | py3-setuptools \ 35 | py3-six \ 36 | py3-toml \ 37 | py3-wheel \ 38 | py3-yaml \ 39 | python3 \ 40 | ruby \ 41 | ruby-bundler \ 42 | sed 43 | 44 | ENV SHELL=/bin/bash \ 45 | LANG=en_US.UTF-8 \ 46 | LANGUAGE=en_US:en \ 47 | LC_ALL=en_US.UTF-8 \ 48 | KRAMDOWN_PERSISTENT=t \ 49 | PIP_BREAK_SYSTEM_PACKAGES=true 50 | 51 | COPY ["entrypoint.sh", "requirements.txt", "Gemfile", "/i-d-template/"] 52 | RUN set -e; \ 53 | install() { \ 54 | file="$1"; url="$2"; sha="$3"; \ 55 | target="/usr/local/bin/$(basename "$file")"; \ 56 | tmp=$(mktemp -t "${tool}XXXXX.tgz"); \ 57 | curl -sSLf "$url" -o "$tmp"; \ 58 | [ $(sha256sum -b "$tmp" | cut -d ' ' -f 1 -) = "$sha" ]; \ 59 | tar xzfO "$tmp" "$file" >"$target"; chmod 755 "$target"; \ 60 | rm -f "$tmp"; \ 61 | }; \ 62 | tool_install() { \ 63 | tool="$1"; version="$2"; sha="$3"; \ 64 | curl -SSLf -o "/usr/local/bin/${tool}" \ 65 | "https://raw.githubusercontent.com/ietf-tools/${tool}-mirror/${version}/${tool}"; \ 66 | [ $(sha256sum -b "/usr/local/bin/${tool}" | cut -d ' ' -f 1 -) = "$sha" ]; \ 67 | chmod 755 "/usr/local/bin/${tool}"; \ 68 | }; \ 69 | set -x; \ 70 | tool_install idnits bfda9518d5b4e8a682f2e6f34a449c2d3ea74539 \ 71 | 0ea07cdc982645a85622ccc2636a4d21cde54137269f0e8a204e1bc519b48818; \ 72 | mmark="2.2.40"; \ 73 | install mmark \ 74 | "https://github.com/mmarkdown/mmark/releases/download/v${mmark}/mmark_${mmark}_linux_amd64.tgz" \ 75 | 720f8cccd5c38a2a333d0a6af4146df1dd798f4cfcb5e83419fc518348bdf7ad; \ 76 | npm install -g aasvg; \ 77 | pip3 install --no-cache-dir --disable-pip-version-check \ 78 | -r /i-d-template/requirements.txt; \ 79 | bundle install --system --gemfile=/i-d-template/Gemfile 80 | 81 | ENTRYPOINT ["/i-d-template/entrypoint.sh"] 82 | -------------------------------------------------------------------------------- /docker/action/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'kramdown-rfc' 4 | -------------------------------------------------------------------------------- /docker/action/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | git config --global --add safe.directory "$GITHUB_WORKSPACE" 4 | git config --global --add safe.directory "$GITHUB_WORKSPACE/./.git" 5 | 6 | if [ ! -f Makefile ]; then 7 | export PRE_SETUP=true 8 | fi 9 | if [ "$1" = "setup" ]; then 10 | echo "Running setup" 11 | exec make -f lib/setup.mk 12 | fi 13 | if [ ! -f Makefile ]; then 14 | echo "Cloning i-d-template into lib for default configuration." 15 | echo "Note: Until setup is complete, the editor's copy will not be updated." 16 | git clone https://github.com/martinthomson/i-d-template lib 17 | cp lib/template/Makefile Makefile 18 | fi 19 | 20 | make .targets.mk 21 | exec make "$@" 22 | -------------------------------------------------------------------------------- /docker/action/requirements.txt: -------------------------------------------------------------------------------- 1 | archive-repo 2 | iddiff 3 | pyang 4 | pyyaml 5 | rfc-tidy 6 | svgcheck 7 | toml 8 | xml2rfc 9 | -------------------------------------------------------------------------------- /docker/circleci/.dockerignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.swp 3 | -------------------------------------------------------------------------------- /docker/circleci/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG REGISTRY=ghcr.io 2 | ARG VERSION=latest 3 | FROM ${REGISTRY}/martinthomson/i-d-template-action:${VERSION} 4 | 5 | ENV USER=idci \ 6 | LOGNAME=idci \ 7 | HOSTNAME=idci \ 8 | HOME=/home/idci 9 | 10 | RUN apk add --no-cache shadow && useradd -d "$HOME" -s "$SHELL" -m "$USER" 11 | WORKDIR $HOME 12 | USER $USER 13 | 14 | ENV KRAMDOWN_REFCACHEDIR=$HOME/.cache/xml2rfc 15 | RUN mkdir -p $KRAMDOWN_REFCACHEDIR 16 | 17 | RUN mkdir -p $HOME/.ssh && \ 18 | echo 'github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==' >> ~/.ssh/known_hosts 19 | 20 | RUN GIT_REFERENCE=$HOME/git-reference; \ 21 | git init -q $GIT_REFERENCE; \ 22 | git -C $GIT_REFERENCE remote add i-d-template https://github.com/martinthomson/i-d-template; \ 23 | git -C $GIT_REFERENCE remote add rfc2629xslt https://github.com/reschke/xml2rfc; \ 24 | git -C $GIT_REFERENCE fetch --all 25 | 26 | # Unset the entrypoint from the base image 27 | ENTRYPOINT [] 28 | -------------------------------------------------------------------------------- /docker/math/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG REGISTRY=ghcr.io 2 | ARG VERSION=latest 3 | FROM ${REGISTRY}/martinthomson/i-d-template-action:${VERSION} 4 | 5 | RUN set -e; \ 6 | apk add --no-cache --virtual .locale_build cmake musl-dev gcc gettext-dev bmake autoconf automake libtool; \ 7 | tmp=$(mktemp -d /tmp/asciitex-XXXXX); \ 8 | git clone --depth=1 https://github.com/larseggert/asciiTeX "$tmp"; \ 9 | cd "$tmp"; \ 10 | cmake -DCMAKE_BUILD_TYPE=Release -DDISABLE_TESTING=On -DCMAKE_INSTALL_PREFIX:PATH=/usr .; \ 11 | make install; \ 12 | cd /; \ 13 | rm -rf "$tmp"; \ 14 | tmp=$(mktemp -d /tmp/utftex-XXXXX); \ 15 | git clone --depth=1 https://github.com/bartp5/libtexprintf "$tmp"; \ 16 | cd "$tmp"; \ 17 | ./autogen.sh; \ 18 | ./configure; \ 19 | make install; \ 20 | cd /; \ 21 | rm -rf "$tmp"; \ 22 | tmp=$(mktemp -d /tmp/kgt-XXXXX); \ 23 | git clone --recursive https://github.com/katef/kgt.git "$tmp"; \ 24 | cd "$tmp"; \ 25 | bmake -r install; \ 26 | cd /; \ 27 | rm -rf "$tmp"; \ 28 | apk del .locale_build 29 | 30 | RUN apk add --no-cache \ 31 | chromium \ 32 | nss \ 33 | freetype \ 34 | harfbuzz \ 35 | ttf-freefont \ 36 | yarn && \ 37 | npm install -g mathjax-node-cli 38 | -------------------------------------------------------------------------------- /example/draft-todo-yourname-protocol.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "TODO - Your title" 3 | abbrev: "TODO - Abbreviation" 4 | docname: draft-todo-yourname-protocol-latest 5 | category: info 6 | 7 | ipr: trust200902 8 | area: General 9 | workgroup: TODO Working Group 10 | keyword: Internet-Draft 11 | 12 | stand_alone: yes 13 | smart_quotes: no 14 | pi: [toc, sortrefs, symrefs] 15 | 16 | author: 17 | - 18 | ins: H. Tschofenig 19 | name: Hannes Tschofenig 20 | organization: Arm Limited 21 | email: hannes.tschofenig@arm.com 22 | 23 | normative: 24 | 25 | informative: 26 | 27 | 28 | --- abstract 29 | 30 | TODO Abstract 31 | 32 | 33 | --- middle 34 | 35 | # Introduction 36 | 37 | TODO Introduction 38 | 39 | 40 | # Conventions and Definitions 41 | 42 | {::boilerplate bcp14-tagged} 43 | 44 | 45 | # Security Considerations 46 | 47 | TODO Security 48 | 49 | 50 | # IANA Considerations 51 | 52 | This document has no IANA actions. 53 | 54 | 55 | 56 | --- back 57 | 58 | # Acknowledgments 59 | {:numbered="false"} 60 | 61 | TODO acknowledge. 62 | -------------------------------------------------------------------------------- /extract-metadata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import os.path 5 | import re 6 | import sys 7 | import xml 8 | import xml.sax 9 | 10 | 11 | def extract_md(filename): 12 | try: 13 | with open(filename, "r") as fh: 14 | section_header = fh.readline().strip() 15 | if section_header != r"%%%" and section_header != r"---": 16 | raise Exception( 17 | 'Unexpected first line in markdown file: got "{section_header}", expected `%%%` or `---`' 18 | ) 19 | header_data = "" 20 | for line in fh: 21 | if line.strip() == section_header: 22 | break 23 | header_data += line 24 | if section_header == r"---": 25 | try: 26 | import yaml 27 | 28 | return next(yaml.safe_load_all(header_data)) 29 | except ImportError as err: 30 | raise Exception( 31 | "Unable to import python `yaml` library, needed for Kramdown processing" 32 | ) from err 33 | if section_header == r"%%%": 34 | try: 35 | import toml 36 | 37 | return toml.loads(header_data) 38 | except ImportError as err: 39 | raise Exception( 40 | "Unable to import python `toml` library, needed for Mmark processing" 41 | ) from err 42 | except Exception as err: 43 | return {} 44 | 45 | 46 | def extract_xml(filename): 47 | parser = xml.sax.make_parser() 48 | handler = XmlHandler() 49 | parser.setContentHandler(handler) 50 | parser.parse(filename) 51 | return handler.metadata 52 | 53 | 54 | class XmlHandler(xml.sax.handler.ContentHandler): 55 | interesting_elements = ["title", "area", "workgroup"] 56 | wsp = re.compile(r"\s+") 57 | 58 | def __init__(self): 59 | self.metadata = {} 60 | self.stack = [] 61 | self.content = "" 62 | self.attrs = {} 63 | self.in_front = False 64 | 65 | def startElement(self, name, attrs): 66 | self.stack.append(name) 67 | self.attrs = attrs 68 | if self.stack == ["rfc", "front"]: 69 | self.in_front = True 70 | 71 | def endElement(self, name): 72 | pop_name = self.stack.pop() 73 | assert name == pop_name 74 | if self.in_front and pop_name == "front": 75 | self.in_front = False 76 | if self.in_front and name in self.interesting_elements: 77 | if name == "title" and self.attrs.get("abbrev", "").strip() != "": 78 | self.metadata["abbrev"] = self.attrs["abbrev"] 79 | self.metadata[name] = self.wsp.sub(" ", self.content.strip()) 80 | self.content = "" 81 | self.attrs = {} 82 | 83 | def characters(self, data): 84 | self.content += data 85 | 86 | def processingInstruction(self, target, data): 87 | self.metadata[target.strip()] = data.strip() 88 | 89 | 90 | extract_funcs = {".md": extract_md, ".xml": extract_xml} 91 | 92 | 93 | if __name__ == "__main__": 94 | filename = sys.argv[1] 95 | target = sys.argv[2] 96 | if os.path.isfile(filename): 97 | fileext = os.path.splitext(filename)[1] 98 | extract_func = extract_funcs.get(fileext, lambda a: {}) 99 | metadata = extract_func(filename) 100 | if target == "abbrev": 101 | value = metadata.get("abbrev", None) 102 | if value == None: 103 | value = metadata.get("title", "") 104 | else: 105 | value = metadata.get(target, "") 106 | else: 107 | value = "" 108 | print(value) 109 | -------------------------------------------------------------------------------- /format-trace.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | trace="$1" 3 | status="$2" 4 | 5 | if [[ "$status" -eq 0 ]]; then 6 | echo "## Build Succeeded" 7 | else 8 | echo "## Build Failed" 9 | fi 10 | echo 11 | 12 | tmp=$(mktemp) 13 | trap 'rm -f $tmp' EXIT 14 | cut -f1 -d' ' "$trace" | sort | uniq | while read -r f; do 15 | failed= 16 | grep "^$f " "$trace" | cut -f2- -d' ' | sort | uniq >"$tmp" 17 | while read -r j s; do 18 | if [[ "$failed" != "$j" && "$s" != "0" ]]; then 19 | if [[ -z "$failed" ]]; then 20 | echo "❌ $f" 21 | echo 22 | fi 23 | echo "
❌ step '$j' failed" 24 | echo 25 | echo '```' 26 | grep "^$f $j " "$trace" | cut -f3- -d' ' | tail +2 27 | echo '```' 28 | echo "
" 29 | echo 30 | failed=$j 31 | fi 32 | done <"$tmp" 33 | if [[ -z "$failed" ]]; then 34 | echo "✅ $f" 35 | echo 36 | fi 37 | done 38 | -------------------------------------------------------------------------------- /get-email.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Given a tag and a draft, attempt to work out who is responsible for the submission. 3 | # 4 | # This tries several things, starting from a simple UPLOAD_EMAIL environment variable. 5 | # Then it looks at the email of the person that tagged the commit, if that is set. 6 | # Then it starts looking for who made the commit or who authored the commit. 7 | # For each of these, GitHub could use a @users.noreply.github.com address. 8 | # In which case, this attempts to use the GitHub API to get their email. 9 | # Finally, this picks the first listed author email address from the draft. 10 | 11 | tried=() 12 | emailok() { 13 | tried+=("$1") 14 | if [ -n "$2" -a "$2" != "noreply@github.com" ]; then 15 | echo "$2" 16 | exit 0 17 | fi 18 | } 19 | 20 | # Exit on errors, 21 | set -e 22 | # ... but don't print anything; we're using tokens. 23 | set +x 24 | 25 | emailok "\$UPLOAD_EMAIL environment variable" "$UPLOAD_EMAIL" 26 | 27 | tag="$1" 28 | draft="$2" 29 | 30 | for k in taggeremail committeremail authoremail; do 31 | tagger="$(git tag --list --format '%('"$k"')' "$tag" | sed -e 's/^$//')" 32 | ghuser="${tagger%@users.noreply.github.com}" 33 | ghuser="${ghuser#*+}" 34 | if [ "$ghuser" = "$tagger" ]; then 35 | emailok "git $k" "$tagger" 36 | elif [ -n "$GITHUB_API_TOKEN" ]; then 37 | script="import json,sys 38 | e=json.load(sys.stdin).get('email') 39 | if isinstance(e, str): print(e)" 40 | userjson="$(curl -SsLf \ 41 | -H "Accept: application/vnd.github+json" \ 42 | -H "Authorization: Bearer $GITHUB_API_TOKEN" \ 43 | -H "X-GitHub-Api-Version: 2022-11-28" \ 44 | "https://api.github.com/users/$ghuser" 2>/dev/null)" 45 | apiok=$? 46 | emailok "github API for $k ($ghuser)" \ 47 | "$([ "$apiok" -eq 0 ] && echo "$userjson" | python -c "$script" 2>/dev/null)" 48 | fi 49 | done 50 | 51 | if [ -n "$draft" ]; then 52 | emailok "draft author" "$(xmllint --xpath '/rfc/front/author[1]/address/email/text()' "$draft" 2>/dev/null)" 53 | fi 54 | 55 | # Give up 56 | { 57 | echo "Unable to find email to use for submission." 58 | echo "Tried:" 59 | for t in "${tried[@]}"; do 60 | echo " $t" 61 | done 62 | } 1>&2 63 | exit 1 64 | -------------------------------------------------------------------------------- /ghpages.mk: -------------------------------------------------------------------------------- 1 | ## Update the gh-pages branch with useful files 2 | 3 | ifeq (true,$(TRAVIS)) 4 | # Travis is a nightmare. It doesn't actually include the branch name in the 5 | # repo at all. Also, it doesn't consistently set the current branch name. 6 | ifneq (,$(TRAVIS_PULL_REQUEST_BRANCH)) 7 | SOURCE_BRANCH := $(TRAVIS_PULL_REQUEST_BRANCH) 8 | else 9 | SOURCE_BRANCH := $(TRAVIS_BRANCH) 10 | endif 11 | else 12 | ifdef GITHUB_REF 13 | ifneq (,$(filter refs/heads/%,$(GITHUB_REF))) 14 | SOURCE_BRANCH := $(patsubst refs/heads/%,%,$(GITHUB_REF)) 15 | else 16 | ifneq (,$(filter refs/tags/%,$(GITHUB_REF))) 17 | SOURCE_BRANCH := $(patsubst refs/tags/%,%,$(GITHUB_REF)) 18 | else 19 | SOURCE_BRANCH := $(notdir $(GITHUB_REF)) 20 | endif 21 | endif 22 | else 23 | SOURCE_BRANCH := $(shell git rev-parse --abbrev-ref HEAD) 24 | ifeq (HEAD,$(SOURCE_BRANCH)) 25 | SOURCE_BRANCH := $(shell git rev-parse --short HEAD) 26 | endif 27 | endif 28 | endif 29 | 30 | # Disable pushing if we're not setup and for pull requests. 31 | # Otherwise, enable it if we appear to have credentials. 32 | ifeq (true,$(PRE_SETUP)) 33 | PUSH_GHPAGES ?= false 34 | endif 35 | ifeq (pull_request,$(GITHUB_EVENT_NAME)) 36 | PUSH_GHPAGES ?= false 37 | endif 38 | ifneq (,$(GITHUB_PUSH_TOKEN)$(CI_HAS_WRITE_KEY)) 39 | PUSH_GHPAGES ?= true 40 | endif 41 | PUSH_GHPAGES ?= false 42 | 43 | # PAGES_BRANCH is where the latest upstream version of the (typically GitHub) 44 | # Pages is fetched from, and where it is pushed to. Different hosters have 45 | # different branch names for this purpose (if they do it that way at all). 46 | ifeq (github.com,$(GITHUB_HOST)) 47 | PAGES_BRANCH ?= gh-pages 48 | else ifeq (tmp,$(GITHUB_HOST)) 49 | # OK, this is gross, but this is what happens when we are operating under test. 50 | PAGES_BRANCH ?= gh-pages 51 | else 52 | # This is common across all Forgejo instances, and as there is no more 53 | # generic default, this is also the catch-all. 54 | PAGES_BRANCH ?= pages 55 | endif 56 | 57 | .IGNORE: fetch-ghpages 58 | .PHONY: fetch-ghpages 59 | fetch-ghpages: 60 | git fetch -qf origin ${PAGES_BRANCH}:${PAGES_BRANCH} 61 | 62 | GHPAGES_ROOT := $(TMPDIR)/ghpages$(PID) 63 | ghpages: $(GHPAGES_ROOT) 64 | $(GHPAGES_ROOT): fetch-ghpages 65 | @git show-ref refs/heads/${PAGES_BRANCH} >/dev/null 2>&1 || \ 66 | (git show-ref refs/remotes/origin/${PAGES_BRANCH} >/dev/null 2>&1 && \ 67 | git branch -t ${PAGES_BRANCH} origin/${PAGES_BRANCH}) || \ 68 | ! echo 'Error: No ${PAGES_BRANCH} branch, run `make -f $(LIBDIR)/setup.mk setup-ghpages` to initialize it.' 69 | git clone -q -b ${PAGES_BRANCH} . $@ 70 | 71 | GHPAGES_TARGET := $(GHPAGES_ROOT)$(filter-out /$(DEFAULT_BRANCH),/$(SOURCE_BRANCH)) 72 | ifneq ($(GHPAGES_TARGET),$(GHPAGES_ROOT)) 73 | $(GHPAGES_TARGET): $(GHPAGES_ROOT) 74 | mkdir -p $@ 75 | endif 76 | 77 | GHPAGES_PUBLISHED := $(drafts_html) $(drafts_txt) $(GHPAGES_EXTRA) 78 | GHPAGES_INSTALLED := $(addprefix $(GHPAGES_TARGET)/,$(GHPAGES_PUBLISHED)) 79 | $(GHPAGES_INSTALLED): $(GHPAGES_PUBLISHED) $(GHPAGES_TARGET) | cleanup-ghpages 80 | cp -f $(notdir $@) $@ 81 | 82 | GHPAGES_ALL := $(GHPAGES_INSTALLED) $(GHPAGES_TARGET)/index.$(INDEX_FORMAT) 83 | $(GHPAGES_TARGET)/index.$(INDEX_FORMAT): $(GHPAGES_INSTALLED) $(DEPS_FILES) | cleanup-ghpages 84 | $(LIBDIR)/build-index.sh $(INDEX_FORMAT) "$(dir $@)" "$(SOURCE_BRANCH)" "$(GITHUB_HOST)" "$(GITHUB_USER)" "$(GITHUB_REPO)" $(drafts_source) >$@ 85 | 86 | ifneq ($(GHPAGES_TARGET),$(GHPAGES_ROOT)) 87 | GHPAGES_ALL += $(GHPAGES_ROOT)/index.$(INDEX_FORMAT) 88 | $(GHPAGES_ROOT)/index.$(INDEX_FORMAT): $(GHPAGES_INSTALLED) $(DEPS_FILES) 89 | $(LIBDIR)/build-index.sh $(INDEX_FORMAT) "$(dir $@)" "$(DEFAULT_BRANCH)" "$(GITHUB_HOST)" "$(GITHUB_USER)" "$(GITHUB_REPO)" $(drafts_source) >$@ 90 | endif 91 | 92 | # GHPAGES_COMMIT_TTL is the number of days worth of commits to keep on the ${PAGES_BRANCH} branch. 93 | GHPAGES_COMMIT_TTL ?= 90 94 | # GHPAGES_BRANCH_TTL is the number of days to retain a directory on ${PAGES_BRANCH} 95 | # after the corresponding branch has been deleted. This is measured from the last change. 96 | GHPAGES_BRANCH_TTL ?= 30 97 | .PHONY: cleanup-ghpages 98 | cleanup-ghpages: $(GHPAGES_ROOT) 99 | -@for remote in `git remote`; do \ 100 | git remote prune $$remote; \ 101 | done; 102 | 103 | ifneq (true,$(CI)) 104 | # Drop old ${PAGES_BRANCH} commits. 105 | # Retain $(GHPAGES_COMMIT_TTL) days of history. 106 | # Only run this if more than $(GHPAGES_COMMIT_TTL)*2 days of history exists. 107 | @KEEP=$$((`date '+%s'`-($(GHPAGES_COMMIT_TTL)*86400))); \ 108 | CUTOFF=$$((`date '+%s'`-($(GHPAGES_COMMIT_TTL)*172800))); \ 109 | ROOT=`git -C $(GHPAGES_ROOT) rev-list --max-parents=0 ${PAGES_BRANCH}`; \ 110 | if [ `git -C $(GHPAGES_ROOT) show -s --format=%ct $$ROOT` -lt $$CUTOFF ]; then \ 111 | NEW_ROOT=`git -C $(GHPAGES_ROOT) rev-list --min-age=$$KEEP --max-count=1 ${PAGES_BRANCH}`; \ 112 | if [ $$NEW_ROOT != $$ROOT ]; then \ 113 | git -C $(GHPAGES_ROOT) replace --graft $$NEW_ROOT && \ 114 | FILTER_BRANCH_SQUELCH_WARNING=1 git -C $(GHPAGES_ROOT) filter-branch ${PAGES_BRANCH}; \ 115 | fi \ 116 | fi 117 | endif 118 | 119 | # Clean up obsolete directories 120 | # Keep old branches for $(GHPAGES_BRANCH_TTL) days after the last changes (on the ${PAGES_BRANCH} branch). 121 | @CUTOFF=$$(($$(date '+%s')-($(GHPAGES_BRANCH_TTL)*86400))); \ 122 | MAYBE_OBSOLETE=`comm -13 <(git branch -a | sed -e 's,.*[ /],,' | sort | uniq) <(ls $(GHPAGES_ROOT) | sed -e 's,.*/,,')`; \ 123 | for item in $$MAYBE_OBSOLETE; do \ 124 | if [ -d "$(GHPAGES_ROOT)/$$item" ] && \ 125 | [ `git -C $(GHPAGES_ROOT) log -n 1 --format=%ct -- $$item` -lt $$CUTOFF ]; then \ 126 | echo "Remove obsolete '$$item'"; \ 127 | git -C $(GHPAGES_ROOT) rm -rfq -- $$item; \ 128 | fi \ 129 | done 130 | 131 | # Clean up contents of target directory 132 | @if [ -d $(GHPAGES_TARGET) ]; then \ 133 | echo git -C $(GHPAGES_ROOT) rm -fq --ignore-unmatch -- $(GHPAGES_TARGET)/draft-*.html $(GHPAGES_TARGET)/draft-*.txt $(addprefix $(GHPAGES_TARGET)/,$(GHPAGES_EXTRA)); \ 134 | git -C $(GHPAGES_ROOT) rm -fq --ignore-unmatch -- $(GHPAGES_TARGET)/draft-*.html $(GHPAGES_TARGET)/draft-*.txt $(addprefix $(GHPAGES_TARGET)/,$(GHPAGES_EXTRA)); \ 135 | fi 136 | 137 | .PHONY: ghpages gh-pages 138 | gh-pages: ghpages 139 | ifneq (,$(MAKE_TRACE)) 140 | ghpages: 141 | @$(call MAKE_TRACE,ghpages) 142 | else 143 | ghpages: $(GHPAGES_ALL) 144 | git -C $(GHPAGES_ROOT) add -f $(GHPAGES_ALL) 145 | if test `git -C $(GHPAGES_ROOT) status --porcelain | grep '^[A-Z]' | wc -l` -gt 0; then \ 146 | git -C $(GHPAGES_ROOT) $(CI_AUTHOR) commit -m "Script updating ${PAGES_BRANCH} from $(shell git rev-parse --short HEAD). [ci skip]"; fi 147 | ifeq (true,$(PUSH_GHPAGES)) 148 | ifneq (,$(if $(CI_HAS_WRITE_KEY),1,$(if $(GITHUB_PUSH_TOKEN),,1))) 149 | $(trace) all -s ghpages-push git -C $(GHPAGES_ROOT) push -f "$(shell git remote get-url --push $(GIT_REMOTE))" ${PAGES_BRANCH} 150 | else 151 | @echo git -C $(GHPAGES_ROOT) push -qf https://****@github.com/$(GITHUB_REPO_FULL) ${PAGES_BRANCH} 152 | @git -C $(GHPAGES_ROOT) push -qf https://$(GITHUB_PUSH_TOKEN)@github.com/$(GITHUB_REPO_FULL) ${PAGES_BRANCH} >/dev/null 2>&1 \ 153 | || $(trace) all -s ghpages-push ! echo "git -C $(GHPAGES_ROOT) push -qf https://****@github.com/$(GITHUB_REPO_FULL) ${PAGES_BRANCH}" 154 | endif 155 | else 156 | ifeq (true,$(CI)) 157 | @echo "*** Warning: pushing to the ${PAGES_BRANCH} branch is disabled." 158 | else 159 | $(trace) all -s ghpages-push git -C $(GHPAGES_ROOT) push -f origin ${PAGES_BRANCH} 160 | endif 161 | endif # PUSH_GHPAGES 162 | -rm -rf $(GHPAGES_ROOT) 163 | endif # MAKE_TRACE 164 | 165 | ## Save published documents to the CI_ARTIFACTS directory 166 | ifneq (,$(CI_ARTIFACTS)) 167 | $(CI_ARTIFACTS): 168 | mkdir -p $@ 169 | 170 | .PHONY: artifacts 171 | artifacts: $(GHPAGES_PUBLISHED) $(CI_ARTIFACTS) 172 | cp -f $(filter-out $(CI_ARTIFACTS),$^) $(CI_ARTIFACTS) 173 | endif 174 | -------------------------------------------------------------------------------- /id.mk: -------------------------------------------------------------------------------- 1 | ## Identify drafts, types and versions 2 | 3 | draft_patterns := draft draft-*[a-z] draft-*[-a-z][0-9] draft-*[a-z0-9][a-z0-9][0-9] 4 | extensions := xml org md 5 | drafts := $(sort $(basename $(wildcard $(foreach pattern,$(draft_patterns),$(foreach ext,$(extensions),$(pattern).$(ext)))))) 6 | drafts += $(sort $(basename $(wildcard $(foreach n,d dd ddd dddd ddddd,$(foreach ext,$(extensions),rfc$(subst d,[0-9],$(n)).$(ext)))))) 7 | 8 | ifeq (0,$(words $(drafts))) 9 | $(warning No file named draft-*.md or draft-*.xml or draft-*.org) 10 | $(error Create a draft file before running make) 11 | endif 12 | 13 | draft_types := $(foreach draft,$(drafts),\ 14 | $(suffix $(firstword $(wildcard $(draft).md $(draft).org $(draft).xml)))) 15 | drafts_source := $(join $(drafts),$(draft_types)) 16 | 17 | drafts_tags := $(shell git tag --sort=refname 2>/dev/null | grep '^draft-') 18 | f_prev_tag = $(lastword $(subst -, ,$(lastword $(filter $(draft)-%,$(drafts_tags))))) 19 | f_next_tag = $(if $(f_prev_tag),$(shell printf "%.2d" $$(( 1$(f_prev_tag) - 99)) ),00) 20 | drafts_next := $(foreach draft,$(filter draft-%,$(drafts)),$(draft)-$(f_next_tag)) 21 | drafts_prev := $(foreach draft,$(filter draft-%,$(drafts)),$(draft)-$(f_prev_tag)) 22 | drafts_with_prev := $(foreach draft,$(filter draft-%,$(drafts)),$(if $(f_prev_tag),$(draft))) 23 | 24 | drafts_txt := $(addsuffix .txt,$(drafts)) 25 | drafts_html := $(addsuffix .html,$(drafts)) 26 | drafts_xml := $(addsuffix .xml,$(drafts)) 27 | drafts_next_txt := $(addprefix $(VERSIONED)/,$(addsuffix .txt,$(drafts_next))) 28 | drafts_next_xml := $(addprefix $(VERSIONED)/,$(addsuffix .xml,$(drafts_next))) 29 | 30 | last_modified = $$(stat $$([ $$(uname -s) = Darwin -o $$(uname -s) = FreeBSD ] && echo -f '%m' || echo -c '%Y') $(1)) 31 | file_size = $$(stat $$([ $$(uname -s) = Darwin -o $$(uname -s) = FreeBSD ] && echo -f '%z' || echo -c '%s') $(1)) 32 | last_commit = $$(git rev-list -n 1 --timestamp $(1) -- $(2) | sed -e 's/ .*//') 33 | 34 | # CI config 35 | CI ?= false 36 | ifneq (,$(GITHUB_REPOSITORY)) 37 | CI_USER ?= $(word 1,$(subst /, ,$(GITHUB_REPOSITORY)) 38 | CI_REPO ?= $(word 2,$(subst /, ,$(GITHUB_REPOSITORY))) 39 | else 40 | CI_USER ?= $(word 1,$(subst /, ,$(TRAVIS_REPO_SLUG)))$(CIRCLE_PROJECT_USERNAME) 41 | CI_REPO ?= $(word 2,$(subst /, ,$(TRAVIS_REPO_SLUG)))$(CIRCLE_PROJECT_REPONAME) 42 | ifneq (,$(CI_USER)) 43 | ifneq (,$(CI_REPO)) 44 | CI_REPO_FULL = $(CI_USER)/$(CI_REPO) 45 | endif 46 | endif 47 | endif 48 | 49 | # CI_IS_PR being true disables some options. 50 | ifdef CI_PULL_REQUESTS 51 | # Circle makes this easy 52 | CI_IS_PR = true 53 | else 54 | ifdef GITHUB_BASE_REF 55 | CI_IS_PR = true 56 | else 57 | ifdef TRAVIS_PULL_REQUEST 58 | ifeq (false,$(TRAVIS_PULL_REQUEST)) 59 | # If $TRAVIS_PULL_REQUEST is the word 'false', it's a branch build. 60 | CI_IS_PR = false 61 | else 62 | CI_IS_PR = true 63 | endif 64 | else 65 | CI_IS_PR = false 66 | endif 67 | endif 68 | endif 69 | CI_ARTIFACTS := $(CIRCLE_ARTIFACTS) 70 | 71 | ifeq (,$(shell git config --global --get user.name)) 72 | CI_AUTHOR = -c user.name="ID Bot" 73 | endif 74 | ifeq (,$(shell git config --global --get user.email)) 75 | CI_AUTHOR += -c user.email="idbot@example.com" 76 | endif 77 | 78 | # Github guesses 79 | GIT_REMOTE ?= origin 80 | export GIT_REMOTE 81 | ifeq (,$(CI_REPO_FULL)) 82 | # The github.com/user/repository part of either a 83 | # git@github.com:user/repository.git or a https://github.com/user/repository 84 | # remote (or any other hoster's domain/user/repository part if it uses a 85 | # similar structure) 86 | GITHUB_REPO_WITHHOST := $(shell git ls-remote --get-url $(GIT_REMOTE) 2>/dev/null |\ 87 | sed -e 's/^[a-zA-Z0-9+.-]*:\/\///;s/.*@//;s/:/\//;s/\.git$$//') 88 | GITHUB_HOST := $(word 1,$(subst /, ,$(GITHUB_REPO_WITHHOST))) 89 | GITHUB_USER := $(word 2,$(subst /, ,$(GITHUB_REPO_WITHHOST))) 90 | GITHUB_REPO := $(word 3,$(subst /, ,$(GITHUB_REPO_WITHHOST))) 91 | GITHUB_REPO_FULL := $(GITHUB_USER)/$(GITHUB_REPO) 92 | else 93 | GITHUB_REPO_FULL := $(CI_REPO_FULL) 94 | CI_HOST ?= github.com 95 | GITHUB_HOST := $(CI_HOST) 96 | GITHUB_USER := $(CI_USER) 97 | GITHUB_REPO := $(CI_REPO) 98 | endif 99 | 100 | # GITHUB_PUSH_TOKEN is used for pushes. 101 | ifneq (,$(and $(GITHUB_ACTOR),$(GITHUB_TOKEN))) 102 | GITHUB_PUSH_TOKEN ?= $(GITHUB_ACTOR):$(GITHUB_TOKEN) 103 | else 104 | GITHUB_PUSH_TOKEN ?= $(GH_TOKEN) 105 | endif 106 | # GITHUB_API_TOKEN is used in the GitHub API. 107 | GITHUB_API_TOKEN ?= $(or $(GITHUB_TOKEN),$(GH_TOKEN)) 108 | 109 | ifeq (,$(BRANCH_FETCH)) 110 | BRANCH_FETCH := true 111 | endif 112 | export BRANCH_FETCH 113 | ifeq (,$(DEFAULT_BRANCH)) 114 | DEFAULT_BRANCH := $(shell BRANCH_FETCH=$(BRANCH_FETCH) $(LIBDIR)/default-branch.py $(GITHUB_USER) $(GITHUB_REPO) $(GITHUB_API_TOKEN)) 115 | endif 116 | export DEFAULT_BRANCH 117 | 118 | PID := $(shell echo $$$$) 119 | -------------------------------------------------------------------------------- /main.mk: -------------------------------------------------------------------------------- 1 | ifeq (,$(TRACE_FILE)) 2 | SUMMARY_REPORT ?= $(GITHUB_STEP_SUMMARY) 3 | ifneq (,$(SUMMARY_REPORT)) 4 | TRACE_FILE := $(shell mktemp) 5 | export TRACE_FILE 6 | 7 | define MAKE_TRACE 8 | $(MAKE) -k $(1); \ 9 | STATUS=$$?; \ 10 | $(LIBDIR)/format-trace.sh $(TRACE_FILE) $$STATUS >>$(SUMMARY_REPORT); \ 11 | rm -f $(TRACE_FILE); \ 12 | exit $$STATUS 13 | endef 14 | 15 | all:: 16 | @$(call MAKE_TRACE,latest lint) 17 | else 18 | all:: latest lint 19 | endif # SUMMARY_REPORT 20 | endif # TRACE_FILE 21 | 22 | latest:: txt html 23 | 24 | MAKEFLAGS += --no-builtin-rules --no-builtin-variables --no-print-directory 25 | .PHONY: all latest 26 | .SUFFIXES: 27 | .DELETE_ON_ERROR: 28 | 29 | ## Modularity 30 | # Basic files (these can't rely on details from .targets.mk) 31 | LIBDIR ?= lib 32 | export LIBDIR 33 | include $(LIBDIR)/config.mk 34 | include $(LIBDIR)/id.mk 35 | include $(LIBDIR)/deps.mk 36 | 37 | # Now include the advanced stuff that can depend on draft information. 38 | include $(LIBDIR)/ghpages.mk 39 | include $(LIBDIR)/archive.mk 40 | include $(LIBDIR)/upload.mk 41 | include $(LIBDIR)/update.mk 42 | 43 | -include .includes.mk 44 | .includes.mk: $(filter %.md,$(drafts_source)) 45 | @rm -f $@ 46 | @for d in $^; do \ 47 | for f in $$(sed -e $$'s/^{::include\\(-nested\\)* \\(.*\\)}$$/\\2/;t\nd' "$$d"); do \ 48 | echo "$${d%.md}.xml: $$f" >> $@; \ 49 | done; \ 50 | done 51 | 52 | ## Basic Targets 53 | .PHONY: txt html pdf 54 | txt:: $(drafts_txt) 55 | html:: $(drafts_html) 56 | pdf:: $(addsuffix .pdf,$(drafts)) 57 | 58 | ## Basic Recipes 59 | .INTERMEDIATE: $(filter-out $(drafts_source),$(addsuffix .xml,$(drafts))) 60 | 61 | ifeq (true,$(CI)) 62 | VERBOSE ?= true 63 | endif 64 | ifeq (true,$(VERBOSE)) 65 | trace := $(trace) -v 66 | echo := echo 67 | at := 68 | else 69 | echo := : 70 | at := @ 71 | endif 72 | 73 | MD_PRE = 74 | ifneq (,$(MD_PREPROCESSOR)) 75 | MD_PRE += | $(trace) $@ -s preprocessor $(MD_PREPROCESSOR) 76 | endif 77 | ifneq (1,$(words $(drafts))) 78 | NOT_CURRENT = $(filter-out $(basename $<),$(drafts)) 79 | MD_PRE += | sed -e '$(join $(addprefix s/,$(addsuffix -latest/,$(NOT_CURRENT))), \ 80 | $(addsuffix /g;,$(NOT_CURRENT)))' 81 | endif 82 | MD_POST = | $(trace) -q $@ -s venue $(python) $(LIBDIR)/add-note.py 83 | ifneq (true,$(USE_XSLT)) 84 | MD_POST += | $(trace) -q $@ -s v2v3 $(xml2rfc) --v2v3 /dev/stdin -o /dev/stdout 85 | endif 86 | ifeq (true,$(TIDY)) 87 | MD_POST += | $(trace) -q $@ -s tidy $(rfc-tidy) 88 | endif 89 | 90 | %.xml: %.md $(DEPS_FILES) 91 | @h=$$(head -1 $< | cut -c 1-4 -); set -o pipefail; \ 92 | if [ "$${h:0:1}" = $$'\ufeff' ]; then echo 'warning: BOM in $<' 1>&2; h="$${h:1:3}"; \ 93 | else h="$${h:0:3}"; fi; \ 94 | if [ "$$h" = '---' ]; then \ 95 | $(echo) '$(subst ','"'"',cat $< $(MD_PRE) | $(kramdown-rfc) --v3 $(MD_POST) >$@)'; \ 96 | cat $< $(MD_PRE) | $(trace) $@ -s kramdown-rfc $(kramdown-rfc) --v3 $(MD_POST) >$@; \ 97 | elif [ "$$h" = '%%%' ]; then \ 98 | $(echo) '$(subst ','"'"',cat $< $(MD_PRE) | $(mmark) $(MD_POST) >$@)'; \ 99 | cat $< $(MD_PRE) | $(trace) $@ -s mmark $(mmark) $(MD_POST) >$@; \ 100 | else \ 101 | ! echo "Unable to detect '%%%' or '---' in markdown file" 1>&2; \ 102 | fi && [ -e $@ ] 103 | 104 | %.xml: %.org $(DEPS_FILES) 105 | $(trace) $@ -s oxtradoc $(oxtradoc) -m outline-to-xml -n "$@" $< | $(xml2rfc) --v2v3 /dev/stdin -o $@ 106 | 107 | XSLTDIR ?= $(LIBDIR)/rfc2629xslt 108 | ifeq (true,$(USE_XSLT)) 109 | $(LIBDIR)/rfc2629.xslt: $(XSLTDIR)/rfc2629.xslt 110 | $(xsltproc) $(XSLTDIR)/to-1.0-xslt.xslt $< > $@ 111 | 112 | $(LIBDIR)/clean-for-DTD.xslt: $(LIBDIR)/rfc2629xslt/clean-for-DTD.xslt $(LIBDIR)/rfc2629-no-doctype.xslt 113 | $(xsltproc) $(XSLTDIR)/to-1.0-xslt.xslt $< > $@ 114 | 115 | $(LIBDIR)/rfc2629-no-doctype.xslt: $(LIBDIR)/rfc2629xslt/rfc2629-no-doctype.xslt 116 | $(xsltproc) $(XSLTDIR)/to-1.0-xslt.xslt $< > $@ 117 | 118 | $(XSLTDIR)/clean-for-DTD.xslt $(XSLTDIR)/rfc2629.xslt: $(XSLTDIR) 119 | $(XSLTDIR): 120 | git clone --depth 10 $(CLONE_ARGS) -b master https://github.com/reschke/xml2rfc $@ 121 | 122 | %.cleanxml: %.xml $(LIBDIR)/clean-for-DTD.xslt $(LIBDIR)/rfc2629.xslt 123 | $(at)$(trace) $@ -s xslt-clean $(xsltproc) --novalid $(LIBDIR)/clean-for-DTD.xslt $< > $@ 124 | 125 | %.html: %.xml $(LIBDIR)/rfc2629.xslt $(LIBDIR)/style.css 126 | $(at)$(trace) $@ -s xslt-html $(xsltproc) --novalid --stringparam xml2rfc-ext-css-contents "$$(cat $(LIBDIR)/style.css)" $(LIBDIR)/rfc2629.xslt $< > $@ 127 | 128 | %.txt: %.cleanxml $(DEPS_FILES) 129 | $(at)$(trace) $@ -s xml2rfc-txt $(xml2rfc) $(XML2RFC_TEXT) $< -o $@ 130 | else 131 | %.html: %.xml $(XML2RFC_CSS) $(DEPS_FILES) 132 | $(at)$(trace) $@ -s xml2rfc-html $(xml2rfc) $(XML2RFC_HTML) $< -o $@ 133 | # Workaround for https://trac.tools.ietf.org/tools/xml2rfc/trac/ticket/470 134 | @-sed -i.rfc-local -e 's,]*href=["'"'"]rfc-local.css["'"'"][^>]*>,,' $@; rm -f $@.rfc-local 135 | ifneq (,$(FAVICON)) 136 | @-sed -i.favicon -e '/]*rel="license">/{p;c \'$$'\n''$(FAVICON)'$$'\n'';}' $@; rm -f $@.favicon 137 | endif 138 | 139 | %.txt: %.xml $(DEPS_FILES) 140 | $(at)$(trace) $@ -s xml2rfc-txt $(xml2rfc) $(XML2RFC_TEXT) $< -o $@ 141 | endif 142 | 143 | %.pdf: %.txt 144 | $(at)$(trace) $@ -s enscript $(enscript) --margins 76::76: -B -q -p - $< | $(ps2pdf) - $@ 145 | 146 | ## Build copies of drafts for submission 147 | .PHONY: next 148 | next:: $(drafts_next_txt) $(drafts_next_xml) 149 | 150 | ## Remind people to use CI 151 | .PHONY: submit 152 | submit:: 153 | @echo "\`make submit\` is not really necessary." 154 | @echo "\`make\` on its own is a pretty good preview." 155 | @echo "To upload a new draft to datatracker, enable CI and try this:" 156 | @echo 157 | @for i in $(drafts_next); do \ 158 | echo " git tag -a $$i"; \ 159 | done 160 | @for i in $(drafts_next); do \ 161 | echo " git push origin $$i"; \ 162 | done 163 | @echo 164 | @echo "Don't forget the \`-a\`." 165 | @echo 166 | @echo "To get a preview, use \`make next\`." 167 | 168 | ## Check for validity 169 | .PHONY: check idnits 170 | check:: idnits 171 | 172 | # Mode can be "normal", "submission", or "forgive-checklist" 173 | idnits_mode ?= normal 174 | ifneq (true,$(NO_NODEJS)) 175 | idnits_bin ?= node_modules/.bin/idnits 176 | $(idnits_bin): 177 | npm install -q --no-save github:ietf-tools/idnits 178 | else 179 | idnits_bin := 180 | endif 181 | 182 | ifneq (,$(shell which script 2>/dev/null)) 183 | faketty = script -qec "$(subst ",\",$(1))" /dev/null 184 | else 185 | faketty = $(1) 186 | endif 187 | 188 | idnits:: $(drafts_next_xml) | $(idnits_bin) 189 | @for i in $^; do \ 190 | [ "$$i" == "$(idnits_bin)" ] || \ 191 | $(trace) "$$i" -s idnits $(call faketty,$(idnits) -m $(idnits_mode) "$$i"); \ 192 | done 193 | 194 | CODESPELL_ARGS := 195 | ifneq (,$(wildcard ./.ignore-words)) 196 | CODESPELL_ARGS += -I .ignore-words 197 | endif 198 | 199 | .PHONY: spellcheck 200 | spellcheck:: $(drafts_source) $(VENV)/codespell$(EXE) 201 | $(trace) $@ codespell $(CODESPELL_ARGS) $(drafts_source) 202 | 203 | ## Build diffs between the current draft versions and the most recent version 204 | draft_diffs := $(addprefix diff-,$(addsuffix .html,$(drafts_with_prev))) 205 | .PHONY: diff 206 | diff: $(draft_diffs) 207 | 208 | ## Generate a test report 209 | ifneq (,$(CIRCLE_TEST_REPORTS)) 210 | TEST_REPORT := $(CIRCLE_TEST_REPORTS)/report/drafts.xml 211 | else 212 | TEST_REPORT := report.xml 213 | endif 214 | all_outputs := $(drafts_html) $(drafts_txt) 215 | .PHONY: report 216 | report: $(TEST_REPORT) 217 | $(TEST_REPORT): 218 | @echo build_report $^ 219 | @mkdir -p $(dir $@) 220 | @echo '' >$@ 221 | @passed=();failed=();for i in $(all_outputs); do \ 222 | if [ -f "$$i" ]; then passed+=("$$i"); else failed+=("$$i"); fi; \ 223 | done; echo '>$@; \ 224 | echo ' tests="'"$$(($${#passed[@]} + $${#failed[@]}))"'"' >>$@; \ 225 | echo ' failures="'"$${#failed[@]}"'">' >>$@; \ 226 | for i in "$${passed[@]}"; do \ 227 | echo ' ' >>$@; \ 228 | done; \ 229 | for i in "$${failed[@]}"; do \ 230 | echo ' ' >>$@; \ 231 | echo ' ' >>$@; \ 232 | echo ' ' >>$@; \ 233 | done; \ 234 | echo '' >>$@ 235 | 236 | .PHONY: lint lint-whitespace lint-default-branch lint-docname 237 | lint:: 238 | ifneq (true,$(CI)) 239 | lint:: lint-default-branch 240 | endif 241 | ifneq (true,$(PRE_SETUP)) 242 | # Disable most lints during repository setup 243 | lint:: lint-docname lint-whitespace 244 | endif 245 | 246 | lint-whitespace:: 247 | @err=0; for f in $(drafts_source); do \ 248 | if [ ! -z "$$(tail -c 1 "$$f")" ]; then \ 249 | $(trace) -q "$$f" -s nl ! echo "$$f has no newline on the last line"; err=1; \ 250 | fi; \ 251 | if ! $(trace) -q "$$f" -s ws ! grep -n $$' \r*$$' "$$f"; then \ 252 | $(if $(TRACE_FILE),echo "$${f%.*} ws $$f contains trailing whitespace" >>$(TRACE_FILE);) \ 253 | echo "$$f contains trailing whitespace"; err=1; \ 254 | fi; \ 255 | done; [ "$$err" -eq 0 ] || ! echo "*** Run 'make fix-lint' to automatically fix some errors" 1>&2 256 | 257 | lint-default-branch:: 258 | @-if ! git rev-parse --abbrev-ref refs/remotes/$(GIT_REMOTE)/HEAD >/dev/null 2>&1; then \ 259 | echo "warning: A default branch for '$(GIT_REMOTE)' is not recorded in this clone."; \ 260 | echo " Running 'make fix-lint' will set the default branch to '$$(git rev-parse --abbrev-ref HEAD)'."; \ 261 | fi 262 | 263 | lint-docname:: 264 | @err=(); for f in $(drafts_source); do \ 265 | if [ "$${f#draft-}" != "$$f" ] && ! grep -q "$${f%.*}-latest" "$$f"; then \ 266 | $(trace) "$$f" -s lint-docname ! echo "$$f does not contain its own name ($${f%.*}-latest)"; err=1; \ 267 | fi; \ 268 | done; [ "$${#err}" -eq 0 ] || ! echo "*** Correct the name of drafts in docname or similar fields" 1>&2 269 | 270 | .PHONY: fix-lint fix-lint-whitespace fix-lint-default-branch 271 | fix-lint:: fix-lint-whitespace fix-lint-default-branch 272 | fix-lint-whitespace:: 273 | for f in $(drafts_source); do \ 274 | [ -z "$$(tail -c 1 "$$f")" ] || echo >>"$$f"; \ 275 | done 276 | sed -i~ -e 's/ *$$//' $(drafts_source) 277 | 278 | fix-lint-default-branch: 279 | if ! git rev-parse --abbrev-ref refs/remotes/$(GIT_REMOTE)/HEAD >/dev/null 2>&1; then \ 280 | echo "ref: refs/remotes/$(GIT_REMOTE)/$$(git rev-parse --abbrev-ref HEAD)" > $$(git rev-parse --git-dir)/refs/remotes/$(GIT_REMOTE)/HEAD; \ 281 | fi 282 | 283 | ## Cleanup 284 | COMMA := , 285 | .PHONY: clean clean-all 286 | clean:: 287 | -rm -f .tags $(targets_file) issues.json \ 288 | $(addsuffix .{txt$(COMMA)html$(COMMA)pdf},$(drafts)) index.html \ 289 | $(addsuffix -[0-9][0-9].{xml$(COMMA)md$(COMMA)org$(COMMA)txt$(COMMA)raw.txt$(COMMA)html$(COMMA)pdf},$(drafts)) \ 290 | $(filter-out $(drafts_source),$(addsuffix .xml,$(drafts))) \ 291 | $(uploads) $(draft_diffs) 292 | clean-all:: clean clean-deps 293 | 294 | include $(LIBDIR)/targets.mk 295 | -------------------------------------------------------------------------------- /pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | exec 1>&2 4 | 5 | BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) 6 | if [ "$BRANCH" = "gh-pages" -o "$BRANCH" = "gh-issues" -o -e .git/MERGE_HEAD ]; then 7 | exit 8 | fi 9 | 10 | hash gmake 2> /dev/null && MAKE=gmake || MAKE=make 11 | 12 | srcfiles=() 13 | xmlfiles=() 14 | htmlfiles=() 15 | function cleanup() { 16 | rm -f "${srcfiles[@]}" "${xmlfiles[@]}" "${htmlfiles[@]}" 17 | } 18 | function abort() { 19 | echo "Commit refused: document build error." 20 | echo "To commit anyway, run \"git commit --no-verify\"" 21 | cleanup 22 | exit 1 23 | } 24 | trap abort ERR 25 | trap cleanup EXIT 26 | 27 | files=($(git status --porcelain draft-* rfc* | sed '/^[MAU]/{s/^.. //;p;};/^[RC]/{s/.* -> //;p;};d' | sort)) 28 | for f in "${files[@]}"; do 29 | tmp="${f%.*}"-tmp$$."${f##*.}" 30 | srcfiles+=("$tmp") 31 | xmlfiles+=("${tmp%.*}.xml") 32 | htmlfiles+=("${tmp%.*}.html") 33 | # This makes a copy of the staged file. 34 | (git show :"$f" 2>/dev/null || cat "$f") \ 35 | | sed -e "s/${f%.*}-latest/${tmp%.*}-latest/g" > "$tmp" 36 | done 37 | [ "${#files[@]}" -eq 0 ] && exit 0 38 | 39 | "$MAKE" "${htmlfiles[@]}" lint "drafts=${xmlfiles[*]%.*}" EXTRA_TARGETS=false 40 | -------------------------------------------------------------------------------- /pre-push.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | err=0 4 | tags=0 5 | while read -r local_ref local_sha remote_ref remote_sha; do 6 | if [[ "${local_ref#refs/tags/draft-}" != "${local_ref}" ]]; then 7 | tag="${local_ref#refs/tags/}" 8 | if [[ -z "$(git for-each-ref --format '%(taggeremail)' "${local_ref}")" ]]; then 9 | echo "pre-push: tag $tag is not an annotated tag" 1>&2 10 | err=1 11 | fi 12 | if ! git ls-tree --name-only "$local_sha" | grep -q "^${tag%-*}\."; then 13 | echo "pre-push: tag $tag does not match an existing file" 1>&2 14 | err=1 15 | fi 16 | tags=$((tags+1)) 17 | fi 18 | done 19 | if [[ $tags -gt 1 ]]; then 20 | echo "pre-push: more than one tag pushed, which circle doesn't currently handle" 1>&2 21 | err=2 22 | fi 23 | [[ $err -eq 0 ]] || echo "pre-push: use git push --no-verify to override this check" 1>&2 24 | exit $err 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | archive-repo 2 | iddiff 3 | pyang 4 | pyyaml 5 | rfc-tidy 6 | svgcheck 7 | toml 8 | xml2rfc 9 | -------------------------------------------------------------------------------- /setup-branch.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | hash realpath 2>/dev/null || function realpath() { cd "$1"; pwd -P; } 6 | 7 | branch="$1" 8 | TMPDIR="${TMPDIR:-/tmp}" 9 | shift 10 | 11 | # Fetch here, but don't abort on failure. 12 | git fetch -qf origin "$branch:$branch" >/dev/null 2>&1 || true 13 | if git show-ref -s "$branch" >/dev/null 2>&1; then 14 | echo "The $branch branch already exists, skipping setup." 15 | exit 16 | fi 17 | 18 | tmp=$(mktemp -d "${TMPDIR}/init-branch-${branch}-XXXXX") 19 | function cleanup() { 20 | rm -rf "$tmp" 21 | } 22 | trap cleanup ERR EXIT 23 | 24 | echo "Initializing $branch branch" 25 | git clone -n . "$tmp" 26 | git -C "$tmp" checkout -q --orphan "$branch" 27 | git -C "$tmp" rm -rfq . 28 | 29 | echo Creating .gitignore and initial files 30 | echo "/${LIBDIR:-"$(basename "$(dirname "$0")")"}/" > "$tmp"/.gitignore 31 | echo "/node_modules/" >> "$tmp"/.gitignore 32 | echo "/package-lock.json" >> "$tmp"/.gitignore 33 | echo "/.requirements.txt" >> "$tmp"/.gitignore 34 | echo "/Gemfile.lock" >> "$tmp"/.gitignore 35 | for f in "$@"; do 36 | touch "$tmp"/"$f" 37 | done 38 | 39 | echo Commit and push to origin/"$branch" 40 | user=() 41 | git config --global --get user.name >/dev/null || user+=(-c user.name='ID Bot') 42 | git config --global --get user.email >/dev/null || user+=(-c user.email='idbot@example.com') 43 | 44 | git -C "$tmp" add .gitignore "$@" 45 | git -C "$tmp" "${user[@]}" commit -m "Automatic setup of $branch." 46 | git -C "$tmp" push origin "$branch" 47 | git push --set-upstream origin "$branch" || \ 48 | echo "Not pushing $branch because it might already exist." 49 | -------------------------------------------------------------------------------- /setup-codeowners.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import subprocess 5 | import sys 6 | import xml.sax 7 | 8 | 9 | class GetAuthorsEmail(xml.sax.handler.ContentHandler): 10 | states = ["", "front", "author", "email"] 11 | 12 | def __init__(self): 13 | self.state = 0 14 | self.email = "" 15 | 16 | def startElement(self, tag, attributes): 17 | if ( 18 | self.state + 1 < len(GetAuthorsEmail.states) 19 | and GetAuthorsEmail.states[self.state + 1] == tag.lower() 20 | ): 21 | self.state = self.state + 1 22 | 23 | def characters(self, content): 24 | if self.state + 1 == len(GetAuthorsEmail.states): 25 | self.email = self.email + content 26 | 27 | def endElement(self, tag): 28 | if tag.lower() != GetAuthorsEmail.states[self.state]: 29 | return 30 | if self.state + 1 == len(GetAuthorsEmail.states): 31 | print(f" {self.email.strip()}", end="") 32 | self.email = "" 33 | self.state = self.state - 1 34 | 35 | @staticmethod 36 | def get_emails(f): 37 | parser = xml.sax.make_parser() 38 | parser.setContentHandler(GetAuthorsEmail()) 39 | parser.parse(f) 40 | print() 41 | 42 | 43 | print("# Automatically generated CODEOWNERS") 44 | print("# Regenerate with `make update-codeowners`") 45 | if len(sys.argv) >= 2: 46 | sink = open(os.devnull, "wb") 47 | for f in sys.argv[1:]: 48 | cmd = f"git ls-tree --name-only @ {f.rpartition('.')[0]}.* | head -1" 49 | s = subprocess.check_output( 50 | cmd, 51 | shell=True, 52 | universal_newlines=True, 53 | encoding="utf-8", 54 | stderr=sink, 55 | ).strip() 56 | if s != "": 57 | print(s, end="") 58 | GetAuthorsEmail.get_emails(f) 59 | else: 60 | GetAuthorsEmail.get_emails(sys.stdin) 61 | -------------------------------------------------------------------------------- /setup-note.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Usage: $0 github.com [drafts...] 4 | 5 | set -e 6 | 7 | host="$1" 8 | user="$2" 9 | repo="$3" 10 | shift 3 11 | 12 | # Determine if the draft is a kramdown draft with a venue section. 13 | hasvenue() { 14 | head -1 "$1" | grep -q "^---" && \ 15 | sed -e '2,/^---/p;d' "$1" | grep -q '^venue:' 16 | } 17 | 18 | if [[ -z "$WG" ]]; then 19 | # Guess the working group name from the drafts 20 | first=true 21 | for d in "$@"; do 22 | # If a kramdown has a venue section, skip it. 23 | if hasvenue "$d"; then 24 | continue 25 | fi 26 | 27 | w="${d#draft-}" 28 | w="${w#*-}" 29 | w="${w%%-*}" 30 | if $first; then 31 | wg="$w" 32 | first=false 33 | elif [[ "$wg" != "$w" ]]; then 34 | echo "Found conflicting working group names in drafts" 1>&2 35 | echo " $wg != $w" 1>&2 36 | wg="" 37 | break 38 | fi 39 | done 40 | else 41 | wg="${WG}" 42 | fi 43 | 44 | if $first; then 45 | exit 0 46 | fi 47 | 48 | . $(dirname "$0")/wg-meta.sh 49 | if ! wgmeta "$wg"; then 50 | wg="" 51 | fi 52 | 53 | { 54 | echo '' 55 | if [[ -n "$wg" ]]; then 56 | echo "Discussion of this document takes place on the 57 | ${wg_name} ${wg_type} mailing list (${wg_mail}), 58 | which is archived at ." 59 | fi 60 | echo "Source for this draft and an issue tracker can be found at 61 | ." 62 | echo '' 63 | } > .note.xml 64 | -------------------------------------------------------------------------------- /setup-readme.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Usage: $0 [draftxml ...] 4 | 5 | user="$1" 6 | repo="$2" 7 | default_branch="${DEFAULT_BRANCH:-$("$(dirname "$0")/default-branch.py")}" 8 | shift 2 9 | 10 | githubio="https://${user}.github.io/${repo}/#go" 11 | 12 | function fixup_other_md() { 13 | markdown=(LICENSE.md CONTRIBUTING.md) 14 | s='s~{WG_NAME}~'"$1"'~g' 15 | s="$s"';s~{GITHUB_USER}~'"$user"'~g' 16 | s="$s"';s~{GITHUB_REPO}~'"$repo"'~g' 17 | s="$s"';s~{GITHUB_BRANCH}~'"$default_branch"'~g' 18 | sed -i~ -e "$s" "${markdown[@]}" 19 | for i in "${markdown[@]}"; do 20 | rm -f "$i"~ 21 | done 22 | } 23 | 24 | function get_title() { 25 | if hash xmllint >/dev/null 2>&1; then 26 | t=($(xmllint --xpath '/rfc/front/title/text()' "$1")) 27 | else 28 | # sed kludge if xmllint isn't available 29 | t=($(sed -e '/]*>/,/<\/title>/{s/.*]*>//;/<\/title>/{s/<\/title>.*//;H;x;q;};H;};d' "$1")) 30 | fi 31 | # haxx: rely on bash parameter normalization to remove redundant whitespace 32 | echo "${t[*]}" 33 | } 34 | 35 | if [[ "$OSTYPE" =~ (darwin|bsd).* ]] ; then 36 | function sed_no_backup() { sed -i '' "$@" ; } 37 | else 38 | function sed_no_backup() { sed -i "$@" ; } 39 | fi 40 | 41 | first=true 42 | for d in "$@"; do 43 | fullname="${d%.xml}" 44 | author=$(echo "${fullname}" | cut -f 2 -d - -) 45 | wg=$(echo "${fullname}" | cut -f 3 -d - -) 46 | wgupper=$(echo "${wg}" | tr 'a-z' 'A-Z') 47 | title=$(get_title "$d") 48 | 49 | if "$first"; then 50 | fixup_other_md "$wg" 51 | 52 | echo "" 53 | echo 54 | 55 | if [ "$author" = "ietf" ]; then 56 | status="Working Group" 57 | status_full="IETF [${wgupper} Working Group](https://datatracker.ietf.org/group/${wg}/documents/) Internet-Draft" 58 | else 59 | status="Individual" 60 | status_full="individual Internet-Draft" 61 | fi 62 | if [ $# -gt 1 ]; then 63 | echo "# ${wgupper} Drafts" 64 | status_full="${status_full}s" 65 | else 66 | echo "# $title" 67 | status_full="the ${status_full}, \"${title}\"" 68 | fi 69 | echo 70 | echo "This is the working area for ${status_full}." 71 | wg_all="$wg" 72 | first=false 73 | elif [ "$wg" != "$wg_all" ]; then 74 | wg_all="" 75 | fi 76 | 77 | if [ $# -gt 1 ]; then 78 | echo 79 | echo "## $title" 80 | fi 81 | echo 82 | echo "* [Editor's Copy](${githubio}.${fullname}.html)" 83 | echo "* [Datatracker Page](https://datatracker.ietf.org/doc/${fullname})" 84 | echo "* [${status} Draft](https://datatracker.ietf.org/doc/html/${fullname})" 85 | echo "* [Compare Editor's Copy to ${status} Draft](${githubio}.${fullname}.diff)" 86 | done 87 | 88 | cat <>CONTRIBUTING.md </dev/null | grep '*' | cut -c 3-) 16 | ifneq (1,$(words $(GIT_ORIG))) 17 | $(error If you are just starting out, please commit something before starting) 18 | endif 19 | 20 | LATEST_WARNING := $(strip $(foreach draft,$(filter-out rfc%,$(drafts_source)),\ 21 | $(shell grep -q $(basename $(draft))-latest $(draft) || \ 22 | echo $(draft) should include a name of $(basename $(draft))-latest))) 23 | ifneq (,$(LATEST_WARNING)) 24 | $(error Check names: $(LATEST_WARNING)) 25 | endif 26 | ifneq (,$(strip $(shell git -c core.excludesfile=$(LIBDIR)/template/.gitignore status -s --porcelain 2>/dev/null | grep -v '^.. $(LIBDIR)'))) 27 | $(error You have uncommitted changes or untracked files, please commit them before running setup) 28 | endif 29 | ifneq ($(GIT_REMOTE),$(shell git remote 2>/dev/null | grep '^$(GIT_REMOTE)$$')) 30 | $(error Please configure a remote called '$(GIT_REMOTE)' before running setup) 31 | endif 32 | ifneq (false,$(CHECK_BRANCH)) 33 | ifeq (,$(shell git show-ref $(GIT_REMOTE)/$(GIT_ORIG))) 34 | $(error Please push the '$(GIT_ORIG)' branch to '$(GIT_REMOTE)', e.g., "git push $(GIT_REMOTE) $(GIT_ORIG)") 35 | endif 36 | endif 37 | 38 | TEMPLATE_FILES := Makefile .gitignore CONTRIBUTING.md LICENSE.md .editorconfig 39 | ifneq (true,$(CI)) 40 | # When this runs in CI, we can't change these due to GitHub permissions. 41 | TEMPLATE_FILES += $(addprefix .github/workflows/,ghpages.yml publish.yml archive.yml update.yml) 42 | # Also exclude CircleCI config because CI setup is only GitHub Actions 43 | TEMPLATE_FILES += .circleci/config.yml 44 | endif 45 | 46 | TEMPLATE_FILE_MK := $(LIBDIR)/.template-files.mk 47 | include $(TEMPLATE_FILE_MK) 48 | $(TEMPLATE_FILE_MK): $(LIBDIR)/setup.mk 49 | @echo '# Automatically generated setup rules' >$@ 50 | @$(foreach f,$(TEMPLATE_FILES),\ 51 | echo $(f): $(LIBDIR)/template/$(f) >>$@;\ 52 | echo ' mkdir -p $$(dir $$@)' >>$@;\ 53 | echo ' -cp $$< $$@' >>$@;) 54 | 55 | .PHONY: setup-files 56 | setup-files: $(TEMPLATE_FILES) README.md .github/CODEOWNERS 57 | git add $(drafts_source) 58 | git add $^ 59 | 60 | ifeq (true,$(USE_XSLT)) 61 | setup-default-branch: setup-makefile-xslt 62 | .PHONY: setup-makefile-xslt 63 | setup-makefile-xslt: Makefile 64 | sed -i~ -e '1{h;s/^.*$$/USE_XSLT := true/;p;x;}' $< 65 | @-rm -f $<~ 66 | git add $< 67 | endif # USE_XSLT 68 | 69 | ifneq (html,$(INDEX_FORMAT)) 70 | setup-default-branch: setup-makefile-index-format 71 | .PHONY: setup-makefile-index-format 72 | setup-makefile-index-format: Makefile 73 | sed -i~ -e '1{h;s/^.*$$/INDEX_FORMAT := $(INDEX_FORMAT)/;p;x;}' $< 74 | @-rm -f $<~ 75 | git add $< 76 | endif # INDEX_FORMAT 77 | 78 | .PHONY: setup-gitignore 79 | setup-gitignore: .gitignore $(LIBDIR)/template/.gitignore 80 | ifndef SUBMODULE 81 | echo /$(LIBDIR) >>$< 82 | endif 83 | $(foreach x,$(filter-out .xml,$(drafts_source)),\ 84 | echo $(basename $(x)).xml >>$<;) 85 | tmp=`mktemp`; \ 86 | (cat $^ | grep -v '^!' | sort -u; cat $^ | grep '^!' | sort -u) >$$tmp && \ 87 | mv -f $$tmp $< 88 | git add $< 89 | 90 | README.md: $(LIBDIR)/setup-readme.sh $(drafts_xml) $(filter %.md, $(TEMPLATE_FILES)) 91 | $(LIBDIR)/setup-readme.sh $(GITHUB_USER) $(GITHUB_REPO) $(filter %.xml,$^) >$@ 92 | git add $@ $(filter %.md, $(TEMPLATE_FILES)) 93 | 94 | .PHONY: setup-note 95 | setup-note: $(LIBDIR)/setup-note.sh 96 | $(LIBDIR)/setup-note.sh $(GITHUB_HOST) $(GITHUB_USER) $(GITHUB_REPO) $(drafts_source) 97 | if [ -s .note.xml ]; then git add .note.xml; fi 98 | 99 | .github/CODEOWNERS: $(LIBDIR)/setup-codeowners.py $(drafts_xml) $(DEPS_FILES) 100 | mkdir -p $(dir $@) 101 | $(python) $(LIBDIR)/setup-codeowners.py $(filter %.xml,$^) >$@ 102 | git add $@ 103 | 104 | .PHONY: setup-master 105 | setup-master: 106 | $(error The setup-master make target was renamed to setup-default-branch) 107 | 108 | .PHONY: setup-default-branch 109 | setup-default-branch: setup-files README.md setup-gitignore setup-note 110 | git $(CI_AUTHOR) commit -m "Setup repository for $(firstword $(drafts)) using https://github.com/martinthomson/i-d-template" 111 | 112 | .PHONY: setup-precommit 113 | setup-precommit: .git/hooks/pre-commit 114 | .git/hooks/pre-commit: 115 | -ln -s ../../$(LIBDIR)/pre-commit.sh $@ 116 | 117 | .PHONY: setup-ghpages 118 | setup-ghpages: 119 | $(LIBDIR)/setup-branch.sh gh-pages index.$(INDEX_FORMAT) archive.json 120 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | font: 16px "Helvetica Neue","Open Sans",Helvetica,Calibri,sans-serif; 4 | color: #333; 5 | font-size-adjust: 0.5; 6 | line-height: 24px; 7 | margin: 75px auto; 8 | max-width: 624px; 9 | padding: 0 5px; 10 | } 11 | 12 | .title, .filename, h1, h2, h3, h4, h5 { 13 | font: 16px "Roboto Condensed","Helvetica Neue","Open Sans",Helvetica,Calibri,sans-serif; 14 | font-size-adjust: 0.5; 15 | font-weight: bold; 16 | color: #333; 17 | line-height: 100%; 18 | margin: 1.2em 0 0.3em; 19 | } 20 | .title, #rfc\.title h1 { font-size: 32px; } 21 | h1, section h1, h2, section h2, section h3, nav h2 { font-size: 20px; } 22 | h3, section h4, h4, section h5 { font-size: 16px; } 23 | h1 a[href], h2 a[href], h3 a[href], h4 a[href] { 24 | color: #333; 25 | } 26 | 27 | table { 28 | margin-left: 0em; 29 | border-collapse: collapse; 30 | } 31 | th { 32 | text-align: left; 33 | border-bottom: 2px solid #ddd; 34 | } 35 | td { 36 | border-top: 1px solid #ddd; 37 | vertical-align: top; 38 | } 39 | tr:nth-child(2n+1) > td, 40 | tr:nth-child(2n+1) > th { 41 | background-color: #f9f9f9; 42 | } 43 | td.reference { 44 | max-width: 200px; 45 | border-top: none; 46 | padding-right: 1em; 47 | } 48 | .right { 49 | text-align: right; 50 | } 51 | 52 | 53 | table.header, table#rfc\.headerblock { 54 | width: 100%; 55 | } 56 | table.header td, table#rfc\.headerblock td { 57 | border: none; 58 | background-color: transparent; 59 | color: black; 60 | padding: 0; 61 | } 62 | .filename { 63 | display: block; 64 | color: rgb(119, 119, 119); 65 | font-size: 20px; 66 | font-weight: normal; 67 | line-height: 100%; 68 | margin: 10px 0 32px; 69 | } 70 | #rfc\.abstract+p, #rfc\.abstract+p code, #rfc\.abstract+p samp, #rfc\.abstract+p tt { 71 | font-size: 20px; 72 | line-height: 28px; 73 | } 74 | 75 | samp, tt, code, pre, span.tt { 76 | font-size: 13.5px; 77 | font-family: Consolas, monospace; 78 | font-size-adjust: none; 79 | } 80 | pre { 81 | background-color: #eee; 82 | border: 1px solid #ddd; 83 | overflow-x: auto; 84 | padding: 5px; 85 | margin: 5px; 86 | } 87 | .figure, caption { 88 | font-style: italic; 89 | margin: 0 1.5em; 90 | text-align: left; 91 | } 92 | 93 | address { 94 | margin: 16px 2px; 95 | line-height: 20px; 96 | } 97 | .vcard { 98 | font-style: normal; 99 | } 100 | .vcardline { 101 | display: block; 102 | } 103 | .vcardline .fn, address b { 104 | font-weight: normal; 105 | } 106 | .vcardline .hidden { 107 | display: none; 108 | } 109 | 110 | dl { 111 | margin-left: 1em; 112 | } 113 | dl.dl-horizontal: { 114 | margin-left: 0; 115 | } 116 | dl > dt { 117 | float: left; 118 | margin-right: 1em; 119 | } 120 | dl.nohang > dt { 121 | float: none; 122 | } 123 | dl > dd { 124 | margin-bottom: .5em; 125 | } 126 | dl.compact > dd { 127 | margin-bottom: 0em; 128 | } 129 | dl > dd > dl { 130 | margin-top: 0.5em; 131 | margin-bottom: 0em; 132 | } 133 | ul.empty { 134 | list-style-type: none; 135 | } 136 | ul.empty li { 137 | margin-top: .5em; 138 | } 139 | 140 | hr { 141 | border: 0; 142 | border-top: 1px solid #eee; 143 | } 144 | hr.noprint { 145 | display: none; 146 | } 147 | 148 | a { 149 | text-decoration: none; 150 | } 151 | a[href] { 152 | color: #2a6496; 153 | } 154 | a[href]:hover { 155 | background-color: #eee; 156 | } 157 | 158 | p, ol, ul, li { 159 | padding: 0; 160 | } 161 | p { 162 | margin: 0.5em 0; 163 | } 164 | ol, ul { 165 | margin: 0.2em 0 0.2em 2em; 166 | } 167 | li { 168 | margin: 0.2em 0; 169 | } 170 | address { 171 | font-style: normal; 172 | } 173 | 174 | ul.toc ul { 175 | margin: 0 0 0 2em; 176 | } 177 | ul.toc li { 178 | list-style: none; 179 | margin: 0; 180 | } 181 | 182 | @media screen and (min-width: 924px) { 183 | body { 184 | padding-right: 350px; 185 | } 186 | body>ul.toc, body>#rfc\.toc { 187 | position: fixed; 188 | bottom: 0; 189 | right: 0; 190 | right: calc(50vw - 500px); 191 | width: 300px; 192 | z-index: 1; 193 | overflow: auto; 194 | overscroll-behavior: contain; 195 | } 196 | body>#rfc\.toc { 197 | top: 55px; 198 | } 199 | body>ul.toc { 200 | top: 100px; 201 | } 202 | 203 | ul.toc { 204 | margin: 0 0 0 4px; 205 | font-size: 12px; 206 | line-height: 20px; 207 | } 208 | ul.toc ul { 209 | margin-left: 1.2em; 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /targets.mk: -------------------------------------------------------------------------------- 1 | targets_file := .targets.mk 2 | targets_drafts := \# $(drafts) 3 | targets_tags := \# $(drafts_tags) 4 | $(targets_file): $(LIBDIR)/build-targets.sh 5 | @echo "$(targets_drafts)" >$@ 6 | @echo "$(targets_tags)" >>$@ 7 | VERSIONED="$(VERSIONED)" $< $(drafts) >>$@ 8 | .PHONY: extra 9 | extra: $(targets_file) 10 | 11 | ifneq (,$(wildcard $(targets_file))) 12 | EXTRA_TARGETS ?= true 13 | endif 14 | 15 | ifeq (true,$(EXTRA_TARGETS)) 16 | # Rough check for when .targets.mk is out of date. 17 | # Note that $(shell ) folds multiple lines into one, which is OK here. 18 | ifneq ($(targets_drafts) $(targets_tags),$(shell head -2 $(targets_file) 2>/dev/null)) 19 | # Force an update of .targets.mk by marking the file as phony. 20 | .PHONY: $(targets_file) 21 | endif 22 | 23 | include $(targets_file) 24 | else 25 | # Backup rule for building files when we don't find the rule. 26 | diff-% $(VERSIONED)/%:: 27 | @$(MAKE) $(targets_file) 28 | @$(MAKE) EXTRA_TARGETS=true $@ 29 | endif 30 | -------------------------------------------------------------------------------- /template/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: martinthomson/i-d-template:latest 6 | resource_class: small 7 | working_directory: ~/draft 8 | 9 | steps: 10 | - run: 11 | name: "Print Configuration" 12 | command: | 13 | xml2rfc --version 14 | gem list -q kramdown-rfc 15 | echo -n 'mmark '; mmark --version 16 | 17 | - restore_cache: 18 | name: "Restoring cache - Git" 19 | keys: 20 | - v2-cache-git-{{ .Branch }}-{{ .Revision }} 21 | - v2-cache-git-{{ .Branch }} 22 | - v2-cache-git- 23 | 24 | - restore_cache: 25 | name: "Restoring cache - References" 26 | keys: 27 | - v1-cache-references-{{ epoch }} 28 | - v1-cache-references- 29 | 30 | # Workaround for https://discuss.circleci.com/t/22437 31 | - run: 32 | name: Tag Checkout 33 | command: | 34 | if [ -n "$CIRCLE_TAG" ] && [ -d .git ]; then 35 | remote=$(echo "$CIRCLE_REPOSITORY_URL" | \ 36 | sed -e 's,/^git.github.com:,https://github.com/,') 37 | git fetch -f "$remote" "refs/tags/$CIRCLE_TAG:refs/tags/$CIRCLE_TAG" || \ 38 | (echo 'Removing .git cache for tag build'; rm -rf .git) 39 | fi 40 | 41 | - checkout 42 | 43 | # Build txt and html versions of drafts 44 | - run: 45 | name: "Build Drafts" 46 | command: make 47 | 48 | # Update editor's copy on gh-pages 49 | - run: 50 | name: "Update GitHub Pages" 51 | command: | 52 | if [ "${CIRCLE_TAG#draft-}" == "$CIRCLE_TAG" ]; then 53 | make gh-pages 54 | fi 55 | 56 | # For tagged builds, upload to the datatracker. 57 | - deploy: 58 | name: "Upload to Datatracker" 59 | command: | 60 | if [ "${CIRCLE_TAG#draft-}" != "$CIRCLE_TAG" ]; then 61 | make upload 62 | fi 63 | 64 | # Archive GitHub Issues 65 | - run: 66 | name: "Archive GitHub Issues" 67 | command: "make archive || make archive DISABLE_ARCHIVE_FETCH=true && make gh-archive" 68 | 69 | # Create and store artifacts 70 | - run: 71 | name: "Create Artifacts" 72 | command: "make artifacts CI_ARTIFACTS=/tmp/artifacts" 73 | 74 | - store_artifacts: 75 | path: /tmp/artifacts 76 | 77 | - run: 78 | name: "Prepare for Caching" 79 | command: "git reflog expire --expire=now --all && git gc --prune=now" 80 | 81 | - save_cache: 82 | name: "Saving Cache - Git" 83 | key: v2-cache-git-{{ .Branch }}-{{ .Revision }} 84 | paths: 85 | - ~/draft/.git 86 | 87 | - save_cache: 88 | name: "Saving Cache - Drafts" 89 | key: v1-cache-references-{{ epoch }} 90 | paths: 91 | - ~/.cache/xml2rfc 92 | 93 | 94 | workflows: 95 | version: 2 96 | build: 97 | jobs: 98 | - build: 99 | filters: 100 | tags: 101 | only: /.*?/ 102 | -------------------------------------------------------------------------------- /template/.editorconfig: -------------------------------------------------------------------------------- 1 | # See http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*.{md,xml,org}] 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /template/.github/workflows/archive.yml: -------------------------------------------------------------------------------- 1 | name: "Archive Issues and Pull Requests" 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * 0,2,4' 6 | repository_dispatch: 7 | types: [archive] 8 | workflow_dispatch: 9 | inputs: 10 | archive_full: 11 | description: 'Recreate the archive from scratch' 12 | default: false 13 | type: boolean 14 | 15 | jobs: 16 | build: 17 | name: "Archive Issues and Pull Requests" 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: write 21 | steps: 22 | - name: "Checkout" 23 | uses: actions/checkout@v4 24 | 25 | # Note: No caching for this build! 26 | 27 | - name: "Update Archive" 28 | uses: martinthomson/i-d-template@v1 29 | env: 30 | ARCHIVE_FULL: ${{ inputs.archive_full }} 31 | with: 32 | make: archive 33 | token: ${{ github.token }} 34 | 35 | - name: "Update GitHub Pages" 36 | uses: martinthomson/i-d-template@v1 37 | with: 38 | make: gh-archive 39 | token: ${{ github.token }} 40 | 41 | - name: "Save Archive" 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: archive 45 | path: archive.json 46 | -------------------------------------------------------------------------------- /template/.github/workflows/ghpages.yml: -------------------------------------------------------------------------------- 1 | name: "Update Editor's Copy" 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - README.md 7 | - CONTRIBUTING.md 8 | - LICENSE.md 9 | - .gitignore 10 | pull_request: 11 | paths-ignore: 12 | - README.md 13 | - CONTRIBUTING.md 14 | - LICENSE.md 15 | - .gitignore 16 | 17 | jobs: 18 | build: 19 | name: "Update Editor's Copy" 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: write 23 | steps: 24 | - name: "Checkout" 25 | uses: actions/checkout@v4 26 | 27 | - name: "Setup" 28 | id: setup 29 | run: date -u "+date=%FT%T" >>"$GITHUB_OUTPUT" 30 | 31 | - name: "Caching" 32 | uses: actions/cache@v4 33 | with: 34 | path: | 35 | .refcache 36 | .venv 37 | .gems 38 | node_modules 39 | .targets.mk 40 | key: i-d-${{ steps.setup.outputs.date }} 41 | restore-keys: i-d- 42 | 43 | - name: "Build Drafts" 44 | uses: martinthomson/i-d-template@v1 45 | with: 46 | token: ${{ github.token }} 47 | 48 | - name: "Update GitHub Pages" 49 | uses: martinthomson/i-d-template@v1 50 | if: ${{ github.event_name == 'push' }} 51 | with: 52 | make: gh-pages 53 | token: ${{ github.token }} 54 | 55 | - name: "Archive Built Drafts" 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: drafts 59 | path: | 60 | draft-*.html 61 | draft-*.txt 62 | -------------------------------------------------------------------------------- /template/.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: "Publish New Draft Version" 2 | 3 | on: 4 | push: 5 | tags: 6 | - "draft-*" 7 | workflow_dispatch: 8 | inputs: 9 | email: 10 | description: "Submitter email" 11 | default: "" 12 | type: string 13 | 14 | jobs: 15 | build: 16 | name: "Publish New Draft Version" 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: "Checkout" 20 | uses: actions/checkout@v4 21 | 22 | # See https://github.com/actions/checkout/issues/290 23 | - name: "Get Tag Annotations" 24 | run: git fetch -f origin ${{ github.ref }}:${{ github.ref }} 25 | 26 | - name: "Setup" 27 | id: setup 28 | run: date -u "+date=%FT%T" >>"$GITHUB_OUTPUT" 29 | 30 | - name: "Caching" 31 | uses: actions/cache@v4 32 | with: 33 | path: | 34 | .refcache 35 | .venv 36 | .gems 37 | node_modules 38 | .targets.mk 39 | key: i-d-${{ steps.setup.outputs.date }} 40 | restore-keys: i-d- 41 | 42 | - name: "Build Drafts" 43 | uses: martinthomson/i-d-template@v1 44 | with: 45 | token: ${{ github.token }} 46 | 47 | - name: "Upload to Datatracker" 48 | uses: martinthomson/i-d-template@v1 49 | with: 50 | make: upload 51 | env: 52 | UPLOAD_EMAIL: ${{ inputs.email }} 53 | 54 | - name: "Archive Submitted Drafts" 55 | uses: actions/upload-artifact@v4 56 | with: 57 | name: published 58 | path: "versioned/draft-*-[0-9][0-9].*" 59 | -------------------------------------------------------------------------------- /template/.github/workflows/update.yml: -------------------------------------------------------------------------------- 1 | name: "Update Generated Files" 2 | # This rule is not run automatically. 3 | # It can be run manually to update all of the files that are part 4 | # of the template, specifically: 5 | # - README.md 6 | # - CONTRIBUTING.md 7 | # - .note.xml 8 | # - .github/CODEOWNERS 9 | # - Makefile 10 | # 11 | # 12 | # This might be useful if you have: 13 | # - added, removed, or renamed drafts (including after adoption) 14 | # - added, removed, or changed draft editors 15 | # - changed the title of drafts 16 | # 17 | # Note that this removes any customizations you have made to 18 | # the affected files. 19 | on: workflow_dispatch 20 | 21 | jobs: 22 | build: 23 | name: "Update Files" 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: "Checkout" 27 | uses: actions/checkout@v4 28 | 29 | - name: "Update Generated Files" 30 | uses: martinthomson/i-d-template@v1 31 | with: 32 | make: update-files 33 | token: ${{ github.token }} 34 | 35 | - name: "Push Update" 36 | run: git push 37 | -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.pdf 3 | *.redxml 4 | *.swp 5 | *.txt 6 | *.upload 7 | *~ 8 | .tags 9 | /*-[0-9][0-9].xml 10 | /.*.mk 11 | /.gems/ 12 | /.refcache 13 | /.venv/ 14 | /.vscode/ 15 | /.idea/ 16 | /node_modules/ 17 | /versioned/ 18 | Gemfile.lock 19 | archive.json 20 | package-lock.json 21 | report.xml 22 | !requirements.txt 23 | -------------------------------------------------------------------------------- /template/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: xenial 3 | 4 | services: 5 | - docker 6 | 7 | env: 8 | DRAFT_DIR: /home/idci/draft 9 | 10 | before_install: 11 | - docker --version 12 | - docker pull martinthomson/i-d-template 13 | 14 | script: 15 | - docker run -d -v "$PWD:/tmp/draft" --tmpfs "$DRAFT_DIR:rw,exec" --name idci 16 | martinthomson/i-d-template sleep 300 17 | - docker exec idci cp -rn /tmp/draft /home/idci 18 | - docker exec -w "$DRAFT_DIR" -e CI=true -e TRAVIS 19 | -e TRAVIS_REPO_SLUG -e TRAVIS_BRANCH -e TRAVIS_TAG -e TRAVIS_PULL_REQUEST 20 | idci make CLONE_ARGS='--reference /home/idci/git-reference' 21 | - docker exec idci ls -l /home/idci/draft/lib 22 | - if [ "${TRAVIS_TAG#draft-}" == "${TRAVIS_TAG}" ]; then 23 | docker exec -w "$DRAFT_DIR" -e CI=true -e GH_TOKEN -e TRAVIS 24 | -e TRAVIS_REPO_SLUG -e TRAVIS_BRANCH -e TRAVIS_TAG -e TRAVIS_PULL_REQUEST 25 | idci make ghpages; 26 | fi 27 | 28 | deploy: 29 | provider: script 30 | script: 31 | - docker exec -w "$DRAFT_DIR" -e CI=true -e GH_TOKEN -e TRAVIS 32 | -e TRAVIS_REPO_SLUG -e TRAVIS_BRANCH -e TRAVIS_TAG -e TRAVIS_PULL_REQUEST 33 | idci make upload 34 | skip_cleanup: true 35 | on: 36 | tags: true 37 | 38 | after_script: 39 | - docker container rm -f idci 40 | -------------------------------------------------------------------------------- /template/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This repository relates to activities in the Internet Engineering Task Force 4 | ([IETF](https://www.ietf.org/)). All material in this repository is considered 5 | Contributions to the IETF Standards Process, as defined in the intellectual 6 | property policies of IETF currently designated as 7 | [BCP 78](https://www.rfc-editor.org/info/bcp78), 8 | [BCP 79](https://www.rfc-editor.org/info/bcp79) and the 9 | [IETF Trust Legal Provisions (TLP) Relating to IETF Documents](http://trustee.ietf.org/trust-legal-provisions.html). 10 | 11 | Any edit, commit, pull request, issue, comment or other change made to this 12 | repository constitutes Contributions to the IETF Standards Process 13 | (https://www.ietf.org/). 14 | 15 | You agree to comply with all applicable IETF policies and procedures, including, 16 | BCP 78, 79, the TLP, and the TLP rules regarding code components (e.g. being 17 | subject to a Simplified BSD License) in Contributions. 18 | -------------------------------------------------------------------------------- /template/LICENSE.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | See the 4 | [guidelines for contributions](https://github.com/{GITHUB_USER}/{GITHUB_REPO}/blob/{GITHUB_BRANCH}/CONTRIBUTING.md). 5 | -------------------------------------------------------------------------------- /template/Makefile: -------------------------------------------------------------------------------- 1 | LIBDIR := lib 2 | include $(LIBDIR)/main.mk 3 | 4 | $(LIBDIR)/main.mk: 5 | ifneq (,$(shell grep "path *= *$(LIBDIR)" .gitmodules 2>/dev/null)) 6 | git submodule sync 7 | git submodule update --init 8 | else 9 | ifneq (,$(wildcard $(ID_TEMPLATE_HOME))) 10 | ln -s "$(ID_TEMPLATE_HOME)" $(LIBDIR) 11 | else 12 | git clone -q --depth 10 -b main \ 13 | https://github.com/martinthomson/i-d-template $(LIBDIR) 14 | endif 15 | endif 16 | -------------------------------------------------------------------------------- /template/issues.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Issue Viewer 6 | 7 | 61 | 62 | 63 |
64 | 65 | 67 | records 68 |
69 |
70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
IDTitleStateAuthorAssigneeLabels
84 |
85 |
86 |
87 |

This page shows GitHub issues in a simple form.

88 |

The filter box above accepts a set of filters, each separated by space.

89 |
    90 |
91 |

You can /sort on id, recent, or closed.

92 |

Pressing enter saves the current search. 93 | Pressing esc leaves the text input area.

94 |

Outside the search box

95 |

Clicking an issue title displays details for the issue including comments. 96 | Pressing n or j moves to the next issue, 97 | and p or k move to the previous one.

98 |

Pressing esc closes the issue view, ' focuses search, and 99 | c clears the search.

100 |
101 |
102 | 103 | 104 | -------------------------------------------------------------------------------- /tests/build.feature: -------------------------------------------------------------------------------- 1 | Feature: Building drafts 2 | 3 | Scenario: Single Kramdown draft can build 4 | Given a configured git repo with a Kramdown draft 5 | when make is run 6 | then it succeeds 7 | and generates documents 8 | 9 | Scenario: Multiple Kramdown drafts can build 10 | Given a configured git repo with multiple Kramdown drafts 11 | when make is run 12 | then it succeeds 13 | and generates documents 14 | 15 | Scenario: Incorrect Kramdown draft fails 16 | Given a configured git repo with a Kramdown draft 17 | when the draft is broken 18 | and make is run 19 | then it fails 20 | 21 | Scenario: Submodule for lib 22 | Given a configured git repo with a Kramdown draft 23 | when lib is added as a submodule 24 | and make is run 25 | then it succeeds 26 | and generates documents 27 | 28 | Scenario: Submodule removed 29 | Given a configured git repo with a Kramdown draft 30 | when the lib dir is removed 31 | and lib is added as a submodule 32 | and the lib dir is removed 33 | and make is run 34 | then it succeeds 35 | and generates documents 36 | 37 | # Scenario: Mmark draft can build 38 | # Given a configured git repo with an Mmark draft 39 | # when we run make 40 | # then make succeeds and generates documents 41 | # 42 | # Scenario: Incorrect Mmark draft fails 43 | # Given a configured git repo with a Mmark draft 44 | # when we break the draft and run make 45 | # then make fails 46 | -------------------------------------------------------------------------------- /tests/environment.py: -------------------------------------------------------------------------------- 1 | from behave import * 2 | from shutil import rmtree 3 | 4 | 5 | def after_scenario(context, scenario): 6 | if "working_dir" in context: 7 | rmtree(context.working_dir) 8 | if "origin_dir" in context: 9 | rmtree(context.origin_dir) 10 | -------------------------------------------------------------------------------- /tests/git.feature: -------------------------------------------------------------------------------- 1 | Feature: git integration 2 | 3 | Scenario: make ghpages 4 | Given a configured git repo with a Kramdown draft 5 | when make "ghpages" is run 6 | then it succeeds 7 | and a branch is created called "gh-pages" containing "index.html" 8 | and documents are added to gh-pages 9 | 10 | Scenario: make ghpages with a non-main default branch 11 | Given a configured git repo with a Kramdown draft 12 | and the default branch is "kerfuffle" 13 | when make "ghpages" is run 14 | then it succeeds 15 | and a branch is created called "gh-pages" containing "index.html" which contains "Editor's drafts for kerfuffle branch" 16 | and a branch is created called "gh-pages" containing "index.html" which contains "referrer_branch = 'kerfuffle'" 17 | 18 | Scenario: make gharchive 19 | Given a configured git repo with a Kramdown draft 20 | when make "ghissues" is run with "PUSH_GHPAGES=false" 21 | then it succeeds 22 | and a branch is created called "gh-pages" containing "archive.json" 23 | 24 | Scenario: git pre-commit hook blocks broken drafts 25 | Given a configured git repo with a Kramdown draft 26 | when the draft is broken 27 | and git commit is run 28 | then it fails 29 | 30 | Scenario: git pre-commit hook does not block other drafts 31 | Given a configured git repo with multiple Kramdown drafts 32 | when the draft is broken 33 | and a non-broken draft is committed 34 | then it succeeds 35 | -------------------------------------------------------------------------------- /tests/setup.feature: -------------------------------------------------------------------------------- 1 | Feature: Initial setup 2 | 3 | Scenario: Run setup script on correctly-set-up directory 4 | Given a git repo with a single Kramdown draft 5 | when the setup script is run 6 | then it succeeds 7 | and a file is created called "Makefile" 8 | and a file is created called "README.md" 9 | and a file is created called "CONTRIBUTING.md" 10 | and a file is created called "LICENSE.md" 11 | and a file is created called ".gitignore" 12 | and a file is created called ".circleci/config.yml" 13 | and a file is created called ".github/workflows/ghpages.yml" 14 | and a file is created called ".github/workflows/publish.yml" 15 | and a file is created called ".github/workflows/archive.yml" 16 | and a file is created called ".github/CODEOWNERS" 17 | and a file is created called ".editorconfig" 18 | and a branch is created called "gh-pages" containing "index.html" 19 | and a branch is created called "gh-pages" containing "archive.json" 20 | and gitignore lists xml files 21 | and gitignore negation rules come last 22 | and a precommit hook is installed 23 | 24 | Scenario: Run setup script with XSLT on correctly-set-up directory 25 | Given a git repo with a single Kramdown draft 26 | when the setup script is run with "USE_XSLT=true" 27 | then it succeeds 28 | and a file is created called "Makefile" which contains "USE_XSLT := true" 29 | and a file is created called "README.md" 30 | and a file is created called "CONTRIBUTING.md" 31 | and a file is created called "LICENSE.md" 32 | and a file is created called ".gitignore" 33 | and a file is created called ".circleci/config.yml" 34 | and a file is created called ".github/workflows/ghpages.yml" 35 | and a file is created called ".github/workflows/publish.yml" 36 | and a file is created called ".github/workflows/archive.yml" 37 | and a file is created called ".github/CODEOWNERS" 38 | and a file is created called ".editorconfig" 39 | and a branch is created called "gh-pages" containing "index.html" 40 | and a branch is created called "gh-pages" containing "archive.json" 41 | and gitignore lists xml files 42 | and gitignore negation rules come last 43 | and a precommit hook is installed 44 | 45 | Scenario: Run setup script with INDEX_FORMAT set 46 | Given a git repo with a single Kramdown draft 47 | when the setup script is run with "INDEX_FORMAT=md" 48 | then it succeeds 49 | and a file is created called "Makefile" which contains "INDEX_FORMAT := md" 50 | and a file is created called "README.md" 51 | and a file is created called "CONTRIBUTING.md" 52 | and a file is created called "LICENSE.md" 53 | and a file is created called ".gitignore" 54 | and a file is created called ".circleci/config.yml" 55 | and a file is created called ".github/workflows/ghpages.yml" 56 | and a file is created called ".github/workflows/publish.yml" 57 | and a file is created called ".github/workflows/archive.yml" 58 | and a file is created called ".github/CODEOWNERS" 59 | and a file is created called ".editorconfig" 60 | and a branch is created called "gh-pages" containing "index.md" 61 | and a branch is created called "gh-pages" containing "archive.json" 62 | and gitignore lists xml files 63 | and gitignore negation rules come last 64 | and a precommit hook is installed 65 | 66 | Scenario: Run setup script when the file contains the wrong name 67 | Given a git repo with a single Kramdown draft 68 | and drafts are modified with sed -e "s/-latest/-01/g" 69 | when the setup script is run 70 | then it fails 71 | and generates a message "Check names" 72 | 73 | Scenario: Run setup script on directory with no draft 74 | Given an empty git repo 75 | and lib is cloned in 76 | when the setup script is run 77 | then it fails 78 | and generates a message "Create a draft file" 79 | 80 | Scenario: Run setup script on directory with multiple drafts 81 | Given a git repo with multiple Kramdown drafts 82 | when the setup script is run 83 | then it succeeds 84 | and gitignore lists xml files 85 | 86 | Scenario: Run setup script on directory with no origin remote 87 | Given a git repo with no origin 88 | and lib is cloned in 89 | and a Kramdown draft is created 90 | when the setup script is run 91 | then it fails 92 | and generates a message "remote" 93 | 94 | Scenario: Run setup script on directory without pushing 95 | Given a git repo with no origin 96 | and an empty origin remote is added 97 | and lib is cloned in 98 | and a Kramdown draft is created 99 | when the setup script is run 100 | then it fails 101 | and generates a message "push" 102 | 103 | Scenario: Retain .gitignore when running setup 104 | Given a git repo with a single Kramdown draft 105 | and a .gitignore with the line "IGNORE-ME" 106 | when the setup script is run 107 | then it succeeds 108 | and gitignore lists "IGNORE-ME" 109 | and gitignore lists xml files 110 | and gitignore negation rules come last 111 | -------------------------------------------------------------------------------- /tests/steps/build_commands.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from behave import * 3 | from subprocess import call 4 | from contextlib import contextmanager 5 | from tempfile import TemporaryFile 6 | from glob import glob 7 | import os 8 | import sys 9 | import fileinput 10 | 11 | git_commit = [ 12 | "git", 13 | "-c", 14 | "user.name=Behave Tests", 15 | "-c", 16 | "user.email=behave@example.com", 17 | "commit", 18 | ] 19 | offline_make_options = [ 20 | "PUSH_GHPAGES=false", 21 | "FETCH_ISSUES=false", 22 | "BRANCH_FETCH=false", 23 | ] 24 | 25 | 26 | @contextmanager 27 | def cd(newdir): 28 | prevdir = os.getcwd() 29 | os.chdir(os.path.expanduser(newdir)) 30 | try: 31 | yield 32 | finally: 33 | os.chdir(prevdir) 34 | 35 | 36 | def run_with_capture(context, command): 37 | with cd(context.working_dir), TemporaryFile(mode="w+") as outFile, TemporaryFile( 38 | mode="w+" 39 | ) as errFile: 40 | context.result = call(command, stdout=outFile, stderr=errFile) 41 | outFile.seek(0) 42 | context.out = outFile.read() 43 | errFile.seek(0) 44 | context.error = errFile.read() 45 | print(context.out) 46 | print(context.error, file=sys.stderr) 47 | 48 | 49 | @when("the setup script is run") 50 | def step_impl(context): 51 | run_with_capture(context, ["make", "-f", "lib/setup.mk"] + offline_make_options) 52 | 53 | 54 | @when('the setup script is run with "{option}"') 55 | def step_impl(context, option): 56 | run_with_capture( 57 | context, ["make", "-f", "lib/setup.mk", option] + offline_make_options 58 | ) 59 | 60 | 61 | @when("make is run") 62 | def step_impl(context): 63 | run_with_capture(context, ["make"] + offline_make_options) 64 | 65 | 66 | @when('make "{target}" is run') 67 | def step_impl(context, target): 68 | run_with_capture(context, ["make", target] + offline_make_options) 69 | 70 | 71 | @when('make "{target}" is run with "{option}"') 72 | def step_impl(context, target, option): 73 | run_with_capture(context, ["make", target, option] + offline_make_options) 74 | 75 | 76 | @when("the draft is broken") 77 | def step_impl(context): 78 | import platform 79 | 80 | if any(p in platform.system() for p in ["Darwin", "BSD"]): 81 | sed_no_backup = ["sed", "-i", ""] 82 | else: 83 | sed_no_backup = ["sed", "-i"] 84 | with cd(context.working_dir): 85 | break_this = glob("draft-*.md")[0] 86 | run_with_capture( 87 | context, 88 | sed_no_backup + ["-e", "s/TODO Security/{{broken-reference}}/", break_this], 89 | ) 90 | context.broken_file = break_this 91 | 92 | 93 | @when("the lib dir is removed") 94 | def step_impl(context): 95 | run_with_capture(context, ["rm", "-rf", "lib"]) 96 | 97 | 98 | @when("lib is added as a submodule") 99 | def step_impl(context): 100 | run_with_capture(context, ["git", "submodule", "add", "-f", os.getcwd(), "lib"]) 101 | 102 | 103 | @when("git commit is run") 104 | def step_impl(context): 105 | with cd(context.working_dir): 106 | run_with_capture(context, git_commit + ["-am", "Committing broken draft"]) 107 | 108 | 109 | @when("a non-broken draft is committed") 110 | def step_impl(context): 111 | with cd(context.working_dir): 112 | drafts = glob("draft-*.md") 113 | drafts.remove(context.broken_file) 114 | commit_this_file = drafts[0] 115 | with open(commit_this_file, "a") as update: 116 | update.write("# One more appendix\n\nCan you see me?\n") 117 | call(["git", "add", commit_this_file]) 118 | run_with_capture(context, git_commit + ["-m", "Only the non-broken file"]) 119 | -------------------------------------------------------------------------------- /tests/steps/repo_setup.py: -------------------------------------------------------------------------------- 1 | from behave import * 2 | from glob import glob 3 | from subprocess import call 4 | from tempfile import mkdtemp, NamedTemporaryFile 5 | from contextlib import contextmanager 6 | import os 7 | import random 8 | import string 9 | 10 | 11 | @contextmanager 12 | def cd(newdir): 13 | prevdir = os.getcwd() 14 | os.chdir(os.path.expanduser(newdir)) 15 | try: 16 | yield 17 | finally: 18 | os.chdir(prevdir) 19 | 20 | 21 | @given("an empty git repo") 22 | def step_impl(context): 23 | context.test_dir = os.getcwd() 24 | context.working_dir = mkdtemp() 25 | context.origin_dir = mkdtemp() 26 | with cd(context.origin_dir): 27 | call(["git", "init", "-b", "main"]) 28 | # Need to checkout another branch so that pushes to main work. 29 | call(["git", "checkout", "-b", "nonce-c1a3d943"]) 30 | 31 | with cd(context.working_dir): 32 | call(["git", "clone", context.origin_dir, "."]) 33 | call(["git", "checkout", "-b", "main"]) 34 | call(["git", "config", "user.name", "Behave Tests"]) 35 | call(["git", "config", "user.email", "behave@example.com"]) 36 | 37 | 38 | @given('the default branch is "{branch}"') 39 | def step_impl(context, branch): 40 | with cd(context.working_dir): 41 | call(["git", "checkout", "-b", branch]) 42 | call(["git", "push", "--set-upstream", "origin", branch]) 43 | call(["git", "remote", "set-head", "origin", branch]) 44 | 45 | 46 | @given("a git repo with no origin") 47 | def step_impl(context): 48 | context.test_dir = os.getcwd() 49 | context.working_dir = mkdtemp() 50 | with cd(context.working_dir): 51 | call(["git", "init"]) 52 | call(["git", "checkout", "--orphan", "main"]) 53 | call(["git", "config", "user.name", "Behave Tests"]) 54 | call(["git", "config", "user.email", "behave@example.com"]) 55 | 56 | 57 | @given("lib is cloned in") 58 | def step_impl(context): 59 | with cd(context.working_dir): 60 | call(["ln", "-s", context.test_dir, "lib"]) 61 | 62 | 63 | @given("the repo is tagged") 64 | def step_impl(context): 65 | with cd(context.working_dir): 66 | md_files = glob("draft-*.md") 67 | for md in md_files: 68 | tag = md.replace(".md", "-00") 69 | call(["git", "tag", "-am", "testing", tag]) 70 | 71 | 72 | @given("an empty origin remote is added") 73 | def step_impl(context): 74 | with cd(context.working_dir): 75 | call(["git", "remote", "add", "origin", mkdtemp()]) 76 | 77 | 78 | @given("a Kramdown draft is created") 79 | def step_impl(context): 80 | with cd(context.working_dir): 81 | random_string = "".join( 82 | random.SystemRandom().choice(string.ascii_lowercase) for n in range(8) 83 | ) 84 | draft_name = "draft-behave-template-" + random_string 85 | file_name = draft_name + ".md" 86 | with open(file_name, "wb") as newFile: 87 | call( 88 | [ 89 | "sed", 90 | "-e", 91 | f"s/draft-todo-yourname-protocol/{draft_name}/", 92 | "lib/example/draft-todo-yourname-protocol.md", 93 | ], 94 | stdout=newFile, 95 | ) 96 | call(["git", "add", file_name]) 97 | call(["git", "commit", "-am", "Initial commit of {}".format(draft_name)]) 98 | 99 | 100 | @given('a .gitignore with the line "{ignore}"') 101 | def step_impl(context, ignore): 102 | with cd(context.working_dir): 103 | with open(".gitignore", "w") as gi: 104 | gi.write("{}\n".format(ignore)) 105 | call(["git", "add", ".gitignore"]) 106 | call(["git", "commit", "-am", "Create .gitignore with '{}'".format(ignore)]) 107 | 108 | 109 | @given("pushed to origin/main") 110 | def step_impl(context): 111 | with cd(context.working_dir): 112 | call(["git", "push", "origin", "main"]) 113 | 114 | 115 | @given("a git repo with a single Kramdown draft") 116 | def step_impl(context): 117 | context.execute_steps( 118 | """ 119 | Given an empty git repo 120 | and lib is cloned in 121 | and a Kramdown draft is created 122 | and pushed to origin/main""" 123 | ) 124 | 125 | 126 | @given("a git repo with multiple Kramdown drafts") 127 | def step_impl(context): 128 | context.execute_steps( 129 | """ 130 | Given a git repo with a single Kramdown draft 131 | and a Kramdown draft is created 132 | and pushed to origin/main""" 133 | ) 134 | 135 | 136 | @given("a configured git repo with a Kramdown draft") 137 | def step_impl(context): 138 | context.execute_steps("Given a git repo with a single Kramdown draft") 139 | with cd(context.working_dir): 140 | context.result = call(["make", "-f", "lib/setup.mk", "BRANCH_FETCH=false"]) 141 | 142 | 143 | @given("a configured git repo with multiple Kramdown drafts") 144 | def step_impl(context): 145 | context.execute_steps("Given a git repo with multiple Kramdown drafts") 146 | with cd(context.working_dir): 147 | context.result = call(["make", "-f", "lib/setup.mk", "BRANCH_FETCH=false"]) 148 | 149 | 150 | @given('drafts are modified with sed -e "{}"') 151 | def step_impl(context, script): 152 | with cd(context.working_dir): 153 | call(["sed", "-i~", "-e", script] + glob("draft-*")) 154 | -------------------------------------------------------------------------------- /tests/steps/results.py: -------------------------------------------------------------------------------- 1 | from behave import * 2 | from contextlib import contextmanager 3 | from subprocess import check_output, check_call 4 | import os 5 | from glob import glob 6 | 7 | 8 | @contextmanager 9 | def cd(newdir): 10 | prevdir = os.getcwd() 11 | os.chdir(os.path.expanduser(newdir)) 12 | try: 13 | yield 14 | finally: 15 | os.chdir(prevdir) 16 | 17 | 18 | @then("it succeeds") 19 | def step_impl(context): 20 | assert context.result == 0 21 | 22 | 23 | @then("it fails") 24 | def step_impl(context): 25 | assert context.result != 0 26 | 27 | 28 | @then('generates a message "{text}"') 29 | def step_impl(context, text): 30 | assert context.error.find(text) != -1 31 | 32 | 33 | @then("gitignore lists xml files") 34 | def step_impl(context): 35 | with cd(context.working_dir): 36 | md_files = glob("draft-*.md") 37 | for md in md_files: 38 | context.execute_steps( 39 | 'then gitignore lists "{}"'.format(md.replace(".md", ".xml")) 40 | ) 41 | 42 | 43 | @then("gitignore negation rules come last") 44 | def step_impl(context): 45 | with cd(context.working_dir): 46 | with open(".gitignore", mode="r") as f: 47 | neg = False 48 | for line in f.read().splitlines(): 49 | if line[0] == "!": 50 | neg = True 51 | else: 52 | assert neg == False 53 | 54 | 55 | @then('gitignore lists "{ignore}"') 56 | def step_impl(context, ignore): 57 | with cd(context.working_dir): 58 | c = check_output(["grep", "-c", ignore, ".gitignore"]).decode("utf-8") 59 | assert int(c) == 1 60 | 61 | 62 | @then("generates documents") 63 | def step_impl(context): 64 | with cd(context.working_dir): 65 | md_files = glob("draft-*.md") 66 | for md in md_files: 67 | txt_file = md.replace(".md", ".txt") 68 | html_file = md.replace(".md", ".html") 69 | assert os.path.isfile(txt_file) 70 | assert os.path.isfile(html_file) 71 | 72 | 73 | @then("generates upload files") 74 | def step_impl(context): 75 | with cd(context.working_dir): 76 | for md in glob("draft-*.md"): 77 | if not "-00.md" in md: 78 | upload_file = "versioned/." + md.replace(".md", "-00.upload") 79 | assert os.path.isfile(upload_file) 80 | 81 | 82 | @then("documents are added to gh-pages") 83 | def step_impl(context): 84 | with cd(context.working_dir): 85 | md_files = glob("draft-*.md") 86 | ghpages_files = check_output( 87 | ["git", "ls-tree", "gh-pages", "--name-only", "-r"] 88 | ).decode("utf-8") 89 | for md in md_files: 90 | txt_file = md.replace(".md", ".txt") 91 | html_file = md.replace(".md", ".html") 92 | assert txt_file in ghpages_files 93 | assert html_file in ghpages_files 94 | 95 | 96 | @then('a file is created called "{filename}" which contains "{text}"') 97 | def step_impl(context, filename, text): 98 | context.execute_steps( 99 | 'then a branch is created called "main" containing "%s" which contains "%s"' 100 | % (filename, text) 101 | ) 102 | 103 | 104 | @then('a file is created called "{filename}"') 105 | def step_impl(context, filename): 106 | context.execute_steps( 107 | 'then a branch is created called "main" containing "%s"' % filename 108 | ) 109 | 110 | 111 | @then( 112 | 'a branch is created called "{branch}" containing "{filename}" which contains "{text}"' 113 | ) 114 | def step_impl(context, branch, filename, text): 115 | context.execute_steps( 116 | 'then a branch is created called "%s" containing "%s"' % (branch, filename) 117 | ) 118 | with cd(context.working_dir): 119 | content = check_output(["git", "show", "%s:%s" % (branch, filename)]).decode( 120 | "utf-8" 121 | ) 122 | assert text in content 123 | 124 | 125 | @then('a branch is created called "{branch}" containing "{filename}"') 126 | def step_impl(context, branch, filename): 127 | with cd(context.working_dir): 128 | files = check_output(["git", "ls-tree", branch, "--name-only", "-r"]).decode( 129 | "utf-8" 130 | ) 131 | assert filename in files 132 | 133 | 134 | @then("a precommit hook is installed") 135 | def step_impl(context): 136 | with cd(context.working_dir): 137 | assert len(glob(".git/hooks/pre-commit")) == 1 138 | -------------------------------------------------------------------------------- /tests/upload.feature: -------------------------------------------------------------------------------- 1 | Feature: Uploading drafts 2 | 3 | Scenario: Simple draft upload works 4 | Given a configured git repo with a Kramdown draft 5 | and the repo is tagged 6 | when make "upload" is run with "curl=f() { echo "HTTP/1.1 200 OK" >> $@; echo "curl $$@" >> $@; };f" 7 | then it succeeds 8 | and generates upload files 9 | 10 | Scenario: HTTP/2 draft upload works 11 | Given a configured git repo with a Kramdown draft 12 | and the repo is tagged 13 | when make "upload" is run with "curl=f() { echo "HTTP/2 200" >> $@; echo "curl $$@" >> $@; };f" 14 | then it succeeds 15 | and generates upload files 16 | 17 | Scenario: Multiple draft upload works 18 | Given a configured git repo with multiple Kramdown drafts 19 | and the repo is tagged 20 | when make "upload" is run with "curl=f() { echo "HTTP/1.1 200 OK" >> $@; echo "curl $$@" >> $@; };f" 21 | then it succeeds 22 | and generates upload files 23 | 24 | Scenario: Failing to upload shows error message 25 | Given a configured git repo with a Kramdown draft 26 | and the repo is tagged 27 | when make "upload" is run with "curl=f() { echo "HTTP/1.1 400 Bad Request" >> $@; echo "curl $$@" >> $@; };f" 28 | then it fails 29 | and generates a message "400 Bad Request" 30 | -------------------------------------------------------------------------------- /trace.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Usage: $0 [-v] [-q] [-s tool] tool [arguments ...] 3 | 4 | verbose=1 5 | if [[ "$1" == "-v" ]]; then 6 | verbose=2 7 | shift 8 | fi 9 | if [[ "$1" == "-q" ]]; then 10 | verbose=0 11 | shift 12 | fi 13 | 14 | file="${1%.*}" 15 | if [[ "$2" == "-s" ]]; then 16 | stage="$3" 17 | shift 3 18 | else 19 | stage="$2" 20 | shift 1 21 | fi 22 | 23 | report() { 24 | status="$1" 25 | } 26 | 27 | tmp="$(mktemp)" 28 | err="$(mktemp)" 29 | trap 'rm -f "$tmp" "$err"' EXIT 30 | set -o pipefail 31 | if [[ "$1" == "!" ]]; then 32 | shift 33 | ! "$@" 34 | else 35 | "$@" 36 | fi > >(tee -a "$tmp") 2> >(tee -a "$tmp" | tee -a "$err" 1>&2) 37 | status="$?" 38 | if [[ -n "$TRACE_FILE" ]]; then 39 | echo "$file $stage $status" >>"$TRACE_FILE" 40 | if [[ "$status" -ne 0 ]]; then 41 | tail -16 "$err" | while read -r line; do 42 | echo "$file $stage $line" >>"$TRACE_FILE" 43 | done 44 | fi 45 | fi 46 | if [[ "$verbose" -eq 1 ]]; then 47 | [[ "$status" -eq 0 ]] && res="\e[32mOK\e[0m" || res="\e[31mFAIL\e[0m" 48 | printf "${file}: \e[35m${stage}\e[0m ... ${res}\n" 1>&2 49 | fi 50 | exit "$status" 51 | -------------------------------------------------------------------------------- /update-venue.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Usage: $0 [drafts...] 4 | 5 | set -e 6 | 7 | . "$(dirname "$0")/wg-meta.sh" 8 | 9 | user="$1" 10 | repo="$2" 11 | shift 2 12 | 13 | if [[ "$OSTYPE" =~ (darwin|bsd).* ]] ; then 14 | function sed_no_backup() { sed -i '' "$@" ; } 15 | else 16 | function sed_no_backup() { sed -i "$@" ; } 17 | fi 18 | 19 | last_wg= 20 | for d in "$@"; do 21 | if ! head -1 "$d" | grep -q "^---"; then 22 | # This only works for kramdown-rfc drafts 23 | continue 24 | fi 25 | w="${d#draft-}" 26 | w="${w#*-}" 27 | w="${w%%-*}" 28 | 29 | sed_no_backup -e '1,/^---/ { 30 | /^venue:/,/^[^# ]/{ 31 | s,^[# ]*github: .*, github: "'"$user/$repo"'", 32 | s,^[# ]*latest: .*, latest: "'"https://$user.github.io/$repo/${d%.*}.html"'", 33 | } 34 | }' "$d" 35 | if [[ "$w" == "$last_wg" ]] || wgmeta "$w"; then 36 | sed_no_backup -e '1,/^---/ { 37 | s!^[# ]*area: .*!area: "'"$wg_area"'"! 38 | s!^[# ]*workgroup: .*!workgroup: "'"$wg_name"'"! 39 | /^venue:/,/^[^# ]/{ 40 | s!^[# ]*group: .*! group: "'"$wg_name"'"! 41 | s!^[# ]*type: .*! type: "'"$wg_type"'"! 42 | s!^[# ]*mail: .*! mail: "'"$wg_mail"'"! 43 | s!^[# ]*arch: .*! arch: "'"$wg_arch"'"! 44 | } 45 | }' "$d" 46 | last_wg="$w" 47 | else 48 | sed_no_backup -e '1,/^---/ { 49 | s,^[# ]*\(area\|workgroup\):,# \1:, 50 | /^venue:/,/^[^# ]/{ 51 | s,^[# ]*\(group\|type\|mail\|arch\):,# \1:,g 52 | } 53 | }' "$d" 54 | fi 55 | done 56 | -------------------------------------------------------------------------------- /update.mk: -------------------------------------------------------------------------------- 1 | .PHONY: update auto_update update-deps 2 | .SILENT: auto_update 3 | .IGNORE: auto_update 4 | 5 | ifneq (true,$(CI)) 6 | ifndef SUBMODULE 7 | UPDATE_COMMAND = echo Updating template && git -C $(LIBDIR) pull && \ 8 | ([ ! -d $(XSLTDIR) ] || git -C $(XSLTDIR) pull) 9 | FETCH_HEAD = $(wildcard $(LIBDIR)/.git/FETCH_HEAD) 10 | else 11 | UPDATE_COMMAND = echo Your template is old, please run `make update` 12 | FETCH_HEAD = $(wildcard .git/modules/$(LIBDIR)/FETCH_HEAD) 13 | endif 14 | 15 | NOW = $$(date '+%s') 16 | ifeq (,$(FETCH_HEAD)) 17 | UPDATE_NEEDED = false 18 | else 19 | UPDATE_INTERVAL := 1209600 # 2 weeks 20 | UPDATE_NEEDED = $(shell [ $$(($(NOW) - $(call last_modified,$(FETCH_HEAD)))) -gt $(UPDATE_INTERVAL) ] && echo true) 21 | endif 22 | 23 | ifeq (true,$(UPDATE_NEEDED)) 24 | latest next:: auto_update 25 | endif 26 | 27 | auto_update: 28 | $(UPDATE_COMMAND) 29 | $(MAKE) update-deps 30 | 31 | update: auto_update 32 | @for i in Makefile $(addprefix .github/workflows/,archive.yml ghpages.yml publish.yml update.yml); do \ 33 | [ -f "$$i" -a -z "$$(comm -13 $$i $(LIBDIR)/template/$$i 2>/dev/null)" ] || \ 34 | echo "warning: $$i is out of date, run \`make update-files\` to update it."; \ 35 | done 36 | @sed -i~ -e 's,-b master https://github.com/martinthomson/i-d-template,-b main https://github.com/martinthomson/i-d-template,' Makefile && \ 37 | [ `git status --porcelain Makefile | grep '^[A-Z]' | wc -l` -eq 0 ] || git $(CI_AUTHOR) commit -m "Update Makefile" Makefile 38 | @dotgit=$$(git rev-parse --git-dir); \ 39 | [ -L "$$dotgit"/hooks/pre-commit ] || \ 40 | ln -s ../../$(LIBDIR)/pre-commit.sh "$$dotgit"/hooks/pre-commit; \ 41 | [ -L "$$dotgit"/hooks/pre-push ] || \ 42 | ln -s ../../$(LIBDIR)/pre-push.sh "$$dotgit"/hooks/pre-push 43 | 44 | else 45 | # In CI, do nothing when asked to update. 46 | auto_update: 47 | update: 48 | endif # CI 49 | 50 | # This function regenerates a file using setup.mk 51 | # $(1) = the file name 52 | # $(2) = an optional test for when the file should NOT be updated 53 | define regenerate 54 | @set -e$(if $(filter-out false,$(VERBOSE)),x,); \ 55 | for f in $(1); do \ 56 | $(if $(2),if $(2); then echo "Skip update for $(1)" 2>&1; break; fi;) \ 57 | if [ -n "$$(git ls-tree -r @ --name-only "$$f")" ]; then \ 58 | amend=--amend; orig=@~; \ 59 | git rm -f "$$f" && \ 60 | git $(CI_AUTHOR) commit -m "Remove old "$$f""; \ 61 | else \ 62 | amend=; orig=@; \ 63 | fi; \ 64 | $(MAKE) -f $(LIBDIR)/setup.mk CHECK_BRANCH=false "$$f"; \ 65 | git add "$$f"; \ 66 | if ! git diff --quiet --cached "$$orig"; then \ 67 | echo "Updating $$f"; \ 68 | git $(CI_AUTHOR) commit $$amend -m "Automatic update of $$f"; \ 69 | elif [ -n "$$amend" ]; then \ 70 | git reset "$$orig" --hard; \ 71 | fi; \ 72 | done 73 | endef 74 | 75 | .PHONY: update-readme update-codeowners update-makefile update-gitignore update-files update-venue update-ci update-workflows 76 | 77 | # Re-run setup for .gitignore. 78 | # This should be an ordering prerequisite for any rule that might create files. 79 | update-gitignore: 80 | $(MAKE) -f $(LIBDIR)/setup.mk CHECK_BRANCH=false setup-gitignore 81 | @if ! git diff --quiet @ .gitignore; then \ 82 | git add .gitignore; \ 83 | git $(CI_AUTHOR) commit -m "Automatic update of .gitignore"; \ 84 | fi 85 | 86 | update-readme: auto_update | update-gitignore 87 | $(call regenerate,README.md,[ "$$(grep -m 1 '^$$/\1/')" = off ]) 88 | 89 | update-codeowners: | update-gitignore 90 | $(call regenerate,.github/CODEOWNERS,[ -f .github/CODEOWNERS -a "$$(head -1 .github/CODEOWNERS 2>/dev/null)" != "# Automatically generated CODEOWNERS" ]) 91 | 92 | # We only need to copy over the rules that include and setup main.mk. 93 | # This keeps anything above the include where it is and moves everything else below these two things. 94 | # There is a tricky part in suppressing any blank line after `include <...>/main.mk` 95 | # when preserving existing lines. 96 | # This uses 'x' and 'n' to get the next line, then conditionally prints a non-blank line. 97 | update-makefile: Makefile $(LIBDIR)/template/Makefile | update-gitignore 98 | @x=$$(mktemp);y=$$(mktemp); mv $< "$$x"; \ 99 | sed -n -e '1,/^include.*main\.mk$$/{/^include.*main\.mk$$/{x;n;/^$$/!p;};d;};/main\.mk:$$/,/^$$/d;p' "$$x" > "$$y"; \ 100 | sed -n -e '1,/^include.*main\.mk$$/{x;1d;p;}' "$$x" > $<; \ 101 | sed -n -e '/^include.*main\.mk$$/,/^$$/p;/main\.mk:$$/,/^$$/p' $(LIBDIR)/template/Makefile >> $<; \ 102 | [ $$(cat "$$y" | wc -l) -gt 0 ] && echo >> $<; cat "$$y" >> $<; \ 103 | rm -f "$$x" "$$y" 104 | @if ! git diff --quiet @ $<; then \ 105 | git add $<; \ 106 | git $(CI_AUTHOR) commit -m "Automatic update of $<"; \ 107 | fi 108 | 109 | 110 | ifneq (true,$(CI)) 111 | UPDATE_CI := update-ci 112 | else 113 | UPDATE_CI := 114 | endif 115 | update-files: auto_update update-codeowners update-gitignore update-makefile update-readme $(UPDATE_CI) 116 | 117 | update-venue: auto_update $(drafts_source) 118 | ./$(LIBDIR)/update-venue.sh $(GITHUB_USER) $(GITHUB_REPO) $(drafts_source) 119 | @if ! git diff --quiet @ $(filter-out auto_update,$^); then \ 120 | git add $(filter-out auto_update,$^); \ 121 | git $(CI_AUTHOR) commit -m "Automatic update of venue information"; \ 122 | fi 123 | 124 | update-workflows: update-ci 125 | update-ci: auto_update | update-gitignore 126 | $(call regenerate,$(addprefix .github/workflows/,ghpages.yml publish.yml archive.yml update.yml)) 127 | -------------------------------------------------------------------------------- /upload.mk: -------------------------------------------------------------------------------- 1 | ifneq (,$(CIRCLE_TAG)$(TRAVIS_TAG)) 2 | draft_releases := $(CIRCLE_TAG)$(TRAVIS_TAG) 3 | else 4 | draft_releases := $(shell git tag --list --points-at HEAD 'draft-*') 5 | endif 6 | 7 | uploads := $(addprefix $(VERSIONED)/.,$(addsuffix .upload,$(draft_releases))) 8 | 9 | ifneq (,$(TRAVIS)) 10 | # Ensure that we build the XML files needed for upload during the main build. 11 | latest:: $(addsuffix .xml,$(draft_releases)) 12 | endif 13 | 14 | .PHONY: upload publish 15 | publish: upload 16 | ifeq (,$(MAKE_TRACE)) 17 | upload: $(uploads) 18 | else 19 | upload: 20 | endif 21 | @[ -n "$(uploads)" ] || ! echo "error: No files to upload. Did you use \`git tag -a\`?" 22 | ifneq (,$(MAKE_TRACE)) 23 | @$(call MAKE_TRACE,$(uploads)) 24 | endif 25 | 26 | .%.upload: %.xml 27 | @set -e$(if $(filter-out false,$(VERBOSE)),x,); tag="$(notdir $(basename $<))"; \ 28 | email="$$($(LIBDIR)/get-email.sh "$$tag" "$<")"; \ 29 | [ -z "$$email" ] && exit 1; \ 30 | replaces() { \ 31 | [ "$${1##*-}" = "00" ] || return; \ 32 | file="$$(git ls-files "$${1%-[0-9][0-9]}.*")"; \ 33 | for last in $$(git log --follow --name-only --format=format: -- "$${file%-[0-9][0-9]}" | \ 34 | sed -e '/^$$/d' | grep -v draft-todo-yourname-protocol | cut -f 2 | uniq | tail +2); do \ 35 | if [ -n "$$(git tag -l "$${last%.*}-[0-9][0-9]")" ]; then \ 36 | echo -F; echo "replaces=$${last%.*}"; break; \ 37 | fi; \ 38 | done; \ 39 | }; \ 40 | $(if $(TRACE_FILE),$(trace) $< -s upload-request )$(curl) -D "$@" \ 41 | -F "user=$$email" -F "xml=@$<" $$(replaces "$$tag") \ 42 | "$(DATATRACKER_UPLOAD_URL)" && echo && \ 43 | (head -1 "$@" | grep -q '^HTTP/\S\S* 20[01]\b' || $(trace) $< -s upload-result ! cat "$@" 1>&2) 44 | 45 | # This ignomonious hack ensures that we can catch missing files properly. 46 | .%.upload: 47 | @if $(MAKE) "$*".xml; then \ 48 | $(MAKE) "$@"; \ 49 | else \ 50 | t="$*"; t="$${t##*/}"; \ 51 | echo "============================================================================"; \ 52 | echo "Warning: A source file for '$$t' does not exist."; \ 53 | echo; \ 54 | if [ $(words $(drafts)) -eq 1 ]; then \ 55 | echo " Maybe you meant to name the label '$(drafts)-$${t##*-}' instead."; \ 56 | else \ 57 | echo " Maybe you meant one of the following instead:"; \ 58 | for d in $(drafts_source); do \ 59 | echo " $${d%.*}-$${t##*-} (from $$d)"; \ 60 | done; \ 61 | fi; \ 62 | echo; \ 63 | echo "If you applied this tag in error, remove it before adding another tag:"; \ 64 | echo " git tag -d '$$t'"; \ 65 | echo " git push -f $(GIT_REMOTE) ':$$t'"; \ 66 | echo; \ 67 | echo "============================================================================"; \ 68 | false; \ 69 | fi 70 | -------------------------------------------------------------------------------- /venv.mk: -------------------------------------------------------------------------------- 1 | # 2 | # SEAMLESSLY MANAGE PYTHON VIRTUAL ENVIRONMENT WITH A MAKEFILE 3 | # 4 | # https://github.com/sio/Makefile.venv v2022.05.11-dev 5 | # 6 | # 7 | # Insert `include Makefile.venv` at the bottom of your Makefile to enable these 8 | # rules. 9 | # 10 | # When writing your Makefile use '$(VENV)/python' to refer to the Python 11 | # interpreter within virtual environment and '$(VENV)/executablename' for any 12 | # other executable in venv. 13 | # 14 | # This Makefile provides the following targets: 15 | # venv 16 | # Use this as a dependency for any target that requires virtual 17 | # environment to be created and configured 18 | # python, ipython 19 | # Use these to launch interactive Python shell within virtual environment 20 | # shell, bash, zsh 21 | # Launch interactive command line shell. "shell" target launches the 22 | # default shell Makefile executes its rules in (usually /bin/sh). 23 | # "bash" and "zsh" can be used to refer to the specific desired shell. 24 | # show-venv 25 | # Show versions of Python and pip, and the path to the virtual environment 26 | # clean-venv 27 | # Remove virtual environment 28 | # $(VENV)/executable_name 29 | # Install `executable_name` with pip. Only packages with names matching 30 | # the name of the corresponding executable are supported. 31 | # Use this as a lightweight mechanism for development dependencies 32 | # tracking. E.g. for one-off tools that are not required in every 33 | # developer's environment, therefore are not included into 34 | # requirements.txt or setup.py. 35 | # Note: 36 | # Rules using such target or dependency MUST be defined below 37 | # `include` directive to make use of correct $(VENV) value. 38 | # Example: 39 | # codestyle: $(VENV)/pyflakes 40 | # $(VENV)/pyflakes . 41 | # See `ipython` target below for another example. 42 | # 43 | # This Makefile can be configured via following variables: 44 | # PY 45 | # Command name for system Python interpreter. It is used only initially to 46 | # create the virtual environment 47 | # Default: python3 48 | # REQUIREMENTS_TXT 49 | # Space separated list of paths to requirements.txt files. 50 | # Paths are resolved relative to current working directory. 51 | # Default: requirements.txt 52 | # 53 | # Non-existent files are treated as hard dependencies, 54 | # recipes for creating such files must be provided by the main Makefile. 55 | # Providing empty value (REQUIREMENTS_TXT=) turns off processing of 56 | # requirements.txt even when the file exists. 57 | # SETUP_PY 58 | # Space separated list of paths to setup.py files. 59 | # Corresponding packages will be installed into venv in editable mode 60 | # along with all their dependencies 61 | # Default: setup.py 62 | # 63 | # Non-existent and empty values are treated in the same way as for REQUIREMENTS_TXT. 64 | # WORKDIR 65 | # Parent directory for the virtual environment. 66 | # Default: current working directory. 67 | # VENVDIR 68 | # Python virtual environment directory. 69 | # Default: $(WORKDIR)/.venv 70 | # 71 | # This Makefile was written for GNU Make and may not work with other make 72 | # implementations. 73 | # 74 | # 75 | # Copyright (c) 2019-2020 Vitaly Potyarkin 76 | # 77 | # Licensed under the Apache License, Version 2.0 78 | # 79 | # 80 | 81 | 82 | # 83 | # Configuration variables 84 | # 85 | 86 | WORKDIR?=. 87 | VENVDIR?=$(WORKDIR)/.venv 88 | REQUIREMENTS_TXT?=$(wildcard requirements.txt) # Multiple paths are supported (space separated) 89 | SETUP_PY?=$(wildcard setup.py) # Multiple paths are supported (space separated) 90 | SETUP_CFG?=$(foreach s,$(SETUP_PY),$(wildcard $(patsubst %setup.py,%setup.cfg,$(s)))) 91 | MARKER=.initialized-with-Makefile.venv 92 | 93 | 94 | # 95 | # Python interpreter detection 96 | # 97 | 98 | _PY_AUTODETECT_MSG=Detected Python interpreter: $(PY). Use PY environment variable to override 99 | 100 | ifeq (ok,$(shell test -e /dev/null 2>&1 && echo ok)) 101 | NULL_STDERR=2>/dev/null 102 | else 103 | NULL_STDERR=2>NUL 104 | endif 105 | 106 | ifndef PY 107 | _PY_OPTION:=python3 108 | ifeq (ok,$(shell $(_PY_OPTION) -c "print('ok')" $(NULL_STDERR))) 109 | PY=$(_PY_OPTION) 110 | endif 111 | endif 112 | 113 | ifndef PY 114 | _PY_OPTION:=$(VENVDIR)/bin/python 115 | ifeq (ok,$(shell $(_PY_OPTION) -c "print('ok')" $(NULL_STDERR))) 116 | PY=$(_PY_OPTION) 117 | $(info $(_PY_AUTODETECT_MSG)) 118 | endif 119 | endif 120 | 121 | ifndef PY 122 | _PY_OPTION:=$(subst /,\,$(VENVDIR)/Scripts/python) 123 | ifeq (ok,$(shell $(_PY_OPTION) -c "print('ok')" $(NULL_STDERR))) 124 | PY=$(_PY_OPTION) 125 | $(info $(_PY_AUTODETECT_MSG)) 126 | endif 127 | endif 128 | 129 | ifndef PY 130 | _PY_OPTION:=py -3 131 | ifeq (ok,$(shell $(_PY_OPTION) -c "print('ok')" $(NULL_STDERR))) 132 | PY=$(_PY_OPTION) 133 | $(info $(_PY_AUTODETECT_MSG)) 134 | endif 135 | endif 136 | 137 | ifndef PY 138 | _PY_OPTION:=python 139 | ifeq (ok,$(shell $(_PY_OPTION) -c "print('ok')" $(NULL_STDERR))) 140 | PY=$(_PY_OPTION) 141 | $(info $(_PY_AUTODETECT_MSG)) 142 | endif 143 | endif 144 | 145 | ifndef PY 146 | define _PY_AUTODETECT_ERR 147 | Could not detect Python interpreter automatically. 148 | Please specify path to interpreter via PY environment variable. 149 | endef 150 | $(error $(_PY_AUTODETECT_ERR)) 151 | endif 152 | 153 | 154 | # 155 | # Internal variable resolution 156 | # 157 | 158 | VENV=$(VENVDIR)/bin 159 | EXE= 160 | # Detect windows 161 | ifeq (win32,$(shell $(PY) -c "import __future__, sys; print(sys.platform)")) 162 | VENV=$(VENVDIR)/Scripts 163 | EXE=.exe 164 | endif 165 | 166 | touch=touch $(1) 167 | ifeq (,$(shell command -v touch $(NULL_STDERR))) 168 | # https://ss64.com/nt/touch.html 169 | touch=type nul >> $(subst /,\,$(1)) && copy /y /b $(subst /,\,$(1))+,, $(subst /,\,$(1)) 170 | endif 171 | 172 | RM?=rm -f 173 | ifeq (,$(shell command -v $(firstword $(RM)) $(NULL_STDERR))) 174 | RMDIR:=rd /s /q 175 | else 176 | RMDIR:=$(RM) -r 177 | endif 178 | 179 | 180 | # 181 | # Virtual environment 182 | # 183 | 184 | .PHONY: venv 185 | venv: $(VENV)/$(MARKER) 186 | 187 | .PHONY: clean-venv 188 | clean-venv: 189 | -$(RMDIR) "$(VENVDIR)" 190 | 191 | .PHONY: show-venv 192 | show-venv: venv 193 | @"$(VENV)/python" -c "import sys; print('Python ' + sys.version.replace('\n',''))" 194 | @"$(VENV)/pip" --version 195 | @echo venv: $(VENVDIR) 196 | 197 | .PHONY: debug-venv 198 | debug-venv: 199 | @echo "PATH (Shell)=$$PATH" 200 | @$(MAKE) --version 201 | $(info PATH (GNU Make)="$(PATH)") 202 | $(info SHELL="$(SHELL)") 203 | $(info PY="$(PY)") 204 | $(info REQUIREMENTS_TXT="$(REQUIREMENTS_TXT)") 205 | $(info SETUP_PY="$(SETUP_PY)") 206 | $(info SETUP_CFG="$(SETUP_CFG)") 207 | $(info VENVDIR="$(VENVDIR)") 208 | $(info VENVDEPENDS="$(VENVDEPENDS)") 209 | $(info WORKDIR="$(WORKDIR)") 210 | 211 | 212 | # 213 | # Dependencies 214 | # 215 | 216 | ifneq ($(strip $(REQUIREMENTS_TXT)),) 217 | VENVDEPENDS+=$(REQUIREMENTS_TXT) 218 | endif 219 | 220 | ifneq ($(strip $(SETUP_PY)),) 221 | VENVDEPENDS+=$(SETUP_PY) 222 | endif 223 | ifneq ($(strip $(SETUP_CFG)),) 224 | VENVDEPENDS+=$(SETUP_CFG) 225 | endif 226 | 227 | $(VENV): 228 | $(PY) -m venv $(VENVDIR) 229 | "$(VENV)/python" -m pip install --no-user $(no-cache-dir) --upgrade pip setuptools wheel 230 | 231 | $(VENV)/$(MARKER): $(VENVDEPENDS) | $(VENV) 232 | ifneq ($(strip $(REQUIREMENTS_TXT)),) 233 | "$(VENV)/python" -m pip install --no-user $(no-cache-dir) $(foreach path,$(REQUIREMENTS_TXT),-r $(path)) 234 | endif 235 | ifneq ($(strip $(SETUP_PY)),) 236 | "$(VENV)/python" -m pip install --no-user $(no-cache-dir) $(foreach path,$(SETUP_PY),-e $(dir $(path))) 237 | endif 238 | $(call touch,$(VENV)/$(MARKER)) 239 | 240 | 241 | # 242 | # Interactive shells 243 | # 244 | 245 | .PHONY: python 246 | python: venv 247 | exec "$(VENV)/python" 248 | 249 | .PHONY: ipython 250 | ipython: $(VENV)/ipython 251 | exec "$(VENV)/ipython" 252 | 253 | .PHONY: shell 254 | shell: venv 255 | . "$(VENV)/activate" && exec $(notdir $(SHELL)) 256 | 257 | .PHONY: bash zsh 258 | bash zsh: venv 259 | . "$(VENV)/activate" && exec $@ 260 | 261 | 262 | # 263 | # Commandline tools (wildcard rule, executable name must match package name) 264 | # 265 | 266 | ifneq ($(EXE),) 267 | $(VENV)/%: $(VENV)/%$(EXE) ; 268 | .PHONY: $(VENV)/% 269 | .PRECIOUS: $(VENV)/%$(EXE) 270 | endif 271 | 272 | $(VENV)/%$(EXE): $(VENV)/$(MARKER) 273 | "$(VENV)/python" -m pip install --no-user $(no-cache-dir) --upgrade $* 274 | $(call touch,$@) 275 | -------------------------------------------------------------------------------- /wg-meta.sh: -------------------------------------------------------------------------------- 1 | # This script isn't run directly. 2 | 3 | # Populates values for: 4 | # wg_name, wg_type, wg_mail, and wg_arch for the identified working group. 5 | wgmeta() { 6 | wg="$1" 7 | [[ -n "$wg" ]] || return 2 8 | api="https://datatracker.ietf.org" 9 | wgmeta="$api/api/v1/group/group/?format=xml&acronym=$wg" 10 | tmp=$(mktemp) 11 | trap 'rm -f $tmp' EXIT 12 | if hash xmllint && curl -SsLf "$wgmeta" -o "$tmp" && 13 | [[ "$(xmllint --xpath '/response/meta/total_count/text()' "$tmp")" == "1" ]]; then 14 | wg_area_url="$(xmllint --xpath '/response/objects/object[1]/parent/text()' "$tmp")" 15 | wg_area="$(curl -Ssf "${api}${wg_area_url}?format=xml" | \ 16 | xmllint --xpath '/object/name/text()' /dev/stdin)" 17 | wg_area="${wg_area% Area}" 18 | wg_name="$(xmllint --xpath '/response/objects/object[1]/name/text()' "$tmp")" 19 | wg_type_url="$(xmllint --xpath '/response/objects/object[1]/type/text()' "$tmp")" 20 | wg_type="$(curl -Ssf "${api}${wg_type_url}?format=xml" | \ 21 | xmllint --xpath '/object/verbose_name/text()' /dev/stdin)" 22 | wg_mail="$(xmllint --xpath '/response/objects/object[1]/list_email/text()' "$tmp")" 23 | wg_arch="$(xmllint --xpath '/response/objects/object[1]/list_archive/text()' "$tmp")" 24 | wg_arch="${wg_arch/#http:/https:}" # Upgrade URLs to https 25 | rm -f "$tmp" 26 | trap - EXIT 27 | else 28 | rm -f "$tmp" 29 | trap - EXIT 30 | return 1 31 | fi 32 | } 33 | --------------------------------------------------------------------------------