feat: store API responses in a local registry for offline mode (#68)

This commit is contained in:
mircearoata 2024-10-02 10:59:56 +02:00 committed by GitHub
parent 7cd93926c6
commit bf6d6b0850
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 334 additions and 541 deletions

118
cli/cache/integrity.go vendored
View file

@ -1,118 +0,0 @@
package cache
import (
"encoding/json"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"time"
"github.com/puzpuzpuz/xsync/v3"
"github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/utils"
)
type hashInfo struct {
Modified time.Time
Hash string
Size int64
}
var hashCache *xsync.MapOf[string, hashInfo]
var integrityFilename = ".integrity"
func getFileHash(file string) (string, error) {
if hashCache == nil {
loadHashCache()
}
cachedHash, ok := hashCache.Load(file)
if !ok {
return cacheFileHash(file)
}
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
stat, err := os.Stat(filepath.Join(downloadCache, file))
if err != nil {
return "", fmt.Errorf("failed to stat file: %w", err)
}
if stat.Size() != cachedHash.Size || stat.ModTime() != cachedHash.Modified {
return cacheFileHash(file)
}
return cachedHash.Hash, nil
}
func cacheFileHash(file string) (string, error) {
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
stat, err := os.Stat(filepath.Join(downloadCache, file))
if err != nil {
return "", fmt.Errorf("failed to stat file: %w", err)
}
f, err := os.Open(filepath.Join(downloadCache, file))
if err != nil {
return "", fmt.Errorf("failed to open file: %w", err)
}
defer f.Close()
hash, err := utils.SHA256Data(f)
if err != nil {
return "", fmt.Errorf("failed to hash file: %w", err)
}
hashCache.Store(file, hashInfo{
Hash: hash,
Size: stat.Size(),
Modified: stat.ModTime(),
})
saveHashCache()
return hash, nil
}
func loadHashCache() {
hashCache = xsync.NewMapOf[string, hashInfo]()
cacheFile := filepath.Join(viper.GetString("cache-dir"), "downloadCache", integrityFilename)
if _, err := os.Stat(cacheFile); os.IsNotExist(err) {
return
}
f, err := os.Open(cacheFile)
if err != nil {
slog.Warn("failed to open hash cache, recreating", slog.Any("err", err))
return
}
defer f.Close()
hashCacheJSON, err := io.ReadAll(f)
if err != nil {
slog.Warn("failed to read hash cache, recreating", slog.Any("err", err))
return
}
var plainCache map[string]hashInfo
if err := json.Unmarshal(hashCacheJSON, &plainCache); err != nil {
slog.Warn("failed to unmarshal hash cache, recreating", slog.Any("err", err))
return
}
for k, v := range plainCache {
hashCache.Store(k, v)
}
}
func saveHashCache() {
cacheFile := filepath.Join(viper.GetString("cache-dir"), "downloadCache", integrityFilename)
plainCache := make(map[string]hashInfo, hashCache.Size())
hashCache.Range(func(k string, v hashInfo) bool {
plainCache[k] = v
return true
})
hashCacheJSON, err := json.Marshal(plainCache)
if err != nil {
slog.Warn("failed to marshal hash cache", slog.Any("err", err))
return
}
if err := os.WriteFile(cacheFile, hashCacheJSON, 0o755); err != nil {
slog.Warn("failed to write hash cache", slog.Any("err", err))
return
}
}

View file

@ -12,43 +12,44 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/mircearoata/pubgrub-go/pubgrub/semver"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const IconFilename = "Resources/Icon128.png" // This is the path UE expects for the icon const IconFilename = "Resources/Icon128.png" // This is the path UE expects for the icon
type File struct { type Mod struct {
Icon *string
ModReference string ModReference string
Hash string Name string
Plugin UPlugin Author string
Size int64 Icon *string
LatestVersion string
} }
var loadedCache *xsync.MapOf[string, []File] var loadedMods *xsync.MapOf[string, Mod]
func GetCache() (*xsync.MapOf[string, []File], error) { func GetCacheMods() (*xsync.MapOf[string, Mod], error) {
if loadedCache != nil { if loadedMods != nil {
return loadedCache, nil return loadedMods, nil
} }
return LoadCache() return LoadCacheMods()
} }
func GetCacheMod(mod string) ([]File, error) { func GetCacheMod(mod string) (Mod, error) {
cache, err := GetCache() cache, err := GetCacheMods()
if err != nil { if err != nil {
return nil, err return Mod{}, err
} }
value, _ := cache.Load(mod) value, _ := cache.Load(mod)
return value, nil return value, nil
} }
func LoadCache() (*xsync.MapOf[string, []File], error) { func LoadCacheMods() (*xsync.MapOf[string, Mod], error) {
loadedCache = xsync.NewMapOf[string, []File]() loadedMods = xsync.NewMapOf[string, Mod]()
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache") downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
if _, err := os.Stat(downloadCache); os.IsNotExist(err) { if _, err := os.Stat(downloadCache); os.IsNotExist(err) {
return loadedCache, nil return loadedMods, nil
} }
items, err := os.ReadDir(downloadCache) items, err := os.ReadDir(downloadCache)
@ -60,32 +61,45 @@ func LoadCache() (*xsync.MapOf[string, []File], error) {
if item.IsDir() { if item.IsDir() {
continue continue
} }
if item.Name() == integrityFilename {
continue
}
_, err = addFileToCache(item.Name()) _, err = addFileToCache(item.Name())
if err != nil { if err != nil {
slog.Error("failed to add file to cache", slog.String("file", item.Name()), slog.Any("err", err)) slog.Error("failed to add file to cache", slog.String("file", item.Name()), slog.Any("err", err))
} }
} }
return loadedCache, nil return loadedMods, nil
} }
func addFileToCache(filename string) (*File, error) { func addFileToCache(filename string) (*Mod, error) {
cacheFile, err := readCacheFile(filename) cacheFile, err := readCacheFile(filename)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read cache file: %w", err) return nil, fmt.Errorf("failed to read cache file: %w", err)
} }
loadedCache.Compute(cacheFile.ModReference, func(oldValue []File, _ bool) ([]File, bool) { loadedMods.Compute(cacheFile.ModReference, func(oldValue Mod, loaded bool) (Mod, bool) {
return append(oldValue, *cacheFile), false if !loaded {
return *cacheFile, false
}
oldVersion, err := semver.NewVersion(oldValue.LatestVersion)
if err != nil {
slog.Error("failed to parse version", slog.String("version", oldValue.LatestVersion), slog.Any("err", err))
return *cacheFile, false
}
newVersion, err := semver.NewVersion(cacheFile.LatestVersion)
if err != nil {
slog.Error("failed to parse version", slog.String("version", cacheFile.LatestVersion), slog.Any("err", err))
return oldValue, false
}
if newVersion.Compare(oldVersion) > 0 {
return *cacheFile, false
}
return oldValue, false
}) })
return cacheFile, nil return cacheFile, nil
} }
func readCacheFile(filename string) (*File, error) { func readCacheFile(filename string) (*Mod, error) {
downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache") downloadCache := filepath.Join(viper.GetString("cache-dir"), "downloadCache")
path := filepath.Join(downloadCache, filename) path := filepath.Join(downloadCache, filename)
stat, err := os.Stat(path) stat, err := os.Stat(path)
@ -132,11 +146,6 @@ func readCacheFile(filename string) (*File, error) {
modReference := strings.TrimSuffix(upluginFile.Name, ".uplugin") modReference := strings.TrimSuffix(upluginFile.Name, ".uplugin")
hash, err := getFileHash(filename)
if err != nil {
return nil, fmt.Errorf("failed to get file hash: %w", err)
}
var iconFile *zip.File var iconFile *zip.File
for _, file := range reader.File { for _, file := range reader.File {
if file.Name == IconFilename { if file.Name == IconFilename {
@ -160,11 +169,11 @@ func readCacheFile(filename string) (*File, error) {
icon = &iconData icon = &iconData
} }
return &File{ return &Mod{
ModReference: modReference, ModReference: modReference,
Hash: hash, Name: uplugin.FriendlyName,
Size: size, Author: uplugin.CreatedBy,
Icon: icon, Icon: icon,
Plugin: uplugin, LatestVersion: uplugin.SemVersion,
}, nil }, nil
} }

View file

@ -8,6 +8,7 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/cli/cache" "github.com/satisfactorymodding/ficsit-cli/cli/cache"
"github.com/satisfactorymodding/ficsit-cli/cli/localregistry"
"github.com/satisfactorymodding/ficsit-cli/cli/provider" "github.com/satisfactorymodding/ficsit-cli/cli/provider"
"github.com/satisfactorymodding/ficsit-cli/ficsit" "github.com/satisfactorymodding/ficsit-cli/ficsit"
) )
@ -45,11 +46,16 @@ func InitCLI(apiOnly bool) (*GlobalContext, error) {
return nil, fmt.Errorf("failed to initialize installations: %w", err) return nil, fmt.Errorf("failed to initialize installations: %w", err)
} }
_, err = cache.LoadCache() _, err = cache.LoadCacheMods()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load cache: %w", err) return nil, fmt.Errorf("failed to load cache: %w", err)
} }
err = localregistry.Init()
if err != nil {
return nil, fmt.Errorf("failed to initialize local registry: %w", err)
}
globalContext = &GlobalContext{ globalContext = &GlobalContext{
Installations: installations, Installations: installations,
Profiles: profiles, Profiles: profiles,

View file

@ -0,0 +1,197 @@
package localregistry
import (
"database/sql"
"fmt"
"log/slog"
"os"
"path/filepath"
"sync"
resolver "github.com/satisfactorymodding/ficsit-resolver"
"github.com/spf13/viper"
// sqlite driver
_ "modernc.org/sqlite"
)
var db *sql.DB
var dbWriteMutex = sync.Mutex{}
func Init() error {
dbPath := filepath.Join(viper.GetString("cache-dir"), "registry.db")
err := os.MkdirAll(filepath.Dir(dbPath), 0o777)
if err != nil {
return fmt.Errorf("failed to create local registry directory: %w", err)
}
db, err = sql.Open("sqlite", dbPath)
if err != nil {
return fmt.Errorf("failed to open database: %w", err)
}
// Set pragmas here because modernc.org/sqlite does not support them in the connection string
_, err = db.Exec(`
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
CREATE TABLE IF NOT EXISTS "versions" (
"id" TEXT NOT NULL PRIMARY KEY,
"mod_reference" TEXT NOT NULL,
"version" TEXT NOT NULL,
"game_version" TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS "mod_reference" ON "versions" ("mod_reference");
CREATE UNIQUE INDEX IF NOT EXISTS "mod_version" ON "versions" ("mod_reference", "version");
CREATE TABLE IF NOT EXISTS "dependencies" (
"version_id" TEXT NOT NULL,
"dependency" TEXT NOT NULL,
"condition" TEXT NOT NULL,
"optional" INT NOT NULL,
FOREIGN KEY ("version_id") REFERENCES "versions" ("id") ON DELETE CASCADE,
PRIMARY KEY ("version_id", "dependency")
);
CREATE TABLE IF NOT EXISTS "targets" (
"version_id" TEXT NOT NULL,
"target_name" TEXT NOT NULL,
"link" TEXT NOT NULL,
"hash" TEXT NOT NULL,
"size" INT NOT NULL,
FOREIGN KEY ("version_id") REFERENCES "versions" ("id") ON DELETE CASCADE,
PRIMARY KEY ("version_id", "target_name")
);
`)
if err != nil {
return fmt.Errorf("failed to setup tables: %w", err)
}
return nil
}
func Add(modReference string, modVersions []resolver.ModVersion) {
dbWriteMutex.Lock()
defer dbWriteMutex.Unlock()
tx, err := db.Begin()
if err != nil {
slog.Error("failed to start local registry transaction", slog.Any("err", err))
return
}
// In case the transaction is not committed, revert and release
defer tx.Rollback() //nolint:errcheck
_, err = tx.Exec("DELETE FROM versions WHERE mod_reference = ?", modReference)
if err != nil {
slog.Error("failed to delete existing mod versions from local registry", slog.Any("err", err))
return
}
for _, modVersion := range modVersions {
l := slog.With(slog.String("mod", modReference), slog.String("version", modVersion.Version))
_, err = tx.Exec("INSERT INTO versions (id, mod_reference, version, game_version) VALUES (?, ?, ?, ?)", modVersion.ID, modReference, modVersion.Version, modVersion.GameVersion)
if err != nil {
l.Error("failed to insert mod version into local registry", slog.Any("err", err))
return
}
for _, dependency := range modVersion.Dependencies {
_, err = tx.Exec("INSERT INTO dependencies (version_id, dependency, condition, optional) VALUES (?, ?, ?, ?)", modVersion.ID, dependency.ModID, dependency.Condition, dependency.Optional)
if err != nil {
l.Error("failed to insert dependency into local registry", slog.String("dependency", dependency.ModID), slog.Any("err", err))
return
}
}
for _, target := range modVersion.Targets {
_, err = tx.Exec("INSERT INTO targets (version_id, target_name, link, hash, size) VALUES (?, ?, ?, ?, ?)", modVersion.ID, target.TargetName, target.Link, target.Hash, target.Size)
if err != nil {
l.Error("failed to insert target into local registry", slog.Any("target", target.TargetName), slog.Any("err", err))
return
}
}
}
err = tx.Commit()
if err != nil {
slog.Error("failed to commit local registry transaction", slog.Any("err", err))
return
}
}
func GetModVersions(modReference string) ([]resolver.ModVersion, error) {
versionRows, err := db.Query("SELECT id, version, game_version FROM versions WHERE mod_reference = ?", modReference)
if err != nil {
return nil, fmt.Errorf("failed to fetch mod versions from local registry: %w", err)
}
defer versionRows.Close()
var versions []resolver.ModVersion
for versionRows.Next() {
var version resolver.ModVersion
err = versionRows.Scan(&version.ID, &version.Version, &version.GameVersion)
if err != nil {
return nil, fmt.Errorf("failed to scan version row: %w", err)
}
dependencies, err := getVersionDependencies(version.ID)
if err != nil {
return nil, err
}
version.Dependencies = dependencies
targets, err := getVersionTargets(version.ID)
if err != nil {
return nil, err
}
version.Targets = targets
versions = append(versions, version)
}
return versions, nil
}
func getVersionDependencies(versionID string) ([]resolver.Dependency, error) {
var dependencies []resolver.Dependency
dependencyRows, err := db.Query("SELECT dependency, condition, optional FROM dependencies WHERE version_id = ?", versionID)
if err != nil {
return nil, fmt.Errorf("failed to fetch dependencies from local registry: %w", err)
}
defer dependencyRows.Close()
for dependencyRows.Next() {
var dependency resolver.Dependency
err = dependencyRows.Scan(&dependency.ModID, &dependency.Condition, &dependency.Optional)
if err != nil {
return nil, fmt.Errorf("failed to scan dependency row: %w", err)
}
dependencies = append(dependencies, dependency)
}
return dependencies, nil
}
func getVersionTargets(versionID string) ([]resolver.Target, error) {
var targets []resolver.Target
targetRows, err := db.Query("SELECT target_name, link, hash, size FROM targets WHERE version_id = ?", versionID)
if err != nil {
return nil, fmt.Errorf("failed to fetch targets from local registry: %w", err)
}
defer targetRows.Close()
for targetRows.Next() {
var target resolver.Target
err = targetRows.Scan(&target.TargetName, &target.Link, &target.Hash, &target.Size)
if err != nil {
return nil, fmt.Errorf("failed to scan target row: %w", err)
}
targets = append(targets, target)
}
return targets, nil
}

View file

@ -8,6 +8,7 @@ import (
resolver "github.com/satisfactorymodding/ficsit-resolver" resolver "github.com/satisfactorymodding/ficsit-resolver"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/satisfactorymodding/ficsit-cli/cli/localregistry"
"github.com/satisfactorymodding/ficsit-cli/ficsit" "github.com/satisfactorymodding/ficsit-cli/ficsit"
) )
@ -29,10 +30,6 @@ func (p FicsitProvider) GetMod(context context.Context, modReference string) (*f
return ficsit.GetMod(context, p.client, modReference) return ficsit.GetMod(context, p.client, modReference)
} }
func (p FicsitProvider) ModVersions(context context.Context, modReference string, filter ficsit.VersionFilter) (*ficsit.ModVersionsResponse, error) {
return ficsit.ModVersions(context, p.client, modReference, filter)
}
func (p FicsitProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) { func (p FicsitProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) {
response, err := ficsit.GetAllModVersions(modID) response, err := ficsit.GetAllModVersions(modID)
if err != nil { if err != nil {
@ -73,6 +70,8 @@ func (p FicsitProvider) ModVersionsWithDependencies(_ context.Context, modID str
} }
} }
localregistry.Add(modID, modVersions)
return modVersions, err return modVersions, err
} }

View file

@ -2,7 +2,6 @@ package provider
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -10,6 +9,7 @@ import (
resolver "github.com/satisfactorymodding/ficsit-resolver" resolver "github.com/satisfactorymodding/ficsit-resolver"
"github.com/satisfactorymodding/ficsit-cli/cli/cache" "github.com/satisfactorymodding/ficsit-cli/cli/cache"
"github.com/satisfactorymodding/ficsit-cli/cli/localregistry"
"github.com/satisfactorymodding/ficsit-cli/ficsit" "github.com/satisfactorymodding/ficsit-cli/ficsit"
) )
@ -20,14 +20,14 @@ func NewLocalProvider() LocalProvider {
} }
func (p LocalProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) { func (p LocalProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit.ModsResponse, error) {
cachedMods, err := cache.GetCache() cachedMods, err := cache.GetCacheMods()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get cache: %w", err) return nil, fmt.Errorf("failed to get cache: %w", err)
} }
mods := make([]ficsit.ModsModsGetModsModsMod, 0) mods := make([]ficsit.ModsModsGetModsModsMod, 0)
cachedMods.Range(func(modReference string, files []cache.File) bool { cachedMods.Range(func(modReference string, cachedMod cache.Mod) bool {
if len(filter.References) > 0 { if len(filter.References) > 0 {
skip := true skip := true
@ -45,7 +45,7 @@ func (p LocalProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit
mods = append(mods, ficsit.ModsModsGetModsModsMod{ mods = append(mods, ficsit.ModsModsGetModsModsMod{
Id: modReference, Id: modReference,
Name: files[0].Plugin.FriendlyName, Name: cachedMod.Name,
Mod_reference: modReference, Mod_reference: modReference,
Last_version_date: time.Now(), Last_version_date: time.Now(),
Created_at: time.Now(), Created_at: time.Now(),
@ -88,18 +88,14 @@ func (p LocalProvider) Mods(_ context.Context, filter ficsit.ModFilter) (*ficsit
} }
func (p LocalProvider) GetMod(_ context.Context, modReference string) (*ficsit.GetModResponse, error) { func (p LocalProvider) GetMod(_ context.Context, modReference string) (*ficsit.GetModResponse, error) {
cachedModFiles, err := cache.GetCacheMod(modReference) cachedMod, err := cache.GetCacheMod(modReference)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get cache: %w", err) return nil, fmt.Errorf("failed to get cache: %w", err)
} }
if len(cachedModFiles) == 0 {
return nil, errors.New("mod not found")
}
authors := make([]ficsit.GetModModAuthorsUserMod, 0) authors := make([]ficsit.GetModModAuthorsUserMod, 0)
for _, author := range strings.Split(cachedModFiles[0].Plugin.CreatedBy, ",") { for _, author := range strings.Split(cachedMod.Author, ",") {
authors = append(authors, ficsit.GetModModAuthorsUserMod{ authors = append(authors, ficsit.GetModModAuthorsUserMod{
Role: "Unknown", Role: "Unknown",
User: ficsit.GetModModAuthorsUserModUser{ User: ficsit.GetModModAuthorsUserModUser{
@ -111,7 +107,7 @@ func (p LocalProvider) GetMod(_ context.Context, modReference string) (*ficsit.G
return &ficsit.GetModResponse{ return &ficsit.GetModResponse{
Mod: ficsit.GetModMod{ Mod: ficsit.GetModMod{
Id: modReference, Id: modReference,
Name: cachedModFiles[0].Plugin.FriendlyName, Name: cachedMod.Name,
Mod_reference: modReference, Mod_reference: modReference,
Created_at: time.Now(), Created_at: time.Now(),
Views: 0, Views: 0,
@ -124,37 +120,25 @@ func (p LocalProvider) GetMod(_ context.Context, modReference string) (*ficsit.G
} }
func (p LocalProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) { func (p LocalProvider) ModVersionsWithDependencies(_ context.Context, modID string) ([]resolver.ModVersion, error) {
cachedModFiles, err := cache.GetCacheMod(modID) modVersions, err := localregistry.GetModVersions(modID)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get cache: %w", err) return nil, fmt.Errorf("failed to get local mod versions: %w", err)
} }
versions := make([]resolver.ModVersion, 0) // TODO: only list as available the versions that have at least one target cached
for _, modFile := range cachedModFiles { return modVersions, nil
versions = append(versions, resolver.ModVersion{
ID: modID + ":" + modFile.Plugin.SemVersion,
Version: modFile.Plugin.SemVersion,
GameVersion: modFile.Plugin.GameVersion,
})
}
return versions, nil
} }
func (p LocalProvider) GetModName(_ context.Context, modReference string) (*resolver.ModName, error) { func (p LocalProvider) GetModName(_ context.Context, modReference string) (*resolver.ModName, error) {
cachedModFiles, err := cache.GetCacheMod(modReference) cachedMod, err := cache.GetCacheMod(modReference)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get cache: %w", err) return nil, fmt.Errorf("failed to get cache: %w", err)
} }
if len(cachedModFiles) == 0 {
return nil, errors.New("mod not found")
}
return &resolver.ModName{ return &resolver.ModName{
ID: modReference, ID: modReference,
Name: cachedModFiles[0].Plugin.FriendlyName, Name: cachedMod.Name,
ModReference: modReference, ModReference: modReference,
}, nil }, nil
} }

View file

@ -17,15 +17,6 @@ func init() {
client = InitAPI() client = InitAPI()
} }
func TestModVersions(t *testing.T) {
response, err := ModVersions(context.Background(), client, "SmartFoundations", VersionFilter{})
testza.AssertNoError(t, err)
testza.AssertNotNil(t, response)
testza.AssertNotNil(t, response.Mod)
testza.AssertNotNil(t, response.Mod.Versions)
testza.AssertNotZero(t, len(response.Mod.Versions))
}
func TestMods(t *testing.T) { func TestMods(t *testing.T) {
response, err := Mods(context.Background(), client, ModFilter{}) response, err := Mods(context.Background(), client, ModFilter{})
testza.AssertNoError(t, err) testza.AssertNoError(t, err)

View file

@ -1,13 +0,0 @@
# @genqlient(omitempty: true)
query ModVersions (
$modId: String!,
$filter: VersionFilter
) {
mod: getModByIdOrReference(modIdOrReference: $modId) {
id
versions (filter: $filter) {
id
version
}
}
}

View file

@ -1,24 +0,0 @@
# @genqlient(omitempty: true)
query ModVersionsWithDependencies (
$modId: String!,
) {
mod: getModByIdOrReference(modIdOrReference: $modId) {
id
versions (filter: { limit: 100 }) {
id
version
link
hash
dependencies {
mod_id
condition
optional
}
targets {
targetName
link
hash
}
}
}
}

View file

@ -355,136 +355,6 @@ func (v *ModFilter) GetHidden() bool { return v.Hidden }
// GetTagIDs returns ModFilter.TagIDs, and is useful for accessing the field via an interface. // GetTagIDs returns ModFilter.TagIDs, and is useful for accessing the field via an interface.
func (v *ModFilter) GetTagIDs() []string { return v.TagIDs } func (v *ModFilter) GetTagIDs() []string { return v.TagIDs }
// ModVersionsMod includes the requested fields of the GraphQL type Mod.
type ModVersionsMod struct {
Id string `json:"id"`
Versions []ModVersionsModVersionsVersion `json:"versions"`
}
// GetId returns ModVersionsMod.Id, and is useful for accessing the field via an interface.
func (v *ModVersionsMod) GetId() string { return v.Id }
// GetVersions returns ModVersionsMod.Versions, and is useful for accessing the field via an interface.
func (v *ModVersionsMod) GetVersions() []ModVersionsModVersionsVersion { return v.Versions }
// ModVersionsModVersionsVersion includes the requested fields of the GraphQL type Version.
type ModVersionsModVersionsVersion struct {
Id string `json:"id"`
Version string `json:"version"`
}
// GetId returns ModVersionsModVersionsVersion.Id, and is useful for accessing the field via an interface.
func (v *ModVersionsModVersionsVersion) GetId() string { return v.Id }
// GetVersion returns ModVersionsModVersionsVersion.Version, and is useful for accessing the field via an interface.
func (v *ModVersionsModVersionsVersion) GetVersion() string { return v.Version }
// ModVersionsResponse is returned by ModVersions on success.
type ModVersionsResponse struct {
Mod ModVersionsMod `json:"mod"`
}
// GetMod returns ModVersionsResponse.Mod, and is useful for accessing the field via an interface.
func (v *ModVersionsResponse) GetMod() ModVersionsMod { return v.Mod }
// ModVersionsWithDependenciesMod includes the requested fields of the GraphQL type Mod.
type ModVersionsWithDependenciesMod struct {
Id string `json:"id"`
Versions []ModVersionsWithDependenciesModVersionsVersion `json:"versions"`
}
// GetId returns ModVersionsWithDependenciesMod.Id, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesMod) GetId() string { return v.Id }
// GetVersions returns ModVersionsWithDependenciesMod.Versions, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesMod) GetVersions() []ModVersionsWithDependenciesModVersionsVersion {
return v.Versions
}
// ModVersionsWithDependenciesModVersionsVersion includes the requested fields of the GraphQL type Version.
type ModVersionsWithDependenciesModVersionsVersion struct {
Id string `json:"id"`
Version string `json:"version"`
Link string `json:"link"`
Hash string `json:"hash"`
Dependencies []ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency `json:"dependencies"`
Targets []ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget `json:"targets"`
}
// GetId returns ModVersionsWithDependenciesModVersionsVersion.Id, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetId() string { return v.Id }
// GetVersion returns ModVersionsWithDependenciesModVersionsVersion.Version, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetVersion() string { return v.Version }
// GetLink returns ModVersionsWithDependenciesModVersionsVersion.Link, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetLink() string { return v.Link }
// GetHash returns ModVersionsWithDependenciesModVersionsVersion.Hash, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetHash() string { return v.Hash }
// GetDependencies returns ModVersionsWithDependenciesModVersionsVersion.Dependencies, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetDependencies() []ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency {
return v.Dependencies
}
// GetTargets returns ModVersionsWithDependenciesModVersionsVersion.Targets, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersion) GetTargets() []ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget {
return v.Targets
}
// ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency includes the requested fields of the GraphQL type VersionDependency.
type ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency struct {
Mod_id string `json:"mod_id"`
Condition string `json:"condition"`
Optional bool `json:"optional"`
}
// GetMod_id returns ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency.Mod_id, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency) GetMod_id() string {
return v.Mod_id
}
// GetCondition returns ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency.Condition, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency) GetCondition() string {
return v.Condition
}
// GetOptional returns ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency.Optional, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionDependenciesVersionDependency) GetOptional() bool {
return v.Optional
}
// ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget includes the requested fields of the GraphQL type VersionTarget.
type ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget struct {
TargetName TargetName `json:"targetName"`
Link string `json:"link"`
Hash string `json:"hash"`
}
// GetTargetName returns ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget.TargetName, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget) GetTargetName() TargetName {
return v.TargetName
}
// GetLink returns ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget.Link, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget) GetLink() string {
return v.Link
}
// GetHash returns ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget.Hash, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesModVersionsVersionTargetsVersionTarget) GetHash() string {
return v.Hash
}
// ModVersionsWithDependenciesResponse is returned by ModVersionsWithDependencies on success.
type ModVersionsWithDependenciesResponse struct {
Mod ModVersionsWithDependenciesMod `json:"mod"`
}
// GetMod returns ModVersionsWithDependenciesResponse.Mod, and is useful for accessing the field via an interface.
func (v *ModVersionsWithDependenciesResponse) GetMod() ModVersionsWithDependenciesMod { return v.Mod }
// ModsModsGetMods includes the requested fields of the GraphQL type GetMods. // ModsModsGetMods includes the requested fields of the GraphQL type GetMods.
type ModsModsGetMods struct { type ModsModsGetMods struct {
Count int `json:"count"` Count int `json:"count"`
@ -675,49 +545,6 @@ const (
OrderDesc Order = "desc" OrderDesc Order = "desc"
) )
type TargetName string
const (
TargetNameWindows TargetName = "Windows"
TargetNameWindowsserver TargetName = "WindowsServer"
TargetNameLinuxserver TargetName = "LinuxServer"
)
type VersionFields string
const (
VersionFieldsCreatedAt VersionFields = "created_at"
VersionFieldsUpdatedAt VersionFields = "updated_at"
VersionFieldsDownloads VersionFields = "downloads"
)
type VersionFilter struct {
Limit int `json:"limit,omitempty"`
Offset int `json:"offset,omitempty"`
Order_by VersionFields `json:"order_by,omitempty"`
Order Order `json:"order,omitempty"`
Search string `json:"search,omitempty"`
Ids []string `json:"ids,omitempty"`
}
// GetLimit returns VersionFilter.Limit, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetLimit() int { return v.Limit }
// GetOffset returns VersionFilter.Offset, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetOffset() int { return v.Offset }
// GetOrder_by returns VersionFilter.Order_by, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetOrder_by() VersionFields { return v.Order_by }
// GetOrder returns VersionFilter.Order, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetOrder() Order { return v.Order }
// GetSearch returns VersionFilter.Search, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetSearch() string { return v.Search }
// GetIds returns VersionFilter.Ids, and is useful for accessing the field via an interface.
func (v *VersionFilter) GetIds() []string { return v.Ids }
// VersionMod includes the requested fields of the GraphQL type Mod. // VersionMod includes the requested fields of the GraphQL type Mod.
type VersionMod struct { type VersionMod struct {
Id string `json:"id"` Id string `json:"id"`
@ -818,26 +645,6 @@ type __GetModNameInput struct {
// GetModId returns __GetModNameInput.ModId, and is useful for accessing the field via an interface. // GetModId returns __GetModNameInput.ModId, and is useful for accessing the field via an interface.
func (v *__GetModNameInput) GetModId() string { return v.ModId } func (v *__GetModNameInput) GetModId() string { return v.ModId }
// __ModVersionsInput is used internally by genqlient
type __ModVersionsInput struct {
ModId string `json:"modId,omitempty"`
Filter VersionFilter `json:"filter,omitempty"`
}
// GetModId returns __ModVersionsInput.ModId, and is useful for accessing the field via an interface.
func (v *__ModVersionsInput) GetModId() string { return v.ModId }
// GetFilter returns __ModVersionsInput.Filter, and is useful for accessing the field via an interface.
func (v *__ModVersionsInput) GetFilter() VersionFilter { return v.Filter }
// __ModVersionsWithDependenciesInput is used internally by genqlient
type __ModVersionsWithDependenciesInput struct {
ModId string `json:"modId,omitempty"`
}
// GetModId returns __ModVersionsWithDependenciesInput.ModId, and is useful for accessing the field via an interface.
func (v *__ModVersionsWithDependenciesInput) GetModId() string { return v.ModId }
// __ModsInput is used internally by genqlient // __ModsInput is used internally by genqlient
type __ModsInput struct { type __ModsInput struct {
Filter ModFilter `json:"filter,omitempty"` Filter ModFilter `json:"filter,omitempty"`
@ -1063,98 +870,6 @@ func GetModName(
return &data, err return &data, err
} }
// The query or mutation executed by ModVersions.
const ModVersions_Operation = `
query ModVersions ($modId: String!, $filter: VersionFilter) {
mod: getModByIdOrReference(modIdOrReference: $modId) {
id
versions(filter: $filter) {
id
version
}
}
}
`
func ModVersions(
ctx context.Context,
client graphql.Client,
modId string,
filter VersionFilter,
) (*ModVersionsResponse, error) {
req := &graphql.Request{
OpName: "ModVersions",
Query: ModVersions_Operation,
Variables: &__ModVersionsInput{
ModId: modId,
Filter: filter,
},
}
var err error
var data ModVersionsResponse
resp := &graphql.Response{Data: &data}
err = client.MakeRequest(
ctx,
req,
resp,
)
return &data, err
}
// The query or mutation executed by ModVersionsWithDependencies.
const ModVersionsWithDependencies_Operation = `
query ModVersionsWithDependencies ($modId: String!) {
mod: getModByIdOrReference(modIdOrReference: $modId) {
id
versions(filter: {limit:100}) {
id
version
link
hash
dependencies {
mod_id
condition
optional
}
targets {
targetName
link
hash
}
}
}
}
`
func ModVersionsWithDependencies(
ctx context.Context,
client graphql.Client,
modId string,
) (*ModVersionsWithDependenciesResponse, error) {
req := &graphql.Request{
OpName: "ModVersionsWithDependencies",
Query: ModVersionsWithDependencies_Operation,
Variables: &__ModVersionsWithDependenciesInput{
ModId: modId,
},
}
var err error
var data ModVersionsWithDependenciesResponse
resp := &graphql.Response{Data: &data}
err = client.MakeRequest(
ctx,
req,
resp,
)
return &data, err
}
// The query or mutation executed by Mods. // The query or mutation executed by Mods.
const Mods_Operation = ` const Mods_Operation = `
query Mods ($filter: ModFilter) { query Mods ($filter: ModFilter) {

27
go.mod
View file

@ -19,6 +19,7 @@ require (
github.com/jackc/puddle/v2 v2.2.1 github.com/jackc/puddle/v2 v2.2.1
github.com/jlaffaye/ftp v0.2.0 github.com/jlaffaye/ftp v0.2.0
github.com/lmittmann/tint v1.0.3 github.com/lmittmann/tint v1.0.3
github.com/mircearoata/pubgrub-go v0.3.3
github.com/muesli/reflow v0.3.0 github.com/muesli/reflow v0.3.0
github.com/pkg/sftp v1.13.6 github.com/pkg/sftp v1.13.6
github.com/pterm/pterm v0.12.71 github.com/pterm/pterm v0.12.71
@ -29,8 +30,9 @@ require (
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.1 github.com/spf13/viper v1.18.1
goftp.io/server/v2 v2.0.1 goftp.io/server/v2 v2.0.1
golang.org/x/crypto v0.16.0 golang.org/x/crypto v0.21.0
golang.org/x/sync v0.5.0 golang.org/x/sync v0.6.0
modernc.org/sqlite v1.32.0
) )
require ( require (
@ -54,10 +56,12 @@ require (
github.com/dlclark/regexp2 v1.10.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gookit/color v1.5.4 // indirect github.com/gookit/color v1.5.4 // indirect
github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/css v1.0.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
@ -69,14 +73,15 @@ require (
github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/microcosm-cc/bluemonday v1.0.26 // indirect github.com/microcosm-cc/bluemonday v1.0.26 // indirect
github.com/mircearoata/pubgrub-go v0.3.3 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect github.com/muesli/termenv v0.15.2 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.4 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
@ -94,13 +99,19 @@ require (
github.com/yuin/goldmark-emoji v1.0.2 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.19.0 // indirect golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.15.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/term v0.15.0 // indirect golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.16.0 // indirect golang.org/x/tools v0.19.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
modernc.org/libc v1.55.3 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.8.0 // indirect
modernc.org/strutil v1.2.0 // indirect
modernc.org/token v1.1.0 // indirect
) )

64
go.sum
View file

@ -86,6 +86,10 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
@ -99,6 +103,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@ -164,6 +170,8 @@ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKt
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
@ -187,6 +195,8 @@ github.com/pterm/pterm v0.12.71 h1:KcEJ98EiVCbzDkFbktJ2gMlr4pn8IzyGb9bwK6ffkuA=
github.com/pterm/pterm v0.12.71/go.mod h1:SUAcoZjRt+yjPWlWba+/Fd8zJJ2lSXBQWf0Z0HbFiIQ= github.com/pterm/pterm v0.12.71/go.mod h1:SUAcoZjRt+yjPWlWba+/Fd8zJJ2lSXBQWf0Z0HbFiIQ=
github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew= github.com/puzpuzpuz/xsync/v3 v3.0.2 h1:3yESHrRFYr6xzkz61LLkvNiPFXxJEAABanTQpKbAaew=
github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/puzpuzpuz/xsync/v3 v3.0.2/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
@ -267,14 +277,14 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8=
golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
@ -290,13 +300,13 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -318,8 +328,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -329,8 +339,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -346,8 +356,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
@ -363,3 +373,29 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
modernc.org/sqlite v1.32.0 h1:6BM4uGza7bWypsw4fdLRsLxut6bHe4c58VeqjRgST8s=
modernc.org/sqlite v1.32.0/go.mod h1:UqoylwmTb9F+IqXERT8bW9zzOWN8qwAIcLdzeBZs4hA=
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=