From 42251f6df8010480e6b6eeda21586d7a74dbe761 Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Tue, 25 Jun 2024 12:39:39 +0100 Subject: [PATCH] feat: add configuration support to enbas SUMMARY This commit adds configuration support to enbas. The configuration is stored as a JSON file in the user specified configuration directory. When using enbas for the first time, the user will first need to execute the new init command in order to generate the configuration. Once this has been generated the user can edit the settings to personalise their experience, login to their account and use enbas as normal. For now the configurable settings included in the configuration are as follows: - The path to the credentials file (by default this is set to a file in the same directory as the configuration file). - The path to the cache directory. - The character limit used for line wrapping. - The programs used for integrations such as paging, media viewing, opening URLs, etc. CHANGES - added the new config type. - added the new init executor for generating a new configuration file. - removed the following top level flags in favour of the new configration support. - cache-dir - pager - image-viewer - video-player - max-terminal-width - added a new error type for use when an unknown media attachment ID is specified. - updated the usage function for the executors to support a case where a flagsets has no flags. - update .golangci.yaml to disable some linters --- .golangci.yaml | 5 +- cmd/enbas/main.go | 135 +++++++++++++----------- internal/client/client.go | 6 +- internal/client/register.go | 2 +- internal/client/token.go | 4 +- internal/config/config.go | 108 +++++++++++++++++++ internal/config/credentials.go | 56 ++++------ internal/executor/accept_or_reject.go | 15 +-- internal/executor/account.go | 12 +-- internal/executor/add.go | 11 +- internal/executor/block_or_unblock.go | 15 +-- internal/executor/commands.go | 3 + internal/executor/create.go | 11 +- internal/executor/delete.go | 11 +- internal/executor/edit.go | 11 +- internal/executor/errors.go | 8 ++ internal/executor/follow_or_unfollow.go | 15 +-- internal/executor/init.go | 61 +++++++++++ internal/executor/login.go | 16 +-- internal/executor/mute_or_unmute.go | 15 +-- internal/executor/remove.go | 11 +- internal/executor/show.go | 31 +++--- internal/executor/switch.go | 12 +-- internal/executor/usage.go | 44 +++++--- internal/executor/version.go | 5 +- internal/executor/whoami.go | 12 +-- internal/internal.go | 2 +- internal/model/application.go | 2 +- internal/model/instance_v2.go | 2 +- internal/utilities/directories.go | 4 +- 30 files changed, 425 insertions(+), 220 deletions(-) create mode 100644 internal/config/config.go create mode 100644 internal/executor/init.go diff --git a/.golangci.yaml b/.golangci.yaml index 2934e8a..6006d52 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -31,5 +31,8 @@ linters-settings: linters: enable-all: true disable: - #- json + - exhaustivestruct + - exhaustruct + - gomnd + - tagliatelle fast: false diff --git a/cmd/enbas/main.go b/cmd/enbas/main.go index 60ac439..d49c22f 100644 --- a/cmd/enbas/main.go +++ b/cmd/enbas/main.go @@ -10,15 +10,16 @@ import ( "os" "strconv" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/executor" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) var ( - binaryVersion string - buildTime string - goVersion string - gitCommit string + binaryVersion string //nolint:gochecknoglobals + buildTime string //nolint:gochecknoglobals + goVersion string //nolint:gochecknoglobals + gitCommit string //nolint:gochecknoglobals ) func main() { @@ -29,22 +30,11 @@ func main() { func run() error { var ( - configDir string - cacheDir string - pager string - imageViewer string - videoPlayer string - maxTerminalWidth int - noColor *bool + configDir string + noColor *bool ) flag.StringVar(&configDir, "config-dir", "", "Specify your config directory") - flag.StringVar(&cacheDir, "cache-dir", "", "Specify your cache directory") - flag.StringVar(&pager, "pager", "", "Specify your preferred pager to page through long outputs. This is disabled by default.") - flag.StringVar(&imageViewer, "image-viewer", "", "Specify your favourite image viewer.") - flag.StringVar(&videoPlayer, "video-player", "", "Specify your favourite video player.") - flag.IntVar(&maxTerminalWidth, "max-terminal-width", 80, "Specify the maximum terminal width when displaying resources on screen.") - flag.BoolFunc("no-color", "Disable ANSI colour output when displaying text on screen", func(value string) error { boolVal, err := strconv.ParseBool(value) if err != nil { @@ -67,7 +57,8 @@ func run() error { return nil } - // If NoColor is still unspecified, check to see if the NO_COLOR environment variable is set + // If NoColor is still unspecified, + // check to see if the NO_COLOR environment variable is set if noColor == nil { noColor = new(bool) if os.Getenv("NO_COLOR") != "" { @@ -80,110 +71,132 @@ func run() error { command := flag.Arg(0) args := flag.Args()[1:] - printer := printer.NewPrinter(*noColor, pager, maxTerminalWidth) + var ( + enbasConfig *config.Config + enbasPrinter *printer.Printer + err error + ) + + switch command { + case executor.CommandInit, executor.CommandVersion: + enbasPrinter = printer.NewPrinter(*noColor, "", 0) + default: + enbasConfig, err = config.NewConfigFromFile(configDir) + if err != nil { + enbasPrinter = printer.NewPrinter(*noColor, "", 0) + enbasPrinter.PrintFailure("unable to load the configuration: " + err.Error() + ".") + + return err + } + + enbasPrinter = printer.NewPrinter(*noColor, enbasConfig.Integrations.Pager, enbasConfig.LineWrapMaxWidth) + } executorMap := map[string]executor.Executor{ executor.CommandAccept: executor.NewAcceptOrRejectExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandAccept, executor.CommandSummaryLookup(executor.CommandAccept), ), executor.CommandAdd: executor.NewAddExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandAdd, executor.CommandSummaryLookup(executor.CommandAdd), ), executor.CommandBlock: executor.NewBlockOrUnblockExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandBlock, executor.CommandSummaryLookup(executor.CommandBlock), ), executor.CommandCreate: executor.NewCreateExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandCreate, executor.CommandSummaryLookup(executor.CommandCreate), ), executor.CommandDelete: executor.NewDeleteExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandDelete, executor.CommandSummaryLookup(executor.CommandDelete), ), executor.CommandEdit: executor.NewEditExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandEdit, executor.CommandSummaryLookup(executor.CommandEdit), ), executor.CommandFollow: executor.NewFollowOrUnfollowExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandFollow, executor.CommandSummaryLookup(executor.CommandFollow), ), - executor.CommandLogin: executor.NewLoginExecutor( - printer, + executor.CommandInit: executor.NewInitExecutor( + enbasPrinter, configDir, + executor.CommandInit, + executor.CommandSummaryLookup(executor.CommandInit), + ), + executor.CommandLogin: executor.NewLoginExecutor( + enbasPrinter, + enbasConfig, executor.CommandLogin, executor.CommandSummaryLookup(executor.CommandLogin), ), executor.CommandMute: executor.NewMuteOrUnmuteExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandMute, executor.CommandSummaryLookup(executor.CommandMute), ), executor.CommandReject: executor.NewAcceptOrRejectExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandReject, executor.CommandSummaryLookup(executor.CommandReject), ), executor.CommandRemove: executor.NewRemoveExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandRemove, executor.CommandSummaryLookup(executor.CommandRemove), ), executor.CommandSwitch: executor.NewSwitchExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandSwitch, executor.CommandSummaryLookup(executor.CommandSwitch), ), executor.CommandUnfollow: executor.NewFollowOrUnfollowExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandUnfollow, executor.CommandSummaryLookup(executor.CommandUnfollow), ), executor.CommandUnmute: executor.NewMuteOrUnmuteExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandUnmute, executor.CommandSummaryLookup(executor.CommandUnmute), ), executor.CommandUnblock: executor.NewBlockOrUnblockExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandUnblock, executor.CommandSummaryLookup(executor.CommandUnblock), ), executor.CommandShow: executor.NewShowExecutor( - printer, - configDir, - cacheDir, - imageViewer, - videoPlayer, + enbasPrinter, + enbasConfig, executor.CommandShow, executor.CommandSummaryLookup(executor.CommandShow), ), executor.CommandVersion: executor.NewVersionExecutor( - printer, + enbasPrinter, executor.CommandVersion, executor.CommandSummaryLookup(executor.CommandVersion), binaryVersion, @@ -192,8 +205,8 @@ func run() error { gitCommit, ), executor.CommandWhoami: executor.NewWhoAmIExecutor( - printer, - configDir, + enbasPrinter, + enbasConfig, executor.CommandWhoami, executor.CommandSummaryLookup(executor.CommandWhoami), ), @@ -201,16 +214,16 @@ func run() error { exe, ok := executorMap[command] if !ok { - err := executor.UnknownCommandError{Command: command} + err = executor.UnknownCommandError{Command: command} - printer.PrintFailure(err.Error() + ".") + enbasPrinter.PrintFailure(err.Error() + ".") flag.Usage() return err } - if err := executor.Execute(exe, args); err != nil { - printer.PrintFailure("(" + command + ") " + err.Error() + ".") + if err = executor.Execute(exe, args); err != nil { + enbasPrinter.PrintFailure("(" + command + ") " + err.Error() + ".") return err } diff --git a/internal/client/client.go b/internal/client/client.go index 27ece40..620b957 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -25,8 +25,8 @@ type Client struct { Timeout time.Duration } -func NewClientFromConfig(configDir string) (*Client, error) { - config, err := config.NewCredentialsConfigFromFile(configDir) +func NewClientFromFile(path string) (*Client, error) { + config, err := config.NewCredentialsConfigFromFile(path) if err != nil { return nil, fmt.Errorf("unable to get the authentication configuration: %w", err) } @@ -51,7 +51,7 @@ func NewClient(authentication config.Credentials) *Client { func (g *Client) AuthCodeURL() string { format := "%s/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code" - escapedRedirectURI := url.QueryEscape(internal.RedirectUri) + escapedRedirectURI := url.QueryEscape(internal.RedirectURI) return fmt.Sprintf( format, diff --git a/internal/client/register.go b/internal/client/register.go index 3800b3a..9be1c32 100644 --- a/internal/client/register.go +++ b/internal/client/register.go @@ -24,7 +24,7 @@ type registerRequest struct { func (g *Client) Register() error { params := registerRequest{ ClientName: internal.ApplicationName, - RedirectUris: internal.RedirectUri, + RedirectUris: internal.RedirectURI, Scopes: "read write", Website: internal.ApplicationWebsite, } diff --git a/internal/client/token.go b/internal/client/token.go index ed8190d..4aeff04 100644 --- a/internal/client/token.go +++ b/internal/client/token.go @@ -17,7 +17,7 @@ import ( var errEmptyAccessToken = errors.New("received an empty access token") type tokenRequest struct { - RedirectUri string `json:"redirect_uri"` + RedirectURI string `json:"redirect_uri"` ClientID string `json:"client_id"` ClientSecret string `json:"client_secret"` GrantType string `json:"grant_type"` @@ -33,7 +33,7 @@ type tokenResponse struct { func (g *Client) UpdateToken(code string) error { params := tokenRequest{ - RedirectUri: internal.RedirectUri, + RedirectURI: internal.RedirectURI, ClientID: g.Authentication.ClientID, ClientSecret: g.Authentication.ClientSecret, GrantType: "authorization_code", diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..348640a --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2024 Dan Anglin +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package config + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" +) + +const ( + configFileName = "config.json" +) + +type Config struct { + CredentialsFile string `json:"credentialsFile"` + CacheDirectory string `json:"cacheDirectory"` + LineWrapMaxWidth int `json:"lineWrapMaxWidth"` + HTTP HTTPConfig `json:"http"` + Integrations Integrations `json:"integrations"` +} + +type HTTPConfig struct { + Timeout int `json:"timeout"` + MediaTimeout int `json:"mediaTimeout"` +} + +type Integrations struct { + Browser string `json:"browser"` + Editor string `json:"editor"` + Pager string `json:"pager"` + ImageViewer string `json:"imageViewer"` + VideoPlayer string `json:"videoPlayer"` +} + +func NewConfigFromFile(configDir string) (*Config, error) { + path := configFile(configDir) + + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("unable to open %s: %w", path, err) + } + defer file.Close() + + var config Config + + if err := json.NewDecoder(file).Decode(&config); err != nil { + return nil, fmt.Errorf("unable to decode the JSON data: %w", err) + } + + return &config, nil +} + +func FileExists(configDir string) (bool, error) { + path := configFile(configDir) + + return utilities.FileExists(path) +} + +func SaveDefaultConfigToFile(configDir string) error { + path := configFile(configDir) + + file, err := os.Create(path) + if err != nil { + return fmt.Errorf("unable to create the file at %s: %w", path, err) + } + defer file.Close() + + config := defaultConfig(configDir) + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + + if err := encoder.Encode(config); err != nil { + return fmt.Errorf("unable to save the JSON data to the config file: %w", err) + } + + return nil +} + +func configFile(configDir string) string { + return filepath.Join(utilities.CalculateConfigDir(configDir), configFileName) +} + +func defaultConfig(configDir string) Config { + credentialsFilePath := defaultCredentialsConfigFile(configDir) + + return Config{ + CredentialsFile: credentialsFilePath, + CacheDirectory: "", + HTTP: HTTPConfig{ + Timeout: 5, + MediaTimeout: 30, + }, + LineWrapMaxWidth: 80, + Integrations: Integrations{ + Browser: "", + Editor: "", + Pager: "", + ImageViewer: "", + VideoPlayer: "", + }, + } +} diff --git a/internal/config/credentials.go b/internal/config/credentials.go index 47f6bef..a2fb554 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -10,13 +10,12 @@ import ( "fmt" "os" "path/filepath" - "strings" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" ) const ( - credentialsFileName = "credentials.json" + defaultCredentialsFileName = "credentials.json" ) type CredentialsConfig struct { @@ -42,35 +41,29 @@ func (e CredentialsNotFoundError) Error() string { // SaveCredentials saves the credentials into the credentials file within the specified configuration // directory. If the directory is not specified then the default directory is used. If the directory // is not present, it will be created. -func SaveCredentials(configDir, username string, credentials Credentials) (string, error) { - if err := utilities.EnsureDirectory(utilities.CalculateConfigDir(configDir)); err != nil { +func SaveCredentials(filePath, username string, credentials Credentials) (string, error) { + directory := filepath.Dir(filePath) + + if err := utilities.EnsureDirectory(utilities.CalculateConfigDir(directory)); err != nil { return "", fmt.Errorf("unable to ensure the configuration directory: %w", err) } var authConfig CredentialsConfig - filepath := credentialsConfigFile(configDir) - - if _, err := os.Stat(filepath); err != nil { + if _, err := os.Stat(filePath); err != nil { if !errors.Is(err, os.ErrNotExist) { - return "", fmt.Errorf("unknown error received when running stat on %s: %w", filepath, err) + return "", fmt.Errorf("unknown error received when running stat on %s: %w", filePath, err) } authConfig.Credentials = make(map[string]Credentials) } else { - authConfig, err = NewCredentialsConfigFromFile(configDir) + authConfig, err = NewCredentialsConfigFromFile(filePath) if err != nil { return "", fmt.Errorf("unable to retrieve the existing authentication configuration: %w", err) } } - instance := "" - - if strings.HasPrefix(credentials.Instance, "https://") { - instance = strings.TrimPrefix(credentials.Instance, "https://") - } else if strings.HasPrefix(credentials.Instance, "http://") { - instance = strings.TrimPrefix(credentials.Instance, "http://") - } + instance := utilities.GetFQDN(credentials.Instance) authenticationName := username + "@" + instance @@ -78,15 +71,15 @@ func SaveCredentials(configDir, username string, credentials Credentials) (strin authConfig.Credentials[authenticationName] = credentials - if err := saveCredentialsConfigFile(authConfig, configDir); err != nil { + if err := saveCredentialsConfigFile(authConfig, filePath); err != nil { return "", fmt.Errorf("unable to save the authentication configuration to file: %w", err) } return authenticationName, nil } -func UpdateCurrentAccount(account string, configDir string) error { - credentialsConfig, err := NewCredentialsConfigFromFile(configDir) +func UpdateCurrentAccount(account string, filePath string) error { + credentialsConfig, err := NewCredentialsConfigFromFile(filePath) if err != nil { return fmt.Errorf("unable to retrieve the existing authentication configuration: %w", err) } @@ -97,19 +90,19 @@ func UpdateCurrentAccount(account string, configDir string) error { credentialsConfig.CurrentAccount = account - if err := saveCredentialsConfigFile(credentialsConfig, configDir); err != nil { + if err := saveCredentialsConfigFile(credentialsConfig, filePath); err != nil { return fmt.Errorf("unable to save the authentication configuration to file: %w", err) } return nil } -func NewCredentialsConfigFromFile(configDir string) (CredentialsConfig, error) { - path := credentialsConfigFile(configDir) - - file, err := os.Open(path) +// NewCredentialsConfigFromFile creates a new CredentialsConfig value from reading +// the credentials file. +func NewCredentialsConfigFromFile(filePath string) (CredentialsConfig, error) { + file, err := os.Open(filePath) if err != nil { - return CredentialsConfig{}, fmt.Errorf("unable to open %s, %w", path, err) + return CredentialsConfig{}, fmt.Errorf("unable to open %s: %w", filePath, err) } defer file.Close() @@ -122,14 +115,11 @@ func NewCredentialsConfigFromFile(configDir string) (CredentialsConfig, error) { return authConfig, nil } -func saveCredentialsConfigFile(authConfig CredentialsConfig, configDir string) error { - path := credentialsConfigFile(configDir) - - file, err := os.Create(path) +func saveCredentialsConfigFile(authConfig CredentialsConfig, filePath string) error { + file, err := os.Create(filePath) if err != nil { - return fmt.Errorf("unable to open %s: %w", path, err) + return fmt.Errorf("unable to create the file at %s: %w", filePath, err) } - defer file.Close() encoder := json.NewEncoder(file) @@ -142,6 +132,6 @@ func saveCredentialsConfigFile(authConfig CredentialsConfig, configDir string) e return nil } -func credentialsConfigFile(configDir string) string { - return filepath.Join(utilities.CalculateConfigDir(configDir), credentialsFileName) +func defaultCredentialsConfigFile(configDir string) string { + return filepath.Join(utilities.CalculateConfigDir(configDir), defaultCredentialsFileName) } diff --git a/internal/executor/accept_or_reject.go b/internal/executor/accept_or_reject.go index 7e32c69..767661c 100644 --- a/internal/executor/accept_or_reject.go +++ b/internal/executor/accept_or_reject.go @@ -9,6 +9,7 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) @@ -16,19 +17,19 @@ type AcceptOrRejectExecutor struct { *flag.FlagSet printer *printer.Printer - configDir string + config *config.Config resourceType string accountName string command string } -func NewAcceptOrRejectExecutor(enbasPrinter *printer.Printer, configDir, name, summary string) *AcceptOrRejectExecutor { +func NewAcceptOrRejectExecutor(enbasPrinter *printer.Printer, config *config.Config, name, summary string) *AcceptOrRejectExecutor { acceptExe := AcceptOrRejectExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: enbasPrinter, - configDir: configDir, - command: name, + printer: enbasPrinter, + config: config, + command: name, } acceptExe.StringVar(&acceptExe.resourceType, flagType, "", "Specify the type of resource to accept or reject") @@ -49,7 +50,7 @@ func (a *AcceptOrRejectExecutor) Execute() error { return UnsupportedTypeError{resourceType: a.resourceType} } - gtsClient, err := client.NewClientFromConfig(a.configDir) + gtsClient, err := client.NewClientFromFile(a.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -58,7 +59,7 @@ func (a *AcceptOrRejectExecutor) Execute() error { } func (a *AcceptOrRejectExecutor) acceptOrRejectFollowRequest(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, false, a.accountName, a.configDir) + accountID, err := getAccountID(gtsClient, false, a.accountName, a.config.CredentialsFile) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } diff --git a/internal/executor/account.go b/internal/executor/account.go index 0d2ec37..b67be29 100644 --- a/internal/executor/account.go +++ b/internal/executor/account.go @@ -12,7 +12,7 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/model" ) -func getAccountID(gtsClient *client.Client, myAccount bool, accountName, configDir string) (string, error) { +func getAccountID(gtsClient *client.Client, myAccount bool, accountName, path string) (string, error) { var ( accountID string err error @@ -20,7 +20,7 @@ func getAccountID(gtsClient *client.Client, myAccount bool, accountName, configD switch { case myAccount: - accountID, err = getMyAccountID(gtsClient, configDir) + accountID, err = getMyAccountID(gtsClient, path) if err != nil { return "", fmt.Errorf("unable to get your account ID: %w", err) } @@ -45,8 +45,8 @@ func getTheirAccountID(gtsClient *client.Client, accountURI string) (string, err return account.ID, nil } -func getMyAccountID(gtsClient *client.Client, configDir string) (string, error) { - account, err := getMyAccount(gtsClient, configDir) +func getMyAccountID(gtsClient *client.Client, path string) (string, error) { + account, err := getMyAccount(gtsClient, path) if err != nil { return "", fmt.Errorf("received an error while getting your account details: %w", err) } @@ -54,8 +54,8 @@ func getMyAccountID(gtsClient *client.Client, configDir string) (string, error) return account.ID, nil } -func getMyAccount(gtsClient *client.Client, configDir string) (model.Account, error) { - authConfig, err := config.NewCredentialsConfigFromFile(configDir) +func getMyAccount(gtsClient *client.Client, path string) (model.Account, error) { + authConfig, err := config.NewCredentialsConfigFromFile(path) if err != nil { return model.Account{}, fmt.Errorf("unable to retrieve the authentication configuration: %w", err) } diff --git a/internal/executor/add.go b/internal/executor/add.go index dce233e..ab6e7e2 100644 --- a/internal/executor/add.go +++ b/internal/executor/add.go @@ -10,6 +10,7 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) @@ -17,7 +18,7 @@ type AddExecutor struct { *flag.FlagSet printer *printer.Printer - configDir string + config *config.Config resourceType string toResourceType string listID string @@ -28,14 +29,14 @@ type AddExecutor struct { content string } -func NewAddExecutor(printer *printer.Printer, configDir, name, summary string) *AddExecutor { +func NewAddExecutor(printer *printer.Printer, config *config.Config, name, summary string) *AddExecutor { emptyArr := make([]string, 0, 3) addExe := AddExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), printer: printer, - configDir: configDir, + config: config, accountNames: MultiStringFlagValue(emptyArr), } @@ -71,7 +72,7 @@ func (a *AddExecutor) Execute() error { return UnsupportedTypeError{resourceType: a.toResourceType} } - gtsClient, err := client.NewClientFromConfig(a.configDir) + gtsClient, err := client.NewClientFromFile(a.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -154,7 +155,7 @@ func (a *AddExecutor) addNoteToAccount(gtsClient *client.Client) error { return fmt.Errorf("unexpected number of accounts specified: want 1, got %d", len(a.accountNames)) } - accountID, err := getAccountID(gtsClient, false, a.accountNames[0], a.configDir) + accountID, err := getAccountID(gtsClient, false, a.accountNames[0], a.config.CredentialsFile) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } diff --git a/internal/executor/block_or_unblock.go b/internal/executor/block_or_unblock.go index d8b4e0c..c383e99 100644 --- a/internal/executor/block_or_unblock.go +++ b/internal/executor/block_or_unblock.go @@ -9,6 +9,7 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) @@ -16,19 +17,19 @@ type BlockOrUnblockExecutor struct { *flag.FlagSet printer *printer.Printer - configDir string + config *config.Config resourceType string accountName string command string } -func NewBlockOrUnblockExecutor(printer *printer.Printer, configDir, name, summary string) *BlockOrUnblockExecutor { +func NewBlockOrUnblockExecutor(printer *printer.Printer, config *config.Config, name, summary string) *BlockOrUnblockExecutor { blockExe := BlockOrUnblockExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, - command: name, + printer: printer, + config: config, + command: name, } blockExe.StringVar(&blockExe.resourceType, flagType, "", "Specify the type of resource to block or unblock") @@ -49,7 +50,7 @@ func (b *BlockOrUnblockExecutor) Execute() error { return UnsupportedTypeError{resourceType: b.resourceType} } - gtsClient, err := client.NewClientFromConfig(b.configDir) + gtsClient, err := client.NewClientFromFile(b.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -62,7 +63,7 @@ func (b *BlockOrUnblockExecutor) blockOrUnblockAccount(gtsClient *client.Client) return FlagNotSetError{flagText: flagAccountName} } - accountID, err := getAccountID(gtsClient, false, b.accountName, b.configDir) + accountID, err := getAccountID(gtsClient, false, b.accountName, b.config.CredentialsFile) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } diff --git a/internal/executor/commands.go b/internal/executor/commands.go index a94673b..b16a551 100644 --- a/internal/executor/commands.go +++ b/internal/executor/commands.go @@ -12,6 +12,7 @@ const ( CommandDelete string = "delete" CommandEdit string = "edit" CommandFollow string = "follow" + CommandInit string = "init" CommandLogin string = "login" CommandMute string = "mute" CommandReject string = "reject" @@ -31,6 +32,7 @@ const ( commandDeleteSummary string = "Delete a specific resource" commandEditSummary string = "Edit a specific resource" commandFollowSummary string = "Follow a resource (e.g. an account)" + commandInitSummary string = "Create a new configuration file in the specified configuration directory" commandLoginSummary string = "Login to an account on GoToSocial" commandMuteSummary string = "Mute a resource (e.g. an account)" commandRejectSummary string = "Reject a request (e.g. a follow request)" @@ -53,6 +55,7 @@ func CommandSummaryMap() map[string]string { CommandDelete: commandDeleteSummary, CommandEdit: commandEditSummary, CommandFollow: commandFollowSummary, + CommandInit: commandInitSummary, CommandLogin: commandLoginSummary, CommandMute: commandMuteSummary, CommandReject: commandRejectSummary, diff --git a/internal/executor/create.go b/internal/executor/create.go index 9aee759..856b261 100644 --- a/internal/executor/create.go +++ b/internal/executor/create.go @@ -10,6 +10,7 @@ import ( "strconv" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" @@ -19,6 +20,7 @@ type CreateExecutor struct { *flag.FlagSet printer *printer.Printer + config *config.Config addPoll bool boostable bool federated bool @@ -27,7 +29,6 @@ type CreateExecutor struct { pollHidesVoteCounts bool replyable bool sensitive *bool - configDir string content string contentType string fromFile string @@ -41,12 +42,12 @@ type CreateExecutor struct { pollOptions MultiStringFlagValue } -func NewCreateExecutor(printer *printer.Printer, configDir, name, summary string) *CreateExecutor { +func NewCreateExecutor(printer *printer.Printer, config *config.Config, name, summary string) *CreateExecutor { createExe := CreateExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, + printer: printer, + config: config, } createExe.BoolVar(&createExe.boostable, flagEnableReposts, true, "Specify if the status can be reposted/boosted by others") @@ -90,7 +91,7 @@ func (c *CreateExecutor) Execute() error { return FlagNotSetError{flagText: flagType} } - gtsClient, err := client.NewClientFromConfig(c.configDir) + gtsClient, err := client.NewClientFromFile(c.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } diff --git a/internal/executor/delete.go b/internal/executor/delete.go index 7d173c6..819204f 100644 --- a/internal/executor/delete.go +++ b/internal/executor/delete.go @@ -9,6 +9,7 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) @@ -16,17 +17,17 @@ type DeleteExecutor struct { *flag.FlagSet printer *printer.Printer - configDir string + config *config.Config resourceType string listID string } -func NewDeleteExecutor(printer *printer.Printer, configDir, name, summary string) *DeleteExecutor { +func NewDeleteExecutor(printer *printer.Printer, config *config.Config, name, summary string) *DeleteExecutor { deleteExe := DeleteExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, + printer: printer, + config: config, } deleteExe.StringVar(&deleteExe.resourceType, flagType, "", "Specify the type of resource to delete") @@ -51,7 +52,7 @@ func (d *DeleteExecutor) Execute() error { return UnsupportedTypeError{resourceType: d.resourceType} } - gtsClient, err := client.NewClientFromConfig(d.configDir) + gtsClient, err := client.NewClientFromFile(d.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } diff --git a/internal/executor/edit.go b/internal/executor/edit.go index e18e2e8..ee92375 100644 --- a/internal/executor/edit.go +++ b/internal/executor/edit.go @@ -9,6 +9,7 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) @@ -17,19 +18,19 @@ type EditExecutor struct { *flag.FlagSet printer *printer.Printer - configDir string + config *config.Config resourceType string listID string listTitle string listRepliesPolicy string } -func NewEditExecutor(printer *printer.Printer, configDir, name, summary string) *EditExecutor { +func NewEditExecutor(printer *printer.Printer, config *config.Config, name, summary string) *EditExecutor { editExe := EditExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, + printer: printer, + config: config, } editExe.StringVar(&editExe.resourceType, flagType, "", "Specify the type of resource to update") @@ -56,7 +57,7 @@ func (e *EditExecutor) Execute() error { return UnsupportedTypeError{resourceType: e.resourceType} } - gtsClient, err := client.NewClientFromConfig(e.configDir) + gtsClient, err := client.NewClientFromFile(e.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } diff --git a/internal/executor/errors.go b/internal/executor/errors.go index 3e5f571..3094371 100644 --- a/internal/executor/errors.go +++ b/internal/executor/errors.go @@ -102,3 +102,11 @@ type NotFollowingError struct { func (e NotFollowingError) Error() string { return "you are not following " + e.Account } + +type UnknownMediaAttachmentError struct { + AttachmentID string +} + +func (e UnknownMediaAttachmentError) Error() string { + return "unknown media attachment '" + e.AttachmentID + "'" +} diff --git a/internal/executor/follow_or_unfollow.go b/internal/executor/follow_or_unfollow.go index 535df04..697c8a3 100644 --- a/internal/executor/follow_or_unfollow.go +++ b/internal/executor/follow_or_unfollow.go @@ -9,6 +9,7 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) @@ -16,7 +17,7 @@ type FollowOrUnfollowExecutor struct { *flag.FlagSet printer *printer.Printer - configDir string + config *config.Config resourceType string accountName string showReposts bool @@ -24,13 +25,13 @@ type FollowOrUnfollowExecutor struct { action string } -func NewFollowOrUnfollowExecutor(printer *printer.Printer, configDir, name, summary string) *FollowOrUnfollowExecutor { +func NewFollowOrUnfollowExecutor(printer *printer.Printer, config *config.Config, name, summary string) *FollowOrUnfollowExecutor { command := FollowOrUnfollowExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, - action: name, + printer: printer, + config: config, + action: name, } command.StringVar(&command.resourceType, flagType, "", "Specify the type of resource to follow") @@ -53,7 +54,7 @@ func (f *FollowOrUnfollowExecutor) Execute() error { return UnsupportedTypeError{resourceType: f.resourceType} } - gtsClient, err := client.NewClientFromConfig(f.configDir) + gtsClient, err := client.NewClientFromFile(f.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -62,7 +63,7 @@ func (f *FollowOrUnfollowExecutor) Execute() error { } func (f *FollowOrUnfollowExecutor) followOrUnfollowAccount(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, false, f.accountName, f.configDir) + accountID, err := getAccountID(gtsClient, false, f.accountName, f.config.CredentialsFile) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } diff --git a/internal/executor/init.go b/internal/executor/init.go new file mode 100644 index 0000000..54581b3 --- /dev/null +++ b/internal/executor/init.go @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2024 Dan Anglin +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" + "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" +) + +type InitExecutor struct { + *flag.FlagSet + + printer *printer.Printer + configDir string +} + +func NewInitExecutor(printer *printer.Printer, configDir, name, summary string) *InitExecutor { + initExe := InitExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + printer: printer, + configDir: configDir, + } + + initExe.Usage = commandUsageFunc(name, summary, initExe.FlagSet) + + return &initExe +} + +func (i *InitExecutor) Execute() error { + if err := utilities.EnsureDirectory(i.configDir); err != nil { + return fmt.Errorf("unable to ensure that the configuration directory is present: %w", err) + } + + i.printer.PrintSuccess("The configuration directory is present.") + + fileExists, err := config.FileExists(i.configDir) + if err != nil { + return fmt.Errorf("unable to check if the config file exists: %w", err) + } + + if fileExists { + i.printer.PrintInfo("The configuration file is already present in " + i.configDir + "\n") + + return nil + } + + if err := config.SaveDefaultConfigToFile(i.configDir); err != nil { + return fmt.Errorf("unable to create a new configuration file in %s: %w", i.configDir, err) + } + + i.printer.PrintSuccess("Successfully created a new configuration file in " + i.configDir) + + return nil +} diff --git a/internal/executor/login.go b/internal/executor/login.go index 37d4115..975ed22 100644 --- a/internal/executor/login.go +++ b/internal/executor/login.go @@ -18,18 +18,18 @@ import ( type LoginExecutor struct { *flag.FlagSet - printer *printer.Printer - configDir string - instance string + printer *printer.Printer + config *config.Config + instance string } -func NewLoginExecutor(printer *printer.Printer, configDir, name, summary string) *LoginExecutor { +func NewLoginExecutor(printer *printer.Printer, config *config.Config, name, summary string) *LoginExecutor { command := LoginExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, - instance: "", + printer: printer, + config: config, + instance: "", } command.StringVar(&command.instance, flagInstance, "", "Specify the instance that you want to login to.") @@ -95,7 +95,7 @@ func (c *LoginExecutor) Execute() error { return fmt.Errorf("unable to verify the credentials: %w", err) } - loginName, err := config.SaveCredentials(c.configDir, account.Username, gtsClient.Authentication) + loginName, err := config.SaveCredentials(c.config.CredentialsFile, account.Username, gtsClient.Authentication) if err != nil { return fmt.Errorf("unable to save the authentication details: %w", err) } diff --git a/internal/executor/mute_or_unmute.go b/internal/executor/mute_or_unmute.go index ff49f5e..b04dd19 100644 --- a/internal/executor/mute_or_unmute.go +++ b/internal/executor/mute_or_unmute.go @@ -9,6 +9,7 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) @@ -16,21 +17,21 @@ type MuteOrUnmuteExecutor struct { *flag.FlagSet printer *printer.Printer + config *config.Config accountName string - configDir string command string resourceType string muteDuration TimeDurationFlagValue muteNotifications bool } -func NewMuteOrUnmuteExecutor(printer *printer.Printer, configDir, name, summary string) *MuteOrUnmuteExecutor { +func NewMuteOrUnmuteExecutor(printer *printer.Printer, config *config.Config, name, summary string) *MuteOrUnmuteExecutor { exe := MuteOrUnmuteExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, - command: name, + printer: printer, + config: config, + command: name, } exe.StringVar(&exe.accountName, flagAccountName, "", "Specify the account name in full (username@domain)") @@ -53,7 +54,7 @@ func (m *MuteOrUnmuteExecutor) Execute() error { return UnsupportedTypeError{resourceType: m.resourceType} } - gtsClient, err := client.NewClientFromConfig(m.configDir) + gtsClient, err := client.NewClientFromFile(m.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -66,7 +67,7 @@ func (m *MuteOrUnmuteExecutor) muteOrUnmuteAccount(gtsClient *client.Client) err return FlagNotSetError{flagText: flagAccountName} } - accountID, err := getAccountID(gtsClient, false, m.accountName, m.configDir) + accountID, err := getAccountID(gtsClient, false, m.accountName, m.config.CredentialsFile) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } diff --git a/internal/executor/remove.go b/internal/executor/remove.go index 3814872..eeff8b0 100644 --- a/internal/executor/remove.go +++ b/internal/executor/remove.go @@ -9,6 +9,7 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) @@ -16,7 +17,7 @@ type RemoveExecutor struct { *flag.FlagSet printer *printer.Printer - configDir string + config *config.Config resourceType string fromResourceType string listID string @@ -24,14 +25,14 @@ type RemoveExecutor struct { accountNames MultiStringFlagValue } -func NewRemoveExecutor(printer *printer.Printer, configDir, name, summary string) *RemoveExecutor { +func NewRemoveExecutor(printer *printer.Printer, config *config.Config, name, summary string) *RemoveExecutor { emptyArr := make([]string, 0, 3) removeExe := RemoveExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), printer: printer, - configDir: configDir, + config: config, accountNames: MultiStringFlagValue(emptyArr), } @@ -63,7 +64,7 @@ func (r *RemoveExecutor) Execute() error { return UnsupportedTypeError{resourceType: r.fromResourceType} } - gtsClient, err := client.NewClientFromConfig(r.configDir) + gtsClient, err := client.NewClientFromFile(r.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -137,7 +138,7 @@ func (r *RemoveExecutor) removeNoteFromAccount(gtsClient *client.Client) error { return fmt.Errorf("unexpected number of accounts specified: want 1, got %d", len(r.accountNames)) } - accountID, err := getAccountID(gtsClient, false, r.accountNames[0], r.configDir) + accountID, err := getAccountID(gtsClient, false, r.accountNames[0], r.config.CredentialsFile) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } diff --git a/internal/executor/show.go b/internal/executor/show.go index 2f09a5f..f8b47a1 100644 --- a/internal/executor/show.go +++ b/internal/executor/show.go @@ -11,6 +11,7 @@ import ( "strings" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" @@ -20,12 +21,11 @@ type ShowExecutor struct { *flag.FlagSet printer *printer.Printer + config *config.Config myAccount bool skipAccountRelationship bool showUserPreferences bool showInBrowser bool - configDir string - cacheRoot string resourceType string accountName string statusID string @@ -34,21 +34,16 @@ type ShowExecutor struct { tag string pollID string fromResourceType string - imageViewer string - videoPlayer string limit int attachmentIDs MultiStringFlagValue } -func NewShowExecutor(printer *printer.Printer, configDir, cacheRoot, imageViewer, videoPlayer, name, summary string) *ShowExecutor { +func NewShowExecutor(printer *printer.Printer, config *config.Config, name, summary string) *ShowExecutor { showExe := ShowExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, - cacheRoot: cacheRoot, - imageViewer: imageViewer, - videoPlayer: videoPlayer, + printer: printer, + config: config, } showExe.BoolVar(&showExe.myAccount, flagMyAccount, false, "Set to true to lookup your account") @@ -100,7 +95,7 @@ func (s *ShowExecutor) Execute() error { return UnsupportedTypeError{resourceType: s.resourceType} } - gtsClient, err := client.NewClientFromConfig(s.configDir) + gtsClient, err := client.NewClientFromFile(s.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -126,7 +121,7 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error { ) if s.myAccount { - account, err = getMyAccount(gtsClient, s.configDir) + account, err = getMyAccount(gtsClient, s.config.CredentialsFile) if err != nil { return fmt.Errorf("received an error while getting the account details: %w", err) } @@ -288,7 +283,7 @@ func (s *ShowExecutor) showLists(gtsClient *client.Client) error { } func (s *ShowExecutor) showFollowers(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.configDir) + accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.config.CredentialsFile) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } @@ -308,7 +303,7 @@ func (s *ShowExecutor) showFollowers(gtsClient *client.Client) error { } func (s *ShowExecutor) showFollowing(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.configDir) + accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.config.CredentialsFile) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } @@ -471,7 +466,7 @@ func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error { } cacheDir := filepath.Join( - utilities.CalculateCacheDir(s.cacheRoot, utilities.GetFQDN(gtsClient.Authentication.Instance)), + utilities.CalculateCacheDir(s.config.CacheDirectory, utilities.GetFQDN(gtsClient.Authentication.Instance)), "media", ) @@ -498,7 +493,7 @@ func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error { for _, attachmentID := range s.attachmentIDs { mediaObj, ok := attachmentsHashMap[attachmentID] if !ok { - return fmt.Errorf("unknown media attachment: %s", attachmentID) + return UnknownMediaAttachmentError{AttachmentID: attachmentID} } split := strings.Split(mediaObj.url, "/") @@ -533,13 +528,13 @@ func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error { } if len(imageFiles) > 0 { - if err := utilities.OpenMedia(s.imageViewer, imageFiles); err != nil { + if err := utilities.OpenMedia(s.config.Integrations.ImageViewer, imageFiles); err != nil { return fmt.Errorf("unable to open the image viewer: %w", err) } } if len(videoFiles) > 0 { - if err := utilities.OpenMedia(s.videoPlayer, videoFiles); err != nil { + if err := utilities.OpenMedia(s.config.Integrations.VideoPlayer, videoFiles); err != nil { return fmt.Errorf("unable to open the video player: %w", err) } } diff --git a/internal/executor/switch.go b/internal/executor/switch.go index 988047d..7c8bf53 100644 --- a/internal/executor/switch.go +++ b/internal/executor/switch.go @@ -15,17 +15,17 @@ import ( type SwitchExecutor struct { *flag.FlagSet - configDir string + config *config.Config toResourceType string accountName string printer *printer.Printer } -func NewSwitchExecutor(printer *printer.Printer, configDir, name, summary string) *SwitchExecutor { +func NewSwitchExecutor(printer *printer.Printer, config *config.Config, name, summary string) *SwitchExecutor { switchExe := SwitchExecutor{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + printer: printer, + config: config, } switchExe.StringVar(&switchExe.toResourceType, flagTo, "", "The account to switch to") @@ -54,7 +54,7 @@ func (s *SwitchExecutor) switchToAccount() error { return NoAccountSpecifiedError{} } - if err := config.UpdateCurrentAccount(s.accountName, s.configDir); err != nil { + if err := config.UpdateCurrentAccount(s.accountName, s.config.CredentialsFile); err != nil { return fmt.Errorf("unable to switch account to the account: %w", err) } diff --git a/internal/executor/usage.go b/internal/executor/usage.go index cc5f1ff..dfd26de 100644 --- a/internal/executor/usage.go +++ b/internal/executor/usage.go @@ -6,7 +6,7 @@ package executor import ( "flag" - "fmt" + "slices" "strings" ) @@ -15,27 +15,41 @@ func commandUsageFunc(name, summary string, flagset *flag.FlagSet) func() { return func() { var builder strings.Builder - fmt.Fprintf( - &builder, - "SUMMARY:\n %s - %s\n\nUSAGE:\n enbas %s [flags]\n\nFLAGS:", - name, - summary, - name, - ) + builder.WriteString("SUMMARY:") + builder.WriteString("\n " + name + " - " + summary) + builder.WriteString("\n\nUSAGE:") + builder.WriteString("\n enbas " + name) + + flagMap := make(map[string]string) flagset.VisitAll(func(f *flag.Flag) { - fmt.Fprintf( - &builder, - "\n --%s\n %s", - f.Name, - f.Usage, - ) + flagMap[f.Name] = f.Usage }) + if len(flagMap) > 0 { + flags := make([]string, len(flagMap)) + ind := 0 + + for f := range flagMap { + flags[ind] = f + ind++ + } + + slices.Sort(flags) + + builder.WriteString(" [flags]") + builder.WriteString("\n\nFLAGS:") + + for _, value := range flags { + builder.WriteString("\n --" + value) + builder.WriteString("\n " + flagMap[value]) + } + } + builder.WriteString("\n") w := flag.CommandLine.Output() - fmt.Fprint(w, builder.String()) + _, _ = w.Write([]byte(builder.String())) } } diff --git a/internal/executor/version.go b/internal/executor/version.go index 4abdf6f..4dbf632 100644 --- a/internal/executor/version.go +++ b/internal/executor/version.go @@ -12,6 +12,7 @@ import ( type VersionExecutor struct { *flag.FlagSet + printer *printer.Printer showFullVersion bool binaryVersion string @@ -21,7 +22,7 @@ type VersionExecutor struct { } func NewVersionExecutor( - enbasPrinter *printer.Printer, + printer *printer.Printer, name, summary, binaryVersion, @@ -32,7 +33,7 @@ func NewVersionExecutor( command := VersionExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: enbasPrinter, + printer: printer, binaryVersion: binaryVersion, buildTime: buildTime, goVersion: goVersion, diff --git a/internal/executor/whoami.go b/internal/executor/whoami.go index a1673cb..4d068e9 100644 --- a/internal/executor/whoami.go +++ b/internal/executor/whoami.go @@ -15,16 +15,16 @@ import ( type WhoAmIExecutor struct { *flag.FlagSet - printer *printer.Printer - configDir string + printer *printer.Printer + config *config.Config } -func NewWhoAmIExecutor(printer *printer.Printer, configDir, name, summary string) *WhoAmIExecutor { +func NewWhoAmIExecutor(printer *printer.Printer, config *config.Config, name, summary string) *WhoAmIExecutor { whoExe := WhoAmIExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, + printer: printer, + config: config, } whoExe.Usage = commandUsageFunc(name, summary, whoExe.FlagSet) @@ -33,7 +33,7 @@ func NewWhoAmIExecutor(printer *printer.Printer, configDir, name, summary string } func (c *WhoAmIExecutor) Execute() error { - config, err := config.NewCredentialsConfigFromFile(c.configDir) + config, err := config.NewCredentialsConfigFromFile(c.config.CredentialsFile) if err != nil { return fmt.Errorf("unable to load the credential config: %w", err) } diff --git a/internal/internal.go b/internal/internal.go index 02566fd..7e36795 100644 --- a/internal/internal.go +++ b/internal/internal.go @@ -7,6 +7,6 @@ package internal const ( ApplicationName = "enbas" ApplicationWebsite = "https://codeflow.dananglin.me.uk/apollo/enbas" - RedirectUri = "urn:ietf:wg:oauth:2.0:oob" + RedirectURI = "urn:ietf:wg:oauth:2.0:oob" UserAgent = "Enbas/0.0.0" ) diff --git a/internal/model/application.go b/internal/model/application.go index 03f91f1..f251fd8 100644 --- a/internal/model/application.go +++ b/internal/model/application.go @@ -9,7 +9,7 @@ type Application struct { ClientSecret string `json:"client_secret"` ID string `json:"id"` Name string `json:"name"` - RedirectUri string `json:"redirect_uri"` + RedirectURI string `json:"redirect_uri"` VapidKey string `json:"vapid_key"` Website string `json:"website"` } diff --git a/internal/model/instance_v2.go b/internal/model/instance_v2.go index c1461d4..83a438f 100644 --- a/internal/model/instance_v2.go +++ b/internal/model/instance_v2.go @@ -95,7 +95,7 @@ type InstanceV2Thumbnail struct { ThumbnailDescription string `json:"thumbnail_description"` ThumbnailType string `json:"thumbnail_type"` URL string `json:"url"` - Versions InstanceV2ThumbnailVersions `json:"versions"` + Versions InstanceV2ThumbnailVersions `json:"versions"` } type InstanceV2ThumbnailVersions struct { diff --git a/internal/utilities/directories.go b/internal/utilities/directories.go index 8a5d871..afa9233 100644 --- a/internal/utilities/directories.go +++ b/internal/utilities/directories.go @@ -20,7 +20,7 @@ func CalculateConfigDir(configDir string) string { configRoot, err := os.UserConfigDir() if err != nil { - configRoot = "." + return filepath.Join(os.Getenv("HOME"), "."+internal.ApplicationName, "config") } return filepath.Join(configRoot, internal.ApplicationName) @@ -33,7 +33,7 @@ func CalculateCacheDir(cacheDir, instanceFQDN string) string { cacheRoot, err := os.UserCacheDir() if err != nil { - cacheRoot = "." + return filepath.Join(os.Getenv("HOME"), "."+internal.ApplicationName, "cache") } return filepath.Join(cacheRoot, internal.ApplicationName, instanceFQDN) -- 2.45.2