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)