Clean up the scripts a bit (...)
- Move the Go files into script/ instead of random places - Rewrite check-contrib.sh into check-authors.go and check-copyright.go - Clean up build.sh a little bit
This commit is contained in:
60
script/authors.go
Normal file
60
script/authors.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// Generates the list of contributors in gui/index.html based on contents of
|
||||
// AUTHORS.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const htmlFile = "gui/syncthing/core/aboutModalView.html"
|
||||
|
||||
func main() {
|
||||
bs := readAll("AUTHORS")
|
||||
lines := strings.Split(string(bs), "\n")
|
||||
nameRe := regexp.MustCompile(`(.+?)\s+<`)
|
||||
authors := make([]string, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
if m := nameRe.FindStringSubmatch(line); len(m) == 2 {
|
||||
authors = append(authors, " <li class=\"auto-generated\">"+m[1]+"</li>")
|
||||
}
|
||||
}
|
||||
sort.Strings(authors)
|
||||
replacement := strings.Join(authors, "\n")
|
||||
|
||||
authorsRe := regexp.MustCompile(`(?s)id="contributor-list">.*?</ul>`)
|
||||
bs = readAll(htmlFile)
|
||||
bs = authorsRe.ReplaceAll(bs, []byte("id=\"contributor-list\">\n"+replacement+"\n </ul>"))
|
||||
|
||||
if err := ioutil.WriteFile(htmlFile, bs, 0644); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func readAll(path string) []byte {
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
bs, err := ioutil.ReadAll(fd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return bs
|
||||
}
|
||||
46
script/benchfilter.go
Normal file
46
script/benchfilter.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// Neatly format benchmarking output which otherwise looks like crap.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
var (
|
||||
benchRe = regexp.MustCompile(`^(Bench[^\s]+)\s+(\d+)\s+(\d+ ns/op)\s*(\d+ B/op)?\s*(\d+ allocs/op)?`)
|
||||
)
|
||||
|
||||
func main() {
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0)
|
||||
br := bufio.NewScanner(os.Stdin)
|
||||
n := 0
|
||||
|
||||
for br.Scan() {
|
||||
line := br.Text()
|
||||
|
||||
if match := benchRe.FindStringSubmatch(line); match != nil {
|
||||
n++
|
||||
for i := range match[2:] {
|
||||
match[2+i] = fmt.Sprintf("%16s", match[2+i])
|
||||
}
|
||||
tw.Write([]byte(strings.Join(match[1:], "\t") + "\n"))
|
||||
} else if n > 0 && strings.HasPrefix(line, "ok") {
|
||||
n = 0
|
||||
tw.Flush()
|
||||
fmt.Printf("%s\n\n", line)
|
||||
}
|
||||
}
|
||||
tw.Flush()
|
||||
}
|
||||
72
script/changelog.go
Normal file
72
script/changelog.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
subjectIssues = regexp.MustCompile(`^([^(]+)\s+\((?:fixes|ref) ([^)]+)\)(?:[^\w])?$`)
|
||||
issueNumbers = regexp.MustCompile(`(#\d+)`)
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
// Display changelog since the version given on the command line, or
|
||||
// figure out the last release if there were no arguments.
|
||||
var prevRel string
|
||||
if flag.NArg() > 0 {
|
||||
prevRel = flag.Arg(0)
|
||||
} else {
|
||||
bs, err := runError("git", "describe", "--abbrev=0", "HEAD^")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
prevRel = string(bs)
|
||||
}
|
||||
|
||||
// Get the git log with subject and author nickname
|
||||
bs, err := runError("git", "log", "--reverse", "--pretty=format:%s|%aN", prevRel+"..")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Split into lines
|
||||
for _, line := range bytes.Split(bs, []byte{'\n'}) {
|
||||
// Split into subject and author
|
||||
fields := bytes.Split(line, []byte{'|'})
|
||||
subj := fields[0]
|
||||
author := fields[1]
|
||||
|
||||
// Check if subject contains a "(fixes ...)" or "(ref ...)""
|
||||
if m := subjectIssues.FindSubmatch(subj); len(m) > 0 {
|
||||
// Find all issue numbers
|
||||
issues := issueNumbers.FindAll(m[2], -1)
|
||||
|
||||
// Format a changelog entry
|
||||
fmt.Printf("* %s (%s, @%s)\n", m[1], bytes.Join(issues, []byte(", ")), author)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runError(cmd string, args ...string) ([]byte, error) {
|
||||
ecmd := exec.Command(cmd, args...)
|
||||
bs, err := ecmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.TrimSpace(bs), nil
|
||||
}
|
||||
132
script/check-authors.go
Normal file
132
script/check-authors.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// Checks for authors that are not mentioned in AUTHORS
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// list of commits that we don't include in our checks; because they are
|
||||
// legacy things that don't check code, are committed with incorrect address,
|
||||
// or for other reasons.
|
||||
var excludeCommits = stringSetFromStrings([]string{
|
||||
"63bd0136fb40a91efaa279cb4b4159d82e8e6904",
|
||||
"4e2feb6fbc791bb8a2daf0ab8efb10775d66343e",
|
||||
"f2459ef3319b2f060dbcdacd0c35a1788a94b8bd",
|
||||
"b61f418bf2d1f7d5a9d7088a20a2a448e5e66801",
|
||||
"a9339d0627fff439879d157c75077f02c9fac61b",
|
||||
"254c63763a3ad42fd82259f1767db526cff94a14",
|
||||
"4b76ec40c07078beaa2c5e250ed7d9bd6276a718",
|
||||
"32a76901a91ff0f663db6f0830e0aedec946e4d0",
|
||||
"3626003f680bad3e63677982576d3a05421e88e9",
|
||||
})
|
||||
|
||||
func init() {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetFlags(0)
|
||||
}
|
||||
|
||||
func main() {
|
||||
actual := actualAuthorEmails()
|
||||
listed := listedAuthorEmails()
|
||||
missing := actual.except(listed)
|
||||
if len(missing) > 0 {
|
||||
log.Println("Missing authors:")
|
||||
for author := range missing {
|
||||
log.Println(" ", author)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// actualAuthorEmails returns the set of author emails found in the actual git
|
||||
// commit log, except those in excluded commits.
|
||||
func actualAuthorEmails() stringSet {
|
||||
cmd := exec.Command("git", "log", "--format=%H %ae")
|
||||
bs, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Fatal("authorEmails:", err)
|
||||
}
|
||||
|
||||
authors := newStringSet()
|
||||
for _, line := range bytes.Split(bs, []byte{'\n'}) {
|
||||
fields := strings.Fields(string(line))
|
||||
if len(fields) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
hash, author := fields[0], fields[1]
|
||||
if excludeCommits.has(hash) {
|
||||
continue
|
||||
}
|
||||
|
||||
authors.add(author)
|
||||
}
|
||||
|
||||
return authors
|
||||
}
|
||||
|
||||
// listedAuthorEmails returns the set of author emails mentioned in AUTHORS
|
||||
func listedAuthorEmails() stringSet {
|
||||
bs, err := ioutil.ReadFile("AUTHORS")
|
||||
if err != nil {
|
||||
log.Fatal("listedAuthorEmails:", err)
|
||||
}
|
||||
|
||||
emailRe := regexp.MustCompile(`<([^>]+)>`)
|
||||
matches := emailRe.FindAllStringSubmatch(string(bs), -1)
|
||||
|
||||
authors := newStringSet()
|
||||
for _, match := range matches {
|
||||
authors.add(match[1])
|
||||
}
|
||||
return authors
|
||||
}
|
||||
|
||||
// A simple string set type
|
||||
|
||||
type stringSet map[string]struct{}
|
||||
|
||||
func newStringSet() stringSet {
|
||||
return make(stringSet)
|
||||
}
|
||||
|
||||
func stringSetFromStrings(ss []string) stringSet {
|
||||
s := newStringSet()
|
||||
for _, e := range ss {
|
||||
s.add(e)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s stringSet) add(e string) {
|
||||
s[e] = struct{}{}
|
||||
}
|
||||
|
||||
func (s stringSet) has(e string) bool {
|
||||
_, ok := s[e]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s stringSet) except(other stringSet) stringSet {
|
||||
diff := newStringSet()
|
||||
for e := range s {
|
||||
if !other.has(e) {
|
||||
diff.add(e)
|
||||
}
|
||||
}
|
||||
return diff
|
||||
}
|
||||
72
script/check-copyright.go
Normal file
72
script/check-copyright.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
// Checks for files missing copyright notice
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// File extensions to check
|
||||
var checkExts = map[string]bool{
|
||||
".go": true,
|
||||
}
|
||||
|
||||
// Valid copyright headers, searched for in the top five lines in each file.
|
||||
var copyrightRegexps = []string{
|
||||
`Copyright`,
|
||||
`package auto`,
|
||||
`automatically generated by genxdr`,
|
||||
}
|
||||
|
||||
var copyrightRe = regexp.MustCompile(strings.Join(copyrightRegexps, "|"))
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
for _, dir := range flag.Args() {
|
||||
err := filepath.Walk(dir, checkCopyright)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkCopyright(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
if !checkExts[filepath.Ext(path)] {
|
||||
return nil
|
||||
}
|
||||
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
scanner := bufio.NewScanner(fd)
|
||||
for i := 0; scanner.Scan() && i < 5; i++ {
|
||||
if copyrightRe.MatchString(scanner.Text()) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("Missing copyright in %s?", path)
|
||||
}
|
||||
106
script/genassets.go
Normal file
106
script/genassets.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"go/format"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
var tpl = template.Must(template.New("assets").Parse(`package auto
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
const (
|
||||
AssetsBuildDate = "{{.BuildDate}}"
|
||||
)
|
||||
|
||||
func Assets() map[string][]byte {
|
||||
var assets = make(map[string][]byte, {{.Assets | len}})
|
||||
{{range $asset := .Assets}}
|
||||
assets["{{$asset.Name}}"], _ = base64.StdEncoding.DecodeString("{{$asset.Data}}"){{end}}
|
||||
return assets
|
||||
}
|
||||
|
||||
`))
|
||||
|
||||
type asset struct {
|
||||
Name string
|
||||
Data string
|
||||
}
|
||||
|
||||
var assets []asset
|
||||
|
||||
func walkerFor(basePath string) filepath.WalkFunc {
|
||||
return func(name string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(filepath.Base(name), ".") {
|
||||
// Skip dotfiles
|
||||
return nil
|
||||
}
|
||||
|
||||
if info.Mode().IsRegular() {
|
||||
fd, err := os.Open(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
gw := gzip.NewWriter(&buf)
|
||||
io.Copy(gw, fd)
|
||||
fd.Close()
|
||||
gw.Flush()
|
||||
gw.Close()
|
||||
|
||||
name, _ = filepath.Rel(basePath, name)
|
||||
assets = append(assets, asset{
|
||||
Name: filepath.ToSlash(name),
|
||||
Data: base64.StdEncoding.EncodeToString(buf.Bytes()),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type templateVars struct {
|
||||
Assets []asset
|
||||
BuildDate string
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
filepath.Walk(flag.Arg(0), walkerFor(flag.Arg(0)))
|
||||
var buf bytes.Buffer
|
||||
tpl.Execute(&buf, templateVars{
|
||||
Assets: assets,
|
||||
BuildDate: time.Now().UTC().Format(http.TimeFormat),
|
||||
})
|
||||
bs, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Stdout.Write(bs)
|
||||
}
|
||||
174
script/transifexdl.go
Normal file
174
script/transifexdl.go
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type stat struct {
|
||||
Translated int `json:"translated_entities"`
|
||||
Untranslated int `json:"untranslated_entities"`
|
||||
}
|
||||
|
||||
type translation struct {
|
||||
Content string
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Lshortfile)
|
||||
|
||||
if u, p := userPass(); u == "" || p == "" {
|
||||
log.Fatal("Need environment variables TRANSIFEX_USER and TRANSIFEX_PASS")
|
||||
}
|
||||
|
||||
curValidLangs := map[string]bool{}
|
||||
for _, lang := range loadValidLangs() {
|
||||
curValidLangs[lang] = true
|
||||
}
|
||||
log.Println(curValidLangs)
|
||||
|
||||
resp := req("https://www.transifex.com/api/2/project/syncthing/resource/gui/stats")
|
||||
|
||||
var stats map[string]stat
|
||||
err := json.NewDecoder(resp.Body).Decode(&stats)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
names := make(map[string]string)
|
||||
|
||||
var langs []string
|
||||
for code, stat := range stats {
|
||||
code = strings.Replace(code, "_", "-", 1)
|
||||
pct := 100 * stat.Translated / (stat.Translated + stat.Untranslated)
|
||||
if pct < 75 || !curValidLangs[code] && pct < 95 {
|
||||
log.Printf("Skipping language %q (too low completion ratio %d%%)", code, pct)
|
||||
os.Remove("lang-" + code + ".json")
|
||||
continue
|
||||
}
|
||||
|
||||
langs = append(langs, code)
|
||||
names[code] = languageName(code)
|
||||
if code == "en" {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("Updating language %q", code)
|
||||
|
||||
resp := req("https://www.transifex.com/api/2/project/syncthing/resource/gui/translation/" + code)
|
||||
var t translation
|
||||
err := json.NewDecoder(resp.Body).Decode(&t)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
fd, err := os.Create("lang-" + code + ".json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fd.WriteString(t.Content)
|
||||
fd.Close()
|
||||
}
|
||||
|
||||
saveValidLangs(langs)
|
||||
saveLanguageNames(names)
|
||||
}
|
||||
|
||||
func saveValidLangs(langs []string) {
|
||||
sort.Strings(langs)
|
||||
fd, err := os.Create("valid-langs.js")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprint(fd, "var validLangs = ")
|
||||
json.NewEncoder(fd).Encode(langs)
|
||||
fd.Close()
|
||||
}
|
||||
|
||||
func saveLanguageNames(names map[string]string) {
|
||||
fd, err := os.Create("prettyprint.js")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Fprint(fd, "var langPrettyprint = ")
|
||||
json.NewEncoder(fd).Encode(names)
|
||||
fd.Close()
|
||||
}
|
||||
|
||||
func userPass() (string, string) {
|
||||
user := os.Getenv("TRANSIFEX_USER")
|
||||
pass := os.Getenv("TRANSIFEX_PASS")
|
||||
return user, pass
|
||||
}
|
||||
|
||||
func req(url string) *http.Response {
|
||||
user, pass := userPass()
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
req.SetBasicAuth(user, pass)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
func loadValidLangs() []string {
|
||||
fd, err := os.Open("valid-langs.js")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
bs, err := ioutil.ReadAll(fd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var langs []string
|
||||
exp := regexp.MustCompile(`\[([a-zA-Z@",-]+)\]`)
|
||||
if matches := exp.FindSubmatch(bs); len(matches) == 2 {
|
||||
langs = strings.Split(string(matches[1]), ",")
|
||||
for i := range langs {
|
||||
// Remove quotes
|
||||
langs[i] = langs[i][1 : len(langs[i])-1]
|
||||
}
|
||||
}
|
||||
|
||||
return langs
|
||||
}
|
||||
|
||||
type languageResponse struct {
|
||||
Code string
|
||||
Name string
|
||||
}
|
||||
|
||||
func languageName(code string) string {
|
||||
var lang languageResponse
|
||||
resp := req("https://www.transifex.com/api/2/language/" + code)
|
||||
defer resp.Body.Close()
|
||||
json.NewDecoder(resp.Body).Decode(&lang)
|
||||
if lang.Name == "" {
|
||||
return code
|
||||
}
|
||||
return lang.Name
|
||||
}
|
||||
124
script/translate.go
Normal file
124
script/translate.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright (C) 2014 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// +build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var trans = make(map[string]string)
|
||||
var attrRe = regexp.MustCompile(`\{\{'([^']+)'\s+\|\s+translate\}\}`)
|
||||
|
||||
func generalNode(n *html.Node, filename string) {
|
||||
translate := false
|
||||
if n.Type == html.ElementNode {
|
||||
if n.Data == "translate" { // for <translate>Text</translate>
|
||||
translate = true
|
||||
} else {
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == "translate" {
|
||||
translate = true
|
||||
break
|
||||
} else {
|
||||
if matches := attrRe.FindStringSubmatch(a.Val); len(matches) == 2 {
|
||||
translation(matches[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if n.Type == html.TextNode {
|
||||
v := strings.TrimSpace(n.Data)
|
||||
if len(v) > 1 && !(strings.HasPrefix(v, "{{") && strings.HasSuffix(v, "}}")) {
|
||||
log.Println("Untranslated text node (" + filename + "):")
|
||||
log.Print("\t" + v)
|
||||
}
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if translate {
|
||||
inTranslate(c, filename)
|
||||
} else {
|
||||
generalNode(c, filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func inTranslate(n *html.Node, filename string) {
|
||||
if n.Type == html.TextNode {
|
||||
translation(n.Data)
|
||||
} else {
|
||||
log.Println("translate node with non-text child < (" + filename + ")")
|
||||
log.Println(n)
|
||||
}
|
||||
if n.FirstChild != nil {
|
||||
log.Println("translate node has children (" + filename + "):")
|
||||
log.Println(n.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func translation(v string) {
|
||||
v = strings.TrimSpace(v)
|
||||
if _, ok := trans[v]; !ok {
|
||||
av := strings.Replace(v, "{%", "{{", -1)
|
||||
av = strings.Replace(av, "%}", "}}", -1)
|
||||
trans[v] = av
|
||||
}
|
||||
}
|
||||
|
||||
func walkerFor(basePath string) filepath.WalkFunc {
|
||||
return func(name string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if filepath.Ext(name) == ".html" && info.Mode().IsRegular() {
|
||||
fd, err := os.Open(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
doc, err := html.Parse(fd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fd.Close()
|
||||
generalNode(doc, filepath.Base(name))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
fd, err := os.Open(os.Args[1])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = json.NewDecoder(fd).Decode(&trans)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fd.Close()
|
||||
|
||||
var guiDir = os.Args[2]
|
||||
|
||||
filepath.Walk(guiDir, walkerFor(guiDir))
|
||||
|
||||
bs, err := json.MarshalIndent(trans, "", " ")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Stdout.Write(bs)
|
||||
os.Stdout.WriteString("\n")
|
||||
}
|
||||
Reference in New Issue
Block a user