Cut out legacy build scripts
parent
726be04841
commit
8d73a17f5b
@ -1,3 +0,0 @@
|
|||||||
continuserv proactively re-generates the spec on filesystem changes, and serves
|
|
||||||
it over HTTP. For notes on using it, see [the main
|
|
||||||
readme](../../README.rst#continuserv).
|
|
@ -1,15 +0,0 @@
|
|||||||
<head>
|
|
||||||
<script>
|
|
||||||
window.onload = function() {
|
|
||||||
var url = new URL(window.location);
|
|
||||||
url.pathname += "api-docs.json";
|
|
||||||
var newLoc = "http://petstore.swagger.io/?url=" + encodeURIComponent(url);
|
|
||||||
document.getElementById("apidocs").href = newLoc;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body><ul>
|
|
||||||
<li><a id="apidocs">api docs</a></li>
|
|
||||||
<li><a href="index.html">spec</a></li>
|
|
||||||
</ul>
|
|
||||||
</body>
|
|
@ -1,274 +0,0 @@
|
|||||||
// continuserv proactively re-generates the spec on filesystem changes, and serves it over HTTP.
|
|
||||||
// It will always serve the most recent version of the spec, and may block an HTTP request until regeneration is finished.
|
|
||||||
// It does not currently pre-empt stale generations, but will block until they are complete.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
fsnotify "gopkg.in/fsnotify/fsnotify.v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
port = flag.Int("port", 8000, "Port on which to serve HTTP")
|
|
||||||
|
|
||||||
mu sync.Mutex // Prevent multiple updates in parallel.
|
|
||||||
toServe atomic.Value // Always contains a bytesOrErr. May be stale unless wg is zero.
|
|
||||||
|
|
||||||
wgMu sync.Mutex // Prevent multiple calls to wg.Wait() or wg.Add(positive number) in parallel.
|
|
||||||
wg sync.WaitGroup // Indicates how many updates are pending.
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
w, err := fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error making watcher: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error getting wd: %v", err)
|
|
||||||
}
|
|
||||||
for ; !exists(path.Join(dir, ".git")); dir = path.Dir(dir) {
|
|
||||||
if dir == "/" {
|
|
||||||
log.Fatalf("Could not find git root")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
walker := makeWalker(dir, w)
|
|
||||||
paths := []string{"api", "changelogs", "event-schemas", "scripts",
|
|
||||||
"specification", "schemas", "data-definitions"}
|
|
||||||
|
|
||||||
for _, p := range paths {
|
|
||||||
filepath.Walk(path.Join(dir, p), walker)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
populateOnce(dir)
|
|
||||||
|
|
||||||
ch := make(chan struct{}, 100) // Buffered to ensure we can multiple-increment wg for pending writes
|
|
||||||
go doPopulate(ch, dir)
|
|
||||||
|
|
||||||
go watchFS(ch, w)
|
|
||||||
fmt.Printf("Listening on port %d\n", *port)
|
|
||||||
http.HandleFunc("/", serve)
|
|
||||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func watchFS(ch chan struct{}, w *fsnotify.Watcher) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case e := <-w.Events:
|
|
||||||
if filter(e) {
|
|
||||||
fmt.Printf("Noticed change to %s, re-generating spec\n", e.Name)
|
|
||||||
ch <- struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeWalker(base string, w *fsnotify.Watcher) filepath.WalkFunc {
|
|
||||||
return func(path string, i os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error walking: %v", err)
|
|
||||||
}
|
|
||||||
if !i.IsDir() {
|
|
||||||
// we set watches on directories, not files
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rel, err := filepath.Rel(base, path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to get relative path of %s: %v", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize slashes
|
|
||||||
rel = filepath.ToSlash(rel)
|
|
||||||
|
|
||||||
// skip a few things that we know don't form part of the spec
|
|
||||||
if rel == "api/node_modules" ||
|
|
||||||
rel == "scripts/gen" ||
|
|
||||||
rel == "scripts/tmp" {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
// log.Printf("Adding watch on %s", path)
|
|
||||||
if err := w.Add(path); err != nil {
|
|
||||||
log.Fatalf("Failed to add watch on %s: %v", path, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if event should trigger re-population
|
|
||||||
func filter(e fsnotify.Event) bool {
|
|
||||||
// vim is *really* noisy about how it writes files
|
|
||||||
if e.Op != fsnotify.Write {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
_, fname := filepath.Split(e.Name)
|
|
||||||
|
|
||||||
// Avoid some temp files that vim or emacs writes
|
|
||||||
if strings.HasSuffix(e.Name, "~") || strings.HasSuffix(e.Name, ".swp") || strings.HasPrefix(fname, ".") ||
|
|
||||||
(strings.HasPrefix(fname, "#") && strings.HasSuffix(fname, "#")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forcefully ignore directories we don't care about (Windows, at least, tries to notify about some directories)
|
|
||||||
filePath := filepath.ToSlash(e.Name) // normalize slashes
|
|
||||||
if strings.Contains(filePath, "/scripts/tmp") ||
|
|
||||||
strings.Contains(filePath, "/scripts/gen") ||
|
|
||||||
strings.Contains(filePath, "/api/node_modules") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func serve(w http.ResponseWriter, req *http.Request) {
|
|
||||||
wgMu.Lock()
|
|
||||||
wg.Wait()
|
|
||||||
wgMu.Unlock()
|
|
||||||
|
|
||||||
m := toServe.Load().(bytesOrErr)
|
|
||||||
if m.err != nil {
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
w.Write([]byte(m.err.Error()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ok := true
|
|
||||||
var b []byte
|
|
||||||
|
|
||||||
file := req.URL.Path
|
|
||||||
if file[0] == '/' {
|
|
||||||
file = file[1:]
|
|
||||||
}
|
|
||||||
b, ok = m.bytes[filepath.FromSlash(file)] // de-normalize slashes
|
|
||||||
|
|
||||||
if ok && file == "api-docs.json" {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
w.Write([]byte(b))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
w.WriteHeader(404)
|
|
||||||
w.Write([]byte("Not found"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func generate(dir string) (map[string][]byte, error) {
|
|
||||||
cmd := exec.Command("python", "gendoc.py")
|
|
||||||
cmd.Dir = path.Join(dir, "scripts")
|
|
||||||
var b bytes.Buffer
|
|
||||||
cmd.Stderr = &b
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating spec: %v\nOutput from gendoc:\n%v", err, b.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// cheekily dump the swagger docs into the gen directory so that it is
|
|
||||||
// easy to serve
|
|
||||||
cmd = exec.Command("python", "dump-swagger.py", "-o", "gen/api-docs.json")
|
|
||||||
cmd.Dir = path.Join(dir, "scripts")
|
|
||||||
cmd.Stderr = &b
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return nil, fmt.Errorf("error generating api docs: %v\nOutput from dump-swagger:\n%v", err, b.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
files := make(map[string][]byte)
|
|
||||||
base := path.Join(dir, "scripts", "gen")
|
|
||||||
walker := func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
rel, err := filepath.Rel(base, path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Failed to get relative path of %s: %v", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
files[rel] = bytes
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := filepath.Walk(base, walker); err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading spec: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the special index
|
|
||||||
indexpath := path.Join(dir, "scripts", "continuserv", "index.html")
|
|
||||||
bytes, err := ioutil.ReadFile(indexpath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading index: %v", err)
|
|
||||||
}
|
|
||||||
files[""] = bytes
|
|
||||||
|
|
||||||
return files, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func populateOnce(dir string) {
|
|
||||||
defer wg.Done()
|
|
||||||
mu.Lock()
|
|
||||||
defer mu.Unlock()
|
|
||||||
|
|
||||||
files, err := generate(dir)
|
|
||||||
toServe.Store(bytesOrErr{files, err})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doPopulate(ch chan struct{}, dir string) {
|
|
||||||
var pending int
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ch:
|
|
||||||
if pending == 0 {
|
|
||||||
wgMu.Lock()
|
|
||||||
wg.Add(1)
|
|
||||||
wgMu.Unlock()
|
|
||||||
}
|
|
||||||
pending++
|
|
||||||
case <-time.After(10 * time.Millisecond):
|
|
||||||
if pending > 0 {
|
|
||||||
pending = 0
|
|
||||||
populateOnce(dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func exists(path string) bool {
|
|
||||||
_, err := os.Stat(path)
|
|
||||||
return !os.IsNotExist(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
type bytesOrErr struct {
|
|
||||||
bytes map[string][]byte // filename -> contents
|
|
||||||
err error
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
with import <nixpkgs> {};
|
|
||||||
|
|
||||||
(python.buildEnv.override {
|
|
||||||
extraLibs = with pythonPackages;
|
|
||||||
[ docutils pyyaml jinja2 pygments ];
|
|
||||||
}).env
|
|
@ -1,561 +0,0 @@
|
|||||||
#! /usr/bin/env python
|
|
||||||
|
|
||||||
# Copyright 2016 OpenMarket Ltd
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from argparse import ArgumentParser
|
|
||||||
from docutils.core import publish_file
|
|
||||||
import copy
|
|
||||||
import fileinput
|
|
||||||
import glob
|
|
||||||
import os
|
|
||||||
import os.path
|
|
||||||
import re
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
docs_dir = os.path.dirname(script_dir)
|
|
||||||
spec_dir = os.path.join(docs_dir, "specification")
|
|
||||||
tmp_dir = os.path.join(script_dir, "tmp")
|
|
||||||
changelog_dir = os.path.join(docs_dir, "changelogs")
|
|
||||||
|
|
||||||
VERBOSE = False
|
|
||||||
|
|
||||||
"""
|
|
||||||
Read a RST file and replace titles with a different title level if required.
|
|
||||||
Args:
|
|
||||||
filename: The name of the file being read (for debugging)
|
|
||||||
file_stream: The open file stream to read from.
|
|
||||||
title_level: The integer which determines the offset to *start* from.
|
|
||||||
title_styles: An array of characters detailing the right title styles to use
|
|
||||||
e.g. ["=", "-", "~", "+"]
|
|
||||||
Returns:
|
|
||||||
string: The file contents with titles adjusted.
|
|
||||||
Example:
|
|
||||||
Assume title_styles = ["=", "-", "~", "+"], title_level = 1, and the file
|
|
||||||
when read line-by-line encounters the titles "===", "---", "---", "===", "---".
|
|
||||||
This function will bump every title encountered down a sub-heading e.g.
|
|
||||||
"=" to "-" and "-" to "~" because title_level = 1, so the output would be
|
|
||||||
"---", "~~~", "~~~", "---", "~~~". There is no bumping "up" a title level.
|
|
||||||
"""
|
|
||||||
def load_with_adjusted_titles(filename, file_stream, title_level, title_styles):
|
|
||||||
rst_lines = []
|
|
||||||
|
|
||||||
prev_line_title_level = 0 # We expect the file to start with '=' titles
|
|
||||||
file_offset = None
|
|
||||||
prev_non_title_line = None
|
|
||||||
for i, line in enumerate(file_stream):
|
|
||||||
if (prev_non_title_line is None
|
|
||||||
or not is_title_line(prev_non_title_line, line, title_styles)
|
|
||||||
):
|
|
||||||
rst_lines.append(line)
|
|
||||||
prev_non_title_line = line
|
|
||||||
continue
|
|
||||||
|
|
||||||
line_title_style = line[0]
|
|
||||||
line_title_level = title_styles.index(line_title_style)
|
|
||||||
|
|
||||||
# Not all files will start with "===" and we should be flexible enough
|
|
||||||
# to allow that. The first title we encounter sets the "file offset"
|
|
||||||
# which is added to the title_level desired.
|
|
||||||
if file_offset is None:
|
|
||||||
file_offset = line_title_level
|
|
||||||
if file_offset != 0:
|
|
||||||
logv((" WARNING: %s starts with a title style of '%s' but '%s' " +
|
|
||||||
"is preferable.") % (filename, line_title_style, title_styles[0]))
|
|
||||||
|
|
||||||
# Sanity checks: Make sure that this file is obeying the title levels
|
|
||||||
# specified and bail if it isn't.
|
|
||||||
# The file is allowed to go 1 deeper or any number shallower
|
|
||||||
if prev_line_title_level - line_title_level < -1:
|
|
||||||
raise Exception(
|
|
||||||
("File '%s' line '%s' has a title " +
|
|
||||||
"style '%s' which doesn't match one of the " +
|
|
||||||
"allowed title styles of %s because the " +
|
|
||||||
"title level before this line was '%s'") %
|
|
||||||
(filename, (i + 1), line_title_style, title_styles,
|
|
||||||
title_styles[prev_line_title_level])
|
|
||||||
)
|
|
||||||
prev_line_title_level = line_title_level
|
|
||||||
|
|
||||||
adjusted_level = (
|
|
||||||
title_level + line_title_level - file_offset
|
|
||||||
)
|
|
||||||
|
|
||||||
# Sanity check: Make sure we can bump down the title and we aren't at the
|
|
||||||
# lowest level already
|
|
||||||
if adjusted_level >= len(title_styles):
|
|
||||||
raise Exception(
|
|
||||||
("Files '%s' line '%s' has a sub-title level too low and it " +
|
|
||||||
"cannot be adjusted to fit. You can add another level to the " +
|
|
||||||
"'title_styles' key in targets.yaml to fix this.") %
|
|
||||||
(filename, (i + 1))
|
|
||||||
)
|
|
||||||
|
|
||||||
if adjusted_level == line_title_level:
|
|
||||||
# no changes required
|
|
||||||
rst_lines.append(line)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Adjusting line levels
|
|
||||||
logv(
|
|
||||||
"File: %s Adjusting %s to %s because file_offset=%s title_offset=%s" %
|
|
||||||
(filename, line_title_style, title_styles[adjusted_level],
|
|
||||||
file_offset, title_level)
|
|
||||||
)
|
|
||||||
rst_lines.append(line.replace(
|
|
||||||
line_title_style,
|
|
||||||
title_styles[adjusted_level]
|
|
||||||
))
|
|
||||||
|
|
||||||
return "".join(rst_lines)
|
|
||||||
|
|
||||||
|
|
||||||
def is_title_line(prev_line, line, title_styles):
|
|
||||||
# The title underline must match at a minimum the length of the title
|
|
||||||
if len(prev_line) > len(line):
|
|
||||||
return False
|
|
||||||
|
|
||||||
line = line.rstrip()
|
|
||||||
|
|
||||||
# must be at least 3 chars long
|
|
||||||
if len(line) < 3:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# must start with a title char
|
|
||||||
title_char = line[0]
|
|
||||||
if title_char not in title_styles:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# all characters must be the same
|
|
||||||
for char in line[1:]:
|
|
||||||
if char != title_char:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# looks like a title line
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def get_rst(file_info, title_level, title_styles, spec_dir, adjust_titles):
|
|
||||||
# string are file paths to RST blobs
|
|
||||||
if isinstance(file_info, str):
|
|
||||||
log("%s %s" % (">" * (1 + title_level), file_info))
|
|
||||||
with open(os.path.join(spec_dir, file_info), "r", encoding="utf-8") as f:
|
|
||||||
rst = None
|
|
||||||
if adjust_titles:
|
|
||||||
rst = load_with_adjusted_titles(
|
|
||||||
file_info, f, title_level, title_styles
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
rst = f.read()
|
|
||||||
|
|
||||||
rst += "\n\n"
|
|
||||||
return rst
|
|
||||||
# dicts look like {0: filepath, 1: filepath} where the key is the title level
|
|
||||||
elif isinstance(file_info, dict):
|
|
||||||
levels = sorted(file_info.keys())
|
|
||||||
rst = []
|
|
||||||
for l in levels:
|
|
||||||
rst.append(get_rst(file_info[l], l, title_styles, spec_dir, adjust_titles))
|
|
||||||
return "".join(rst)
|
|
||||||
# lists are multiple file paths e.g. [filepath, filepath]
|
|
||||||
elif isinstance(file_info, list):
|
|
||||||
rst = []
|
|
||||||
for f in file_info:
|
|
||||||
rst.append(get_rst(f, title_level, title_styles, spec_dir, adjust_titles))
|
|
||||||
return "".join(rst)
|
|
||||||
raise Exception(
|
|
||||||
"The following 'file' entry in this target isn't a string, list or dict. " +
|
|
||||||
"It really really should be. Entry: %s" % (file_info,)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def build_spec(target, out_filename):
|
|
||||||
log("Building templated file %s" % out_filename)
|
|
||||||
with open(out_filename, "w", encoding="utf-8") as outfile:
|
|
||||||
for file_info in target["files"]:
|
|
||||||
section = get_rst(
|
|
||||||
file_info=file_info,
|
|
||||||
title_level=0,
|
|
||||||
title_styles=target["title_styles"],
|
|
||||||
spec_dir=spec_dir,
|
|
||||||
adjust_titles=True
|
|
||||||
)
|
|
||||||
outfile.write(section)
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
Replaces relative title styles with actual title styles.
|
|
||||||
|
|
||||||
The templating system has no idea what the right title style is when it produces
|
|
||||||
RST because it depends on the build target. As a result, it uses relative title
|
|
||||||
styles defined in targets.yaml to say "down a level, up a level, same level".
|
|
||||||
|
|
||||||
This function replaces these relative titles with actual title styles from the
|
|
||||||
array in targets.yaml.
|
|
||||||
"""
|
|
||||||
def fix_relative_titles(target, filename, out_filename):
|
|
||||||
log("Fix relative titles, %s -> %s" % (filename, out_filename))
|
|
||||||
title_styles = target["title_styles"]
|
|
||||||
relative_title_chars = [
|
|
||||||
target["relative_title_styles"]["subtitle"],
|
|
||||||
target["relative_title_styles"]["sametitle"],
|
|
||||||
target["relative_title_styles"]["supertitle"]
|
|
||||||
]
|
|
||||||
relative_title_matcher = re.compile(
|
|
||||||
"^[" + re.escape("".join(relative_title_chars)) + "]{3,}$"
|
|
||||||
)
|
|
||||||
title_matcher = re.compile(
|
|
||||||
"^[" + re.escape("".join(title_styles)) + "]{3,}$"
|
|
||||||
)
|
|
||||||
current_title_style = None
|
|
||||||
with open(filename, "r", encoding="utf-8") as infile:
|
|
||||||
with open(out_filename, "w", encoding="utf-8") as outfile:
|
|
||||||
for line in infile.readlines():
|
|
||||||
if not relative_title_matcher.match(line):
|
|
||||||
if title_matcher.match(line):
|
|
||||||
current_title_style = line[0]
|
|
||||||
outfile.write(line)
|
|
||||||
continue
|
|
||||||
line_char = line[0]
|
|
||||||
replacement_char = None
|
|
||||||
current_title_level = title_styles.index(current_title_style)
|
|
||||||
if line_char == target["relative_title_styles"]["subtitle"]:
|
|
||||||
if (current_title_level + 1) == len(title_styles):
|
|
||||||
raise Exception(
|
|
||||||
"Encountered sub-title line style but we can't go " +
|
|
||||||
"any lower."
|
|
||||||
)
|
|
||||||
replacement_char = title_styles[current_title_level + 1]
|
|
||||||
elif line_char == target["relative_title_styles"]["sametitle"]:
|
|
||||||
replacement_char = title_styles[current_title_level]
|
|
||||||
elif line_char == target["relative_title_styles"]["supertitle"]:
|
|
||||||
if (current_title_level - 1) < 0:
|
|
||||||
raise Exception(
|
|
||||||
"Encountered super-title line style but we can't go " +
|
|
||||||
"any higher."
|
|
||||||
)
|
|
||||||
replacement_char = title_styles[current_title_level - 1]
|
|
||||||
else:
|
|
||||||
raise Exception(
|
|
||||||
"Unknown relative line char %s" % (line_char,)
|
|
||||||
)
|
|
||||||
|
|
||||||
outfile.write(
|
|
||||||
line.replace(line_char, replacement_char)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def rst2html(i, o, stylesheets):
|
|
||||||
log("rst2html %s -> %s" % (i, o))
|
|
||||||
with open(i, "r", encoding="utf-8") as in_file:
|
|
||||||
with open(o, "w", encoding="utf-8") as out_file:
|
|
||||||
publish_file(
|
|
||||||
source=in_file,
|
|
||||||
destination=out_file,
|
|
||||||
reader_name="standalone",
|
|
||||||
parser_name="restructuredtext",
|
|
||||||
writer_name="html",
|
|
||||||
settings_overrides={
|
|
||||||
"stylesheet_path": stylesheets,
|
|
||||||
"syntax_highlight": "short",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def addAnchors(path):
|
|
||||||
log("add anchors %s" % path)
|
|
||||||
|
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
replacement = r'<p><a class="anchor" id="\2"></a></p>\n\1'
|
|
||||||
with open(path, "w", encoding="utf-8") as f:
|
|
||||||
for line in lines:
|
|
||||||
line = re.sub(r'(<h\d id="#?(.*?)">)', replacement, line.rstrip())
|
|
||||||
line = re.sub(r'(<div class="section" id="(.*?)">)', replacement, line.rstrip())
|
|
||||||
f.write(line + "\n")
|
|
||||||
|
|
||||||
|
|
||||||
def run_through_template(input_files, set_verbose, substitutions):
|
|
||||||
args = [
|
|
||||||
'python', script_dir+'/templating/build.py',
|
|
||||||
"-o", tmp_dir,
|
|
||||||
"-i", "matrix_templates",
|
|
||||||
]
|
|
||||||
|
|
||||||
for k, v in substitutions.items():
|
|
||||||
args.append("--substitution=%s=%s" % (k, v))
|
|
||||||
|
|
||||||
if set_verbose:
|
|
||||||
args.insert(2, "-v")
|
|
||||||
|
|
||||||
args.extend(input_files)
|
|
||||||
|
|
||||||
log("EXEC: %s" % " ".join(args))
|
|
||||||
log(" ==== build.py output ==== ")
|
|
||||||
subprocess.check_call(args)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Extract and resolve groups for the given target in the given targets listing.
|
|
||||||
Args:
|
|
||||||
all_targets (dict): The parsed YAML file containing a list of targets
|
|
||||||
target_name (str): The name of the target to extract from the listings.
|
|
||||||
Returns:
|
|
||||||
dict: Containing "filees" (a list of file paths), "relative_title_styles"
|
|
||||||
(a dict of relative style keyword to title character) and "title_styles"
|
|
||||||
(a list of characters which represent the global title style to follow,
|
|
||||||
with the top section title first, the second section second, and so on.)
|
|
||||||
"""
|
|
||||||
def get_build_target(all_targets, target_name):
|
|
||||||
build_target = {
|
|
||||||
"title_styles": [],
|
|
||||||
"relative_title_styles": {},
|
|
||||||
"files": []
|
|
||||||
}
|
|
||||||
|
|
||||||
build_target["title_styles"] = all_targets["title_styles"]
|
|
||||||
build_target["relative_title_styles"] = all_targets["relative_title_styles"]
|
|
||||||
target = all_targets["targets"].get(target_name)
|
|
||||||
if not target:
|
|
||||||
raise Exception(
|
|
||||||
"No target by the name '" + target_name + "' exists in '" +
|
|
||||||
targets_listing + "'."
|
|
||||||
)
|
|
||||||
if not isinstance(target.get("files"), list):
|
|
||||||
raise Exception(
|
|
||||||
"Found target but 'files' key is not a list."
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_group(group_id, depth):
|
|
||||||
group_name = group_id[len("group:"):]
|
|
||||||
group = all_targets.get("groups", {}).get(group_name)
|
|
||||||
if not group:
|
|
||||||
raise Exception(
|
|
||||||
"Tried to find group '%s' but it doesn't exist." % group_name
|
|
||||||
)
|
|
||||||
if not isinstance(group, list):
|
|
||||||
raise Exception(
|
|
||||||
"Expected group '%s' to be a list but it isn't." % group_name
|
|
||||||
)
|
|
||||||
# deep copy so changes to depths don't contaminate multiple uses of this group
|
|
||||||
group = copy.deepcopy(group)
|
|
||||||
# swap relative depths for absolute ones
|
|
||||||
for i, entry in enumerate(group):
|
|
||||||
if isinstance(entry, dict):
|
|
||||||
group[i] = {
|
|
||||||
(rel_depth + depth): v for (rel_depth, v) in entry.items()
|
|
||||||
}
|
|
||||||
return group
|
|
||||||
|
|
||||||
resolved_files = []
|
|
||||||
for file_entry in target["files"]:
|
|
||||||
# file_entry is a group id
|
|
||||||
if isinstance(file_entry, str) and file_entry.startswith("group:"):
|
|
||||||
group = get_group(file_entry, 0)
|
|
||||||
# The group may be resolved to a list of file entries, in which case
|
|
||||||
# we want to extend the array to insert each of them rather than
|
|
||||||
# insert the entire list as a single element (which is what append does)
|
|
||||||
if isinstance(group, list):
|
|
||||||
resolved_files.extend(group)
|
|
||||||
else:
|
|
||||||
resolved_files.append(group)
|
|
||||||
# file_entry is a dict which has more file entries as values
|
|
||||||
elif isinstance(file_entry, dict):
|
|
||||||
resolved_entry = {}
|
|
||||||
for (depth, entry) in file_entry.items():
|
|
||||||
if not isinstance(entry, str):
|
|
||||||
raise Exception(
|
|
||||||
"Double-nested depths are not supported. Entry: %s" % (file_entry,)
|
|
||||||
)
|
|
||||||
if entry.startswith("group:"):
|
|
||||||
resolved_entry[depth] = get_group(entry, depth)
|
|
||||||
else:
|
|
||||||
# map across without editing (e.g. normal file path)
|
|
||||||
resolved_entry[depth] = entry
|
|
||||||
resolved_files.append(resolved_entry)
|
|
||||||
continue
|
|
||||||
# file_entry is just a plain ol' file path
|
|
||||||
else:
|
|
||||||
resolved_files.append(file_entry)
|
|
||||||
build_target["files"] = resolved_files
|
|
||||||
return build_target
|
|
||||||
|
|
||||||
def log(line):
|
|
||||||
print("gendoc: %s" % line)
|
|
||||||
|
|
||||||
def logv(line):
|
|
||||||
if VERBOSE:
|
|
||||||
print("gendoc:V: %s" % line)
|
|
||||||
|
|
||||||
|
|
||||||
def cleanup_env():
|
|
||||||
shutil.rmtree(tmp_dir)
|
|
||||||
|
|
||||||
|
|
||||||
def mkdirp(d) :
|
|
||||||
if not os.path.exists(d):
|
|
||||||
os.makedirs(d)
|
|
||||||
|
|
||||||
|
|
||||||
def main(targets, dest_dir, keep_intermediates, substitutions):
|
|
||||||
try:
|
|
||||||
mkdirp(dest_dir)
|
|
||||||
except Exception as e:
|
|
||||||
log("Error creating destination directory '%s': %s" % (dest_dir, str(e)))
|
|
||||||
return 1
|
|
||||||
try:
|
|
||||||
mkdirp(tmp_dir)
|
|
||||||
except Exception as e:
|
|
||||||
log("Error creating temporary directory '%s': %s" % (tmp_dir, str(e)))
|
|
||||||
return 1
|
|
||||||
|
|
||||||
with open(os.path.join(spec_dir, "targets.yaml"), "r") as targ_file:
|
|
||||||
target_defs = yaml.load(targ_file.read())
|
|
||||||
|
|
||||||
if targets == ["all"]:
|
|
||||||
targets = target_defs["targets"].keys()
|
|
||||||
|
|
||||||
log("Building spec [targets=%s]" % targets)
|
|
||||||
|
|
||||||
templated_files = {} # map from target name to templated file
|
|
||||||
|
|
||||||
for target_name in targets:
|
|
||||||
templated_file = os.path.join(tmp_dir, "templated_%s.rst" % (target_name,))
|
|
||||||
|
|
||||||
target = get_build_target(target_defs, target_name)
|
|
||||||
build_spec(target=target, out_filename=templated_file)
|
|
||||||
templated_files[target_name] = templated_file
|
|
||||||
|
|
||||||
# we do all the templating at once, because it's slow
|
|
||||||
run_through_template(templated_files.values(), VERBOSE, substitutions)
|
|
||||||
|
|
||||||
stylesheets = glob.glob(os.path.join(script_dir, "css", "*.css"))
|
|
||||||
|
|
||||||
for target_name, templated_file in templated_files.items():
|
|
||||||
target = target_defs["targets"].get(target_name)
|
|
||||||
version_label = None
|
|
||||||
if target:
|
|
||||||
version_label = target.get("version_label")
|
|
||||||
if version_label:
|
|
||||||
for old, new in substitutions.items():
|
|
||||||
version_label = version_label.replace(old, new)
|
|
||||||
|
|
||||||
rst_file = os.path.join(tmp_dir, "spec_%s.rst" % (target_name,))
|
|
||||||
if version_label:
|
|
||||||
d = os.path.join(dest_dir, target_name.split('@')[0])
|
|
||||||
if not os.path.exists(d):
|
|
||||||
os.mkdir(d)
|
|
||||||
html_file = os.path.join(d, "%s.html" % version_label)
|
|
||||||
else:
|
|
||||||
html_file = os.path.join(dest_dir, "%s.html" % (target_name, ))
|
|
||||||
|
|
||||||
fix_relative_titles(
|
|
||||||
target=target_defs, filename=templated_file,
|
|
||||||
out_filename=rst_file,
|
|
||||||
)
|
|
||||||
rst2html(rst_file, html_file, stylesheets=stylesheets)
|
|
||||||
addAnchors(html_file)
|
|
||||||
|
|
||||||
if not keep_intermediates:
|
|
||||||
cleanup_env()
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
def list_targets():
|
|
||||||
with open(os.path.join(spec_dir, "targets.yaml"), "r") as targ_file:
|
|
||||||
target_defs = yaml.load(targ_file.read())
|
|
||||||
targets = target_defs["targets"].keys()
|
|
||||||
print("\n".join(targets))
|
|
||||||
|
|
||||||
|
|
||||||
def extract_major(s):
|
|
||||||
major_version = s
|
|
||||||
match = re.match("^(r\d+)(\.\d+)*$", s)
|
|
||||||
if match:
|
|
||||||
major_version = match.group(1)
|
|
||||||
return major_version
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
parser = ArgumentParser(
|
|
||||||
"gendoc.py - Generate the Matrix specification as HTML."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--nodelete", "-n", action="store_true",
|
|
||||||
help="Do not delete intermediate files. They will be found in scripts/tmp/"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--target", "-t", action="append",
|
|
||||||
help="Specify the build target to build from specification/targets.yaml. " +
|
|
||||||
"The value 'all' will build all of the targets therein."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--verbose", "-v", action="store_true",
|
|
||||||
help="Turn on verbose mode."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--client_release", "-c", action="store", default="unstable",
|
|
||||||
help="The client-server release tag to generate, e.g. r1.2"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--server_release", "-s", action="store", default="unstable",
|
|
||||||
help="The server-server release tag to generate, e.g. r1.2"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--appservice_release", "-a", action="store", default="unstable",
|
|
||||||
help="The appservice release tag to generate, e.g. r1.2"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--push_gateway_release", "-p", action="store", default="unstable",
|
|
||||||
help="The push gateway release tag to generate, e.g. r1.2"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--identity_release", "-i", action="store", default="unstable",
|
|
||||||
help="The identity service release tag to generate, e.g. r1.2"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--list_targets", action="store_true",
|
|
||||||
help="Do not update the specification. Instead print a list of targets.",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--dest", "-d", default=os.path.join(script_dir, "gen"),
|
|
||||||
help="Set destination directory (default: scripts/gen)",
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
VERBOSE = args.verbose
|
|
||||||
|
|
||||||
if args.list_targets:
|
|
||||||
list_targets()
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
substitutions = {
|
|
||||||
"%CLIENT_RELEASE_LABEL%": args.client_release,
|
|
||||||
# we hardcode the major versions. This ends up in the example
|
|
||||||
# API URLs. When we have released a new major version, we'll
|
|
||||||
# have to bump them.
|
|
||||||
"%CLIENT_MAJOR_VERSION%": "r0",
|
|
||||||
"%SERVER_RELEASE_LABEL%": args.server_release,
|
|
||||||
"%APPSERVICE_RELEASE_LABEL%": args.appservice_release,
|
|
||||||
"%IDENTITY_RELEASE_LABEL%": args.identity_release,
|
|
||||||
"%PUSH_GATEWAY_RELEASE_LABEL%": args.push_gateway_release,
|
|
||||||
}
|
|
||||||
|
|
||||||
exit (main(args.target or ["all"], args.dest, args.nodelete, substitutions))
|
|
@ -1,218 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# proposals.py: generate an RST file (proposals.rst) from queries to github.com/matrix.org/matrix-doc/issues.
|
|
||||||
|
|
||||||
import requests
|
|
||||||
import re
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
# a list of the labels we care about
|
|
||||||
LABELS_LIST=[
|
|
||||||
'proposal-in-review',
|
|
||||||
'proposed-final-comment-period',
|
|
||||||
'final-comment-period',
|
|
||||||
'finished-final-comment-period',
|
|
||||||
'spec-pr-missing',
|
|
||||||
'spec-pr-in-review',
|
|
||||||
'merged',
|
|
||||||
'proposal-postponed',
|
|
||||||
'abandoned',
|
|
||||||
'obsolete',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
authors = set()
|
|
||||||
prs = set()
|
|
||||||
|
|
||||||
def getpage(url):
|
|
||||||
"""Request the given URL, and extract the pagecount from the response headers
|
|
||||||
|
|
||||||
Args:
|
|
||||||
url (str): URL to fetch
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Tuple[int, list]: number of pages, and the list of items on this page
|
|
||||||
"""
|
|
||||||
resp = requests.get(url)
|
|
||||||
|
|
||||||
pagecount = 1
|
|
||||||
for link in resp.links.values():
|
|
||||||
if link['rel'] == 'last':
|
|
||||||
# we extract the pagecount from the `page` param of the last url
|
|
||||||
# in the response, eg
|
|
||||||
# 'https://api.github.com/repositories/24998719/issues?state=all&labels=proposal&page=10'
|
|
||||||
pagecount = int(re.search('page=(\d+)', link['url']).group(1))
|
|
||||||
|
|
||||||
val = resp.json()
|
|
||||||
if not isinstance(val, list):
|
|
||||||
print(val) # Just dump the raw (likely error) response to the log
|
|
||||||
raise Exception("Error calling %s" % url)
|
|
||||||
return (pagecount, val)
|
|
||||||
|
|
||||||
def getbylabel(label):
|
|
||||||
"""Fetch all the issues with a given label
|
|
||||||
|
|
||||||
Args:
|
|
||||||
label (str): label to fetch
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Iterator[dict]: an iterator over the issue list.
|
|
||||||
"""
|
|
||||||
urlbase = 'https://api.github.com/repos/matrix-org/matrix-doc/issues?state=all&labels=' + label + '&page='
|
|
||||||
page = 1
|
|
||||||
while True:
|
|
||||||
(pagecount, results) = getpage(urlbase + str(page))
|
|
||||||
for i in results:
|
|
||||||
yield i
|
|
||||||
page += 1
|
|
||||||
if page > pagecount:
|
|
||||||
return
|
|
||||||
|
|
||||||
def print_issue_list(text_file, label, issues):
|
|
||||||
text_file.write(label + "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n")
|
|
||||||
|
|
||||||
if (len(issues) == 0):
|
|
||||||
text_file.write("No proposals.\n\n")
|
|
||||||
return
|
|
||||||
|
|
||||||
text_file.write(".. list-table::\n :header-rows: 1\n :widths: auto\n :stub-columns: 1\n\n")
|
|
||||||
text_file.write(" * - MSC\n")
|
|
||||||
text_file.write(" - Proposal Title\n")
|
|
||||||
text_file.write(" - Creation Date\n")
|
|
||||||
text_file.write(" - Update Date\n")
|
|
||||||
text_file.write(" - Documentation\n")
|
|
||||||
text_file.write(" - Author\n")
|
|
||||||
text_file.write(" - Shepherd\n")
|
|
||||||
text_file.write(" - PRs\n")
|
|
||||||
|
|
||||||
for item in issues:
|
|
||||||
# set the created date, find local field, otherwise Github
|
|
||||||
body = str(item['body'])
|
|
||||||
created = re.search('^Date: (.+?)\n', body, flags=re.MULTILINE)
|
|
||||||
if created is not None:
|
|
||||||
created = created.group(1).strip()
|
|
||||||
try:
|
|
||||||
created = datetime.strptime(created, "%d/%m/%Y")
|
|
||||||
created = created.strftime('%Y-%m-%d')
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
created = datetime.strptime(created, "%Y-%m-%d")
|
|
||||||
created = created.strftime('%Y-%m-%d')
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else :
|
|
||||||
created = datetime.strptime(item['created_at'], "%Y-%m-%dT%XZ")
|
|
||||||
created = created.strftime('%Y-%m-%d')
|
|
||||||
item['created'] = created
|
|
||||||
|
|
||||||
issues_to_print = sorted(issues, key=lambda issue_sort: issue_sort["created"])
|
|
||||||
|
|
||||||
for item in issues_to_print:
|
|
||||||
# MSC number
|
|
||||||
text_file.write(" * - `MSC" + str(item['number']) + " <" + item['html_url'] + ">`_\n")
|
|
||||||
|
|
||||||
# title from Github issue
|
|
||||||
text_file.write(" - " + item['title'] + "\n")
|
|
||||||
|
|
||||||
# created date
|
|
||||||
text_file.write(" - " + item['created'] + "\n")
|
|
||||||
|
|
||||||
# last updated, purely Github
|
|
||||||
updated = datetime.strptime(item['updated_at'], "%Y-%m-%dT%XZ")
|
|
||||||
text_file.write(" - " + updated.strftime('%Y-%m-%d') + "\n")
|
|
||||||
|
|
||||||
# list of document links (urls comma-separated)
|
|
||||||
maindoc = re.search('^Documentation: (.+?)$', str(item['body']), flags=re.MULTILINE)
|
|
||||||
if maindoc is not None:
|
|
||||||
maindoc = maindoc.group(1)
|
|
||||||
doc_list_formatted = ["`" + str(item['number']) + "-" + str(i) + " <" + x.strip() + ">`_" for i, x in enumerate(maindoc.split(','),1)]
|
|
||||||
text_file.write(" - " + ', '.join(doc_list_formatted))
|
|
||||||
else:
|
|
||||||
text_file.write(" - ")
|
|
||||||
text_file.write("\n")
|
|
||||||
|
|
||||||
# author list, if missing just use Github issue creator
|
|
||||||
author = re.search('^Author: (.+?)$', str(item['body']), flags=re.MULTILINE)
|
|
||||||
if author is not None:
|
|
||||||
author_list_formatted = set()
|
|
||||||
author_list = author.group(1)
|
|
||||||
for a in author_list.split(","):
|
|
||||||
authors.add(a.strip())
|
|
||||||
author_list_formatted.add("`" + str(a.strip()) + "`_")
|
|
||||||
text_file.write(" - " + ', '.join(author_list_formatted))
|
|
||||||
else:
|
|
||||||
author = "@" + item['user']['login']
|
|
||||||
authors.add(author)
|
|
||||||
text_file.write(" - `" + str(author) + "`_")
|
|
||||||
text_file.write("\n")
|
|
||||||
|
|
||||||
# shepherd (currently only one)
|
|
||||||
shepherd = re.search('Shepherd: (.+?)\n', str(item['body']))
|
|
||||||
if shepherd is not None:
|
|
||||||
authors.add(shepherd.group(1).strip())
|
|
||||||
shepherd = "`" + shepherd.group(1).strip() + "`_"
|
|
||||||
text_file.write(" - " + str(shepherd) + "\n")
|
|
||||||
|
|
||||||
# PRs
|
|
||||||
try:
|
|
||||||
pr_list = re.search('PRs: (.+?)$', str(item['body']))
|
|
||||||
if pr_list is not None:
|
|
||||||
pr_list_formatted = set()
|
|
||||||
pr_list = pr_list.group(1)
|
|
||||||
for p in pr_list.split(","):
|
|
||||||
if re.match(r"#\d", p.strip()):
|
|
||||||
prs.add(p.strip())
|
|
||||||
pr_list_formatted.add("`PR" + str(p.strip()) + "`_")
|
|
||||||
elif re.match(r"https://github.com/matrix-org/matrix-doc/pulls/\d", p.strip()):
|
|
||||||
pr = "#" + p.strip().replace('https://github.com/matrix-org/matrix-doc/pulls/', '')
|
|
||||||
prs.add(pr)
|
|
||||||
pr_list_formatted.add("`PR" + str(pr) + "`_")
|
|
||||||
else:
|
|
||||||
raise RuntimeWarning
|
|
||||||
text_file.write(" - " + ', '.join(pr_list_formatted))
|
|
||||||
text_file.write("\n")
|
|
||||||
else:
|
|
||||||
text_file.write(" - \n")
|
|
||||||
except:
|
|
||||||
print("exception parsing PRs for MSC" + str(item['number']))
|
|
||||||
text_file.write(" - \n")
|
|
||||||
|
|
||||||
text_file.write("\n\n\n")
|
|
||||||
|
|
||||||
|
|
||||||
# first get all of the issues, filtering by label
|
|
||||||
issues = {n: [] for n in LABELS_LIST}
|
|
||||||
# use the magic 'None' key for a proposal in progress
|
|
||||||
issues[None] = []
|
|
||||||
|
|
||||||
for prop in getbylabel('proposal'):
|
|
||||||
print("%s: %s" % (prop['number'], [l['name'] for l in prop['labels']]))
|
|
||||||
found_label = False
|
|
||||||
for label in prop['labels']:
|
|
||||||
label_name = label['name']
|
|
||||||
if label_name in issues:
|
|
||||||
issues[label_name].append(prop)
|
|
||||||
found_label = True
|
|
||||||
|
|
||||||
# if it doesn't have any other label, assume it's work-in-progress
|
|
||||||
if not found_label:
|
|
||||||
issues[None].append(prop)
|
|
||||||
|
|
||||||
text_file = open("specification/proposals.rst", "w")
|
|
||||||
|
|
||||||
text_file.write("Tables of Tracked Proposals\n---------------------------\n\n")
|
|
||||||
|
|
||||||
print_issue_list(text_file, "<work-in-progress>", issues[None])
|
|
||||||
for label in LABELS_LIST:
|
|
||||||
print_issue_list(text_file, label, issues[label])
|
|
||||||
|
|
||||||
text_file.write("\n")
|
|
||||||
|
|
||||||
for author in authors:
|
|
||||||
text_file.write("\n.. _" + author + ": https://github.com/" + author[1:])
|
|
||||||
|
|
||||||
for pr in prs:
|
|
||||||
text_file.write("\n.. _PR" + pr + ": https://github.com/matrix-org/matrix-doc/pull/" + pr.replace('#', ''))
|
|
||||||
|
|
||||||
text_file.close()
|
|
@ -1,38 +0,0 @@
|
|||||||
#! /bin/bash
|
|
||||||
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
cd `dirname $0`/..
|
|
||||||
|
|
||||||
virtualenv -p python3 env
|
|
||||||
. env/bin/activate
|
|
||||||
|
|
||||||
# Print out the python versions for debugging purposes
|
|
||||||
python --version
|
|
||||||
pip --version
|
|
||||||
|
|
||||||
# Install python dependencies
|
|
||||||
pip install -r scripts/requirements.txt
|
|
||||||
|
|
||||||
# Install node dependencies
|
|
||||||
npm install --prefix=scripts
|
|
||||||
|
|
||||||
# do sanity checks on the examples and swagger
|
|
||||||
scripts/check-event-schema-examples.py
|
|
||||||
scripts/check-swagger-sources.py
|
|
||||||
node scripts/validator.js --schema "data/api/client-server"
|
|
||||||
|
|
||||||
: ${GOPATH:=${WORKSPACE}/.gopath}
|
|
||||||
mkdir -p "${GOPATH}"
|
|
||||||
export GOPATH
|
|
||||||
go get github.com/hashicorp/golang-lru
|
|
||||||
go get gopkg.in/fsnotify/fsnotify.v1
|
|
||||||
|
|
||||||
# make sure that the scripts build
|
|
||||||
(cd scripts/continuserv && go build)
|
|
||||||
(cd scripts/speculator && go build)
|
|
||||||
|
|
||||||
# build the spec for matrix.org.
|
|
||||||
# (we don't actually use it on travis, but it's still useful to check we
|
|
||||||
# can build it. On Buildkite, this is then used to deploy to matrix.org).
|
|
||||||
./scripts/generate-matrix-org-assets
|
|
Loading…
Reference in New Issue