96 lines
2.8 KiB
Go
96 lines
2.8 KiB
Go
package scraper
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3"
|
|
|
|
"github.com/go-enry/go-license-detector/v4/licensedb"
|
|
"github.com/go-enry/go-license-detector/v4/licensedb/filer"
|
|
)
|
|
|
|
// forgejoFiler adapts the Forgejo content API to the filer.Filer interface so
|
|
// that go-license-detector can inspect a repository's files without a local
|
|
// checkout. ReadDir serves a cached root listing when available (to avoid an
|
|
// extra ListContents call) and falls back to the API otherwise. ReadFile
|
|
// downloads a file's bytes at the given ref (default branch).
|
|
type forgejoFiler struct {
|
|
client *forgejo.Client
|
|
owner string
|
|
repo string
|
|
ref string
|
|
// cachedRoot, when non-nil, is served for ReadDir("") to avoid a second
|
|
// root listing that was already fetched by the caller.
|
|
cachedRoot []filer.File
|
|
}
|
|
|
|
var _ filer.Filer = (*forgejoFiler)(nil)
|
|
|
|
func (f *forgejoFiler) ReadDir(dirPath string) ([]filer.File, error) {
|
|
if dirPath == "" && f.cachedRoot != nil {
|
|
return f.cachedRoot, nil
|
|
}
|
|
entries, _, err := f.client.ListContents(f.owner, f.repo, f.ref, dirPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list contents %q: %w", dirPath, err)
|
|
}
|
|
files := make([]filer.File, 0, len(entries))
|
|
for _, e := range entries {
|
|
if e == nil {
|
|
continue
|
|
}
|
|
files = append(files, filer.File{
|
|
Name: e.Name,
|
|
IsDir: e.Type == "dir",
|
|
})
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
func (f *forgejoFiler) ReadFile(path string) ([]byte, error) {
|
|
// GetFile returns raw file bytes (binary-safe, base64-decoded).
|
|
data, _, err := f.client.GetFile(f.owner, f.repo, f.ref, path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read file %q: %w", path, err)
|
|
}
|
|
return data, nil
|
|
}
|
|
|
|
func (f *forgejoFiler) Close() {}
|
|
|
|
func (f *forgejoFiler) PathsAreAlwaysSlash() bool { return true }
|
|
|
|
// licensesFor returns the SPDX identifiers detected for the repository. It
|
|
// uses go-license-detector over the repository's files on its default branch.
|
|
// The given root listing is served to the detector to avoid a duplicate,
|
|
// already-available API call. An empty slice is returned when no license can
|
|
// be determined (the repo is still kept in results -- license failures are
|
|
// non-fatal).
|
|
func (s *ForgejoScraper) licensesFor(owner, repo, ref string, root []filer.File) []string {
|
|
detector := &forgejoFiler{
|
|
client: s.Client,
|
|
owner: owner,
|
|
repo: repo,
|
|
ref: ref,
|
|
cachedRoot: root,
|
|
}
|
|
|
|
matches, err := licensedb.Detect(detector)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
// Keep only the highest-confidence match, sorted deterministically.
|
|
ids := make([]string, 0, len(matches))
|
|
bestID, bestConf := "", float32(-1)
|
|
for id, m := range matches {
|
|
if m.Confidence > bestConf {
|
|
bestConf = m.Confidence
|
|
bestID = id
|
|
}
|
|
}
|
|
if bestID != "" {
|
|
ids = append(ids, bestID)
|
|
}
|
|
return ids
|
|
}
|