From bffe0ca1b7d9c775550019f04d49372fbc17a0b7 Mon Sep 17 00:00:00 2001 From: xxqfamous Date: Tue, 23 May 2023 16:26:28 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=96=B0=E6=A2=B3=E7=90=86=E6=89=93?= =?UTF-8?q?=E5=8C=85=EF=BC=8C=E5=8A=A0=E5=85=A5options,public,templates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.go | 14 ++ build.sh | 15 +++ build/generate-bindata.go | 87 +++++++++++++ build/generate-emoji.go | 225 +++++++++++++++++++++++++++++++++ build/generate-gitignores.go | 131 +++++++++++++++++++ build/generate-images.js | 83 ++++++++++++ build/generate-licenses.go | 119 +++++++++++++++++ build/generate-svg.js | 63 +++++++++ build/gocovmerge.go | 120 ++++++++++++++++++ build/update-locales.sh | 23 ++++ go.mod | 2 + go.sum | 4 + modules/schemas/issue.json | 114 +++++++++++++++++ modules/schemas/label.json | 28 ++++ modules/schemas/milestone.json | 67 ++++++++++ modules/schemas/reaction.json | 29 +++++ 16 files changed, 1124 insertions(+) create mode 100644 build.go create mode 100644 build.sh create mode 100644 build/generate-bindata.go create mode 100644 build/generate-emoji.go create mode 100644 build/generate-gitignores.go create mode 100755 build/generate-images.js create mode 100644 build/generate-licenses.go create mode 100755 build/generate-svg.js create mode 100644 build/gocovmerge.go create mode 100755 build/update-locales.sh create mode 100644 modules/schemas/issue.json create mode 100644 modules/schemas/label.json create mode 100644 modules/schemas/milestone.json create mode 100644 modules/schemas/reaction.json diff --git a/build.go b/build.go new file mode 100644 index 0000000..d410e17 --- /dev/null +++ b/build.go @@ -0,0 +1,14 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//go:build vendor + +package main + +// Libraries that are included to vendor utilities used during build. +// These libraries will not be included in a normal compilation. + +import ( + // for embed + _ "github.com/shurcooL/vfsgen" +) diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..573317a --- /dev/null +++ b/build.sh @@ -0,0 +1,15 @@ +#!/bin/bash +echo "go mod tidy..." +go mod tidy + +echo "go mod vendor..." +go mod vendor + +echo "go generate bindata.go for migration,options,public,templates..." +go run build/generate-bindata.go modules/schemas migration vendor/code.gitea.io/gitea/modules/migration/bindata.go +go run build/generate-bindata.go options options vendor/code.gitea.io/gitea/modules/options/bindata.go +go run build/generate-bindata.go public public vendor/code.gitea.io/gitea/modules/public/bindata.go +go run build/generate-bindata.go templates templates vendor/code.gitea.io/gitea/modules/templates/bindata.go + +echo "go build -tags 'bindata' -o gitea_hat main.go..." +go build -tags 'bindata' -o gitea_hat main.go diff --git a/build/generate-bindata.go b/build/generate-bindata.go new file mode 100644 index 0000000..efd172f --- /dev/null +++ b/build/generate-bindata.go @@ -0,0 +1,87 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build ignore +// +build ignore + +package main + +import ( + "bytes" + "crypto/sha1" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "path/filepath" + "strconv" + + "github.com/shurcooL/vfsgen" +) + +func needsUpdate(dir string, filename string) (bool, []byte) { + needRegen := false + _, err := os.Stat(filename) + if err != nil { + needRegen = true + } + + oldHash, err := ioutil.ReadFile(filename + ".hash") + if err != nil { + oldHash = []byte{} + } + + hasher := sha1.New() + + err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + _, _ = hasher.Write([]byte(info.Name())) + _, _ = hasher.Write([]byte(info.ModTime().String())) + _, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16))) + return nil + }) + if err != nil { + return true, oldHash + } + + newHash := hasher.Sum([]byte{}) + + if bytes.Compare(oldHash, newHash) != 0 { + + return true, newHash + } + + return needRegen, newHash +} + +func main() { + if len(os.Args) != 4 { + log.Fatal("Insufficient number of arguments. Need: directory packageName filename") + } + + dir, packageName, filename := os.Args[1], os.Args[2], os.Args[3] + + update, newHash := needsUpdate(dir, filename) + + if !update { + fmt.Printf("bindata for %s already up-to-date\n", packageName) + return + } + + fmt.Printf("generating bindata for %s\n", packageName) + var fsTemplates http.FileSystem = http.Dir(dir) + err := vfsgen.Generate(fsTemplates, vfsgen.Options{ + PackageName: packageName, + BuildTags: "bindata", + VariableName: "Assets", + Filename: filename, + }) + if err != nil { + log.Fatalf("%v\n", err) + } + _ = ioutil.WriteFile(filename+".hash", newHash, 0666) +} diff --git a/build/generate-emoji.go b/build/generate-emoji.go new file mode 100644 index 0000000..f8e6498 --- /dev/null +++ b/build/generate-emoji.go @@ -0,0 +1,225 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Copyright 2015 Kenneth Shaw +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +//go:build ignore +// +build ignore + +package main + +import ( + "flag" + "fmt" + "go/format" + "io/ioutil" + "log" + "net/http" + "regexp" + "sort" + "strconv" + "strings" + "unicode/utf8" + + jsoniter "github.com/json-iterator/go" +) + +const ( + gemojiURL = "https://raw.githubusercontent.com/github/gemoji/master/db/emoji.json" + maxUnicodeVersion = 12 +) + +var ( + flagOut = flag.String("o", "modules/emoji/emoji_data.go", "out") +) + +// Gemoji is a set of emoji data. +type Gemoji []Emoji + +// Emoji represents a single emoji and associated data. +type Emoji struct { + Emoji string `json:"emoji"` + Description string `json:"description,omitempty"` + Aliases []string `json:"aliases"` + UnicodeVersion string `json:"unicode_version,omitempty"` + SkinTones bool `json:"skin_tones,omitempty"` +} + +// Don't include some fields in JSON +func (e Emoji) MarshalJSON() ([]byte, error) { + type emoji Emoji + x := emoji(e) + x.UnicodeVersion = "" + x.Description = "" + x.SkinTones = false + json := jsoniter.ConfigCompatibleWithStandardLibrary + return json.Marshal(x) +} + +func main() { + var err error + + flag.Parse() + + // generate data + buf, err := generate() + if err != nil { + log.Fatal(err) + } + + // write + err = ioutil.WriteFile(*flagOut, buf, 0644) + if err != nil { + log.Fatal(err) + } +} + +var replacer = strings.NewReplacer( + "main.Gemoji", "Gemoji", + "main.Emoji", "\n", + "}}", "},\n}", + ", Description:", ", ", + ", Aliases:", ", ", + ", UnicodeVersion:", ", ", + ", SkinTones:", ", ", +) + +var emojiRE = regexp.MustCompile(`\{Emoji:"([^"]*)"`) + +func generate() ([]byte, error) { + var err error + + // load gemoji data + res, err := http.Get(gemojiURL) + if err != nil { + return nil, err + } + defer res.Body.Close() + + // read all + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + // unmarshal + var data Gemoji + json := jsoniter.ConfigCompatibleWithStandardLibrary + err = json.Unmarshal(body, &data) + if err != nil { + return nil, err + } + + var skinTones = make(map[string]string) + + skinTones["\U0001f3fb"] = "Light Skin Tone" + skinTones["\U0001f3fc"] = "Medium-Light Skin Tone" + skinTones["\U0001f3fd"] = "Medium Skin Tone" + skinTones["\U0001f3fe"] = "Medium-Dark Skin Tone" + skinTones["\U0001f3ff"] = "Dark Skin Tone" + + var tmp Gemoji + + //filter out emoji that require greater than max unicode version + for i := range data { + val, _ := strconv.ParseFloat(data[i].UnicodeVersion, 64) + if int(val) <= maxUnicodeVersion { + tmp = append(tmp, data[i]) + } + } + data = tmp + + sort.Slice(data, func(i, j int) bool { + return data[i].Aliases[0] < data[j].Aliases[0] + }) + + aliasMap := make(map[string]int, len(data)) + + for i, e := range data { + if e.Emoji == "" || len(e.Aliases) == 0 { + continue + } + for _, a := range e.Aliases { + if a == "" { + continue + } + aliasMap[a] = i + } + } + + // gitea customizations + i, ok := aliasMap["tada"] + if ok { + data[i].Aliases = append(data[i].Aliases, "hooray") + } + i, ok = aliasMap["laughing"] + if ok { + data[i].Aliases = append(data[i].Aliases, "laugh") + } + + // write a JSON file to use with tribute (write before adding skin tones since we can't support them there yet) + file, _ := json.Marshal(data) + _ = ioutil.WriteFile("assets/emoji.json", file, 0644) + + // Add skin tones to emoji that support it + var ( + s []string + newEmoji string + newDescription string + newData Emoji + ) + + for i := range data { + if data[i].SkinTones { + for k, v := range skinTones { + s = strings.Split(data[i].Emoji, "") + + if utf8.RuneCountInString(data[i].Emoji) == 1 { + s = append(s, k) + } else { + // insert into slice after first element because all emoji that support skin tones + // have that modifier placed at this spot + s = append(s, "") + copy(s[2:], s[1:]) + s[1] = k + } + + newEmoji = strings.Join(s, "") + newDescription = data[i].Description + ": " + v + newAlias := data[i].Aliases[0] + "_" + strings.ReplaceAll(v, " ", "_") + + newData = Emoji{newEmoji, newDescription, []string{newAlias}, "12.0", false} + data = append(data, newData) + } + } + } + + // add header + str := replacer.Replace(fmt.Sprintf(hdr, gemojiURL, data)) + + // change the format of the unicode string + str = emojiRE.ReplaceAllStringFunc(str, func(s string) string { + var err error + s, err = strconv.Unquote(s[len("{Emoji:"):]) + if err != nil { + panic(err) + } + return "{" + strconv.QuoteToASCII(s) + }) + + // format + return format.Source([]byte(str)) +} + +const hdr = ` +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package emoji + +// Code generated by gen.go. DO NOT EDIT. +// Sourced from %s +// +var GemojiData = %#v +` diff --git a/build/generate-gitignores.go b/build/generate-gitignores.go new file mode 100644 index 0000000..d0b972e --- /dev/null +++ b/build/generate-gitignores.go @@ -0,0 +1,131 @@ +//go:build ignore +// +build ignore + +package main + +import ( + "archive/tar" + "compress/gzip" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "path/filepath" + "strings" + + "code.gitea.io/gitea/modules/util" +) + +func main() { + var ( + prefix = "gitea-gitignore" + url = "https://api.github.com/repos/github/gitignore/tarball" + githubApiToken = "" + githubUsername = "" + destination = "" + ) + + flag.StringVar(&destination, "dest", "options/gitignore/", "destination for the gitignores") + flag.StringVar(&githubUsername, "username", "", "github username") + flag.StringVar(&githubApiToken, "token", "", "github api token") + flag.Parse() + + file, err := ioutil.TempFile(os.TempDir(), prefix) + + if err != nil { + log.Fatalf("Failed to create temp file. %s", err) + } + + defer util.Remove(file.Name()) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Fatalf("Failed to download archive. %s", err) + } + + if len(githubApiToken) > 0 && len(githubUsername) > 0 { + req.SetBasicAuth(githubUsername, githubApiToken) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatalf("Failed to download archive. %s", err) + } + defer resp.Body.Close() + + if _, err := io.Copy(file, resp.Body); err != nil { + log.Fatalf("Failed to copy archive to file. %s", err) + } + + if _, err := file.Seek(0, 0); err != nil { + log.Fatalf("Failed to reset seek on archive. %s", err) + } + + gz, err := gzip.NewReader(file) + + if err != nil { + log.Fatalf("Failed to gunzip the archive. %s", err) + } + + tr := tar.NewReader(gz) + + filesToCopy := make(map[string]string, 0) + + for { + hdr, err := tr.Next() + + if err == io.EOF { + break + } + + if err != nil { + log.Fatalf("Failed to iterate archive. %s", err) + } + + if filepath.Ext(hdr.Name) != ".gitignore" { + continue + } + + if hdr.Typeflag == tar.TypeSymlink { + fmt.Printf("Found symlink %s -> %s\n", hdr.Name, hdr.Linkname) + filesToCopy[strings.TrimSuffix(filepath.Base(hdr.Name), ".gitignore")] = strings.TrimSuffix(filepath.Base(hdr.Linkname), ".gitignore") + continue + } + + out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".gitignore"))) + + if err != nil { + log.Fatalf("Failed to create new file. %s", err) + } + + defer out.Close() + + if _, err := io.Copy(out, tr); err != nil { + log.Fatalf("Failed to write new file. %s", err) + } else { + fmt.Printf("Written %s\n", out.Name()) + } + } + + for dst, src := range filesToCopy { + // Read all content of src to data + src = path.Join(destination, src) + data, err := ioutil.ReadFile(src) + if err != nil { + log.Fatalf("Failed to read src file. %s", err) + } + // Write data to dst + dst = path.Join(destination, dst) + err = ioutil.WriteFile(dst, data, 0644) + if err != nil { + log.Fatalf("Failed to write new file. %s", err) + } + fmt.Printf("Written (copy of %s) %s\n", src, dst) + } + + fmt.Println("Done") +} diff --git a/build/generate-images.js b/build/generate-images.js new file mode 100755 index 0000000..b661681 --- /dev/null +++ b/build/generate-images.js @@ -0,0 +1,83 @@ +import imageminZopfli from 'imagemin-zopfli'; +import {optimize, extendDefaultPlugins} from 'svgo'; +import {fabric} from 'fabric'; +import fs from 'fs'; +import {resolve, dirname} from 'path'; +import {fileURLToPath} from 'url'; + +const {readFile, writeFile} = fs.promises; +const __dirname = dirname(fileURLToPath(import.meta.url)); +const logoFile = resolve(__dirname, '../assets/logo.svg'); + +function exit(err) { + if (err) console.error(err); + process.exit(err ? 1 : 0); +} + +function loadSvg(svg) { + return new Promise((resolve) => { + fabric.loadSVGFromString(svg, (objects, options) => { + resolve({objects, options}); + }); + }); +} + +async function generate(svg, outputFile, {size, bg}) { + if (outputFile.endsWith('.svg')) { + const {data} = optimize(svg, { + plugins: extendDefaultPlugins([ + 'removeDimensions', + { + name: 'addAttributesToSVGElement', + params: {attributes: [{width: size}, {height: size}]} + }, + ]), + }); + await writeFile(outputFile, data); + return; + } + + const {objects, options} = await loadSvg(svg); + const canvas = new fabric.Canvas(); + canvas.setDimensions({width: size, height: size}); + const ctx = canvas.getContext('2d'); + ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1); + + if (bg) { + canvas.add(new fabric.Rect({ + left: 0, + top: 0, + height: size * (1 / (size / options.height)), + width: size * (1 / (size / options.width)), + fill: 'white', + })); + } + + canvas.add(fabric.util.groupSVGElements(objects, options)); + canvas.renderAll(); + + let png = Buffer.from([]); + for await (const chunk of canvas.createPNGStream()) { + png = Buffer.concat([png, chunk]); + } + + png = await imageminZopfli({more: true})(png); + await writeFile(outputFile, png); +} + +async function main() { + const gitea = process.argv.slice(2).includes('gitea'); + const svg = await readFile(logoFile, 'utf8'); + + await Promise.all([ + generate(svg, resolve(__dirname, '../public/img/logo.svg'), {size: 32}), + generate(svg, resolve(__dirname, '../public/img/logo.png'), {size: 512}), + generate(svg, resolve(__dirname, '../public/img/favicon.png'), {size: 180}), + generate(svg, resolve(__dirname, '../public/img/avatar_default.png'), {size: 200}), + generate(svg, resolve(__dirname, '../public/img/apple-touch-icon.png'), {size: 180, bg: true}), + gitea && generate(svg, resolve(__dirname, '../public/img/gitea.svg'), {size: 32}), + ]); +} + +main().then(exit).catch(exit); + diff --git a/build/generate-licenses.go b/build/generate-licenses.go new file mode 100644 index 0000000..4009a03 --- /dev/null +++ b/build/generate-licenses.go @@ -0,0 +1,119 @@ +//go:build ignore +// +build ignore + +package main + +import ( + "archive/tar" + "compress/gzip" + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "os" + "path" + "path/filepath" + "strings" + + "code.gitea.io/gitea/modules/util" +) + +func main() { + var ( + prefix = "gitea-licenses" + url = "https://api.github.com/repos/spdx/license-list-data/tarball" + githubApiToken = "" + githubUsername = "" + destination = "" + ) + + flag.StringVar(&destination, "dest", "options/license/", "destination for the licenses") + flag.StringVar(&githubUsername, "username", "", "github username") + flag.StringVar(&githubApiToken, "token", "", "github api token") + flag.Parse() + + file, err := ioutil.TempFile(os.TempDir(), prefix) + + if err != nil { + log.Fatalf("Failed to create temp file. %s", err) + } + + defer util.Remove(file.Name()) + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + log.Fatalf("Failed to download archive. %s", err) + } + + if len(githubApiToken) > 0 && len(githubUsername) > 0 { + req.SetBasicAuth(githubUsername, githubApiToken) + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.Fatalf("Failed to download archive. %s", err) + } + + defer resp.Body.Close() + + if _, err := io.Copy(file, resp.Body); err != nil { + log.Fatalf("Failed to copy archive to file. %s", err) + } + + if _, err := file.Seek(0, 0); err != nil { + log.Fatalf("Failed to reset seek on archive. %s", err) + } + + gz, err := gzip.NewReader(file) + + if err != nil { + log.Fatalf("Failed to gunzip the archive. %s", err) + } + + tr := tar.NewReader(gz) + + for { + hdr, err := tr.Next() + + if err == io.EOF { + break + } + + if err != nil { + log.Fatalf("Failed to iterate archive. %s", err) + } + + if !strings.Contains(hdr.Name, "/text/") { + continue + } + + if filepath.Ext(hdr.Name) != ".txt" { + continue + } + + if strings.HasPrefix(filepath.Base(hdr.Name), "README") { + continue + } + + if strings.HasPrefix(filepath.Base(hdr.Name), "deprecated_") { + continue + } + out, err := os.Create(path.Join(destination, strings.TrimSuffix(filepath.Base(hdr.Name), ".txt"))) + + if err != nil { + log.Fatalf("Failed to create new file. %s", err) + } + + defer out.Close() + + if _, err := io.Copy(out, tr); err != nil { + log.Fatalf("Failed to write new file. %s", err) + } else { + fmt.Printf("Written %s\n", out.Name()) + } + } + + fmt.Println("Done") +} diff --git a/build/generate-svg.js b/build/generate-svg.js new file mode 100755 index 0000000..cde3f31 --- /dev/null +++ b/build/generate-svg.js @@ -0,0 +1,63 @@ +import fastGlob from 'fast-glob'; +import {optimize, extendDefaultPlugins} from 'svgo'; +import {resolve, parse, dirname} from 'path'; +import fs from 'fs'; +import {fileURLToPath} from 'url'; + +const {readFile, writeFile, mkdir} = fs.promises; +const __dirname = dirname(fileURLToPath(import.meta.url)); +const glob = (pattern) => fastGlob.sync(pattern, {cwd: resolve(__dirname), absolute: true}); +const outputDir = resolve(__dirname, '../public/img/svg'); + +function exit(err) { + if (err) console.error(err); + process.exit(err ? 1 : 0); +} + +async function processFile(file, {prefix, fullName} = {}) { + let name; + + if (fullName) { + name = fullName; + } else { + name = parse(file).name; + if (prefix) name = `${prefix}-${name}`; + if (prefix === 'octicon') name = name.replace(/-[0-9]+$/, ''); // chop of '-16' on octicons + } + + const {data} = optimize(await readFile(file, 'utf8'), { + plugins: extendDefaultPlugins([ + 'removeXMLNS', + 'removeDimensions', + {name: 'prefixIds', params: {prefix: () => name}}, + { + name: 'addClassesToSVGElement', + params: {classNames: ['svg', name]}, + }, + { + name: 'addAttributesToSVGElement', + params: {attributes: [{'width': '16'}, {'height': '16'}, {'aria-hidden': 'true'}]}, + }, + ]), + }); + await writeFile(resolve(outputDir, `${name}.svg`), data); +} + +function processFiles(pattern, opts) { + return glob(pattern).map((file) => processFile(file, opts)); +} + +async function main() { + try { + await mkdir(outputDir); + } catch {} + + await Promise.all([ + ...processFiles('../node_modules/@primer/octicons/build/svg/*-16.svg', {prefix: 'octicon'}), + ...processFiles('../web_src/svg/*.svg'), + ...processFiles('../public/img/gitea.svg', {fullName: 'gitea-gitea'}), + ]); +} + +main().then(exit).catch(exit); + diff --git a/build/gocovmerge.go b/build/gocovmerge.go new file mode 100644 index 0000000..b38cf5e --- /dev/null +++ b/build/gocovmerge.go @@ -0,0 +1,120 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Copyright (c) 2015, Wade Simmons +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +// gocovmerge takes the results from multiple `go test -coverprofile` runs and +// merges them into one profile + +//go:build ignore +// +build ignore + +package main + +import ( + "flag" + "fmt" + "io" + "log" + "os" + "sort" + + "golang.org/x/tools/cover" +) + +func mergeProfiles(p *cover.Profile, merge *cover.Profile) { + if p.Mode != merge.Mode { + log.Fatalf("cannot merge profiles with different modes") + } + // Since the blocks are sorted, we can keep track of where the last block + // was inserted and only look at the blocks after that as targets for merge + startIndex := 0 + for _, b := range merge.Blocks { + startIndex = mergeProfileBlock(p, b, startIndex) + } +} + +func mergeProfileBlock(p *cover.Profile, pb cover.ProfileBlock, startIndex int) int { + sortFunc := func(i int) bool { + pi := p.Blocks[i+startIndex] + return pi.StartLine >= pb.StartLine && (pi.StartLine != pb.StartLine || pi.StartCol >= pb.StartCol) + } + + i := 0 + if sortFunc(i) != true { + i = sort.Search(len(p.Blocks)-startIndex, sortFunc) + } + i += startIndex + if i < len(p.Blocks) && p.Blocks[i].StartLine == pb.StartLine && p.Blocks[i].StartCol == pb.StartCol { + if p.Blocks[i].EndLine != pb.EndLine || p.Blocks[i].EndCol != pb.EndCol { + log.Fatalf("OVERLAP MERGE: %v %v %v", p.FileName, p.Blocks[i], pb) + } + switch p.Mode { + case "set": + p.Blocks[i].Count |= pb.Count + case "count", "atomic": + p.Blocks[i].Count += pb.Count + default: + log.Fatalf("unsupported covermode: '%s'", p.Mode) + } + } else { + if i > 0 { + pa := p.Blocks[i-1] + if pa.EndLine >= pb.EndLine && (pa.EndLine != pb.EndLine || pa.EndCol > pb.EndCol) { + log.Fatalf("OVERLAP BEFORE: %v %v %v", p.FileName, pa, pb) + } + } + if i < len(p.Blocks)-1 { + pa := p.Blocks[i+1] + if pa.StartLine <= pb.StartLine && (pa.StartLine != pb.StartLine || pa.StartCol < pb.StartCol) { + log.Fatalf("OVERLAP AFTER: %v %v %v", p.FileName, pa, pb) + } + } + p.Blocks = append(p.Blocks, cover.ProfileBlock{}) + copy(p.Blocks[i+1:], p.Blocks[i:]) + p.Blocks[i] = pb + } + return i + 1 +} + +func addProfile(profiles []*cover.Profile, p *cover.Profile) []*cover.Profile { + i := sort.Search(len(profiles), func(i int) bool { return profiles[i].FileName >= p.FileName }) + if i < len(profiles) && profiles[i].FileName == p.FileName { + mergeProfiles(profiles[i], p) + } else { + profiles = append(profiles, nil) + copy(profiles[i+1:], profiles[i:]) + profiles[i] = p + } + return profiles +} + +func dumpProfiles(profiles []*cover.Profile, out io.Writer) { + if len(profiles) == 0 { + return + } + fmt.Fprintf(out, "mode: %s\n", profiles[0].Mode) + for _, p := range profiles { + for _, b := range p.Blocks { + fmt.Fprintf(out, "%s:%d.%d,%d.%d %d %d\n", p.FileName, b.StartLine, b.StartCol, b.EndLine, b.EndCol, b.NumStmt, b.Count) + } + } +} + +func main() { + flag.Parse() + + var merged []*cover.Profile + + for _, file := range flag.Args() { + profiles, err := cover.ParseProfiles(file) + if err != nil { + log.Fatalf("failed to parse profiles: %v", err) + } + for _, p := range profiles { + merged = addProfile(merged, p) + } + } + + dumpProfiles(merged, os.Stdout) +} diff --git a/build/update-locales.sh b/build/update-locales.sh new file mode 100755 index 0000000..046f48e --- /dev/null +++ b/build/update-locales.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +mv ./options/locale/locale_en-US.ini ./options/ + +# Make sure to only change lines that have the translation enclosed between quotes +sed -i -r -e '/^[a-zA-Z0-9_.-]+[ ]*=[ ]*".*"$/ { + s/^([a-zA-Z0-9_.-]+)[ ]*="/\1=/ + s/\\"/"/g + s/"$// + }' ./options/locale/*.ini + +# Remove translation under 25% of en_us +baselines=$(wc -l "./options/locale_en-US.ini" | cut -d" " -f1) +baselines=$((baselines / 4)) +for filename in ./options/locale/*.ini; do + lines=$(wc -l "$filename" | cut -d" " -f1) + if [ $lines -lt $baselines ]; then + echo "Removing $filename: $lines/$baselines" + rm "$filename" + fi +done + +mv ./options/locale_en-US.ini ./options/locale/ diff --git a/go.mod b/go.mod index 5c36cf0..51b38b7 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/json-iterator/go v1.1.12 github.com/klauspost/cpuid/v2 v2.2.3 github.com/russross/blackfriday/v2 v2.1.0 + github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 github.com/urfave/cli v1.22.12 golang.org/x/net v0.8.0 golang.org/x/text v0.8.0 @@ -180,6 +181,7 @@ require ( github.com/rs/xid v1.4.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect + github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/skeema/knownhosts v1.1.0 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect diff --git a/go.sum b/go.sum index 74eaa0a..f6e54ca 100644 --- a/go.sum +++ b/go.sum @@ -1119,7 +1119,11 @@ github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NF github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= +github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU= +github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY= github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= diff --git a/modules/schemas/issue.json b/modules/schemas/issue.json new file mode 100644 index 0000000..25753c3 --- /dev/null +++ b/modules/schemas/issue.json @@ -0,0 +1,114 @@ +{ + "title": "Issue", + "description": "Issues associated to a repository within a forge (Gitea, GitLab, etc.).", + + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "number": { + "description": "Unique identifier, relative to the repository.", + "type": "number" + }, + "poster_id": { + "description": "Unique identifier of the user who authored the issue.", + "type": "number" + }, + "poster_name": { + "description": "Name of the user who authored the issue.", + "type": "string" + }, + "poster_email": { + "description": "Email of the user who authored the issue.", + "type": "string" + }, + "title": { + "description": "Short description displayed as the title.", + "type": "string" + }, + "content": { + "description": "Long, multiline, description.", + "type": "string" + }, + "ref": { + "description": "Target branch in the repository.", + "type": "string" + }, + "milestone": { + "description": "Name of the milestone.", + "type": "string" + }, + "state": { + "description": "A 'closed' issue will not see any activity in the future, otherwise it is 'open'.", + "enum": [ + "closed", + "open" + ] + }, + "is_locked": { + "description": "A locked issue can only be modified by privileged users.", + "type": "boolean" + }, + "created": { + "description": "Creation time.", + "type": "string", + "format": "date-time" + }, + "updated": { + "description": "Last update time.", + "type": "string", + "format": "date-time" + }, + "closed": { + "description": "The last time 'state' changed to 'closed'.", + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, + "labels": { + "description": "List of labels.", + "type": "array", + "items": { + "$ref": "label.json" + } + }, + "reactions": { + "description": "List of reactions.", + "type": "array", + "items": { + "$ref": "reaction.json" + } + }, + "assignees": { + "description": "List of assignees.", + "type": "array", + "items": { + "description": "Name of a user assigned to the issue.", + "type": "string" + } + } + }, + "required": [ + "number", + "poster_id", + "poster_name", + "title", + "content", + "state", + "is_locked", + "created", + "updated" + ] + }, + + "$schema": "http://json-schema.org/draft-04/schema#", + "$id": "http://example.com/issue.json", + "$$target": "issue.json" +} diff --git a/modules/schemas/label.json b/modules/schemas/label.json new file mode 100644 index 0000000..561a2e3 --- /dev/null +++ b/modules/schemas/label.json @@ -0,0 +1,28 @@ +{ + "title": "Label", + "description": "Label associated to an issue.", + + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "description": "Name of the label, unique within the repository.", + "type": "string" + }, + "color": { + "description": "Color code of the label.", + "type": "string" + }, + "description": { + "description": "Long, multiline, description.", + "type": "string" + } + }, + "required": [ + "name" + ], + + "$schema": "http://json-schema.org/draft-04/schema#", + "$id": "label.json", + "$$target": "label.json" +} diff --git a/modules/schemas/milestone.json b/modules/schemas/milestone.json new file mode 100644 index 0000000..7024ef4 --- /dev/null +++ b/modules/schemas/milestone.json @@ -0,0 +1,67 @@ +{ + "title": "Milestone", + "description": "Milestone associated to a repository within a forge.", + + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "title": { + "description": "Short description.", + "type": "string" + }, + "description": { + "description": "Long, multiline, description.", + "type": "string" + }, + "deadline": { + "description": "Deadline after which the milestone is overdue.", + "type": "string", + "format": "date-time" + }, + "created": { + "description": "Creation time.", + "type": "string", + "format": "date-time" + }, + "updated": { + "description": "Last update time.", + "type": "string", + "format": "date-time" + }, + "closed": { + "description": "The last time 'state' changed to 'closed'.", + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, + "state": { + "description": "A 'closed' issue will not see any activity in the future, otherwise it is 'open'.", + "enum": [ + "closed", + "open" + ] + } + }, + "required": [ + "title", + "description", + "deadline", + "created", + "updated", + "closed", + "state" + ] + }, + + "$schema": "http://json-schema.org/draft-04/schema#", + "$id": "http://example.com/milestone.json", + "$$target": "milestone.json" +} diff --git a/modules/schemas/reaction.json b/modules/schemas/reaction.json new file mode 100644 index 0000000..2565251 --- /dev/null +++ b/modules/schemas/reaction.json @@ -0,0 +1,29 @@ +{ + "title": "Reaction", + "description": "Reaction associated to an issue or a comment.", + + "type": "object", + "additionalProperties": false, + "properties": { + "user_id": { + "description": "Unique identifier of the user who authored the reaction.", + "type": "number" + }, + "user_name": { + "description": "Name of the user who authored the reaction.", + "type": "string" + }, + "content": { + "description": "Representation of the reaction", + "type": "string" + } + }, + "required": [ + "user_id", + "content" + ], + + "$schema": "http://json-schema.org/draft-04/schema#", + "$id": "http://example.com/reaction.json", + "$$target": "reaction.json" +}