chore(gui): update dependency copyrights, add script for periodic maintenance (#10067)

### Purpose

This PR parses the output of `go mod graph` and updates the copyright
list in our [about
modal](486eebc4ac/gui/default/syncthing/core/aboutModalView.html (L38)).

If there are no changes, the program is silent. Otherwise, it reports
what additions, and deletions it made. It does not rewrite existing
copyright notices, but it does remove notices that we no longer use, as
well as add new ones.

It uses a GitHub API to try to determine the copyright string in the
license file. If one is not found, it defaults to `Copyright ©
<this_year> the <owner/repo> authors`. If a proper copyright is found,
simply update the notice in `aboutModalView.html`, and it will be used.
This commit is contained in:
Ross Smith II
2025-04-22 22:41:05 -07:00
committed by GitHub
parent 486eebc4ac
commit 93ae30d889
3 changed files with 557 additions and 18 deletions

View File

@@ -23,6 +23,7 @@ case "${1:-default}" in
prerelease)
script authors
script copyrights
build weblate
pushd man ; ./refresh.sh ; popd
git add -A gui man AUTHORS

View File

@@ -38,45 +38,94 @@ Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Tomasz Wilczyński, Al
<div id="about-includes" class="tab-pane">
<p translate>Syncthing includes the following software or portions thereof:</p>
<ul class="list-unstyled two-columns" id="copyright-notices">
<li><a href="http://getbootstrap.com/">Bootstrap</a>, Copyright &copy; 2011-2016 Twitter, Inc.</li>
<li><a href="https://getbootstrap.com/">Bootstrap</a>, Copyright &copy; 2011-2016 Twitter, Inc.</li>
<li><a href="https://angularjs.org/">AngularJS</a>, Copyright &copy; 2010-2014, 2016 Google, Inc.</li>
<li><a href="http://www.daterangepicker.com/">Date Range Picker</a>, Copyright &copy; 2012-2018 Dan Grossman.</li>
<li><a href="https://www.daterangepicker.com/">Date Range Picker</a>, Copyright &copy; 2012-2018 Dan Grossman.</li>
<li><a href="https://github.com/mar10/fancytree">JQuery Fancytree Plugin</a>, Copyright &copy; 2008-2018 Martin Wendt.</li>
<li><a href="https://fontawesome.com/">Font Awesome</a>Copyright &copy; 2024 Fonticons, Inc.</li>
<li><a href="https://forkaweso.me/Fork-Awesome/">Fork Awesome</a>, Copyright &copy; 2018 Dave Gandy &amp; Fork Awesome.</li>
<li><a href="http://jquery.com/">jQuery JavaScript Library</a>, Copyright &copy; jQuery Foundation and other contributors.</li>
<li><a href="http://momentjs.com/">moment.js</a>, Copyright &copy; JS Foundation and other contributors.</li>
<li><a href="https://evanhahn.github.io/HumanizeDuration.js/">HumanDuration.js</a>, Copyright &copy; 2013-2024 Evan Hahn, portions copyright &copy; 2024 Ross Smith II.</li>
<li><a href="https://jquery.com/">jQuery JavaScript Library</a>, Copyright &copy; jQuery Foundation and other contributors.</li>
<li><a href="https://leafletjs.com/">leaflet.js</a>, Copyright &copy; 2010-2025 Volodymyr Agafonkin, Copyright &copy; 2010-2011 CloudMade.</li>
<li><a href="https://momentjs.com/">moment.js</a>, Copyright &copy; JS Foundation and other contributors.</li>
<li><a href="https://golang.org/">The Go Programming Language</a>, Copyright &copy; 2009 The Go Authors.</li>
<li><a href="https://prometheus.io/">Prometheus</a>, Copyright &copy; 2012-2015 The Prometheus Authors.</li>
<li><a href="https://github.com/AudriusButkevicius/go-nat-pmp">AudriusButkevicius/go-nat-pmp</a>, Copyright &copy; 2013 John Howard Palevich.</li>
<li><a href="https://github.com/AudriusButkevicius/recli">AudriusButkevicius/recli</a>, Copyright &copy; 2019 Audrius Butkevicius.</li>
<li><a href="https://github.com/Azure/azure-sdk-for-go">Azure/azure-sdk-for-go</a>, Copyright &copy; Microsoft Corporation.</li>
<li><a href="https://github.com/Azure/go-ntlmssp">Azure/go-ntlmssp</a>, Copyright &copy; 2016 Microsoft.</li>
<li><a href="https://github.com/alecthomas/kong">alecthomas/kong</a>, Copyright &copy; 2018 Alec Thomas.</li>
<li><a href="https://github.com/aws/aws-sdk-go">aws/aws-sdk-go</a>, Copyright &copy; 2015 Amazon.com, Inc. or its affiliates, Copyright 2014-2015 Stripe, Inc.</li>
<li><a href="https://github.com/beorn7/perks">beorn7/perks</a>, Copyright &copy; 2013 Blake Mizerany.</li>
<li><a href="https://github.com/pierrec/lz4">pierrec/lz4</a>, Copyright &copy; 2015 Pierre Curto.</li>
<li><a href="https://github.com/calmh/du">calmh/du</a>, Public domain.</li>
<li><a href="https://github.com/calmh/incontainer">calmh/incontainer</a>, Copyright &copy; 2022 calmh.</li>
<li><a href="https://github.com/calmh/xdr">calmh/xdr</a>, Copyright &copy; 2014 Jakob Borg.</li>
<li><a href="https://github.com/ccding/go-stun">ccding/go-stun</a>, Copyright &copy; 2016 Cong Ding.</li>
<li><a href="https://github.com/cenkalti/backoff">cenkalti/backoff</a>, Copyright &copy; 2014 Cenk Altı.</li>
<li><a href="https://github.com/certifi/gocertifi">certifi/gocertifi</a>, Copyright &copy; 2025, the certifi/gocertifi authors.</li>
<li><a href="https://github.com/cespare/xxhash">cespare/xxhash</a>, Copyright &copy; 2016 Caleb Spare.</li>
<li><a href="https://github.com/chmduquesne/rollinghash">chmduquesne/rollinghash</a>, Copyright &copy; 2015 Christophe-Marie Duquesne.</li>
<li><a href="https://github.com/cpuguy83/go-md2man">cpuguy83/go-md2man</a>, Copyright &copy; 2014 Brian Goff.</li>
<li><a href="https://github.com/d4l3k/messagediff">d4l3k/messagediff</a>, Copyright &copy; 2015 Tristan Rice.</li>
<li><a href="https://github.com/davecgh/go-spew">davecgh/go-spew</a>, Copyright &copy; 2012-2016 Dave Collins <dave@davec.name>.</li>
<li><a href="https://github.com/ebitengine/purego">ebitengine/purego</a>, Copyright &copy; 2022 The Ebitengine Authors.</li>
<li><a href="https://github.com/fsnotify/fsnotify">fsnotify/fsnotify</a>, Copyright &copy; 2012 The Go Authors.</li>
<li><a href="https://github.com/getsentry/raven-go">getsentry/raven-go</a>, Copyright &copy; 2013 Apollic Software, LLC.</li>
<li><a href="https://github.com/go-asn1-ber/asn1-ber">go-asn1-ber/asn1-ber</a>, Copyright &copy; 2011-2015 Michael Mitton (mmitton@gmail.com).</li>
<li><a href="https://github.com/go-ldap/ldap">go-ldap/ldap</a>, Copyright &copy; 2011-2015 Michael Mitton (mmitton@gmail.com).</li>
<li><a href="https://github.com/go-ole/go-ole">go-ole/go-ole</a>, Copyright &copy; 2013-2017 Yasuhiro Matsumoto, <mattn.jp@gmail.com>.</li>
<li><a href="https://github.com/go-task/slim-sprig">go-task/slim-sprig</a>, Copyright &copy; 2013-2020 Masterminds.</li>
<li><a href="https://github.com/uber-go/automaxprocs">go.uber.org/automaxprocs</a>, Copyright &copy; 2017 Uber Technologies, Inc.</li>
<li><a href="https://github.com/uber-go/mock">go.uber.org/mock</a>, Copyright &copy; 2010-2022 Google LLC.</li>
<li><a href="https://github.com/gobwas/glob">gobwas/glob</a>, Copyright &copy; 2016 Sergey Kamardin.</li>
<li><a href="https://github.com/golang/groupcache">golang/groupcache</a>, Copyright &copy; 2013 Google Inc.</li>
<li><a href="https://github.com/golang/protobuf">golang/protobuf</a>, Copyright &copy; 2010 The Go Authors.</li>
<li><a href="https://github.com/gofrs/flock">gofrs/flock</a>, Copyright &copy; 2018-2025, The Gofrs.</li>
<li><a href="https://github.com/golang/snappy">golang/snappy</a>, Copyright &copy; 2011 The Snappy-Go Authors.</li>
<li><a href="https://github.com/protocolbuffers/protobuf-go">google.golang.org/protobuf</a>, Copyright &copy; 2018 The Go Authors.</li>
<li><a href="https://github.com/google/pprof">google/pprof</a>, Copyright &copy; 2016 Google Inc.</li>
<li><a href="https://github.com/google/uuid">google/uuid</a>, Copyright &copy; 2009,2014 Google Inc.</li>
<li><a href="https://github.com/go-yaml/yaml">gopkg.in/yaml.v3</a>, Copyright &copy; 2006-2010 Kirill Simonov.</li>
<li><a href="https://github.com/greatroar/blobloom">greatroar/blobloom</a>, Copyright &copy; 2020-2024 the Blobloom authors.</li>
<li><a href="https://github.com/hashicorp/errwrap">hashicorp/errwrap</a>, Copyright &copy; 2014 HashiCorp, Inc.</li>
<li><a href="https://github.com/hashicorp/go-multierror">hashicorp/go-multierror</a>, Copyright &copy; 2014 HashiCorp, Inc.</li>
<li><a href="https://github.com/hashicorp/golang-lru">hashicorp/golang-lru</a>, Copyright &copy; 2014 HashiCorp, Inc.</li>
<li><a href="https://github.com/jackpal/gateway">jackpal/gateway</a>, Copyright &copy; 2010 Jack Palevich.</li>
<li><a href="https://github.com/jackpal/go-nat-pmp">jackpal/go-nat-pmp</a>, Copyright 2013 John Howard Palevich.</li>
<li><a href="https://github.com/jmespath/go-jmespath">jmespath/go-jmespath</a>, Copyright &copy; 2015 James Saryerwinnie.</li>
<li><a href="https://github.com/julienschmidt/httprouter">julienschmidt/httprouter</a>, Copyright &copy; 2013, Julien Schmidt.</li>
<li><a href="https://github.com/kballard/go-shellquote">kballard/go-shellquote</a>, Copyright &copy; 2014 Kevin Ballard.</li>
<li><a href="https://github.com/mattn/go-isatty">mattn/go-isatty</a>, Copyright &copy; Yasuhiro MATSUMOTO.</li>
<li><a href="https://github.com/matttproud/golang_protobuf_extensions">matttproud/golang_protobuf_extensions</a>, Copyright &copy; 2012 Matt T. Proud.</li>
<li><a href="https://github.com/klauspost/compress">klauspost/compress</a>, Copyright &copy; 2012 The Go Authors.</li>
<li><a href="https://github.com/lufia/plan9stats">lufia/plan9stats</a>, Copyright &copy; 2019, KADOTA, Kyohei.</li>
<li><a href="https://github.com/maruel/panicparse">maruel/panicparse</a>, Copyright 2015 Marc-Antoine Ruel.</li>
<li><a href="https://github.com/maxbrunsfeld/counterfeiter">maxbrunsfeld/counterfeiter</a>, Copyright &copy; 2014 maxbrunsfeld.</li>
<li><a href="https://github.com/maxmind/geoipupdate">maxmind/geoipupdate</a>, Copyright &copy; 2018-2024 by MaxMind, Inc.</li>
<li><a href="https://github.com/miscreant/miscreant.go">miscreant/miscreant.go</a>, Copyright &copy; 2017-2019 The Miscreant Developers.</li>
<li><a href="https://github.com/munnerz/goautoneg">munnerz/goautoneg</a>, Copyright &copy; 2011, Open Knowledge Foundation Ltd.</li>
<li><a href="https://github.com/nxadm/tail">nxadm/tail</a>, Copyright &copy; 2014 ActiveState.</li>
<li><a href="https://github.com/onsi/ginkgo">onsi/ginkgo</a>, Copyright &copy; 2013-2014 Onsi Fakhouri.</li>
<li><a href="https://github.com/oschwald/geoip2-golang">oschwald/geoip2-golang</a>, Copyright &copy; 2015, Gregory J. Oschwald.</li>
<li><a href="https://github.com/oschwald/maxminddb-golang">oschwald/maxminddb-golang</a>, Copyright &copy; 2015, Gregory J. Oschwald.</li>
<li><a href="https://github.com/petermattis/goid">petermattis/goid</a>, Copyright &copy; 2015-2016 Peter Mattis.</li>
<li><a href="https://github.com/pierrec/lz4">pierrec/lz4</a>, Copyright &copy; 2015 Pierre Curto.</li>
<li><a href="https://github.com/pkg/errors">pkg/errors</a>, Copyright &copy; 2015, Dave Cheney.</li>
<li><a href="https://github.com/pmezard/go-difflib">pmezard/go-difflib</a>, Copyright &copy; 2013, Patrick Mezard.</li>
<li><a href="https://github.com/posener/complete">posener/complete</a>, Copyright &copy; 2017 Eyal Posener.</li>
<li><a href="https://github.com/power-devops/perfstat">power-devops/perfstat</a>, Copyright &copy; 2020 Power DevOps.</li>
<li><a href="https://github.com/puzpuzpuz/xsync">puzpuzpuz/xsync</a>, Copyright &copy; 2025, the puzpuzpuz/xsync authors.</li>
<li><a href="https://github.com/quic-go/quic-go">quic-go/quic-go</a>, Copyright &copy; 2016 the quic-go authors & Google, Inc.</li>
<li><a href="https://github.com/rabbitmq/amqp091-go">rabbitmq/amqp091-go</a>, Copyright &copy; 2021 VMware, Inc. or its affiliates.</li>
<li><a href="https://github.com/rcrowley/go-metrics">rcrowley/go-metrics</a>, Copyright &copy; 2012 Richard Crowley.</li>
<li><a href="https://github.com/sasha-s/go-deadlock">sasha-s/go-deadlock</a>, Copyright &copy; 2016 sasha-s.</li>
<li><a href="https://github.com/riywo/loginshell">riywo/loginshell</a>, Copyright &copy; 2019 Ryosuke IWANAGA.</li>
<li><a href="https://github.com/russross/blackfriday">russross/blackfriday</a>, Copyright &copy; 2011 Russ Ross.</li>
<li><a href="https://github.com/shirou/gopsutil">shirou/gopsutil</a>, Copyright &copy; 2014, WAKAYAMA Shirou.</li>
<li><a href="https://github.com/kubernetes-sigs/yaml">sigs.k8s.io/yaml</a>, Copyright &copy; 2014 Sam Ghods.</li>
<li><a href="https://github.com/stretchr/objx">stretchr/objx</a>, Copyright &copy; 2014 Stretchr, Inc.</li>
<li><a href="https://github.com/stretchr/testify">stretchr/testify</a>, Copyright &copy; 2012-2020 Mat Ryer, Tyler Bunnell and contributors.</li>
<li><a href="https://github.com/syncthing/notify">syncthing/notify</a>, Copyright &copy; 2014-2015 The Notify Authors.</li>
<li><a href="https://github.com/syndtr/goleveldb">syndtr/goleveldb</a>, Copyright &copy; 2012 Suryandaru Triandana.</li>
<li><a href="https://github.com/thejerf/suture">thejerf/suture</a>, Copyright &copy; 2014-2015 Barracuda Networks, Inc.</li>
<li><a href="https://github.com/urfave/cli">urfave/cli</a>, Copyright &copy; 2016 Jeremy Saenz &amp; Contributors.</li>
<li><a href="https://github.com/tklauser/go-sysconf">tklauser/go-sysconf</a>, Copyright &copy; 2018-2022, Tobias Klauser.</li>
<li><a href="https://github.com/tklauser/numcpus">tklauser/numcpus</a>, Copyright &copy; 2018-2024 Tobias Klauser.</li>
<li><a href="https://github.com/urfave/cli">urfave/cli</a>, Copyright &copy; 2016 Jeremy Saenz & Contributors.</li>
<li><a href="https://github.com/vitrun/qart">vitrun/qart</a>, Copyright &copy; 2010-2011 The Go Authors.</li>
<li><a href="https://gopkg.in/asn1-ber.v1">gopkg.in/asn1-ber.v1</a>, Copyright &copy; 2011-2015 Michael Mitton, portions Copyright &copy; 2015-2016 go-asn1-ber Authors.</li>
<li><a href="https://gopkg.in/ldap.v2">gopkg.in/ldap.v2</a>, Copyright &copy; 2011-2015 Michael Mitton, portions Copyright &copy; 2015-2016 go-ldap Authors.</li>
<li><a href="https://golang.org">The Go Programming Language</a>, Copyright &copy; 2009 The Go Authors.</li>
<li>Font Awesome by Dave Gandy - <a href="http://fontawesome.io/">http://fontawesome.io</a></li>
<li><a href="https://github.com/willabides/kongplete">willabides/kongplete</a>, Copyright &copy; 2020 WillAbides.</li>
<li><a href="https://github.com/yusufpapurcu/wmi">yusufpapurcu/wmi</a>, Copyright &copy; 2013 Stack Exchange.</li>
</ul>
</div>

489
script/copyrights.go Normal file
View File

@@ -0,0 +1,489 @@
// Copyright (C) 2025 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 https://mozilla.org/MPL/2.0/.
//go:build ignore
// +build ignore
// Updates the list of software copyrights in aboutModalView.html based on the
// output of `go mod graph`.
package main
import (
"bufio"
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"regexp"
"slices"
"strconv"
"strings"
"time"
"golang.org/x/net/html"
)
var copyrightMap = map[string]string{
// https://github.com/aws/aws-sdk-go/blob/main/NOTICE.txt#L2
"aws/aws-sdk-go": "Copyright &copy; 2015 Amazon.com, Inc. or its affiliates, Copyright 2014-2015 Stripe, Inc",
// https://github.com/ccding/go-stun/blob/master/main.go#L1
"ccding/go-stun": "Copyright &copy; 2016 Cong Ding",
// https://github.com/search?q=repo%3Acertifi%2Fgocertifi%20copyright&type=code
// "certifi/gocertifi": "No copyrights found",
// https://github.com/search?q=repo%3Aebitengine%2Fpurego%20copyright&type=code
"ebitengine/purego": "Copyright &copy; 2022 The Ebitengine Authors",
// https://github.com/search?q=repo%3Agoogle%2Fpprof%20copyright&type=code
"google/pprof": "Copyright &copy; 2016 Google Inc",
// https://github.com/greatroar/blobloom/blob/master/README.md?plain=1#L74
"greatroar/blobloom": "Copyright &copy; 2020-2024 the Blobloom authors",
// https://github.com/jmespath/go-jmespath/blob/master/NOTICE#L2
"jmespath/go-jmespath": "Copyright &copy; 2015 James Saryerwinnie",
// https://github.com/maxmind/geoipupdate/blob/main/README.md?plain=1#L140
"maxmind/geoipupdate": "Copyright &copy; 2018-2024 by MaxMind, Inc",
// https://github.com/search?q=repo%3Apuzpuzpuz%2Fxsync%20copyright&type=code
// "puzpuzpuz/xsync": "No copyrights found",
// https://github.com/search?q=repo%3Atklauser%2Fnumcpus%20copyright&type=code
"tklauser/numcpus": "Copyright &copy; 2018-2024 Tobias Klauser",
// https://github.com/search?q=repo%3Auber-go%2Fmock%20copyright&type=code
"go.uber.org/mock": "Copyright &copy; 2010-2022 Google LLC",
}
var urlMap = map[string]string{
"fontawesome.io": "https://github.com/FortAwesome/Font-Awesome",
"go.uber.org/automaxprocs": "https://github.com/uber-go/automaxprocs",
"go.uber.org/mock": "https://github.com/uber-go/mock",
"google.golang.org/protobuf": "https://github.com/protocolbuffers/protobuf-go",
"gopkg.in/yaml.v2": "", // ignore, as gopkg.in/yaml.v3 supersedes
"gopkg.in/yaml.v3": "https://github.com/go-yaml/yaml",
"sigs.k8s.io/yaml": "https://github.com/kubernetes-sigs/yaml",
}
const htmlFile = "gui/default/syncthing/core/aboutModalView.html"
type Type int
const (
// TypeJS defines non-Go copyright notices
TypeJS Type = iota
// TypeKeep defines Go copyright notices for packages that are still used.
TypeKeep
// TypeToss defines Go copyright notices for packages that are no longer used.
TypeToss
// TypeNew defines Go copyright notices for new packages found via `go mod graph`.
TypeNew
)
type CopyrightNotice struct {
Type Type
Name string
HTML string
Module string
URL string
Copyright string
RepoURL string
RepoCopyrights []string
}
var copyrightRe = regexp.MustCompile(`(?s)id="copyright-notices">(.+?)</ul>`)
func main() {
bs := readAll(htmlFile)
matches := copyrightRe.FindStringSubmatch(string(bs))
if len(matches) <= 1 {
log.Fatal("Cannot find id copyright-notices in ", htmlFile)
}
modules := getModules()
notices := parseCopyrightNotices(matches[1])
old := len(notices)
// match up modules to notices
matched := map[string]bool{}
removes := 0
for i, notice := range notices {
if notice.Type == TypeJS {
continue
}
found := ""
for _, module := range modules {
if strings.Contains(module, notice.Name) {
found = module
break
}
}
if found != "" {
matched[found] = true
notices[i].Module = found
continue
}
removes++
fmt.Printf("Removing: %-40s %-55s %s\n", notice.Name, notice.URL, notice.Copyright)
notices[i].Type = TypeToss
}
// add new modules to notices
adds := 0
for _, module := range modules {
_, ok := matched[module]
if ok {
continue
}
adds++
notice := CopyrightNotice{}
notice.Name = module
if strings.HasPrefix(notice.Name, "github.com/") {
notice.Name = strings.ReplaceAll(notice.Name, "github.com/", "")
}
notice.Type = TypeNew
url, ok := urlMap[module]
if ok {
notice.URL = url
notice.RepoURL = url
} else {
notice.URL = "https://" + module
notice.RepoURL = "https://" + module
}
notices = append(notices, notice)
}
if removes == 0 && adds == 0 {
// authors.go is quiet, so let's be quiet too.
// fmt.Printf("No changes detected in %d modules and %d notices\n", len(modules), len(notices))
os.Exit(0)
}
// get copyrights via Github API for new modules
notfound := 0
for i, n := range notices {
if n.Type != TypeNew {
continue
}
copyright, ok := copyrightMap[n.Name]
if ok {
notices[i].Copyright = copyright
continue
}
notices[i].Copyright = defaultCopyright(n)
if strings.Contains(n.URL, "github.com/") {
notices[i].RepoURL = notices[i].URL
owner, repo := parseGitHubURL(n.URL)
licenseText := getLicenseText(owner, repo)
notices[i].RepoCopyrights = extractCopyrights(licenseText, n)
if len(notices[i].RepoCopyrights) > 0 {
notices[i].Copyright = notices[i].RepoCopyrights[0]
}
notices[i].HTML = fmt.Sprintf("<li><a href=\"%s\">%s</a>, %s.</li>", n.URL, n.Name, notices[i].Copyright)
if len(notices[i].RepoCopyrights) > 0 {
continue
}
}
fmt.Printf("Copyright not found: %-30s : using %q\n", n.Name, notices[i].Copyright)
notfound++
}
replacements := write(notices, bs)
fmt.Printf("Removed: %3d\n", removes)
fmt.Printf("Added: %3d\n", adds)
fmt.Printf("Copyrights not found: %3d\n", notfound)
fmt.Printf("Old package count: %3d\n", old)
fmt.Printf("New package count: %3d\n", replacements)
}
func write(notices []CopyrightNotice, bs []byte) int {
keys := make([]string, 0, len(notices))
noticeMap := make(map[string]CopyrightNotice, 0)
for _, n := range notices {
if n.Type != TypeKeep && n.Type != TypeNew {
continue
}
if n.Type == TypeNew {
fmt.Printf("Adding: %-40s %-55s %s\n", n.Name, n.URL, n.Copyright)
}
keys = append(keys, n.Name)
noticeMap[n.Name] = n
}
slices.Sort(keys)
indent := " "
replacements := []string{}
for _, n := range notices {
if n.Type != TypeJS {
continue
}
replacements = append(replacements, indent+n.HTML)
}
for _, k := range keys {
n := noticeMap[k]
line := fmt.Sprintf("%s<li><a href=\"%s\">%s</a>, %s.</li>", indent, n.URL, n.Name, n.Copyright)
replacements = append(replacements, line)
}
replacement := strings.Join(replacements, "\n")
bs = copyrightRe.ReplaceAll(bs, []byte("id=\"copyright-notices\">\n"+replacement+"\n </ul>"))
writeFile(htmlFile, string(bs))
return len(replacements)
}
func readAll(path string) []byte {
fd, err := os.Open(path)
if err != nil {
log.Fatal(err)
}
defer fd.Close()
bs, err := io.ReadAll(fd)
if err != nil {
log.Fatal(err)
}
return bs
}
func writeFile(path string, data string) {
err := os.WriteFile(path, []byte(data), 0o644)
if err != nil {
log.Fatal(err)
}
}
func getModules() []string {
cmd := exec.Command("go", "mod", "graph")
output, err := cmd.Output()
if err != nil {
log.Fatal(err)
}
seen := make(map[string]struct{})
scanner := bufio.NewScanner(bytes.NewReader(output))
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) == 0 {
continue
}
if !strings.HasPrefix(fields[0], "github.com/syncthing/syncthing") {
continue
}
// Get left-hand side of dependency pair (before '@')
mod := strings.SplitN(fields[1], "@", 2)[0]
// Keep only first 3 path components
parts := strings.Split(mod, "/")
if len(parts) == 1 {
continue
}
short := strings.Join(parts[:min(len(parts), 3)], "/")
if strings.HasPrefix(short, "golang.org/x") ||
strings.HasPrefix(short, "github.com/prometheus") ||
short == "go" {
continue
}
seen[short] = struct{}{}
}
adds := make([]string, 0)
for k := range seen {
adds = append(adds, k)
}
slices.Sort(adds)
return adds
}
func parseCopyrightNotices(input string) []CopyrightNotice {
doc, err := html.Parse(strings.NewReader("<ul>" + input + "</ul>"))
if err != nil {
log.Fatal(err)
}
var notices []CopyrightNotice
typ := TypeJS
var f func(*html.Node)
f = func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "li" {
var notice CopyrightNotice
var aFound bool
for c := n.FirstChild; c != nil; c = c.NextSibling {
if c.Type == html.ElementNode && c.Data == "a" {
aFound = true
for _, attr := range c.Attr {
if attr.Key == "href" {
notice.URL = attr.Val
}
}
if c.FirstChild != nil && c.FirstChild.Type == html.TextNode {
notice.Name = strings.TrimSpace(c.FirstChild.Data)
}
} else if c.Type == html.TextNode && aFound {
// Anything after <a> is considered the copyright
notice.Copyright = strings.TrimSpace(html.UnescapeString(c.Data))
notice.Copyright = strings.Trim(notice.Copyright, "., ")
}
if typ == TypeJS && strings.Contains(notice.URL, "AudriusButkevicius") {
typ = TypeKeep
}
notice.Type = typ
var buf strings.Builder
_ = html.Render(&buf, n)
notice.HTML = buf.String()
}
notice.Copyright = strings.ReplaceAll(notice.Copyright, "©", "&copy;")
notice.HTML = strings.ReplaceAll(notice.HTML, "©", "&copy;")
notices = append(notices, notice)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
f(doc)
return notices
}
func parseGitHubURL(u string) (string, string) {
parsed, err := url.Parse(u)
if err != nil {
log.Fatal(err)
}
parts := strings.Split(strings.Trim(parsed.Path, "/"), "/")
if len(parts) < 2 {
log.Fatal(fmt.Errorf("invalid GitHub URL: %q", parsed.Path))
}
return parts[0], parts[1]
}
func getLicenseText(owner, repo string) string {
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/license", owner, repo)
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Accept", "application/vnd.github.v3+json")
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
var result struct {
Content string `json:"content"`
Encoding string `json:"encoding"`
}
body, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &result)
if err != nil {
log.Fatal(err)
}
if result.Encoding != "base64" {
log.Fatal(fmt.Sprintf("unexpected encoding: %s", result.Encoding))
}
decoded, err := base64.StdEncoding.DecodeString(result.Content)
if err != nil {
log.Fatal(err)
}
return string(decoded)
}
func extractCopyrights(license string, notice CopyrightNotice) []string {
lines := strings.Split(license, "\n")
re := regexp.MustCompile(`(?i)^\s*(copyright\s*(?:©|\(c\)|&copy;|19|20).*)$`)
copyrights := []string{}
for _, line := range lines {
if matches := re.FindStringSubmatch(strings.TrimSpace(line)); len(matches) == 2 {
copyright := strings.TrimSpace(matches[1])
re := regexp.MustCompile(`(?i)all rights reserved`)
copyright = re.ReplaceAllString(copyright, "")
copyright = strings.ReplaceAll(copyright, "©", "&copy;")
copyright = strings.ReplaceAll(copyright, "(C)", "&copy;")
copyright = strings.ReplaceAll(copyright, "(c)", "&copy;")
copyright = strings.Trim(copyright, "., ")
copyrights = append(copyrights, copyright)
}
}
if len(copyrights) > 0 {
return copyrights
}
return []string{}
}
func defaultCopyright(n CopyrightNotice) string {
year := time.Now().Format("2006")
return fmt.Sprintf("Copyright &copy; %v, the %s authors", year, n.Name)
}
func writeNotices(path string, notices []CopyrightNotice) {
s := ""
for i, n := range notices {
s += "# : " + strconv.Itoa(i) + "\n" + n.String()
}
writeFile(path, s)
}
func (n CopyrightNotice) String() string {
return fmt.Sprintf("Type : %v\nHTML : %v\nName : %v\nModule : %v\nURL : %v\nCopyright: %v\nRepoURL : %v\nRepoCopys: %v\n\n",
n.Type, n.HTML, n.Name, n.Module, n.URL, n.Copyright, n.RepoURL, strings.Join(n.RepoCopyrights, ","))
}
func (t Type) String() string {
switch t {
case TypeJS:
return "TypeJS"
case TypeKeep:
return "TypeKeep"
case TypeToss:
return "TypeToss"
case TypeNew:
return "TypeNew"
default:
return "unknown"
}
}