Tooling, Mod Browser, Cleanup, CI
This commit is contained in:
parent
6f63878987
commit
e329e48e9b
44 changed files with 2155 additions and 285 deletions
27
.github/workflows/build.yaml
vendored
Normal file
27
.github/workflows/build.yaml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
name: build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.17
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Go Generate
|
||||
run: go generate -tags tools -x ./...
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v2
|
||||
|
||||
- name: Build
|
||||
run: go build -v -o ficsit-cli .
|
||||
env:
|
||||
CGO_ENABLED: 1
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -109,3 +109,9 @@ modules.xml
|
|||
.idea/sonarlint
|
||||
|
||||
# End of https://www.gitignore.io/api/go,jetbrains+all
|
||||
|
||||
dist/
|
||||
/testdata
|
||||
/.graphqlconfig
|
||||
schema.graphql
|
||||
*.log
|
40
.golangci.yml
Normal file
40
.golangci.yml
Normal file
|
@ -0,0 +1,40 @@
|
|||
linters-settings:
|
||||
wrapcheck:
|
||||
ignoreSigs:
|
||||
- .Errorf(
|
||||
- errors.New(
|
||||
- errors.Unwrap(
|
||||
- .Wrap(
|
||||
- .Wrapf(
|
||||
- .WithMessage(
|
||||
- .WithMessagef(
|
||||
- .WithStack(
|
||||
|
||||
ignorePackageGlobs:
|
||||
- github.com/satisfactorymodding/ficsit-cli/*
|
||||
|
||||
linters:
|
||||
disable-all: true
|
||||
enable:
|
||||
- deadcode
|
||||
- errcheck
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- structcheck
|
||||
- typecheck
|
||||
- unused
|
||||
- varcheck
|
||||
- bidichk
|
||||
- contextcheck
|
||||
- durationcheck
|
||||
- errorlint
|
||||
- goconst
|
||||
- goimports
|
||||
- revive
|
||||
- ifshort
|
||||
- misspell
|
||||
- prealloc
|
||||
- whitespace
|
||||
- wrapcheck
|
47
.goreleaser.yml
Normal file
47
.goreleaser.yml
Normal file
|
@ -0,0 +1,47 @@
|
|||
project_name: ficsit
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go generate -x -tags tools ./...
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
- darwin
|
||||
goarch:
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
- ppc64le
|
||||
goarm:
|
||||
- 7
|
||||
|
||||
universal_binaries:
|
||||
- replace: true
|
||||
|
||||
archives:
|
||||
- format: binary
|
||||
allow_different_binary_count: true
|
||||
|
||||
nfpms:
|
||||
- formats:
|
||||
- apk
|
||||
- deb
|
||||
- rpm
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
snapshot:
|
||||
name_template: "{{ .Tag }}-next"
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
17
cfg/test_defaults.go
Normal file
17
cfg/test_defaults.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package cfg
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func SetDefaults() {
|
||||
_, file, _, _ := runtime.Caller(0)
|
||||
viper.SetDefault("cache-dir", filepath.Clean(filepath.Join(filepath.Dir(file), "../", "testdata", "cache")))
|
||||
viper.SetDefault("profiles-file", "profiles.json")
|
||||
viper.SetDefault("installations-file", "installations.json")
|
||||
viper.SetDefault("dry-run", false)
|
||||
viper.SetDefault("api", "https://api.ficsit.app/v2/query")
|
||||
}
|
47
cli/context.go
Normal file
47
cli/context.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
package cli
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
type GlobalContext struct {
|
||||
Installations *Installations
|
||||
Profiles *Profiles
|
||||
}
|
||||
|
||||
var globalContext *GlobalContext
|
||||
|
||||
func InitCLI() (*GlobalContext, error) {
|
||||
if globalContext != nil {
|
||||
return globalContext, nil
|
||||
}
|
||||
|
||||
profiles, err := InitProfiles()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to initialize profiles")
|
||||
}
|
||||
|
||||
installations, err := InitInstallations()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to initialize installations")
|
||||
}
|
||||
|
||||
ctx := &GlobalContext{
|
||||
Installations: installations,
|
||||
Profiles: profiles,
|
||||
}
|
||||
|
||||
globalContext = ctx
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (g *GlobalContext) Save() error {
|
||||
if err := g.Installations.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := g.Profiles.Save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,4 +1,175 @@
|
|||
package cli
|
||||
|
||||
type Installation struct {
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type InstallationsVersion int
|
||||
|
||||
const (
|
||||
InitialInstallationsVersion = InstallationsVersion(iota)
|
||||
|
||||
// Always last
|
||||
nextInstallationsVersion
|
||||
)
|
||||
|
||||
type Installations struct {
|
||||
Version InstallationsVersion `json:"version"`
|
||||
Installations []*Installation `json:"installations"`
|
||||
SelectedInstallation string `json:"selected_installation"`
|
||||
}
|
||||
|
||||
type Installation struct {
|
||||
Path string `json:"path"`
|
||||
Profile string `json:"profile"`
|
||||
}
|
||||
|
||||
func InitInstallations() (*Installations, error) {
|
||||
cacheDir := viper.GetString("cache-dir")
|
||||
|
||||
installationsFile := path.Join(cacheDir, viper.GetString("installations-file"))
|
||||
_, err := os.Stat(installationsFile)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, errors.Wrap(err, "failed to stat installations file")
|
||||
}
|
||||
|
||||
_, err := os.Stat(cacheDir)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, errors.Wrap(err, "failed to read cache directory")
|
||||
}
|
||||
|
||||
err = os.MkdirAll(cacheDir, 0755)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create cache directory")
|
||||
}
|
||||
}
|
||||
|
||||
emptyInstallations := Installations{
|
||||
Version: nextInstallationsVersion - 1,
|
||||
}
|
||||
|
||||
if err := emptyInstallations.Save(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to save empty installations")
|
||||
}
|
||||
}
|
||||
|
||||
installationsData, err := os.ReadFile(installationsFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read installations")
|
||||
}
|
||||
|
||||
var installations Installations
|
||||
if err := json.Unmarshal(installationsData, &installations); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal installations")
|
||||
}
|
||||
|
||||
if installations.Version >= nextInstallationsVersion {
|
||||
return nil, fmt.Errorf("unknown installations version: %d", installations.Version)
|
||||
}
|
||||
|
||||
return &installations, nil
|
||||
}
|
||||
|
||||
func (i *Installations) Save() error {
|
||||
if viper.GetBool("dry-run") {
|
||||
log.Info().Msg("dry-run: skipping installation saving")
|
||||
return nil
|
||||
}
|
||||
|
||||
installationsFile := path.Join(viper.GetString("cache-dir"), viper.GetString("installations-file"))
|
||||
|
||||
log.Info().Str("path", installationsFile).Msg("saving installations")
|
||||
|
||||
installationsJSON, err := json.MarshalIndent(i, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal installations")
|
||||
}
|
||||
|
||||
if err := os.WriteFile(installationsFile, installationsJSON, 0755); err != nil {
|
||||
return errors.Wrap(err, "failed to write installations")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Installations) AddInstallation(ctx *GlobalContext, installPath string, profile string) (*Installation, error) {
|
||||
installation := &Installation{
|
||||
Path: installPath,
|
||||
Profile: profile,
|
||||
}
|
||||
|
||||
if err := installation.Validate(ctx); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to validate installation")
|
||||
}
|
||||
|
||||
newStat, err := os.Stat(installation.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to stat installation directory")
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, install := range ctx.Installations.Installations {
|
||||
stat, err := os.Stat(install.Path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
found = os.SameFile(newStat, stat)
|
||||
if found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
return nil, errors.New("installation already present")
|
||||
}
|
||||
|
||||
i.Installations = append(i.Installations, installation)
|
||||
|
||||
return installation, nil
|
||||
}
|
||||
|
||||
func (i *Installations) GetInstallation(installPath string) *Installation {
|
||||
for _, install := range i.Installations {
|
||||
if install.Path == installPath {
|
||||
return install
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Installation) Validate(ctx *GlobalContext) error {
|
||||
found := false
|
||||
for _, p := range ctx.Profiles.Profiles {
|
||||
if p.Name == i.Profile {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return errors.New("profile not found")
|
||||
}
|
||||
|
||||
// TODO Validate installation path
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Installation) Install(ctx *GlobalContext) error {
|
||||
if err := i.Validate(ctx); err != nil {
|
||||
return errors.Wrap(err, "failed to validate installation")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
35
cli/installations_test.go
Normal file
35
cli/installations_test.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/MarvinJWendt/testza"
|
||||
"github.com/satisfactorymodding/ficsit-cli/cfg"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cfg.SetDefaults()
|
||||
}
|
||||
|
||||
func TestInstallationsInit(t *testing.T) {
|
||||
installations, err := InitInstallations()
|
||||
testza.AssertNoError(t, err)
|
||||
testza.AssertNotNil(t, installations)
|
||||
}
|
||||
|
||||
func TestAddInstallation(t *testing.T) {
|
||||
ctx, err := InitCLI()
|
||||
testza.AssertNoError(t, err)
|
||||
|
||||
profileName := "InstallationTest"
|
||||
profile := ctx.Profiles.AddProfile(profileName)
|
||||
testza.AssertNoError(t, profile.AddMod("AreaActions", ">=1.6.5"))
|
||||
testza.AssertNoError(t, profile.AddMod("ArmorModules__Modpack_All", ">=1.4.1"))
|
||||
|
||||
installation, err := ctx.Installations.AddInstallation(ctx, "../testdata/server", profileName)
|
||||
testza.AssertNoError(t, err)
|
||||
testza.AssertNotNil(t, installation)
|
||||
|
||||
err = installation.Install(ctx)
|
||||
testza.AssertNoError(t, err)
|
||||
}
|
198
cli/profiles.go
198
cli/profiles.go
|
@ -1,4 +1,200 @@
|
|||
package cli
|
||||
|
||||
type Profile struct {
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/satisfactorymodding/ficsit-cli/utils"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const defaultProfileName = "Default"
|
||||
|
||||
var defaultProfile = Profile{
|
||||
Name: defaultProfileName,
|
||||
}
|
||||
|
||||
type ProfilesVersion int
|
||||
|
||||
const (
|
||||
InitialProfilesVersion = ProfilesVersion(iota)
|
||||
|
||||
// Always last
|
||||
nextProfilesVersion
|
||||
)
|
||||
|
||||
type Profiles struct {
|
||||
Version ProfilesVersion `json:"version"`
|
||||
Profiles []*Profile `json:"profiles"`
|
||||
SelectedProfile string `json:"selected_profile"`
|
||||
}
|
||||
|
||||
type Profile struct {
|
||||
Name string `json:"name"`
|
||||
Mods map[string]ProfileMod `json:"mods"`
|
||||
}
|
||||
|
||||
type ProfileMod struct {
|
||||
Version string `json:"version"`
|
||||
InstalledVersion string `json:"installed_version"`
|
||||
}
|
||||
|
||||
func InitProfiles() (*Profiles, error) {
|
||||
cacheDir := viper.GetString("cache-dir")
|
||||
|
||||
profilesFile := path.Join(cacheDir, viper.GetString("profiles-file"))
|
||||
_, err := os.Stat(profilesFile)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, errors.Wrap(err, "failed to stat profiles file")
|
||||
}
|
||||
|
||||
_, err := os.Stat(cacheDir)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, errors.Wrap(err, "failed to read cache directory")
|
||||
}
|
||||
|
||||
err = os.MkdirAll(cacheDir, 0755)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create cache directory")
|
||||
}
|
||||
}
|
||||
|
||||
emptyProfiles := Profiles{
|
||||
Version: nextProfilesVersion - 1,
|
||||
Profiles: []*Profile{&defaultProfile},
|
||||
}
|
||||
|
||||
if err := emptyProfiles.Save(); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to save empty profiles")
|
||||
}
|
||||
}
|
||||
|
||||
profilesData, err := os.ReadFile(profilesFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read profiles")
|
||||
}
|
||||
|
||||
var profiles Profiles
|
||||
if err := json.Unmarshal(profilesData, &profiles); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal profiles")
|
||||
}
|
||||
|
||||
if profiles.Version >= nextProfilesVersion {
|
||||
return nil, fmt.Errorf("unknown profiles version: %d", profiles.Version)
|
||||
}
|
||||
|
||||
if len(profiles.Profiles) == 0 {
|
||||
profiles.Profiles = []*Profile{&defaultProfile}
|
||||
profiles.SelectedProfile = defaultProfileName
|
||||
}
|
||||
|
||||
if profiles.SelectedProfile == "" {
|
||||
profiles.SelectedProfile = profiles.Profiles[0].Name
|
||||
}
|
||||
|
||||
return &profiles, nil
|
||||
}
|
||||
|
||||
// Save the profiles to the profiles file.
|
||||
func (p *Profiles) Save() error {
|
||||
if viper.GetBool("dry-run") {
|
||||
log.Info().Msg("dry-run: skipping profile saving")
|
||||
return nil
|
||||
}
|
||||
|
||||
profilesFile := path.Join(viper.GetString("cache-dir"), viper.GetString("profiles-file"))
|
||||
|
||||
log.Info().Str("path", profilesFile).Msg("saving profiles")
|
||||
|
||||
profilesJSON, err := json.MarshalIndent(p, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal profiles")
|
||||
}
|
||||
|
||||
if err := os.WriteFile(profilesFile, profilesJSON, 0755); err != nil {
|
||||
return errors.Wrap(err, "failed to write profiles")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddProfile adds a new profile with the given name to the profiles list.
|
||||
func (p *Profiles) AddProfile(name string) *Profile {
|
||||
profile := &Profile{
|
||||
Name: name,
|
||||
}
|
||||
|
||||
p.Profiles = append(p.Profiles, profile)
|
||||
|
||||
return profile
|
||||
}
|
||||
|
||||
// DeleteProfile deletes the profile with the given name.
|
||||
func (p *Profiles) DeleteProfile(name string) {
|
||||
i := 0
|
||||
for _, profile := range p.Profiles {
|
||||
if profile.Name == name {
|
||||
break
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
|
||||
if i < len(p.Profiles) {
|
||||
p.Profiles = append(p.Profiles[:i], p.Profiles[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
// GetProfile returns the profile with the given name or nil if it doesn't exist.
|
||||
func (p *Profiles) GetProfile(name string) *Profile {
|
||||
for _, profile := range p.Profiles {
|
||||
if profile.Name == name {
|
||||
return profile
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddMod adds a mod to the profile with given version.
|
||||
func (p *Profile) AddMod(reference string, version string) error {
|
||||
if p.Mods == nil {
|
||||
p.Mods = make(map[string]ProfileMod)
|
||||
}
|
||||
|
||||
if !utils.SemVerRegex.MatchString(version) {
|
||||
return errors.New("invalid semver version")
|
||||
}
|
||||
|
||||
p.Mods[reference] = ProfileMod{
|
||||
Version: version,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveMod removes a mod from the profile.
|
||||
func (p *Profile) RemoveMod(reference string) {
|
||||
if p.Mods == nil {
|
||||
return
|
||||
}
|
||||
|
||||
delete(p.Mods, reference)
|
||||
}
|
||||
|
||||
// HasMod returns true if the profile has a mod with the given reference
|
||||
func (p *Profile) HasMod(reference string) bool {
|
||||
if p.Mods == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
_, ok := p.Mods[reference]
|
||||
|
||||
return ok
|
||||
}
|
||||
|
|
18
cli/profiles_test.go
Normal file
18
cli/profiles_test.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/MarvinJWendt/testza"
|
||||
"github.com/satisfactorymodding/ficsit-cli/cfg"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cfg.SetDefaults()
|
||||
}
|
||||
|
||||
func TestProfilesInit(t *testing.T) {
|
||||
profiles, err := InitProfiles()
|
||||
testza.AssertNoError(t, err)
|
||||
testza.AssertNotNil(t, profiles)
|
||||
}
|
24
cmd/cli.go
Normal file
24
cmd/cli.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(cliCmd)
|
||||
}
|
||||
|
||||
var cliCmd = &cobra.Command{
|
||||
Use: "cli",
|
||||
Short: "Start interactive CLI",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
global, err := cli.InitCLI()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return tea.RunTea(global)
|
||||
},
|
||||
}
|
|
@ -1,84 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/spf13/cobra"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Slice of strings with placeholder text.
|
||||
var fakeInstallList = strings.Split("pseudo-excel pseudo-photoshop pseudo-chrome pseudo-outlook pseudo-explorer "+
|
||||
"pseudo-dops pseudo-git pseudo-vsc pseudo-intellij pseudo-minecraft pseudo-scoop pseudo-chocolatey", " ")
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(downloadCmd)
|
||||
}
|
||||
|
||||
var downloadCmd = &cobra.Command{
|
||||
Use: "download",
|
||||
Aliases: []string{"dl"},
|
||||
Short: "Download a mod",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
conn, err := net.Dial("udp", "127.0.0.1:15777")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
const protoVersion = 0
|
||||
encoded := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(encoded, uint64(time.Now().UnixMilli()))
|
||||
|
||||
query := append([]byte{0, protoVersion}, encoded...)
|
||||
spew.Dump("Query:", query)
|
||||
|
||||
if _, err := conn.Write(query); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response := make([]byte, 17)
|
||||
_, err = bufio.NewReader(conn).Read(response)
|
||||
|
||||
spew.Dump("Response:", response)
|
||||
|
||||
serverQueryID := response[0]
|
||||
serverProtocolVersion := response[1]
|
||||
serverTimestamp := binary.LittleEndian.Uint64(response[2:10])
|
||||
serverState := response[10]
|
||||
serverNetCL := binary.LittleEndian.Uint32(response[11:15])
|
||||
beaconPort := binary.LittleEndian.Uint16(response[15:])
|
||||
|
||||
fmt.Printf("Server query ID: %d\n", serverQueryID)
|
||||
fmt.Printf("Server protocol version: %d\n", serverProtocolVersion)
|
||||
fmt.Printf("Server timestamp: %d\n", serverTimestamp)
|
||||
fmt.Printf("Server state: %d\n", serverState)
|
||||
fmt.Printf("Server net CL: %d\n", serverNetCL)
|
||||
fmt.Printf("Server beacon port: %d\n", beaconPort)
|
||||
//for i := 0; i < 5; i++ {
|
||||
// log.Info().Int("i", i).Msg("Foo")
|
||||
// time.Sleep(time.Second)
|
||||
//}
|
||||
//
|
||||
//p, _ := pterm.DefaultProgressbar.WithTotal(len(fakeInstallList)).WithTitle("Downloading stuff").Start()
|
||||
//
|
||||
//for i := 0; i < p.Total; i++ {
|
||||
// p.UpdateTitle("Downloading " + fakeInstallList[i]) // Update the title of the progressbar.
|
||||
// pterm.Success.Println("Downloading " + fakeInstallList[i]) // If a progressbar is running, each print will be printed above the progressbar.
|
||||
// p.Increment() // Increment the progressbar by one. Use Add(x int) to increment by a custom amount.
|
||||
// time.Sleep(time.Millisecond * 350) // Sleep 350 milliseconds.
|
||||
//}
|
||||
//
|
||||
//for i := 0; i < 5; i++ {
|
||||
// log.Info().Int("i", i).Msg("Bar")
|
||||
// time.Sleep(time.Second)
|
||||
//}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
69
cmd/root.go
69
cmd/root.go
|
@ -1,21 +1,24 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/pterm/pterm"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "ficsit",
|
||||
Short: "cli mod manager for satisfactory",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
viper.SetConfigName("config")
|
||||
viper.AddConfigPath(".")
|
||||
viper.SetEnvPrefix("ficsit")
|
||||
|
@ -30,24 +33,50 @@ var rootCmd = &cobra.Command{
|
|||
|
||||
zerolog.SetGlobalLevel(level)
|
||||
|
||||
writers := make([]io.Writer, 0)
|
||||
if viper.GetBool("pretty") {
|
||||
pterm.EnableStyling()
|
||||
log.Logger = zerolog.New(os.Stdout).With().Timestamp().Logger().Output(zerolog.ConsoleWriter{
|
||||
Out: os.Stdout,
|
||||
TimeFormat: time.RFC3339,
|
||||
})
|
||||
} else {
|
||||
pterm.DisableStyling()
|
||||
}
|
||||
|
||||
if !viper.GetBool("quiet") {
|
||||
writers = append(writers, zerolog.ConsoleWriter{
|
||||
Out: os.Stdout,
|
||||
TimeFormat: time.RFC3339,
|
||||
})
|
||||
}
|
||||
|
||||
if viper.GetString("log-file") != "" {
|
||||
logFile, err := os.OpenFile(viper.GetString("log-file"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to open log file")
|
||||
}
|
||||
|
||||
writers = append(writers, logFile)
|
||||
}
|
||||
|
||||
log.Logger = zerolog.New(io.MultiWriter(writers...)).With().Timestamp().Logger()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
// Execute tea as default
|
||||
cmd, _, err := rootCmd.Find(os.Args[1:])
|
||||
|
||||
cli := len(os.Args) >= 2 && os.Args[1] == "cli"
|
||||
if (len(os.Args) <= 1 || os.Args[1] != "help") && (err != nil || cmd == rootCmd) {
|
||||
tea.RunTea()
|
||||
return
|
||||
args := append([]string{"cli"}, os.Args[1:]...)
|
||||
rootCmd.SetArgs(args)
|
||||
cli = true
|
||||
}
|
||||
|
||||
// Always be quiet in CLI mode
|
||||
if cli {
|
||||
viper.Set("quiet", true)
|
||||
}
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
|
@ -62,12 +91,28 @@ func init() {
|
|||
}
|
||||
|
||||
rootCmd.PersistentFlags().String("log", "info", "The log level to output")
|
||||
rootCmd.PersistentFlags().String("log-file", "", "File to output logs to")
|
||||
rootCmd.PersistentFlags().Bool("quiet", false, "Do not log anything to console")
|
||||
rootCmd.PersistentFlags().Bool("pretty", true, "Whether to render pretty terminal output")
|
||||
|
||||
rootCmd.PersistentFlags().String("cache-dir", path.Join(baseCacheDir, "ficsit"), "The cache directory")
|
||||
rootCmd.PersistentFlags().Bool("dry-run", false, "Dry-run. Do not save any changes")
|
||||
|
||||
rootCmd.PersistentFlags().String("cache-dir", filepath.Clean(filepath.Join(baseCacheDir, "ficsit")), "The cache directory")
|
||||
rootCmd.PersistentFlags().String("profiles-file", "profiles.json", "The profiles file")
|
||||
rootCmd.PersistentFlags().String("installations-file", "installations.json", "The installations file")
|
||||
|
||||
rootCmd.PersistentFlags().String("api", "https://api.ficsit.app/v2/query", "URL for API")
|
||||
|
||||
_ = viper.BindPFlag("log", rootCmd.PersistentFlags().Lookup("log"))
|
||||
_ = viper.BindPFlag("log-file", rootCmd.PersistentFlags().Lookup("log-file"))
|
||||
_ = viper.BindPFlag("quiet", rootCmd.PersistentFlags().Lookup("quiet"))
|
||||
_ = viper.BindPFlag("pretty", rootCmd.PersistentFlags().Lookup("pretty"))
|
||||
|
||||
_ = viper.BindPFlag("dry-run", rootCmd.PersistentFlags().Lookup("dry-run"))
|
||||
|
||||
_ = viper.BindPFlag("cache-dir", rootCmd.PersistentFlags().Lookup("cache-dir"))
|
||||
_ = viper.BindPFlag("profiles-file", rootCmd.PersistentFlags().Lookup("profiles-file"))
|
||||
_ = viper.BindPFlag("installations-file", rootCmd.PersistentFlags().Lookup("installations-file"))
|
||||
|
||||
_ = viper.BindPFlag("api", rootCmd.PersistentFlags().Lookup("api"))
|
||||
}
|
||||
|
|
1
ficsit/.gitignore
vendored
Normal file
1
ficsit/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
types.go
|
36
ficsit/api_test.go
Normal file
36
ficsit/api_test.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package ficsit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
"github.com/MarvinJWendt/testza"
|
||||
"github.com/satisfactorymodding/ficsit-cli/cfg"
|
||||
)
|
||||
|
||||
var client graphql.Client
|
||||
|
||||
func init() {
|
||||
cfg.SetDefaults()
|
||||
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.GetMod)
|
||||
testza.AssertNotNil(t, response.GetMod.Versions)
|
||||
testza.AssertNotZero(t, len(response.GetMod.Versions))
|
||||
}
|
||||
|
||||
func TestMods(t *testing.T) {
|
||||
response, err := Mods(context.Background(), client, ModFilter{})
|
||||
testza.AssertNoError(t, err)
|
||||
testza.AssertNotNil(t, response)
|
||||
testza.AssertNotNil(t, response.GetMods)
|
||||
testza.AssertNotNil(t, response.GetMods.Mods)
|
||||
testza.AssertNotZero(t, response.GetMods.Count)
|
||||
testza.AssertNotZero(t, len(response.GetMods.Mods))
|
||||
}
|
18
ficsit/queries/mod.graphql
Normal file
18
ficsit/queries/mod.graphql
Normal file
|
@ -0,0 +1,18 @@
|
|||
query GetMod ($modId: ModID!) {
|
||||
getMod(modId: $modId) {
|
||||
id
|
||||
mod_reference
|
||||
name
|
||||
views
|
||||
downloads
|
||||
authors {
|
||||
role
|
||||
user {
|
||||
username
|
||||
}
|
||||
}
|
||||
full_description
|
||||
source_url
|
||||
created_at
|
||||
}
|
||||
}
|
13
ficsit/queries/mod_versions.graphql
Normal file
13
ficsit/queries/mod_versions.graphql
Normal file
|
@ -0,0 +1,13 @@
|
|||
# @genqlient(omitempty: true)
|
||||
query ModVersions (
|
||||
$modId: ModID!,
|
||||
$filter: VersionFilter
|
||||
) {
|
||||
getMod(modId: $modId) {
|
||||
id
|
||||
versions (filter: $filter) {
|
||||
id
|
||||
version
|
||||
}
|
||||
}
|
||||
}
|
11
ficsit/queries/mods.graphql
Normal file
11
ficsit/queries/mods.graphql
Normal file
|
@ -0,0 +1,11 @@
|
|||
# @genqlient(omitempty: true)
|
||||
query Mods ($filter: ModFilter) {
|
||||
getMods (filter: $filter) {
|
||||
count
|
||||
mods {
|
||||
id
|
||||
name
|
||||
mod_reference
|
||||
}
|
||||
}
|
||||
}
|
12
ficsit/root.go
Normal file
12
ficsit/root.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package ficsit
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func InitAPI() graphql.Client {
|
||||
return graphql.NewClient(viper.GetString("api"), http.DefaultClient)
|
||||
}
|
23
genqlient.yaml
Normal file
23
genqlient.yaml
Normal file
|
@ -0,0 +1,23 @@
|
|||
# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||
schema: schema.graphql
|
||||
operations:
|
||||
- ficsit/queries/*.graphql
|
||||
generated: ficsit/types.go
|
||||
package: ficsit
|
||||
bindings:
|
||||
UserID:
|
||||
type: string
|
||||
ModReference:
|
||||
type: string
|
||||
BootstrapVersionID:
|
||||
type: string
|
||||
ModID:
|
||||
type: string
|
||||
VersionID:
|
||||
type: string
|
||||
GuideID:
|
||||
type: string
|
||||
SMLVersionID:
|
||||
type: string
|
||||
Date:
|
||||
type: time.Time
|
23
go.mod
23
go.mod
|
@ -3,10 +3,14 @@ module github.com/satisfactorymodding/ficsit-cli
|
|||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/Khan/genqlient v0.3.0
|
||||
github.com/MarvinJWendt/testza v0.2.10
|
||||
github.com/charmbracelet/bubbles v0.9.0
|
||||
github.com/charmbracelet/bubbletea v0.19.0
|
||||
github.com/charmbracelet/glamour v0.3.0
|
||||
github.com/charmbracelet/lipgloss v0.4.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pterm/pterm v0.12.33
|
||||
github.com/rs/zerolog v1.25.0
|
||||
github.com/spf13/cobra v1.2.1
|
||||
|
@ -14,23 +18,33 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/agnivade/levenshtein v1.0.3 // indirect
|
||||
github.com/alecthomas/chroma v0.8.2 // indirect
|
||||
github.com/alexflint/go-arg v1.4.2 // indirect
|
||||
github.com/alexflint/go-scalar v1.0.0 // indirect
|
||||
github.com/atomicgo/cursor v0.0.1 // indirect
|
||||
github.com/atotto/clipboard v0.1.2 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/containerd/console v1.0.2 // indirect
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
|
||||
github.com/dlclark/regexp2 v1.2.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.1 // indirect
|
||||
github.com/gookit/color v1.4.2 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.5 // indirect
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.6 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.2 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.9.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pelletier/go-toml v1.9.4 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.0 // indirect
|
||||
github.com/spf13/afero v1.6.0 // indirect
|
||||
|
@ -38,10 +52,17 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.2.0 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.1.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||
github.com/yuin/goldmark v1.3.5 // indirect
|
||||
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 // indirect
|
||||
golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
golang.org/x/tools v0.1.5 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/ini.v1 v1.63.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
|
79
go.sum
79
go.sum
|
@ -43,15 +43,37 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
|||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Khan/genqlient v0.3.0 h1:G35N630mNCW+j0rqSJUsvNkPLoX0bjrllRMnaQTbCak=
|
||||
github.com/Khan/genqlient v0.3.0/go.mod h1:o9QUG7O7GhCeB3C83scbUQtdp+tdErC8OkVbSxIw1g4=
|
||||
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
|
||||
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
|
||||
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
|
||||
github.com/MarvinJWendt/testza v0.2.10 h1:cX4zE9TofXxe72a6EPIYAxC+8cVWTsmmgsXTZIT+5bQ=
|
||||
github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSrDrXDpX2bG66k=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
|
||||
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/chroma v0.8.2 h1:x3zkuE2lUk/RIekyAJ3XRqSCP4zwWDfcw/YJCuCAACg=
|
||||
github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
github.com/alexflint/go-arg v1.4.2 h1:lDWZAXxpAnZUq4qwb86p/3rIJJ2Li81EoMbTMujhVa0=
|
||||
github.com/alexflint/go-arg v1.4.2/go.mod h1:9iRbDxne7LcR/GSvEr7ma++GLpdIU1zrghf2y2768kM=
|
||||
github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70=
|
||||
github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
|
@ -60,8 +82,11 @@ github.com/atomicgo/cursor v0.0.1 h1:xdogsqa6YYlLfM+GyClC/Lchf7aiMerFiZQn7soTOoU
|
|||
github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk=
|
||||
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
|
||||
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
|
||||
github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/charmbracelet/bubbles v0.9.0 h1:lqJ8FXwoLceQF2J0A+dWo1Cuu1dNyjbW4Opgdi2vkhw=
|
||||
|
@ -69,6 +94,8 @@ github.com/charmbracelet/bubbles v0.9.0/go.mod h1:NWT/c+0rYEnYChz5qCyX4Lj6fDw9gG
|
|||
github.com/charmbracelet/bubbletea v0.14.1/go.mod h1:b5lOf5mLjMg1tRn1HVla54guZB+jvsyV0yYAQja95zE=
|
||||
github.com/charmbracelet/bubbletea v0.19.0 h1:1gz4rbxl3qZik/oP8QW2vUtul2gO8RDDzmoLGERpTQc=
|
||||
github.com/charmbracelet/bubbletea v0.19.0/go.mod h1:VuXF2pToRxDUHcBUcPmCRUHRvFATM4Ckb/ql1rBl3KA=
|
||||
github.com/charmbracelet/glamour v0.3.0 h1:3H+ZrKlSg8s+WU6V7eF2eRVYt8lCueffbi7r2+ffGkc=
|
||||
github.com/charmbracelet/glamour v0.3.0/go.mod h1:TzF0koPZhqq0YVBNL100cPHznAAjVj7fksX2RInwjGw=
|
||||
github.com/charmbracelet/harmonica v0.1.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
|
||||
github.com/charmbracelet/lipgloss v0.3.0/go.mod h1:VkhdBS2eNAmRkTwRKLJCFhCOVkjntMusBDxv7TXahuk=
|
||||
github.com/charmbracelet/lipgloss v0.4.0 h1:768h64EFkGUr8V5yAKV7/Ta0NiVceiPaV+PphaW1K9g=
|
||||
|
@ -86,10 +113,16 @@ github.com/containerd/console v1.0.2 h1:Pi6D+aZXM+oUw1czuKgH5IJ+y0jhYcwBJfx5/Ghn
|
|||
github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
@ -104,10 +137,12 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
|
|||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -181,6 +216,11 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf
|
|||
github.com/gookit/color v1.4.2 h1:tXy44JFSFkKnELV6WaMo/lLfu/meqITX3iAV52do7lk=
|
||||
github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
|
@ -230,10 +270,12 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
|
||||
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
|
@ -244,10 +286,13 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
|
|||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/microcosm-cc/bluemonday v1.0.6 h1:ZOvqHKtnx0fUpnbQm3m3zKFWE+DRC+XB1onh8JoEObE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.6/go.mod h1:HOT/6NaBlR0f9XlxD3zolN6Z3N8Lp4pvhp+jLS5ihnI=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
|
@ -258,6 +303,7 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
|
|||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
|
||||
|
@ -267,12 +313,17 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/reflow v0.2.0/go.mod h1:qT22vjVmM9MIUeLgsVYe/Ye7eZlbv9dZjL3dVhUqLX8=
|
||||
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
|
||||
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||
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/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
|
||||
|
@ -297,6 +348,7 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
|||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.25.0 h1:Rj7XygbUHKUlDPcVdoLyR91fJBsduXj5fRxyqIQj/II=
|
||||
github.com/rs/zerolog v1.25.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI=
|
||||
|
@ -306,7 +358,12 @@ github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYI
|
|||
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
|
||||
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
|
@ -325,6 +382,8 @@ github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH
|
|||
github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk=
|
||||
github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
|
@ -334,13 +393,21 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
|
||||
github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os=
|
||||
github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
|
@ -398,6 +465,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -435,7 +503,9 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420 h1:a8jGStKg0XqKDlKqjLrXn0ioF5MH36pT7Z0BRTqLhbk=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -494,6 +564,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -544,6 +615,7 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
|
@ -551,6 +623,7 @@ golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
|
@ -567,6 +640,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
|
@ -597,10 +671,12 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
|||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
|
@ -738,6 +814,7 @@ gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
|
|||
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
|
@ -754,3 +831,5 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
|||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
|
||||
|
|
48
tea/components/header.go
Normal file
48
tea/components/header.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||
)
|
||||
|
||||
var _ tea.Model = (*headerComponent)(nil)
|
||||
|
||||
type headerComponent struct {
|
||||
root RootModel
|
||||
labelStyle lipgloss.Style
|
||||
}
|
||||
|
||||
func NewHeaderComponent(root RootModel) tea.Model {
|
||||
return headerComponent{
|
||||
root: root,
|
||||
labelStyle: utils.LabelStyle,
|
||||
}
|
||||
}
|
||||
|
||||
func (h headerComponent) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h headerComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return h, nil
|
||||
}
|
||||
|
||||
func (h headerComponent) View() string {
|
||||
out := h.labelStyle.Render("Installation: ")
|
||||
if h.root.GetCurrentInstallation() != nil {
|
||||
out += h.root.GetCurrentInstallation().Path
|
||||
} else {
|
||||
out += "None"
|
||||
}
|
||||
out += "\n"
|
||||
|
||||
out += h.labelStyle.Render("Profile: ")
|
||||
if h.root.GetCurrentProfile() != nil {
|
||||
out += h.root.GetCurrentProfile().Name
|
||||
} else {
|
||||
out += "None"
|
||||
}
|
||||
|
||||
return lipgloss.NewStyle().Margin(1, 0).Render(out)
|
||||
}
|
25
tea/components/types.go
Normal file
25
tea/components/types.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package components
|
||||
|
||||
import (
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli"
|
||||
)
|
||||
|
||||
type RootModel interface {
|
||||
GetGlobal() *cli.GlobalContext
|
||||
|
||||
GetCurrentProfile() *cli.Profile
|
||||
SetCurrentProfile(profile *cli.Profile) error
|
||||
|
||||
GetCurrentInstallation() *cli.Installation
|
||||
SetCurrentInstallation(installation *cli.Installation) error
|
||||
|
||||
GetAPIClient() graphql.Client
|
||||
|
||||
Size() tea.WindowSizeMsg
|
||||
SetSize(size tea.WindowSizeMsg)
|
||||
|
||||
View() string
|
||||
Height() int
|
||||
}
|
55
tea/keys.go
55
tea/keys.go
|
@ -1,55 +0,0 @@
|
|||
package tea
|
||||
|
||||
import "github.com/charmbracelet/bubbles/key"
|
||||
|
||||
type keyMap struct {
|
||||
Up key.Binding
|
||||
Down key.Binding
|
||||
Left key.Binding
|
||||
Right key.Binding
|
||||
Enter key.Binding
|
||||
Help key.Binding
|
||||
Quit key.Binding
|
||||
}
|
||||
|
||||
func (k keyMap) ShortHelp() []key.Binding {
|
||||
return []key.Binding{k.Help, k.Quit}
|
||||
}
|
||||
|
||||
func (k keyMap) FullHelp() [][]key.Binding {
|
||||
return [][]key.Binding{
|
||||
{k.Up, k.Down, k.Left, k.Right},
|
||||
{k.Enter, k.Help, k.Quit},
|
||||
}
|
||||
}
|
||||
|
||||
var keys = keyMap{
|
||||
Up: key.NewBinding(
|
||||
key.WithKeys("up", "k"),
|
||||
key.WithHelp("↑/k", "move up"),
|
||||
),
|
||||
Down: key.NewBinding(
|
||||
key.WithKeys("down", "j"),
|
||||
key.WithHelp("↓/j", "move down"),
|
||||
),
|
||||
Left: key.NewBinding(
|
||||
key.WithKeys("left", "h"),
|
||||
key.WithHelp("←/h", "move left"),
|
||||
),
|
||||
Right: key.NewBinding(
|
||||
key.WithKeys("right", "l"),
|
||||
key.WithHelp("→/l", "move right"),
|
||||
),
|
||||
Enter: key.NewBinding(
|
||||
key.WithKeys("enter"),
|
||||
key.WithHelp("Enter", "confirm selection"),
|
||||
),
|
||||
Help: key.NewBinding(
|
||||
key.WithKeys("?"),
|
||||
key.WithHelp("?", "toggle help"),
|
||||
),
|
||||
Quit: key.NewBinding(
|
||||
key.WithKeys("q", "esc", "ctrl+c"),
|
||||
key.WithHelp("q", "quit"),
|
||||
),
|
||||
}
|
85
tea/root.go
85
tea/root.go
|
@ -1,76 +1,89 @@
|
|||
package tea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Khan/genqlient/graphql"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli"
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/scenes"
|
||||
"os"
|
||||
)
|
||||
|
||||
var docStyle = lipgloss.NewStyle().Margin(1, 2)
|
||||
|
||||
type item string
|
||||
|
||||
func (i item) FilterValue() string { return string(i) }
|
||||
|
||||
type rootModel struct {
|
||||
currentModel tea.Model
|
||||
currentProfile *cli.Profile
|
||||
currentInstallation *cli.Installation
|
||||
global *cli.GlobalContext
|
||||
apiClient graphql.Client
|
||||
currentSize tea.WindowSizeMsg
|
||||
headerComponent tea.Model
|
||||
}
|
||||
|
||||
func (m *rootModel) ChangeScene(model tea.Model) {
|
||||
m.currentModel = model
|
||||
func newModel(global *cli.GlobalContext) *rootModel {
|
||||
m := &rootModel{
|
||||
global: global,
|
||||
currentProfile: global.Profiles.GetProfile(global.Profiles.SelectedProfile),
|
||||
currentInstallation: global.Installations.GetInstallation(global.Installations.SelectedInstallation),
|
||||
apiClient: ficsit.InitAPI(),
|
||||
currentSize: tea.WindowSizeMsg{
|
||||
Width: 20,
|
||||
Height: 14,
|
||||
},
|
||||
}
|
||||
|
||||
m.headerComponent = components.NewHeaderComponent(m)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *rootModel) GetCurrentProfile() *cli.Profile {
|
||||
return m.currentProfile
|
||||
}
|
||||
|
||||
func (m *rootModel) SetCurrentProfile(profile *cli.Profile) {
|
||||
func (m *rootModel) SetCurrentProfile(profile *cli.Profile) error {
|
||||
m.currentProfile = profile
|
||||
m.global.Profiles.SelectedProfile = profile.Name
|
||||
return m.global.Save()
|
||||
}
|
||||
|
||||
func (m *rootModel) GetCurrentInstallation() *cli.Installation {
|
||||
return m.currentInstallation
|
||||
}
|
||||
|
||||
func (m *rootModel) SetCurrentInstallation(installation *cli.Installation) {
|
||||
func (m *rootModel) SetCurrentInstallation(installation *cli.Installation) error {
|
||||
m.currentInstallation = installation
|
||||
m.global.Installations.SelectedInstallation = installation.Path
|
||||
return m.global.Save()
|
||||
}
|
||||
|
||||
func newModel() rootModel {
|
||||
m := rootModel{}
|
||||
m.currentModel = scenes.NewMainMenu(&m)
|
||||
return m
|
||||
func (m *rootModel) GetAPIClient() graphql.Client {
|
||||
return m.apiClient
|
||||
}
|
||||
|
||||
func (m rootModel) Init() tea.Cmd {
|
||||
return m.currentModel.Init()
|
||||
func (m *rootModel) Size() tea.WindowSizeMsg {
|
||||
return m.currentSize
|
||||
}
|
||||
|
||||
func (m rootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
m.currentModel, cmd = m.currentModel.Update(msg)
|
||||
return m, cmd
|
||||
func (m *rootModel) SetSize(size tea.WindowSizeMsg) {
|
||||
m.currentSize = size
|
||||
}
|
||||
|
||||
func (m rootModel) View() string {
|
||||
|
||||
style := lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("220"))
|
||||
|
||||
out := style.Render("Installation:") + " " + "// TODO" + "\n"
|
||||
out += style.Render("Profile:") + " " + "// TODO" + "\n"
|
||||
out += "\n"
|
||||
|
||||
return out + m.currentModel.View()
|
||||
func (m *rootModel) View() string {
|
||||
return m.headerComponent.View()
|
||||
}
|
||||
|
||||
func RunTea() {
|
||||
if err := tea.NewProgram(newModel()).Start(); err != nil {
|
||||
fmt.Printf("Could not start program :(\n%v\n", err)
|
||||
os.Exit(1)
|
||||
func (m *rootModel) Height() int {
|
||||
return lipgloss.Height(m.View()) + 1
|
||||
}
|
||||
|
||||
func (m *rootModel) GetGlobal() *cli.GlobalContext {
|
||||
return m.global
|
||||
}
|
||||
|
||||
func RunTea(global *cli.GlobalContext) error {
|
||||
if err := tea.NewProgram(scenes.NewMainMenu(newModel(global))).Start(); err != nil {
|
||||
return errors.Wrap(err, "internal tea error")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
90
tea/scenes/exit_menu.go
Normal file
90
tea/scenes/exit_menu.go
Normal file
|
@ -0,0 +1,90 @@
|
|||
package scenes
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||
)
|
||||
|
||||
var _ tea.Model = (*exitMenu)(nil)
|
||||
|
||||
type exitMenu struct {
|
||||
root components.RootModel
|
||||
list list.Model
|
||||
}
|
||||
|
||||
func NewExitMenu(root components.RootModel) tea.Model {
|
||||
model := mainMenu{
|
||||
root: root,
|
||||
}
|
||||
|
||||
items := []list.Item{
|
||||
utils.SimpleItem{
|
||||
Title: "Exit Saving Changes",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
if err := root.GetGlobal().Save(); err != nil {
|
||||
panic(err) // TODO
|
||||
}
|
||||
return currentModel, tea.Quit
|
||||
},
|
||||
},
|
||||
utils.SimpleItem{
|
||||
Title: "Exit Discarding Changes",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
return currentModel, tea.Quit
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
model.list = list.NewModel(items, utils.ItemDelegate{}, root.Size().Width, root.Size().Height-root.Height())
|
||||
model.list.SetShowStatusBar(false)
|
||||
model.list.SetFilteringEnabled(false)
|
||||
model.list.Title = "Save Changes?"
|
||||
model.list.Styles = utils.ListStyles
|
||||
model.list.DisableQuitKeybindings()
|
||||
model.list.SetSize(model.list.Width(), model.list.Height())
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
func (m exitMenu) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m exitMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
log.Warn().Msg(spew.Sdump(msg))
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch keypress := msg.String(); keypress {
|
||||
case KeyControlC:
|
||||
return m, tea.Quit
|
||||
case KeyEnter:
|
||||
i, ok := m.list.SelectedItem().(utils.SimpleItem)
|
||||
if ok {
|
||||
if i.Activate != nil {
|
||||
i.Activate(msg, m)
|
||||
return m, tea.Quit
|
||||
}
|
||||
}
|
||||
return m, tea.Quit
|
||||
default:
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
top, right, bottom, left := lipgloss.NewStyle().Margin(2, 2).GetMargin()
|
||||
m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom)
|
||||
m.root.SetSize(msg)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m exitMenu) View() string {
|
||||
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.list.View())
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
package scenes
|
||||
|
||||
import tea "github.com/charmbracelet/bubbletea"
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
)
|
||||
|
||||
func NewInstallations(root RootModel) tea.Model {
|
||||
func NewInstallations(root components.RootModel, parent tea.Model) tea.Model {
|
||||
return nil
|
||||
}
|
||||
|
|
6
tea/scenes/keys.go
Normal file
6
tea/scenes/keys.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package scenes
|
||||
|
||||
const (
|
||||
KeyControlC = "ctrl+c"
|
||||
KeyEnter = "enter"
|
||||
)
|
|
@ -1,78 +1,82 @@
|
|||
package scenes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/charmbracelet/bubbles/help"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"io"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||
)
|
||||
|
||||
var _ tea.Model = (*mainMenu)(nil)
|
||||
|
||||
type mainMenu struct {
|
||||
root RootModel
|
||||
help help.Model
|
||||
inputStyle lipgloss.Style
|
||||
lastKey string
|
||||
quitting bool
|
||||
list list.Model
|
||||
root components.RootModel
|
||||
list list.Model
|
||||
}
|
||||
|
||||
type menuItem struct {
|
||||
Title string
|
||||
ModelFn func(model RootModel) tea.Model
|
||||
}
|
||||
|
||||
func (i menuItem) FilterValue() string { return i.Title }
|
||||
|
||||
type itemDelegate struct{}
|
||||
|
||||
func (d itemDelegate) Height() int { return 1 }
|
||||
func (d itemDelegate) Spacing() int { return 0 }
|
||||
func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
||||
func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
||||
i, ok := listItem.(menuItem)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
style := lipgloss.NewStyle().PaddingLeft(2)
|
||||
|
||||
str := style.Render("o " + i.Title)
|
||||
if index == m.Index() {
|
||||
str = style.Foreground(lipgloss.Color("202")).Render("• " + i.Title)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, str)
|
||||
}
|
||||
|
||||
func NewMainMenu(root RootModel) tea.Model {
|
||||
items := []list.Item{
|
||||
menuItem{
|
||||
Title: "Installations",
|
||||
ModelFn: NewInstallations,
|
||||
},
|
||||
menuItem{
|
||||
Title: "Profiles",
|
||||
ModelFn: NewProfiles,
|
||||
},
|
||||
menuItem{
|
||||
Title: "Mods",
|
||||
ModelFn: NewMods,
|
||||
},
|
||||
}
|
||||
|
||||
l := list.NewModel(items, itemDelegate{}, 20, 14)
|
||||
l.SetShowStatusBar(false)
|
||||
l.SetFilteringEnabled(false)
|
||||
l.SetShowTitle(false)
|
||||
l.Styles.PaginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(2)
|
||||
l.Styles.HelpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(2).PaddingBottom(1)
|
||||
|
||||
return mainMenu{
|
||||
func NewMainMenu(root components.RootModel) tea.Model {
|
||||
model := mainMenu{
|
||||
root: root,
|
||||
list: l,
|
||||
}
|
||||
|
||||
items := []list.Item{
|
||||
utils.SimpleItem{
|
||||
Title: "Installations",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
newModel := NewInstallations(root, currentModel)
|
||||
return newModel, newModel.Init()
|
||||
},
|
||||
},
|
||||
utils.SimpleItem{
|
||||
Title: "Profiles",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
newModel := NewProfiles(root, currentModel)
|
||||
return newModel, newModel.Init()
|
||||
},
|
||||
},
|
||||
utils.SimpleItem{
|
||||
Title: "Mods",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
newModel := NewMods(root, currentModel)
|
||||
return newModel, newModel.Init()
|
||||
},
|
||||
},
|
||||
utils.SimpleItem{
|
||||
Title: "Apply Changes",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
// TODO Apply changes to all changed profiles
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
utils.SimpleItem{
|
||||
Title: "Save",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
if err := root.GetGlobal().Save(); err != nil {
|
||||
panic(err) // TODO Handle Error
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
utils.SimpleItem{
|
||||
Title: "Exit",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
newModel := NewExitMenu(root)
|
||||
return newModel, newModel.Init()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
model.list = list.NewModel(items, utils.ItemDelegate{}, root.Size().Width, root.Size().Height-root.Height())
|
||||
model.list.SetShowStatusBar(false)
|
||||
model.list.SetFilteringEnabled(false)
|
||||
model.list.Title = "Main Menu"
|
||||
model.list.Styles = utils.ListStyles
|
||||
model.list.SetSize(model.list.Width(), model.list.Height())
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
func (m mainMenu) Init() tea.Cmd {
|
||||
|
@ -80,19 +84,26 @@ func (m mainMenu) Init() tea.Cmd {
|
|||
}
|
||||
|
||||
func (m mainMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
log.Warn().Msg(spew.Sdump(msg))
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch keypress := msg.String(); keypress {
|
||||
case "ctrl+c":
|
||||
fallthrough
|
||||
case "q":
|
||||
m.quitting = true
|
||||
case KeyControlC:
|
||||
return m, tea.Quit
|
||||
case "enter":
|
||||
i, ok := m.list.SelectedItem().(menuItem)
|
||||
case "q":
|
||||
newModel := NewExitMenu(m.root)
|
||||
return newModel, newModel.Init()
|
||||
case KeyEnter:
|
||||
i, ok := m.list.SelectedItem().(utils.SimpleItem)
|
||||
if ok {
|
||||
if i.ModelFn != nil {
|
||||
m.root.ChangeScene(i.ModelFn(m.root))
|
||||
if i.Activate != nil {
|
||||
newModel, cmd := i.Activate(msg, m)
|
||||
if newModel != nil || cmd != nil {
|
||||
if newModel == nil {
|
||||
newModel = m
|
||||
}
|
||||
return newModel, cmd
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
|
@ -105,11 +116,12 @@ func (m mainMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||
case tea.WindowSizeMsg:
|
||||
top, right, bottom, left := lipgloss.NewStyle().Margin(2, 2).GetMargin()
|
||||
m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom)
|
||||
m.root.SetSize(msg)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m mainMenu) View() string {
|
||||
return m.list.View()
|
||||
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.list.View())
|
||||
}
|
||||
|
|
135
tea/scenes/mod.go
Normal file
135
tea/scenes/mod.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
package scenes
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||
)
|
||||
|
||||
var _ tea.Model = (*modMenu)(nil)
|
||||
|
||||
type modMenu struct {
|
||||
root components.RootModel
|
||||
list list.Model
|
||||
parent tea.Model
|
||||
}
|
||||
|
||||
func NewModMenu(root components.RootModel, parent tea.Model, mod utils.Mod) tea.Model {
|
||||
model := modMenu{
|
||||
root: root,
|
||||
parent: parent,
|
||||
}
|
||||
|
||||
var items []list.Item
|
||||
if root.GetCurrentProfile().HasMod(mod.Reference) {
|
||||
items = []list.Item{
|
||||
utils.SimpleItem{
|
||||
Title: "Remove Mod",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
root.GetCurrentProfile().RemoveMod(mod.Reference)
|
||||
return currentModel.(modMenu).parent, nil
|
||||
},
|
||||
},
|
||||
utils.SimpleItem{
|
||||
Title: "Change Version",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
newModel := NewModVersion(root, currentModel.(modMenu).parent, mod)
|
||||
return newModel, newModel.Init()
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
items = []list.Item{
|
||||
utils.SimpleItem{
|
||||
Title: "Install Mod",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
err := root.GetCurrentProfile().AddMod(mod.Reference, ">=0.0.0")
|
||||
if err != nil {
|
||||
panic(err) // TODO Handle Error
|
||||
}
|
||||
return currentModel.(modMenu).parent, nil
|
||||
},
|
||||
},
|
||||
utils.SimpleItem{
|
||||
Title: "Install Mod with specific version",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
newModel := NewModVersion(root, currentModel.(modMenu).parent, mod)
|
||||
return newModel, newModel.Init()
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
items = append(items, utils.SimpleItem{
|
||||
Title: "View Mod info",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
newModel := NewModInfo(root, currentModel, mod)
|
||||
return newModel, newModel.Init()
|
||||
},
|
||||
})
|
||||
|
||||
model.list = list.NewModel(items, utils.ItemDelegate{}, root.Size().Width, root.Size().Height-root.Height())
|
||||
model.list.SetShowStatusBar(false)
|
||||
model.list.SetFilteringEnabled(false)
|
||||
model.list.Title = mod.Name
|
||||
model.list.Styles = utils.ListStyles
|
||||
model.list.SetSize(model.list.Width(), model.list.Height())
|
||||
model.list.KeyMap.Quit.SetHelp("q", "back")
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
func (m modMenu) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m modMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
log.Warn().Msg(spew.Sdump(msg))
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch keypress := msg.String(); keypress {
|
||||
case KeyControlC:
|
||||
return m, tea.Quit
|
||||
case "q":
|
||||
if m.parent != nil {
|
||||
m.parent.Update(m.root.Size())
|
||||
return m.parent, nil
|
||||
}
|
||||
return m, tea.Quit
|
||||
case KeyEnter:
|
||||
i, ok := m.list.SelectedItem().(utils.SimpleItem)
|
||||
if ok {
|
||||
if i.Activate != nil {
|
||||
newModel, cmd := i.Activate(msg, m)
|
||||
if newModel != nil || cmd != nil {
|
||||
if newModel == nil {
|
||||
newModel.Update(m.root.Size())
|
||||
newModel = m
|
||||
}
|
||||
return newModel, cmd
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return m, tea.Quit
|
||||
default:
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
top, right, bottom, left := lipgloss.NewStyle().Margin(2, 2).GetMargin()
|
||||
m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom)
|
||||
m.root.SetSize(msg)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m modMenu) View() string {
|
||||
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.list.View())
|
||||
}
|
202
tea/scenes/mod_info.go
Normal file
202
tea/scenes/mod_info.go
Normal file
|
@ -0,0 +1,202 @@
|
|||
package scenes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/help"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/glamour"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||
)
|
||||
|
||||
var _ tea.Model = (*modVersionMenu)(nil)
|
||||
|
||||
type modInfo struct {
|
||||
root components.RootModel
|
||||
viewport viewport.Model
|
||||
spinner spinner.Model
|
||||
parent tea.Model
|
||||
modData chan ficsit.GetModGetMod
|
||||
ready bool
|
||||
help help.Model
|
||||
keys modInfoKeyMap
|
||||
}
|
||||
|
||||
type modInfoKeyMap struct {
|
||||
Up key.Binding
|
||||
UpHalf key.Binding
|
||||
UpPage key.Binding
|
||||
Down key.Binding
|
||||
DownHalf key.Binding
|
||||
DownPage key.Binding
|
||||
Help key.Binding
|
||||
Back key.Binding
|
||||
}
|
||||
|
||||
func (k modInfoKeyMap) ShortHelp() []key.Binding {
|
||||
return []key.Binding{k.Help, k.Back}
|
||||
}
|
||||
|
||||
func (k modInfoKeyMap) FullHelp() [][]key.Binding {
|
||||
return [][]key.Binding{
|
||||
{k.Up, k.UpHalf, k.UpPage},
|
||||
{k.Down, k.DownHalf, k.DownPage},
|
||||
{k.Help, k.Back},
|
||||
}
|
||||
}
|
||||
|
||||
func NewModInfo(root components.RootModel, parent tea.Model, mod utils.Mod) tea.Model {
|
||||
model := modInfo{
|
||||
root: root,
|
||||
viewport: viewport.Model{},
|
||||
spinner: spinner.NewModel(),
|
||||
parent: parent,
|
||||
modData: make(chan ficsit.GetModGetMod),
|
||||
ready: false,
|
||||
help: help.NewModel(),
|
||||
keys: modInfoKeyMap{
|
||||
Up: key.NewBinding(key.WithHelp("↑/k", "move up")),
|
||||
UpHalf: key.NewBinding(key.WithHelp("u", "up half page")),
|
||||
UpPage: key.NewBinding(key.WithHelp("pgup/b", "page up")),
|
||||
Down: key.NewBinding(key.WithHelp("↓/j", "move down")),
|
||||
DownHalf: key.NewBinding(key.WithHelp("d", "down half page")),
|
||||
DownPage: key.NewBinding(key.WithHelp("pgdn/ /f", "page down")),
|
||||
Help: key.NewBinding(key.WithHelp("?", "toggle help")),
|
||||
Back: key.NewBinding(key.WithHelp("q", "back")),
|
||||
},
|
||||
}
|
||||
|
||||
model.spinner.Spinner = spinner.MiniDot
|
||||
model.help.Width = root.Size().Width
|
||||
|
||||
go func() {
|
||||
fullMod, err := ficsit.GetMod(context.TODO(), root.GetAPIClient(), mod.ID)
|
||||
|
||||
if err != nil {
|
||||
panic(err) // TODO Handle Error
|
||||
}
|
||||
|
||||
if fullMod == nil {
|
||||
panic("mod is nil") // TODO Handle Error
|
||||
}
|
||||
|
||||
model.modData <- fullMod.GetMod
|
||||
}()
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
func (m modInfo) Init() tea.Cmd {
|
||||
return tea.Batch(utils.Ticker(), spinner.Tick)
|
||||
}
|
||||
|
||||
func (m modInfo) CalculateSizes(msg tea.WindowSizeMsg) (tea.Model, tea.Cmd) {
|
||||
if m.viewport.Width == 0 {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
bottomPadding := 2
|
||||
if m.help.ShowAll {
|
||||
bottomPadding = 4
|
||||
}
|
||||
|
||||
top, right, bottom, left := lipgloss.NewStyle().Margin(m.root.Height(), 3, bottomPadding).GetMargin()
|
||||
m.viewport.Width = msg.Width - left - right
|
||||
m.viewport.Height = msg.Height - top - bottom
|
||||
m.root.SetSize(msg)
|
||||
|
||||
m.help.Width = m.viewport.Width
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m modInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch keypress := msg.String(); keypress {
|
||||
case KeyControlC:
|
||||
return m, tea.Quit
|
||||
case "q":
|
||||
if m.parent != nil {
|
||||
m.parent.Update(m.root.Size())
|
||||
return m.parent, nil
|
||||
}
|
||||
return m, tea.Quit
|
||||
case "?":
|
||||
m.help.ShowAll = !m.help.ShowAll
|
||||
newModel, cmd := m.CalculateSizes(m.root.Size())
|
||||
return newModel, cmd
|
||||
default:
|
||||
var cmd tea.Cmd
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
return m.CalculateSizes(msg)
|
||||
case spinner.TickMsg:
|
||||
var cmd tea.Cmd
|
||||
m.spinner, cmd = m.spinner.Update(msg)
|
||||
return m, cmd
|
||||
case utils.TickMsg:
|
||||
select {
|
||||
case mod := <-m.modData:
|
||||
bottomPadding := 2
|
||||
if m.help.ShowAll {
|
||||
bottomPadding = 4
|
||||
}
|
||||
|
||||
top, right, bottom, left := lipgloss.NewStyle().Margin(m.root.Height(), 3, bottomPadding).GetMargin()
|
||||
m.viewport = viewport.Model{Width: m.root.Size().Width - left - right, Height: m.root.Size().Height - top - bottom}
|
||||
|
||||
title := lipgloss.NewStyle().Padding(0, 2).Render(utils.TitleStyle.Render(mod.Name)) + "\n"
|
||||
|
||||
sidebar := ""
|
||||
sidebar += utils.LabelStyle.Render("Views: ") + strconv.Itoa(mod.Views) + "\n"
|
||||
sidebar += utils.LabelStyle.Render("Downloads: ") + strconv.Itoa(mod.Downloads) + "\n"
|
||||
sidebar += "\n"
|
||||
sidebar += utils.LabelStyle.Render("Authors:") + "\n"
|
||||
|
||||
for _, author := range mod.Authors {
|
||||
sidebar += "\n"
|
||||
sidebar += utils.LabelStyle.Render(author.User.Username) + " - " + author.Role
|
||||
}
|
||||
|
||||
description, err := glamour.Render(mod.Full_description, "dark")
|
||||
if err != nil {
|
||||
panic(err) // TODO Handle Error
|
||||
}
|
||||
|
||||
bottomPart := lipgloss.JoinHorizontal(lipgloss.Top, sidebar, strings.TrimSpace(description))
|
||||
|
||||
m.viewport.SetContent(lipgloss.JoinVertical(lipgloss.Left, title, bottomPart))
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
return m, cmd
|
||||
default:
|
||||
return m, utils.Ticker()
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m modInfo) View() string {
|
||||
if m.viewport.Height == 0 {
|
||||
spinnerView := lipgloss.NewStyle().Padding(0, 2, 1).Render(m.spinner.View() + " Loading...")
|
||||
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), spinnerView)
|
||||
}
|
||||
|
||||
helpBar := lipgloss.NewStyle().Padding(1, 2).Render(m.help.View(m.keys))
|
||||
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.viewport.View(), helpBar)
|
||||
}
|
74
tea/scenes/mod_semver.go
Normal file
74
tea/scenes/mod_semver.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package scenes
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||
)
|
||||
|
||||
var _ tea.Model = (*modSemver)(nil)
|
||||
|
||||
type modSemver struct {
|
||||
root components.RootModel
|
||||
parent tea.Model
|
||||
input textinput.Model
|
||||
title string
|
||||
mod utils.Mod
|
||||
}
|
||||
|
||||
func NewModSemver(root components.RootModel, parent tea.Model, mod utils.Mod) tea.Model {
|
||||
model := modSemver{
|
||||
root: root,
|
||||
parent: parent,
|
||||
input: textinput.NewModel(),
|
||||
title: lipgloss.NewStyle().Padding(0, 2).Render(utils.TitleStyle.Render(mod.Name)),
|
||||
mod: mod,
|
||||
}
|
||||
|
||||
model.input.Placeholder = ">=1.2.3"
|
||||
model.input.Focus()
|
||||
model.input.Width = root.Size().Width
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
func (m modSemver) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m modSemver) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
log.Warn().Msg(spew.Sdump(msg))
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch keypress := msg.String(); keypress {
|
||||
case KeyControlC:
|
||||
return m, tea.Quit
|
||||
case "q":
|
||||
newModel := NewExitMenu(m.root)
|
||||
return newModel, newModel.Init()
|
||||
case KeyEnter:
|
||||
err := m.root.GetCurrentProfile().AddMod(m.mod.Reference, m.input.Value())
|
||||
if err != nil {
|
||||
panic(err) // TODO Handle Error
|
||||
}
|
||||
return m.parent, nil
|
||||
default:
|
||||
var cmd tea.Cmd
|
||||
m.input, cmd = m.input.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
m.root.SetSize(msg)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m modSemver) View() string {
|
||||
inputView := lipgloss.NewStyle().Padding(1, 2).Render(m.input.View())
|
||||
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.title, inputView)
|
||||
}
|
119
tea/scenes/mod_version.go
Normal file
119
tea/scenes/mod_version.go
Normal file
|
@ -0,0 +1,119 @@
|
|||
package scenes
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||
)
|
||||
|
||||
var _ tea.Model = (*modVersionMenu)(nil)
|
||||
|
||||
type modVersionMenu struct {
|
||||
root components.RootModel
|
||||
list list.Model
|
||||
parent tea.Model
|
||||
}
|
||||
|
||||
func NewModVersion(root components.RootModel, parent tea.Model, mod utils.Mod) tea.Model {
|
||||
model := modMenu{
|
||||
root: root,
|
||||
parent: parent,
|
||||
}
|
||||
|
||||
items := []list.Item{
|
||||
utils.SimpleItem{
|
||||
Title: "Select Version",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
newModel := NewModVersionList(root, currentModel.(modMenu).parent, mod)
|
||||
return newModel, newModel.Init()
|
||||
},
|
||||
},
|
||||
utils.SimpleItem{
|
||||
Title: "Enter Custom SemVer",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
newModel := NewModSemver(root, currentModel.(modMenu).parent, mod)
|
||||
return newModel, newModel.Init()
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if root.GetCurrentProfile().HasMod(mod.Reference) {
|
||||
items = append([]list.Item{
|
||||
utils.SimpleItem{
|
||||
Title: "Latest",
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
err := root.GetCurrentProfile().AddMod(mod.Reference, ">=0.0.0")
|
||||
if err != nil {
|
||||
panic(err) // TODO Handle Error
|
||||
}
|
||||
return currentModel.(modMenu).parent, nil
|
||||
},
|
||||
},
|
||||
}, items...)
|
||||
}
|
||||
|
||||
model.list = list.NewModel(items, utils.ItemDelegate{}, root.Size().Width, root.Size().Height-root.Height())
|
||||
model.list.SetShowStatusBar(false)
|
||||
model.list.SetFilteringEnabled(false)
|
||||
model.list.Title = mod.Name
|
||||
model.list.Styles = utils.ListStyles
|
||||
model.list.SetSize(model.list.Width(), model.list.Height())
|
||||
model.list.KeyMap.Quit.SetHelp("q", "back")
|
||||
|
||||
return model
|
||||
}
|
||||
|
||||
func (m modVersionMenu) Init() tea.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m modVersionMenu) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
log.Warn().Msg(spew.Sdump(msg))
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch keypress := msg.String(); keypress {
|
||||
case KeyControlC:
|
||||
return m, tea.Quit
|
||||
case "q":
|
||||
if m.parent != nil {
|
||||
m.parent.Update(m.root.Size())
|
||||
return m.parent, nil
|
||||
}
|
||||
return m, tea.Quit
|
||||
case KeyEnter:
|
||||
i, ok := m.list.SelectedItem().(utils.SimpleItem)
|
||||
if ok {
|
||||
if i.Activate != nil {
|
||||
newModel, cmd := i.Activate(msg, m)
|
||||
if newModel != nil || cmd != nil {
|
||||
if newModel == nil {
|
||||
newModel.Update(m.root.Size())
|
||||
newModel = m
|
||||
}
|
||||
return newModel, cmd
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return m, tea.Quit
|
||||
default:
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
top, right, bottom, left := lipgloss.NewStyle().Margin(2, 2).GetMargin()
|
||||
m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom)
|
||||
m.root.SetSize(msg)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m modVersionMenu) View() string {
|
||||
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.list.View())
|
||||
}
|
|
@ -1,7 +1,155 @@
|
|||
package scenes
|
||||
|
||||
import tea "github.com/charmbracelet/bubbletea"
|
||||
import (
|
||||
"context"
|
||||
|
||||
func NewMods(root RootModel) tea.Model {
|
||||
return nil
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||
)
|
||||
|
||||
var _ tea.Model = (*modsList)(nil)
|
||||
|
||||
type modsList struct {
|
||||
root components.RootModel
|
||||
list list.Model
|
||||
parent tea.Model
|
||||
items chan []list.Item
|
||||
}
|
||||
|
||||
func NewMods(root components.RootModel, parent tea.Model) tea.Model {
|
||||
// TODO Color mods that are installed in current profile
|
||||
l := list.NewModel([]list.Item{}, utils.ItemDelegate{}, root.Size().Width, root.Size().Height-root.Height())
|
||||
l.SetShowStatusBar(true)
|
||||
l.SetFilteringEnabled(false)
|
||||
l.SetSpinner(spinner.MiniDot)
|
||||
l.Title = "Mods"
|
||||
l.Styles = utils.ListStyles
|
||||
l.SetSize(l.Width(), l.Height())
|
||||
l.KeyMap.Quit.SetHelp("q", "back")
|
||||
|
||||
m := &modsList{
|
||||
root: root,
|
||||
list: l,
|
||||
parent: parent,
|
||||
items: make(chan []list.Item),
|
||||
}
|
||||
|
||||
go func() {
|
||||
items := make([]list.Item, 0)
|
||||
allMods := make([]ficsit.ModsGetModsModsMod, 0)
|
||||
offset := 0
|
||||
for {
|
||||
mods, err := ficsit.Mods(context.TODO(), root.GetAPIClient(), ficsit.ModFilter{
|
||||
Limit: 100,
|
||||
Offset: offset,
|
||||
Order_by: ficsit.ModFieldsLastVersionDate,
|
||||
Order: ficsit.OrderDesc,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err) // TODO Handle Error
|
||||
}
|
||||
|
||||
if len(mods.GetMods.Mods) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
allMods = append(allMods, mods.GetMods.Mods...)
|
||||
|
||||
for i := 0; i < len(mods.GetMods.Mods); i++ {
|
||||
currentOffset := offset
|
||||
currentI := i
|
||||
items = append(items, utils.SimpleItem{
|
||||
Title: mods.GetMods.Mods[i].Name,
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
mod := allMods[currentOffset+currentI]
|
||||
return NewModMenu(root, currentModel, utils.Mod{
|
||||
Name: mod.Name,
|
||||
ID: mod.Id,
|
||||
Reference: mod.Mod_reference,
|
||||
}), nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
offset += len(mods.GetMods.Mods)
|
||||
}
|
||||
|
||||
m.items <- items
|
||||
}()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m modsList) Init() tea.Cmd {
|
||||
return utils.Ticker()
|
||||
}
|
||||
|
||||
func (m modsList) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
log.Info().Msg(spew.Sdump(msg))
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch keypress := msg.String(); keypress {
|
||||
case KeyControlC:
|
||||
return m, tea.Quit
|
||||
case "q":
|
||||
if m.parent != nil {
|
||||
m.parent.Update(m.root.Size())
|
||||
return m.parent, nil
|
||||
}
|
||||
return m, tea.Quit
|
||||
case KeyEnter:
|
||||
i, ok := m.list.SelectedItem().(utils.SimpleItem)
|
||||
if ok {
|
||||
if i.Activate != nil {
|
||||
newModel, cmd := i.Activate(msg, m)
|
||||
if newModel != nil || cmd != nil {
|
||||
if newModel == nil {
|
||||
newModel = m
|
||||
}
|
||||
return newModel, cmd
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return m, tea.Quit
|
||||
default:
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
top, right, bottom, left := lipgloss.NewStyle().Margin(m.root.Height(), 2, 0).GetMargin()
|
||||
m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom)
|
||||
m.root.SetSize(msg)
|
||||
case spinner.TickMsg:
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
case utils.TickMsg:
|
||||
select {
|
||||
case items := <-m.items:
|
||||
m.list.StopSpinner()
|
||||
cmd := m.list.SetItems(items)
|
||||
// Done to refresh keymap
|
||||
m.list.SetFilteringEnabled(m.list.FilteringEnabled())
|
||||
return m, cmd
|
||||
default:
|
||||
start := m.list.StartSpinner()
|
||||
return m, tea.Batch(utils.Ticker(), start)
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m modsList) View() string {
|
||||
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.list.View())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package scenes
|
||||
|
||||
import tea "github.com/charmbracelet/bubbletea"
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
)
|
||||
|
||||
func NewProfiles(root RootModel) tea.Model {
|
||||
func NewProfiles(root components.RootModel, parent tea.Model) tea.Model {
|
||||
return nil
|
||||
}
|
||||
|
|
155
tea/scenes/select_mod_version.go
Normal file
155
tea/scenes/select_mod_version.go
Normal file
|
@ -0,0 +1,155 @@
|
|||
package scenes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/satisfactorymodding/ficsit-cli/ficsit"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/components"
|
||||
"github.com/satisfactorymodding/ficsit-cli/tea/utils"
|
||||
)
|
||||
|
||||
var _ tea.Model = (*selectModVersionList)(nil)
|
||||
|
||||
type selectModVersionList struct {
|
||||
root components.RootModel
|
||||
list list.Model
|
||||
parent tea.Model
|
||||
items chan []list.Item
|
||||
}
|
||||
|
||||
func NewModVersionList(root components.RootModel, parent tea.Model, mod utils.Mod) tea.Model {
|
||||
l := list.NewModel([]list.Item{}, utils.ItemDelegate{}, root.Size().Width, root.Size().Height-root.Height())
|
||||
l.SetShowStatusBar(true)
|
||||
l.SetFilteringEnabled(false)
|
||||
l.SetSpinner(spinner.MiniDot)
|
||||
l.Title = fmt.Sprintf("Versions (%s)", mod.Name)
|
||||
l.Styles = utils.ListStyles
|
||||
l.SetSize(l.Width(), l.Height())
|
||||
l.KeyMap.Quit.SetHelp("q", "back")
|
||||
|
||||
m := &selectModVersionList{
|
||||
root: root,
|
||||
list: l,
|
||||
parent: parent,
|
||||
items: make(chan []list.Item),
|
||||
}
|
||||
|
||||
go func() {
|
||||
items := make([]list.Item, 0)
|
||||
allVersions := make([]ficsit.ModVersionsGetModVersionsVersion, 0)
|
||||
offset := 0
|
||||
for {
|
||||
versions, err := ficsit.ModVersions(context.TODO(), root.GetAPIClient(), mod.ID, ficsit.VersionFilter{
|
||||
Limit: 100,
|
||||
Offset: offset,
|
||||
Order: ficsit.OrderDesc,
|
||||
Order_by: ficsit.VersionFieldsCreatedAt,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err) // TODO
|
||||
}
|
||||
|
||||
if len(versions.GetMod.Versions) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
allVersions = append(allVersions, versions.GetMod.Versions...)
|
||||
|
||||
for i := 0; i < len(versions.GetMod.Versions); i++ {
|
||||
currentOffset := offset
|
||||
currentI := i
|
||||
items = append(items, utils.SimpleItem{
|
||||
Title: versions.GetMod.Versions[i].Version,
|
||||
Activate: func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd) {
|
||||
version := allVersions[currentOffset+currentI]
|
||||
err := root.GetCurrentProfile().AddMod(mod.Reference, version.Version)
|
||||
if err != nil {
|
||||
panic(err) // TODO
|
||||
}
|
||||
return currentModel.(selectModVersionList).parent, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
offset += len(versions.GetMod.Versions)
|
||||
}
|
||||
|
||||
m.items <- items
|
||||
}()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (m selectModVersionList) Init() tea.Cmd {
|
||||
return utils.Ticker()
|
||||
}
|
||||
|
||||
func (m selectModVersionList) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
log.Info().Msg(spew.Sdump(msg))
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch keypress := msg.String(); keypress {
|
||||
case KeyControlC:
|
||||
return m, tea.Quit
|
||||
case "q":
|
||||
if m.parent != nil {
|
||||
m.parent.Update(m.root.Size())
|
||||
return m.parent, nil
|
||||
}
|
||||
return m, tea.Quit
|
||||
case KeyEnter:
|
||||
i, ok := m.list.SelectedItem().(utils.SimpleItem)
|
||||
if ok {
|
||||
if i.Activate != nil {
|
||||
newModel, cmd := i.Activate(msg, m)
|
||||
if newModel != nil || cmd != nil {
|
||||
if newModel == nil {
|
||||
newModel = m
|
||||
}
|
||||
return newModel, cmd
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return m, tea.Quit
|
||||
default:
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
top, right, bottom, left := lipgloss.NewStyle().Margin(m.root.Height(), 2, 0).GetMargin()
|
||||
m.list.SetSize(msg.Width-left-right, msg.Height-top-bottom)
|
||||
m.root.SetSize(msg)
|
||||
case spinner.TickMsg:
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
return m, cmd
|
||||
case utils.TickMsg:
|
||||
select {
|
||||
case items := <-m.items:
|
||||
m.list.StopSpinner()
|
||||
cmd := m.list.SetItems(items)
|
||||
// Done to refresh keymap
|
||||
m.list.SetFilteringEnabled(m.list.FilteringEnabled())
|
||||
return m, cmd
|
||||
default:
|
||||
start := m.list.StartSpinner()
|
||||
return m, tea.Batch(utils.Ticker(), start)
|
||||
}
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m selectModVersionList) View() string {
|
||||
return lipgloss.JoinVertical(lipgloss.Left, m.root.View(), m.list.View())
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package scenes
|
||||
|
||||
import (
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/satisfactorymodding/ficsit-cli/cli"
|
||||
)
|
||||
|
||||
type RootModel interface {
|
||||
ChangeScene(model tea.Model)
|
||||
|
||||
GetCurrentProfile() *cli.Profile
|
||||
SetCurrentProfile(profile *cli.Profile)
|
||||
|
||||
GetCurrentInstallation() *cli.Installation
|
||||
SetCurrentInstallation(installation *cli.Installation)
|
||||
}
|
51
tea/utils/basic_list.go
Normal file
51
tea/utils/basic_list.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
var _ ListItem = (*SimpleItem)(nil)
|
||||
var _ list.Item = (*SimpleItem)(nil)
|
||||
|
||||
type SimpleItem struct {
|
||||
Title string
|
||||
Activate func(msg tea.Msg, currentModel tea.Model) (tea.Model, tea.Cmd)
|
||||
}
|
||||
|
||||
func (n SimpleItem) FilterValue() string {
|
||||
return n.Title
|
||||
}
|
||||
|
||||
func (n SimpleItem) GetTitle() string {
|
||||
return n.Title
|
||||
}
|
||||
|
||||
type ListItem interface {
|
||||
GetTitle() string
|
||||
}
|
||||
|
||||
type ItemDelegate struct{}
|
||||
|
||||
func (d ItemDelegate) Height() int { return 1 }
|
||||
func (d ItemDelegate) Spacing() int { return 0 }
|
||||
func (d ItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
|
||||
func (d ItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) {
|
||||
i, ok := listItem.(ListItem)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
style := lipgloss.NewStyle().PaddingLeft(2)
|
||||
|
||||
str := style.Render("o " + i.GetTitle())
|
||||
if index == m.Index() {
|
||||
str = style.Foreground(lipgloss.Color("202")).Render("• " + i.GetTitle())
|
||||
}
|
||||
|
||||
fmt.Fprint(w, str)
|
||||
}
|
19
tea/utils/styles.go
Normal file
19
tea/utils/styles.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
var (
|
||||
ListStyles list.Styles
|
||||
LabelStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("202"))
|
||||
TitleStyle = list.DefaultStyles().Title.Background(lipgloss.Color("22"))
|
||||
)
|
||||
|
||||
func init() {
|
||||
ListStyles = list.DefaultStyles()
|
||||
ListStyles.Title = TitleStyle
|
||||
ListStyles.HelpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(2).PaddingBottom(1)
|
||||
ListStyles.PaginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(2)
|
||||
}
|
15
tea/utils/tick.go
Normal file
15
tea/utils/tick.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
type TickMsg struct{}
|
||||
|
||||
func Ticker() tea.Cmd {
|
||||
return tea.Tick(time.Millisecond*50, func(time.Time) tea.Msg {
|
||||
return TickMsg{}
|
||||
})
|
||||
}
|
7
tea/utils/types.go
Normal file
7
tea/utils/types.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package utils
|
||||
|
||||
type Mod struct {
|
||||
Name string
|
||||
ID string
|
||||
Reference string
|
||||
}
|
8
tools.go
Normal file
8
tools.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
//go:build tools
|
||||
// +build tools
|
||||
|
||||
package smr
|
||||
|
||||
import _ "github.com/Khan/genqlient"
|
||||
|
||||
//go:generate go run github.com/Khan/genqlient
|
5
utils/version.go
Normal file
5
utils/version.go
Normal file
|
@ -0,0 +1,5 @@
|
|||
package utils
|
||||
|
||||
import "regexp"
|
||||
|
||||
var SemVerRegex = regexp.MustCompile(`^(<=|<|>|>=|\^)?(0|[1-9]\d*)\.(0|[1-9]d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`)
|
Loading…
Reference in a new issue