From 55b93c658630636e0efebf93f50fe13896c2e523 Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Thu, 23 May 2024 18:06:49 +0100 Subject: [PATCH] fix(breaking): update project structure Move all executors to the internal folder package. This PR also comes with additional breaking changes. Changes: - refactor: move all executors to the internal/executor package. - refactor: update naming patterns for constants, variables, custom types, etc. - fix(breaking): renamed the update command to edit. - fix(breaking): update the flags for the switch command to make it more generic. - fix(breaking): renamed the show-account-relationship flag to show-relationship. - fix: update the print message from the whoami command. --- cmd/enbas/add.go | 147 ------------ cmd/enbas/block.go | 80 ------- cmd/enbas/delete.go | 67 ------ cmd/enbas/errors.go | 54 +---- cmd/enbas/flags.go | 21 -- cmd/enbas/main.go | 240 +++++++++++--------- cmd/enbas/remove.go | 140 ------------ cmd/enbas/switch.go | 42 ---- cmd/enbas/update.go | 91 -------- cmd/enbas/usage.go | 31 +-- cmd/enbas/whoami.go | 36 --- {cmd/enbas => internal/executor}/account.go | 4 +- internal/executor/add.go | 146 ++++++++++++ internal/executor/block.go | 80 +++++++ internal/executor/const.go | 32 +++ {cmd/enbas => internal/executor}/create.go | 34 +-- internal/executor/delete.go | 66 ++++++ internal/executor/errors.go | 55 +++++ internal/executor/executor.go | 21 ++ internal/executor/flags.go | 21 ++ {cmd/enbas => internal/executor}/follow.go | 32 +-- {cmd/enbas => internal/executor}/login.go | 24 +- internal/executor/remove.go | 140 ++++++++++++ {cmd/enbas => internal/executor}/show.go | 88 +++---- internal/executor/switch.go | 57 +++++ internal/executor/update.go | 91 ++++++++ internal/executor/usage.go | 37 +++ {cmd/enbas => internal/executor}/version.go | 17 +- internal/executor/whoami.go | 36 +++ 29 files changed, 1014 insertions(+), 916 deletions(-) delete mode 100644 cmd/enbas/add.go delete mode 100644 cmd/enbas/block.go delete mode 100644 cmd/enbas/delete.go delete mode 100644 cmd/enbas/flags.go delete mode 100644 cmd/enbas/remove.go delete mode 100644 cmd/enbas/switch.go delete mode 100644 cmd/enbas/update.go delete mode 100644 cmd/enbas/whoami.go rename {cmd/enbas => internal/executor}/account.go (97%) create mode 100644 internal/executor/add.go create mode 100644 internal/executor/block.go create mode 100644 internal/executor/const.go rename {cmd/enbas => internal/executor}/create.go (50%) create mode 100644 internal/executor/delete.go create mode 100644 internal/executor/errors.go create mode 100644 internal/executor/executor.go create mode 100644 internal/executor/flags.go rename {cmd/enbas => internal/executor}/follow.go (62%) rename {cmd/enbas => internal/executor}/login.go (80%) create mode 100644 internal/executor/remove.go rename {cmd/enbas => internal/executor}/show.go (67%) create mode 100644 internal/executor/switch.go create mode 100644 internal/executor/update.go create mode 100644 internal/executor/usage.go rename {cmd/enbas => internal/executor}/version.go (77%) create mode 100644 internal/executor/whoami.go diff --git a/cmd/enbas/add.go b/cmd/enbas/add.go deleted file mode 100644 index 40bbdeb..0000000 --- a/cmd/enbas/add.go +++ /dev/null @@ -1,147 +0,0 @@ -package main - -import ( - "errors" - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/client" -) - -type addCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - resourceType string - toResourceType string - listID string - accountNames accountNames - content string -} - -func newAddCommand(tlf topLevelFlags, name, summary string) *addCommand { - emptyArr := make([]string, 0, 3) - - command := addCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - accountNames: accountNames(emptyArr), - topLevelFlags: tlf, - } - - command.StringVar(&command.resourceType, resourceTypeFlag, "", "specify the resource type to add (e.g. account, note)") - command.StringVar(&command.toResourceType, addToFlag, "", "specify the target resource type to add to (e.g. list, account, etc)") - command.StringVar(&command.listID, listIDFlag, "", "the ID of the list to add to") - command.Var(&command.accountNames, accountNameFlag, "the name of the account to add to the resource") - command.StringVar(&command.content, contentFlag, "", "the content of the note") - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *addCommand) Execute() error { - if c.toResourceType == "" { - return flagNotSetError{flagText: addToFlag} - } - - funcMap := map[string]func(*client.Client) error{ - listResource: c.addToList, - accountResource: c.addToAccount, - } - - doFunc, ok := funcMap[c.toResourceType] - if !ok { - return unsupportedResourceTypeError{resourceType: c.toResourceType} - } - - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to create the GoToSocial client; %w", err) - } - - return doFunc(gtsClient) -} - -func (c *addCommand) addToList(gtsClient *client.Client) error { - funcMap := map[string]func(*client.Client) error{ - accountResource: c.addAccountsToList, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedAddOperationError{ - ResourceType: c.resourceType, - AddToResourceType: c.toResourceType, - } - } - - return doFunc(gtsClient) -} - -func (c *addCommand) addAccountsToList(gtsClient *client.Client) error { - if c.listID == "" { - return flagNotSetError{flagText: listIDFlag} - } - - if len(c.accountNames) == 0 { - return noAccountSpecifiedError{} - } - - accountIDs := make([]string, len(c.accountNames)) - - for i := range c.accountNames { - accountID, err := getTheirAccountID(gtsClient, c.accountNames[i]) - if err != nil { - return fmt.Errorf("unable to get the account ID for %s, %w", c.accountNames[i], err) - } - - accountIDs[i] = accountID - } - - if err := gtsClient.AddAccountsToList(c.listID, accountIDs); err != nil { - return fmt.Errorf("unable to add the accounts to the list; %w", err) - } - - fmt.Println("Successfully added the account(s) to the list.") - - return nil -} - -func (c *addCommand) addToAccount(gtsClient *client.Client) error { - funcMap := map[string]func(*client.Client) error{ - noteResource: c.addNoteToAccount, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedAddOperationError{ - ResourceType: c.resourceType, - AddToResourceType: c.toResourceType, - } - } - - return doFunc(gtsClient) -} - -func (c *addCommand) addNoteToAccount(gtsClient *client.Client) error { - if len(c.accountNames) != 1 { - return fmt.Errorf("unexpected number of accounts specified; want 1, got %d", len(c.accountNames)) - } - - accountID, err := getAccountID(gtsClient, false, c.accountNames[0], c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("received an error while getting the account ID; %w", err) - } - - if c.content == "" { - return errors.New("the note content should not be empty") - } - - if err := gtsClient.SetPrivateNote(accountID, c.content); err != nil { - return fmt.Errorf("unable to add the private note to the account; %w", err) - } - - fmt.Println("Successfully added the private note to the account.") - - return nil -} diff --git a/cmd/enbas/block.go b/cmd/enbas/block.go deleted file mode 100644 index c7650d3..0000000 --- a/cmd/enbas/block.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/client" -) - -type blockCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - resourceType string - accountName string - unblock bool -} - -func newBlockCommand(tlf topLevelFlags, name, summary string, unblock bool) *blockCommand { - command := blockCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - - topLevelFlags: tlf, - unblock: unblock, - } - - command.StringVar(&command.resourceType, resourceTypeFlag, "", "specify the type of resource to block or unblock") - command.StringVar(&command.accountName, accountNameFlag, "", "specify the account name in full (username@domain)") - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *blockCommand) Execute() error { - funcMap := map[string]func(*client.Client) error{ - accountResource: c.blockAccount, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} - } - - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to create the GoToSocial client; %w", err) - } - - return doFunc(gtsClient) -} - -func (c *blockCommand) blockAccount(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, false, c.accountName, c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("received an error while getting the account ID; %w", err) - } - - if c.unblock { - return c.unblockAccount(gtsClient, accountID) - } - - if err := gtsClient.BlockAccount(accountID); err != nil { - return fmt.Errorf("unable to block the account; %w", err) - } - - fmt.Println("Successfully blocked the account.") - - return nil -} - -func (c *blockCommand) unblockAccount(gtsClient *client.Client, accountID string) error { - if err := gtsClient.UnblockAccount(accountID); err != nil { - return fmt.Errorf("unable to unblock the account; %w", err) - } - - fmt.Println("Successfully unblocked the account.") - - return nil -} diff --git a/cmd/enbas/delete.go b/cmd/enbas/delete.go deleted file mode 100644 index 8708947..0000000 --- a/cmd/enbas/delete.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/client" -) - -type deleteCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - resourceType string - listID string -} - -func newDeleteCommand(tlf topLevelFlags, name, summary string) *deleteCommand { - command := deleteCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - - topLevelFlags: tlf, - } - - command.StringVar(&command.resourceType, resourceTypeFlag, "", "specify the type of resource to delete") - command.StringVar(&command.listID, listIDFlag, "", "specify the ID of the list to delete") - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *deleteCommand) Execute() error { - if c.resourceType == "" { - return flagNotSetError{flagText: resourceTypeFlag} - } - - funcMap := map[string]func(*client.Client) error{ - listResource: c.deleteList, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} - } - - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to create the GoToSocial client; %w", err) - } - - return doFunc(gtsClient) -} - -func (c *deleteCommand) deleteList(gtsClient *client.Client) error { - if c.listID == "" { - return flagNotSetError{flagText: listIDFlag} - } - - if err := gtsClient.DeleteList(c.listID); err != nil { - return fmt.Errorf("unable to delete the list; %w", err) - } - - fmt.Println("The list was successfully deleted.") - - return nil -} diff --git a/cmd/enbas/errors.go b/cmd/enbas/errors.go index 670113d..739acbc 100644 --- a/cmd/enbas/errors.go +++ b/cmd/enbas/errors.go @@ -1,57 +1,9 @@ package main -type flagNotSetError struct { - flagText string -} - -func (e flagNotSetError) Error() string { - return "the flag '" + e.flagText + "' is not set" -} - -type unsupportedResourceTypeError struct { - resourceType string -} - -func (e unsupportedResourceTypeError) Error() string { - return "unsupported resource type '" + e.resourceType + "'" -} - -type invalidTimelineCategoryError struct { - category string -} - -func (e invalidTimelineCategoryError) Error() string { - return "'" + e.category + "' is not a valid timeline category (please choose home, public, tag or list)" -} - -type unknownSubcommandError struct { +type unknownCommandError struct { subcommand string } -func (e unknownSubcommandError) Error() string { - return "unknown subcommand '" + e.subcommand + "'" -} - -type noAccountSpecifiedError struct{} - -func (e noAccountSpecifiedError) Error() string { - return "no account specified in this request" -} - -type unsupportedAddOperationError struct { - ResourceType string - AddToResourceType string -} - -func (e unsupportedAddOperationError) Error() string { - return "adding '" + e.ResourceType + "' to '" + e.AddToResourceType + "' is not supported" -} - -type unsupportedRemoveOperationError struct { - ResourceType string - RemoveFromResourceType string -} - -func (e unsupportedRemoveOperationError) Error() string { - return "removing '" + e.ResourceType + "' from '" + e.RemoveFromResourceType + "' is not supported" +func (e unknownCommandError) Error() string { + return "unknown command '" + e.subcommand + "'" } diff --git a/cmd/enbas/flags.go b/cmd/enbas/flags.go deleted file mode 100644 index 912c9df..0000000 --- a/cmd/enbas/flags.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import "strings" - -type accountNames []string - -func (a *accountNames) String() string { - return strings.Join(*a, ", ") -} - -func (a *accountNames) Set(value string) error { - if len(value) > 0 { - *a = append(*a, value) - } - - return nil -} - -type topLevelFlags struct { - configDir string -} diff --git a/cmd/enbas/main.go b/cmd/enbas/main.go index 43690ca..d4aa348 100644 --- a/cmd/enbas/main.go +++ b/cmd/enbas/main.go @@ -4,48 +4,34 @@ import ( "flag" "fmt" "os" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/executor" ) const ( - accountNameFlag = "account-name" - addToFlag = "to" - contentFlag = "content" - instanceFlag = "instance" - limitFlag = "limit" - listIDFlag = "list-id" - listTitleFlag = "list-title" - listRepliesPolicyFlag = "list-replies-policy" - myAccountFlag = "my-account" - notifyFlag = "notify" - removeFromFlag = "from" - resourceTypeFlag = "type" - showAccountRelationshipFlag = "show-account-relationship" - showUserPreferencesFlag = "show-preferences" - showRepostsFlag = "show-reposts" - statusIDFlag = "status-id" - tagFlag = "tag" - timelineCategoryFlag = "timeline-category" - toAccountFlag = "to-account" + commandLogin string = "login" + commandVersion string = "version" + commandShow string = "show" + commandSwitch string = "switch" + commandCreate string = "create" + commandDelete string = "delete" + commandEdit string = "edit" + commandWhoami string = "whoami" + commandAdd string = "add" + commandRemove string = "remove" + commandFollow string = "follow" + commandUnfollow string = "unfollow" + commandBlock string = "block" + commandUnblock string = "unblock" ) -const ( - accountResource = "account" - blockedResource = "blocked" - followersResource = "followers" - followingResource = "following" - instanceResource = "instance" - listResource = "list" - noteResource = "note" - statusResource = "status" - timelineResource = "timeline" +var ( + binaryVersion string + buildTime string + goVersion string + gitCommit string ) -type Executor interface { - Name() string - Parse(args []string) error - Execute() error -} - func main() { if err := run(); err != nil { fmt.Printf("ERROR: %v.\n", err) @@ -54,45 +40,28 @@ func main() { } func run() error { - const ( - login string = "login" - version string = "version" - showResource string = "show" - switchAccount string = "switch" - createResource string = "create" - deleteResource string = "delete" - updateResource string = "update" - whoami string = "whoami" - add string = "add" - remove string = "remove" - follow string = "follow" - unfollow string = "unfollow" - block string = "block" - unblock string = "unblock" - ) - - summaries := map[string]string{ - login: "login to an account on GoToSocial", - version: "print the application's version and build information", - showResource: "print details about a specified resource", - switchAccount: "switch to an account", - createResource: "create a specific resource", - deleteResource: "delete a specific resource", - updateResource: "update a specific resource", - whoami: "print the account that you are currently logged in to", - add: "add a resource to another resource", - remove: "remove a resource from another resource", - follow: "follow a resource (e.g. an account)", - unfollow: "unfollow a resource (e.g. an account)", - block: "block a resource (e.g. an account)", - unblock: "unblock a resource (e.g. an account)", + commandSummaries := map[string]string{ + commandLogin: "login to an account on GoToSocial", + commandVersion: "print the application's version and build information", + commandShow: "print details about a specified resource", + commandSwitch: "perform a switch operation (e.g. switch logged in accounts)", + commandCreate: "create a specific resource", + commandDelete: "delete a specific resource", + commandEdit: "edit a specific resource", + commandWhoami: "print the account that you are currently logged in to", + commandAdd: "add a resource to another resource", + commandRemove: "remove a resource from another resource", + commandFollow: "follow a resource (e.g. an account)", + commandUnfollow: "unfollow a resource (e.g. an account)", + commandBlock: "block a resource (e.g. an account)", + commandUnblock: "unblock a resource (e.g. an account)", } - tlf := topLevelFlags{} + topLevelFlags := executor.TopLevelFlags{} - flag.StringVar(&tlf.configDir, "config-dir", "", "specify your config directory") + flag.StringVar(&topLevelFlags.ConfigDir, "config-dir", "", "specify your config directory") - flag.Usage = enbasUsageFunc(summaries) + flag.Usage = usageFunc(commandSummaries) flag.Parse() @@ -102,52 +71,107 @@ func run() error { return nil } - subcommand := flag.Arg(0) + command := flag.Arg(0) args := flag.Args()[1:] - var executor Executor + var err error - switch subcommand { - case login: - executor = newLoginCommand(tlf, login, summaries[login]) - case version: - executor = newVersionCommand(version, summaries[version]) - case showResource: - executor = newShowCommand(tlf, showResource, summaries[showResource]) - case switchAccount: - executor = newSwitchCommand(tlf, switchAccount, summaries[switchAccount]) - case createResource: - executor = newCreateCommand(tlf, createResource, summaries[createResource]) - case deleteResource: - executor = newDeleteCommand(tlf, deleteResource, summaries[deleteResource]) - case updateResource: - executor = newUpdateCommand(tlf, updateResource, summaries[updateResource]) - case whoami: - executor = newWhoAmICommand(tlf, whoami, summaries[whoami]) - case add: - executor = newAddCommand(tlf, add, summaries[add]) - case remove: - executor = newRemoveCommand(tlf, remove, summaries[remove]) - case follow: - executor = newFollowCommand(tlf, follow, summaries[follow], false) - case unfollow: - executor = newFollowCommand(tlf, unfollow, summaries[unfollow], true) - case block: - executor = newBlockCommand(tlf, block, summaries[block], false) - case unblock: - executor = newBlockCommand(tlf, unblock, summaries[unblock], true) + switch command { + case commandAdd: + exe := executor.NewAddExecutor( + topLevelFlags, + commandAdd, + commandSummaries[commandAdd], + ) + err = executor.Execute(exe, args) + case commandBlock: + exe := executor.NewBlockExecutor( + topLevelFlags, + commandBlock, + commandSummaries[commandBlock], + false, + ) + err = executor.Execute(exe, args) + case commandCreate: + exe := executor.NewCreateExecutor( + topLevelFlags, + commandCreate, + commandSummaries[commandCreate], + ) + err = executor.Execute(exe, args) + case commandDelete: + exe := executor.NewDeleteExecutor( + topLevelFlags, + commandDelete, + commandSummaries[commandDelete], + ) + err = executor.Execute(exe, args) + case commandEdit: + exe := executor.NewEditExecutor( + topLevelFlags, + commandEdit, + commandSummaries[commandEdit], + ) + err = executor.Execute(exe, args) + case commandFollow: + exe := executor.NewFollowExecutor( + topLevelFlags, + commandFollow, + commandSummaries[commandFollow], + false, + ) + err = executor.Execute(exe, args) + case commandLogin: + exe := executor.NewLoginExecutor( + topLevelFlags, + commandLogin, + commandSummaries[commandLogin], + ) + err = executor.Execute(exe, args) + case commandRemove: + exe := executor.NewRemoveExecutor( + topLevelFlags, + commandRemove, + commandSummaries[commandRemove], + ) + err = executor.Execute(exe, args) + case commandSwitch: + exe := executor.NewSwitchExecutor( + topLevelFlags, + commandSwitch, + commandSummaries[commandSwitch], + ) + err = executor.Execute(exe, args) + case commandUnfollow: + exe := executor.NewFollowExecutor(topLevelFlags, commandUnfollow, commandSummaries[commandUnfollow], true) + err = executor.Execute(exe, args) + case commandUnblock: + exe := executor.NewBlockExecutor(topLevelFlags, commandUnblock, commandSummaries[commandUnblock], true) + err = executor.Execute(exe, args) + case commandShow: + exe := executor.NewShowExecutor(topLevelFlags, commandShow, commandSummaries[commandShow]) + err = executor.Execute(exe, args) + case commandVersion: + exe := executor.NewVersionExecutor( + commandVersion, + commandSummaries[commandVersion], + binaryVersion, + buildTime, + goVersion, + gitCommit, + ) + err = executor.Execute(exe, args) + case commandWhoami: + exe := executor.NewWhoAmIExecutor(topLevelFlags, commandWhoami, commandSummaries[commandWhoami]) + err = executor.Execute(exe, args) default: flag.Usage() - return unknownSubcommandError{subcommand} + return unknownCommandError{command} } - if err := executor.Parse(args); err != nil { - return fmt.Errorf("unable to parse the command line flags; %w", err) - } - - if err := executor.Execute(); err != nil { - return fmt.Errorf("received an error after executing %q; %w", executor.Name(), err) + if err != nil { + return fmt.Errorf("received an error executing the command; %w", err) } return nil diff --git a/cmd/enbas/remove.go b/cmd/enbas/remove.go deleted file mode 100644 index f985c77..0000000 --- a/cmd/enbas/remove.go +++ /dev/null @@ -1,140 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/client" -) - -type removeCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - resourceType string - fromResourceType string - listID string - accountNames accountNames -} - -func newRemoveCommand(tlf topLevelFlags, name, summary string) *removeCommand { - emptyArr := make([]string, 0, 3) - - command := removeCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - accountNames: accountNames(emptyArr), - topLevelFlags: tlf, - } - - command.StringVar(&command.resourceType, resourceTypeFlag, "", "specify the resource type to remove (e.g. account, note)") - command.StringVar(&command.fromResourceType, removeFromFlag, "", "specify the resource type to remove from (e.g. list, account, etc)") - command.StringVar(&command.listID, listIDFlag, "", "the ID of the list to remove from") - command.Var(&command.accountNames, accountNameFlag, "the name of the account to remove from the resource") - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *removeCommand) Execute() error { - if c.fromResourceType == "" { - return flagNotSetError{flagText: removeFromFlag} - } - - funcMap := map[string]func(*client.Client) error{ - listResource: c.removeFromList, - accountResource: c.removeFromAccount, - } - - doFunc, ok := funcMap[c.fromResourceType] - if !ok { - return unsupportedResourceTypeError{resourceType: c.fromResourceType} - } - - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to create the GoToSocial client; %w", err) - } - - return doFunc(gtsClient) -} - -func (c *removeCommand) removeFromList(gtsClient *client.Client) error { - funcMap := map[string]func(*client.Client) error{ - accountResource: c.removeAccountsFromList, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedRemoveOperationError{ - ResourceType: c.resourceType, - RemoveFromResourceType: c.fromResourceType, - } - } - - return doFunc(gtsClient) -} - -func (c *removeCommand) removeAccountsFromList(gtsClient *client.Client) error { - if c.listID == "" { - return flagNotSetError{flagText: listIDFlag} - } - - if len(c.accountNames) == 0 { - return noAccountSpecifiedError{} - } - - accountIDs := make([]string, len(c.accountNames)) - - for i := range c.accountNames { - accountID, err := getTheirAccountID(gtsClient, c.accountNames[i]) - if err != nil { - return fmt.Errorf("unable to get the account ID for %s, %w", c.accountNames[i], err) - } - - accountIDs[i] = accountID - } - - if err := gtsClient.RemoveAccountsFromList(c.listID, accountIDs); err != nil { - return fmt.Errorf("unable to remove the accounts from the list; %w", err) - } - - fmt.Println("Successfully removed the account(s) from the list.") - - return nil -} - -func (c *removeCommand) removeFromAccount(gtsClient *client.Client) error { - funcMap := map[string]func(*client.Client) error{ - noteResource: c.removeNoteFromAccount, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedRemoveOperationError{ - ResourceType: c.resourceType, - RemoveFromResourceType: c.fromResourceType, - } - } - - return doFunc(gtsClient) -} - -func (c *removeCommand) removeNoteFromAccount(gtsClient *client.Client) error { - if len(c.accountNames) != 1 { - return fmt.Errorf("unexpected number of accounts specified; want 1, got %d", len(c.accountNames)) - } - - accountID, err := getAccountID(gtsClient, false, c.accountNames[0], c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("received an error while getting the account ID; %w", err) - } - - if err := gtsClient.SetPrivateNote(accountID, ""); err != nil { - return fmt.Errorf("unable to remove the private note from the account; %w", err) - } - - fmt.Println("Successfully removed the private note from the account.") - - return nil -} diff --git a/cmd/enbas/switch.go b/cmd/enbas/switch.go deleted file mode 100644 index fcef938..0000000 --- a/cmd/enbas/switch.go +++ /dev/null @@ -1,42 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/config" -) - -type switchCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - toAccount string -} - -func newSwitchCommand(tlf topLevelFlags, name, summary string) *switchCommand { - command := switchCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, - } - - command.StringVar(&command.toAccount, toAccountFlag, "", "the account to switch to") - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *switchCommand) Execute() error { - if c.toAccount == "" { - return flagNotSetError{flagText: toAccountFlag} - } - - if err := config.UpdateCurrentAccount(c.toAccount, c.topLevelFlags.configDir); err != nil { - return fmt.Errorf("unable to switch accounts; %w", err) - } - - fmt.Printf("The current account is now set to %q.\n", c.toAccount) - - return nil -} diff --git a/cmd/enbas/update.go b/cmd/enbas/update.go deleted file mode 100644 index 0eae167..0000000 --- a/cmd/enbas/update.go +++ /dev/null @@ -1,91 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/client" - "codeflow.dananglin.me.uk/apollo/enbas/internal/model" -) - -type updateCommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags - resourceType string - listID string - listTitle string - listRepliesPolicy string -} - -func newUpdateCommand(tlf topLevelFlags, name, summary string) *updateCommand { - command := updateCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, - } - - command.StringVar(&command.resourceType, resourceTypeFlag, "", "specify the type of resource to update") - command.StringVar(&command.listID, listIDFlag, "", "specify the ID of the list to update") - command.StringVar(&command.listTitle, listTitleFlag, "", "specify the title of the list") - command.StringVar(&command.listRepliesPolicy, listRepliesPolicyFlag, "", "specify the policy of the replies for this list (valid values are followed, list and none)") - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *updateCommand) Execute() error { - if c.resourceType == "" { - return flagNotSetError{flagText: resourceTypeFlag} - } - - funcMap := map[string]func(*client.Client) error{ - listResource: c.updateList, - } - - doFunc, ok := funcMap[c.resourceType] - if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} - } - - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to create the GoToSocial client; %w", err) - } - - return doFunc(gtsClient) -} - -func (c *updateCommand) updateList(gtsClient *client.Client) error { - if c.listID == "" { - return flagNotSetError{flagText: listIDFlag} - } - - list, err := gtsClient.GetList(c.listID) - if err != nil { - return fmt.Errorf("unable to get the list; %w", err) - } - - if c.listTitle != "" { - list.Title = c.listTitle - } - - if c.listRepliesPolicy != "" { - repliesPolicy, err := model.ParseListRepliesPolicy(c.listRepliesPolicy) - if err != nil { - return fmt.Errorf("unable to parse the list replies policy; %w", err) - } - - list.RepliesPolicy = repliesPolicy - } - - updatedList, err := gtsClient.UpdateList(list) - if err != nil { - return fmt.Errorf("unable to update the list; %w", err) - } - - fmt.Println("Successfully updated the list.") - fmt.Println(updatedList) - - return nil -} diff --git a/cmd/enbas/usage.go b/cmd/enbas/usage.go index e055937..1aaea70 100644 --- a/cmd/enbas/usage.go +++ b/cmd/enbas/usage.go @@ -7,36 +7,7 @@ import ( "strings" ) -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, - ) - - flagset.VisitAll(func(f *flag.Flag) { - fmt.Fprintf( - &builder, - "\n --%s\n %s", - f.Name, - f.Usage, - ) - }) - - builder.WriteString("\n") - - w := flag.CommandLine.Output() - - fmt.Fprint(w, builder.String()) - } -} - -func enbasUsageFunc(summaries map[string]string) func() { +func usageFunc(summaries map[string]string) func() { cmds := make([]string, len(summaries)) ind := 0 diff --git a/cmd/enbas/whoami.go b/cmd/enbas/whoami.go deleted file mode 100644 index 60fb06c..0000000 --- a/cmd/enbas/whoami.go +++ /dev/null @@ -1,36 +0,0 @@ -package main - -import ( - "flag" - "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/config" -) - -type whoAmICommand struct { - *flag.FlagSet - - topLevelFlags topLevelFlags -} - -func newWhoAmICommand(tlf topLevelFlags, name, summary string) *whoAmICommand { - command := whoAmICommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, - } - - command.Usage = commandUsageFunc(name, summary, command.FlagSet) - - return &command -} - -func (c *whoAmICommand) Execute() error { - config, err := config.NewCredentialsConfigFromFile(c.topLevelFlags.configDir) - if err != nil { - return fmt.Errorf("unable to load the credential config; %w", err) - } - - fmt.Printf("You are %s\n", config.CurrentAccount) - - return nil -} diff --git a/cmd/enbas/account.go b/internal/executor/account.go similarity index 97% rename from cmd/enbas/account.go rename to internal/executor/account.go index 91d29f6..a88ccf0 100644 --- a/cmd/enbas/account.go +++ b/internal/executor/account.go @@ -1,4 +1,4 @@ -package main +package executor import ( "fmt" @@ -26,7 +26,7 @@ func getAccountID(gtsClient *client.Client, myAccount bool, accountName, configD return "", fmt.Errorf("unable to get their account ID; %w", err) } default: - return "", noAccountSpecifiedError{} + return "", NoAccountSpecifiedError{} } return accountID, nil diff --git a/internal/executor/add.go b/internal/executor/add.go new file mode 100644 index 0000000..d7956a8 --- /dev/null +++ b/internal/executor/add.go @@ -0,0 +1,146 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" +) + +type AddExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + toResourceType string + listID string + accountNames AccountNames + content string +} + +func NewAddExecutor(tlf TopLevelFlags, name, summary string) *AddExecutor { + emptyArr := make([]string, 0, 3) + + addExe := AddExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + accountNames: AccountNames(emptyArr), + topLevelFlags: tlf, + } + + addExe.StringVar(&addExe.resourceType, flagType, "", "specify the resource type to add (e.g. account, note)") + addExe.StringVar(&addExe.toResourceType, flagTo, "", "specify the target resource type to add to (e.g. list, account, etc)") + addExe.StringVar(&addExe.listID, flagListID, "", "the ID of the list to add to") + addExe.Var(&addExe.accountNames, flagAccountName, "the name of the account to add to the resource") + addExe.StringVar(&addExe.content, flagContent, "", "the content of the note") + + addExe.Usage = commandUsageFunc(name, summary, addExe.FlagSet) + + return &addExe +} + +func (a *AddExecutor) Execute() error { + if a.toResourceType == "" { + return FlagNotSetError{flagText: flagTo} + } + + funcMap := map[string]func(*client.Client) error{ + resourceList: a.addToList, + resourceAccount: a.addToAccount, + } + + doFunc, ok := funcMap[a.toResourceType] + if !ok { + return UnsupportedTypeError{resourceType: a.toResourceType} + } + + gtsClient, err := client.NewClientFromConfig(a.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + return doFunc(gtsClient) +} + +func (a *AddExecutor) addToList(gtsClient *client.Client) error { + funcMap := map[string]func(*client.Client) error{ + resourceAccount: a.addAccountsToList, + } + + doFunc, ok := funcMap[a.resourceType] + if !ok { + return UnsupportedAddOperationError{ + ResourceType: a.resourceType, + AddToResourceType: a.toResourceType, + } + } + + return doFunc(gtsClient) +} + +func (a *AddExecutor) addAccountsToList(gtsClient *client.Client) error { + if a.listID == "" { + return FlagNotSetError{flagText: flagListID} + } + + if len(a.accountNames) == 0 { + return NoAccountSpecifiedError{} + } + + accountIDs := make([]string, len(a.accountNames)) + + for ind := range a.accountNames { + accountID, err := getTheirAccountID(gtsClient, a.accountNames[ind]) + if err != nil { + return fmt.Errorf("unable to get the account ID for %s, %w", a.accountNames[ind], err) + } + + accountIDs[ind] = accountID + } + + if err := gtsClient.AddAccountsToList(a.listID, accountIDs); err != nil { + return fmt.Errorf("unable to add the accounts to the list; %w", err) + } + + fmt.Println("Successfully added the account(s) to the list.") + + return nil +} + +func (a *AddExecutor) addToAccount(gtsClient *client.Client) error { + funcMap := map[string]func(*client.Client) error{ + resourceNote: a.addNoteToAccount, + } + + doFunc, ok := funcMap[a.resourceType] + if !ok { + return UnsupportedAddOperationError{ + ResourceType: a.resourceType, + AddToResourceType: a.toResourceType, + } + } + + return doFunc(gtsClient) +} + +func (a *AddExecutor) addNoteToAccount(gtsClient *client.Client) error { + if len(a.accountNames) != 1 { + return fmt.Errorf("unexpected number of accounts specified; want 1, got %d", len(a.accountNames)) + } + + accountID, err := getAccountID(gtsClient, false, a.accountNames[0], a.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("received an error while getting the account ID; %w", err) + } + + if a.content == "" { + return EmptyContentError{} + } + + if err := gtsClient.SetPrivateNote(accountID, a.content); err != nil { + return fmt.Errorf("unable to add the private note to the account; %w", err) + } + + fmt.Println("Successfully added the private note to the account.") + + return nil +} diff --git a/internal/executor/block.go b/internal/executor/block.go new file mode 100644 index 0000000..33b73f3 --- /dev/null +++ b/internal/executor/block.go @@ -0,0 +1,80 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" +) + +type BlockExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + accountName string + unblock bool +} + +func NewBlockExecutor(tlf TopLevelFlags, name, summary string, unblock bool) *BlockExecutor { + blockExe := BlockExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + topLevelFlags: tlf, + unblock: unblock, + } + + blockExe.StringVar(&blockExe.resourceType, flagType, "", "specify the type of resource to block or unblock") + blockExe.StringVar(&blockExe.accountName, flagAccountName, "", "specify the account name in full (username@domain)") + + blockExe.Usage = commandUsageFunc(name, summary, blockExe.FlagSet) + + return &blockExe +} + +func (b *BlockExecutor) Execute() error { + funcMap := map[string]func(*client.Client) error{ + resourceAccount: b.blockAccount, + } + + doFunc, ok := funcMap[b.resourceType] + if !ok { + return UnsupportedTypeError{resourceType: b.resourceType} + } + + gtsClient, err := client.NewClientFromConfig(b.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + return doFunc(gtsClient) +} + +func (b *BlockExecutor) blockAccount(gtsClient *client.Client) error { + accountID, err := getAccountID(gtsClient, false, b.accountName, b.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("received an error while getting the account ID; %w", err) + } + + if b.unblock { + return b.unblockAccount(gtsClient, accountID) + } + + if err := gtsClient.BlockAccount(accountID); err != nil { + return fmt.Errorf("unable to block the account; %w", err) + } + + fmt.Println("Successfully blocked the account.") + + return nil +} + +func (b *BlockExecutor) unblockAccount(gtsClient *client.Client, accountID string) error { + if err := gtsClient.UnblockAccount(accountID); err != nil { + return fmt.Errorf("unable to unblock the account; %w", err) + } + + fmt.Println("Successfully unblocked the account.") + + return nil +} diff --git a/internal/executor/const.go b/internal/executor/const.go new file mode 100644 index 0000000..0d61614 --- /dev/null +++ b/internal/executor/const.go @@ -0,0 +1,32 @@ +package executor + +const ( + flagAccountName = "account-name" + flagContent = "content" + flagInstance = "instance" + flagLimit = "limit" + flagListID = "list-id" + flagListTitle = "list-title" + flagListRepliesPolicy = "list-replies-policy" + flagMyAccount = "my-account" + flagNotify = "notify" + flagFrom = "from" + flagType = "type" + flagShowRelationship = "show-relationship" + flagShowPreferences = "show-preferences" + flagShowReposts = "show-reposts" + flagStatusID = "status-id" + flagTag = "tag" + flagTimelineCategory = "timeline-category" + flagTo = "to" + + resourceAccount = "account" + resourceBlocked = "blocked" + resourceFollowers = "followers" + resourceFollowing = "following" + resourceInstance = "instance" + resourceList = "list" + resourceNote = "note" + resourceStatus = "status" + resourceTimeline = "timeline" +) diff --git a/cmd/enbas/create.go b/internal/executor/create.go similarity index 50% rename from cmd/enbas/create.go rename to internal/executor/create.go index 5468599..0dcf6e3 100644 --- a/cmd/enbas/create.go +++ b/internal/executor/create.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -8,56 +8,56 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/model" ) -type createCommand struct { +type CreateExecutor struct { *flag.FlagSet - topLevelFlags topLevelFlags + topLevelFlags TopLevelFlags resourceType string listTitle string listRepliesPolicy string } -func newCreateCommand(tlf topLevelFlags, name, summary string) *createCommand { - command := createCommand{ +func NewCreateExecutor(tlf TopLevelFlags, name, summary string) *CreateExecutor { + createExe := CreateExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), topLevelFlags: tlf, } - command.StringVar(&command.resourceType, resourceTypeFlag, "", "specify the type of resource to create") - command.StringVar(&command.listTitle, listTitleFlag, "", "specify the title of the list") - command.StringVar(&command.listRepliesPolicy, listRepliesPolicyFlag, "list", "specify the policy of the replies for this list (valid values are followed, list and none)") + createExe.StringVar(&createExe.resourceType, flagType, "", "specify the type of resource to create") + createExe.StringVar(&createExe.listTitle, flagListTitle, "", "specify the title of the list") + createExe.StringVar(&createExe.listRepliesPolicy, flagListRepliesPolicy, "list", "specify the policy of the replies for this list (valid values are followed, list and none)") - command.Usage = commandUsageFunc(name, summary, command.FlagSet) + createExe.Usage = commandUsageFunc(name, summary, createExe.FlagSet) - return &command + return &createExe } -func (c *createCommand) Execute() error { +func (c *CreateExecutor) Execute() error { if c.resourceType == "" { - return flagNotSetError{flagText: resourceTypeFlag} + return FlagNotSetError{flagText: flagType} } - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) + gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client; %w", err) } funcMap := map[string]func(*client.Client) error{ - listResource: c.createList, + resourceList: c.createList, } doFunc, ok := funcMap[c.resourceType] if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} + return UnsupportedTypeError{resourceType: c.resourceType} } return doFunc(gtsClient) } -func (c *createCommand) createList(gtsClient *client.Client) error { +func (c *CreateExecutor) createList(gtsClient *client.Client) error { if c.listTitle == "" { - return flagNotSetError{flagText: listTitleFlag} + return FlagNotSetError{flagText: flagListTitle} } repliesPolicy, err := model.ParseListRepliesPolicy(c.listRepliesPolicy) diff --git a/internal/executor/delete.go b/internal/executor/delete.go new file mode 100644 index 0000000..b95a18b --- /dev/null +++ b/internal/executor/delete.go @@ -0,0 +1,66 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" +) + +type DeleteExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + listID string +} + +func NewDeleteExecutor(tlf TopLevelFlags, name, summary string) *DeleteExecutor { + deleteExe := DeleteExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + topLevelFlags: tlf, + } + + deleteExe.StringVar(&deleteExe.resourceType, flagType, "", "specify the type of resource to delete") + deleteExe.StringVar(&deleteExe.listID, flagListID, "", "specify the ID of the list to delete") + + deleteExe.Usage = commandUsageFunc(name, summary, deleteExe.FlagSet) + + return &deleteExe +} + +func (d *DeleteExecutor) Execute() error { + if d.resourceType == "" { + return FlagNotSetError{flagText: flagType} + } + + funcMap := map[string]func(*client.Client) error{ + resourceList: d.deleteList, + } + + doFunc, ok := funcMap[d.resourceType] + if !ok { + return UnsupportedTypeError{resourceType: d.resourceType} + } + + gtsClient, err := client.NewClientFromConfig(d.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + return doFunc(gtsClient) +} + +func (d *DeleteExecutor) deleteList(gtsClient *client.Client) error { + if d.listID == "" { + return FlagNotSetError{flagText: flagListID} + } + + if err := gtsClient.DeleteList(d.listID); err != nil { + return fmt.Errorf("unable to delete the list; %w", err) + } + + fmt.Println("The list was successfully deleted.") + + return nil +} diff --git a/internal/executor/errors.go b/internal/executor/errors.go new file mode 100644 index 0000000..0f1e2d5 --- /dev/null +++ b/internal/executor/errors.go @@ -0,0 +1,55 @@ +package executor + +type FlagNotSetError struct { + flagText string +} + +func (e FlagNotSetError) Error() string { + return "the flag '" + e.flagText + "' is not set" +} + +type UnsupportedTypeError struct { + resourceType string +} + +func (e UnsupportedTypeError) Error() string { + return "unsupported resource type '" + e.resourceType + "'" +} + +type InvalidTimelineCategoryError struct { + category string +} + +func (e InvalidTimelineCategoryError) Error() string { + return "'" + e.category + "' is not a valid timeline category (please choose home, public, tag or list)" +} + +type NoAccountSpecifiedError struct{} + +func (e NoAccountSpecifiedError) Error() string { + return "no account specified in this request" +} + +type UnsupportedAddOperationError struct { + ResourceType string + AddToResourceType string +} + +func (e UnsupportedAddOperationError) Error() string { + return "adding '" + e.ResourceType + "' to '" + e.AddToResourceType + "' is not supported" +} + +type UnsupportedRemoveOperationError struct { + ResourceType string + RemoveFromResourceType string +} + +func (e UnsupportedRemoveOperationError) Error() string { + return "removing '" + e.ResourceType + "' from '" + e.RemoveFromResourceType + "' is not supported" +} + +type EmptyContentError struct{} + +func (e EmptyContentError) Error() string { + return "content should not be empty" +} diff --git a/internal/executor/executor.go b/internal/executor/executor.go new file mode 100644 index 0000000..3b50789 --- /dev/null +++ b/internal/executor/executor.go @@ -0,0 +1,21 @@ +package executor + +import "fmt" + +type Executor interface { + Name() string + Parse(args []string) error + Execute() error +} + +func Execute(executor Executor, args []string) error { + if err := executor.Parse(args); err != nil { + return fmt.Errorf("unable to parse the command line flags; %w", err) + } + + if err := executor.Execute(); err != nil { + return fmt.Errorf("unable to execute the command %q; %w", executor.Name(), err) + } + + return nil +} diff --git a/internal/executor/flags.go b/internal/executor/flags.go new file mode 100644 index 0000000..9c64dc3 --- /dev/null +++ b/internal/executor/flags.go @@ -0,0 +1,21 @@ +package executor + +import "strings" + +type AccountNames []string + +func (a *AccountNames) String() string { + return strings.Join(*a, ", ") +} + +func (a *AccountNames) Set(value string) error { + if len(value) > 0 { + *a = append(*a, value) + } + + return nil +} + +type TopLevelFlags struct { + ConfigDir string +} diff --git a/cmd/enbas/follow.go b/internal/executor/follow.go similarity index 62% rename from cmd/enbas/follow.go rename to internal/executor/follow.go index 47b4f98..ee8291c 100644 --- a/cmd/enbas/follow.go +++ b/internal/executor/follow.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -7,10 +7,10 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/client" ) -type followCommand struct { +type FollowExecutor struct { *flag.FlagSet - topLevelFlags topLevelFlags + topLevelFlags TopLevelFlags resourceType string accountName string showReposts bool @@ -18,34 +18,34 @@ type followCommand struct { unfollow bool } -func newFollowCommand(tlf topLevelFlags, name, summary string, unfollow bool) *followCommand { - command := followCommand{ +func NewFollowExecutor(tlf TopLevelFlags, name, summary string, unfollow bool) *FollowExecutor { + command := FollowExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), unfollow: unfollow, topLevelFlags: tlf, } - command.StringVar(&command.resourceType, resourceTypeFlag, "", "specify the type of resource to follow") - command.StringVar(&command.accountName, accountNameFlag, "", "specify the account name in full (username@domain)") - command.BoolVar(&command.showReposts, showRepostsFlag, true, "show reposts from the account you want to follow") - command.BoolVar(&command.notify, notifyFlag, false, "get notifications when the account you want to follow posts a status") + command.StringVar(&command.resourceType, flagType, "", "specify the type of resource to follow") + command.StringVar(&command.accountName, flagAccountName, "", "specify the account name in full (username@domain)") + command.BoolVar(&command.showReposts, flagShowReposts, true, "show reposts from the account you want to follow") + command.BoolVar(&command.notify, flagNotify, false, "get notifications when the account you want to follow posts a status") command.Usage = commandUsageFunc(name, summary, command.FlagSet) return &command } -func (c *followCommand) Execute() error { +func (c *FollowExecutor) Execute() error { funcMap := map[string]func(*client.Client) error{ - accountResource: c.followAccount, + resourceAccount: c.followAccount, } doFunc, ok := funcMap[c.resourceType] if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} + return UnsupportedTypeError{resourceType: c.resourceType} } - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) + gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client; %w", err) } @@ -53,8 +53,8 @@ func (c *followCommand) Execute() error { return doFunc(gtsClient) } -func (c *followCommand) followAccount(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, false, c.accountName, c.topLevelFlags.configDir) +func (c *FollowExecutor) followAccount(gtsClient *client.Client) error { + accountID, err := getAccountID(gtsClient, false, c.accountName, c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("received an error while getting the account ID; %w", err) } @@ -72,7 +72,7 @@ func (c *followCommand) followAccount(gtsClient *client.Client) error { return nil } -func (c *followCommand) unfollowAccount(gtsClient *client.Client, accountID string) error { +func (c *FollowExecutor) unfollowAccount(gtsClient *client.Client, accountID string) error { if err := gtsClient.UnfollowAccount(accountID); err != nil { return fmt.Errorf("unable to unfollow the account; %w", err) } diff --git a/cmd/enbas/login.go b/internal/executor/login.go similarity index 80% rename from cmd/enbas/login.go rename to internal/executor/login.go index 00aab30..3defad4 100644 --- a/cmd/enbas/login.go +++ b/internal/executor/login.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -10,32 +10,32 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" ) -type loginCommand struct { +type LoginExecutor struct { *flag.FlagSet - topLevelFlags topLevelFlags - instance string + topLevelFlags TopLevelFlags + instance string } -func newLoginCommand(tlf topLevelFlags, name, summary string) *loginCommand { - command := loginCommand{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), +func NewLoginExecutor(tlf TopLevelFlags, name, summary string) *LoginExecutor { + command := LoginExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), topLevelFlags: tlf, - instance: "", + instance: "", } - command.StringVar(&command.instance, instanceFlag, "", "specify the instance that you want to login to.") + command.StringVar(&command.instance, flagInstance, "", "specify the instance that you want to login to.") command.Usage = commandUsageFunc(name, summary, command.FlagSet) return &command } -func (c *loginCommand) Execute() error { +func (c *LoginExecutor) Execute() error { var err error if c.instance == "" { - return flagNotSetError{flagText: instanceFlag} + return FlagNotSetError{flagText: flagInstance} } instance := c.instance @@ -91,7 +91,7 @@ Once you have the code please copy and paste it below. return fmt.Errorf("unable to verify the credentials; %w", err) } - loginName, err := config.SaveCredentials(c.topLevelFlags.configDir, account.Username, gtsClient.Authentication) + loginName, err := config.SaveCredentials(c.topLevelFlags.ConfigDir, account.Username, gtsClient.Authentication) if err != nil { return fmt.Errorf("unable to save the authentication details; %w", err) } diff --git a/internal/executor/remove.go b/internal/executor/remove.go new file mode 100644 index 0000000..a8a50bc --- /dev/null +++ b/internal/executor/remove.go @@ -0,0 +1,140 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" +) + +type RemoveExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + fromResourceType string + listID string + accountNames AccountNames +} + +func NewRemoveExecutor(tlf TopLevelFlags, name, summary string) *RemoveExecutor { + emptyArr := make([]string, 0, 3) + + removeExe := RemoveExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + accountNames: AccountNames(emptyArr), + topLevelFlags: tlf, + } + + removeExe.StringVar(&removeExe.resourceType, flagType, "", "specify the resource type to remove (e.g. account, note)") + removeExe.StringVar(&removeExe.fromResourceType, flagFrom, "", "specify the resource type to remove from (e.g. list, account, etc)") + removeExe.StringVar(&removeExe.listID, flagListID, "", "the ID of the list to remove from") + removeExe.Var(&removeExe.accountNames, flagAccountName, "the name of the account to remove from the resource") + + removeExe.Usage = commandUsageFunc(name, summary, removeExe.FlagSet) + + return &removeExe +} + +func (r *RemoveExecutor) Execute() error { + if r.fromResourceType == "" { + return FlagNotSetError{flagText: flagFrom} + } + + funcMap := map[string]func(*client.Client) error{ + resourceList: r.removeFromList, + resourceAccount: r.removeFromAccount, + } + + doFunc, ok := funcMap[r.fromResourceType] + if !ok { + return UnsupportedTypeError{resourceType: r.fromResourceType} + } + + gtsClient, err := client.NewClientFromConfig(r.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + return doFunc(gtsClient) +} + +func (r *RemoveExecutor) removeFromList(gtsClient *client.Client) error { + funcMap := map[string]func(*client.Client) error{ + resourceAccount: r.removeAccountsFromList, + } + + doFunc, ok := funcMap[r.resourceType] + if !ok { + return UnsupportedRemoveOperationError{ + ResourceType: r.resourceType, + RemoveFromResourceType: r.fromResourceType, + } + } + + return doFunc(gtsClient) +} + +func (r *RemoveExecutor) removeAccountsFromList(gtsClient *client.Client) error { + if r.listID == "" { + return FlagNotSetError{flagText: flagListID} + } + + if len(r.accountNames) == 0 { + return NoAccountSpecifiedError{} + } + + accountIDs := make([]string, len(r.accountNames)) + + for i := range r.accountNames { + accountID, err := getTheirAccountID(gtsClient, r.accountNames[i]) + if err != nil { + return fmt.Errorf("unable to get the account ID for %s, %w", r.accountNames[i], err) + } + + accountIDs[i] = accountID + } + + if err := gtsClient.RemoveAccountsFromList(r.listID, accountIDs); err != nil { + return fmt.Errorf("unable to remove the accounts from the list; %w", err) + } + + fmt.Println("Successfully removed the account(s) from the list.") + + return nil +} + +func (r *RemoveExecutor) removeFromAccount(gtsClient *client.Client) error { + funcMap := map[string]func(*client.Client) error{ + resourceNote: r.removeNoteFromAccount, + } + + doFunc, ok := funcMap[r.resourceType] + if !ok { + return UnsupportedRemoveOperationError{ + ResourceType: r.resourceType, + RemoveFromResourceType: r.fromResourceType, + } + } + + return doFunc(gtsClient) +} + +func (r *RemoveExecutor) removeNoteFromAccount(gtsClient *client.Client) error { + if len(r.accountNames) != 1 { + return fmt.Errorf("unexpected number of accounts specified; want 1, got %d", len(r.accountNames)) + } + + accountID, err := getAccountID(gtsClient, false, r.accountNames[0], r.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("received an error while getting the account ID; %w", err) + } + + if err := gtsClient.SetPrivateNote(accountID, ""); err != nil { + return fmt.Errorf("unable to remove the private note from the account; %w", err) + } + + fmt.Println("Successfully removed the private note from the account.") + + return nil +} diff --git a/cmd/enbas/show.go b/internal/executor/show.go similarity index 67% rename from cmd/enbas/show.go rename to internal/executor/show.go index e2b703f..6930d07 100644 --- a/cmd/enbas/show.go +++ b/internal/executor/show.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -9,9 +9,9 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" ) -type showCommand struct { +type ShowExecutor struct { *flag.FlagSet - topLevelFlags topLevelFlags + topLevelFlags TopLevelFlags myAccount bool showAccountRelationship bool showUserPreferences bool @@ -24,50 +24,50 @@ type showCommand struct { limit int } -func newShowCommand(tlf topLevelFlags, name, summary string) *showCommand { - command := showCommand{ +func NewShowExecutor(tlf TopLevelFlags, name, summary string) *ShowExecutor { + command := ShowExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), topLevelFlags: tlf, } - command.BoolVar(&command.myAccount, myAccountFlag, false, "set to true to lookup your account") - command.BoolVar(&command.showAccountRelationship, showAccountRelationshipFlag, false, "show your relationship to the specified account") - command.BoolVar(&command.showUserPreferences, showUserPreferencesFlag, false, "show your preferences") - command.StringVar(&command.resourceType, resourceTypeFlag, "", "specify the type of resource to display") - command.StringVar(&command.accountName, accountNameFlag, "", "specify the account name in full (username@domain)") - command.StringVar(&command.statusID, statusIDFlag, "", "specify the ID of the status to display") - command.StringVar(&command.timelineCategory, timelineCategoryFlag, "home", "specify the type of timeline to display (valid values are home, public, list and tag)") - command.StringVar(&command.listID, listIDFlag, "", "specify the ID of the list to display") - command.StringVar(&command.tag, tagFlag, "", "specify the name of the tag to use") - command.IntVar(&command.limit, limitFlag, 20, "specify the limit of items to display") + command.BoolVar(&command.myAccount, flagMyAccount, false, "set to true to lookup your account") + command.BoolVar(&command.showAccountRelationship, flagShowRelationship, false, "show your relationship to the specified account") + command.BoolVar(&command.showUserPreferences, flagShowPreferences, false, "show your preferences") + command.StringVar(&command.resourceType, flagType, "", "specify the type of resource to display") + command.StringVar(&command.accountName, flagAccountName, "", "specify the account name in full (username@domain)") + command.StringVar(&command.statusID, flagStatusID, "", "specify the ID of the status to display") + command.StringVar(&command.timelineCategory, flagTimelineCategory, "home", "specify the type of timeline to display (valid values are home, public, list and tag)") + command.StringVar(&command.listID, flagListID, "", "specify the ID of the list to display") + command.StringVar(&command.tag, flagTag, "", "specify the name of the tag to use") + command.IntVar(&command.limit, flagLimit, 20, "specify the limit of items to display") command.Usage = commandUsageFunc(name, summary, command.FlagSet) return &command } -func (c *showCommand) Execute() error { +func (c *ShowExecutor) Execute() error { if c.resourceType == "" { - return flagNotSetError{flagText: resourceTypeFlag} + return FlagNotSetError{flagText: flagType} } funcMap := map[string]func(*client.Client) error{ - instanceResource: c.showInstance, - accountResource: c.showAccount, - statusResource: c.showStatus, - timelineResource: c.showTimeline, - listResource: c.showList, - followersResource: c.showFollowers, - followingResource: c.showFollowing, - blockedResource: c.showBlocked, + resourceInstance: c.showInstance, + resourceAccount: c.showAccount, + resourceStatus: c.showStatus, + resourceTimeline: c.showTimeline, + resourceList: c.showList, + resourceFollowers: c.showFollowers, + resourceFollowing: c.showFollowing, + resourceBlocked: c.showBlocked, } doFunc, ok := funcMap[c.resourceType] if !ok { - return unsupportedResourceTypeError{resourceType: c.resourceType} + return UnsupportedTypeError{resourceType: c.resourceType} } - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.configDir) + gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client; %w", err) } @@ -75,7 +75,7 @@ func (c *showCommand) Execute() error { return doFunc(gtsClient) } -func (c *showCommand) showInstance(gtsClient *client.Client) error { +func (c *ShowExecutor) showInstance(gtsClient *client.Client) error { instance, err := gtsClient.GetInstance() if err != nil { return fmt.Errorf("unable to retrieve the instance details; %w", err) @@ -86,20 +86,20 @@ func (c *showCommand) showInstance(gtsClient *client.Client) error { return nil } -func (c *showCommand) showAccount(gtsClient *client.Client) error { +func (c *ShowExecutor) showAccount(gtsClient *client.Client) error { var ( account model.Account err error ) if c.myAccount { - account, err = getMyAccount(gtsClient, c.topLevelFlags.configDir) + account, err = getMyAccount(gtsClient, c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("received an error while getting the account details; %w", err) } } else { if c.accountName == "" { - return flagNotSetError{flagText: accountNameFlag} + return FlagNotSetError{flagText: flagAccountName} } account, err = getAccount(gtsClient, c.accountName) @@ -131,9 +131,9 @@ func (c *showCommand) showAccount(gtsClient *client.Client) error { return nil } -func (c *showCommand) showStatus(gtsClient *client.Client) error { +func (c *ShowExecutor) showStatus(gtsClient *client.Client) error { if c.statusID == "" { - return flagNotSetError{flagText: statusIDFlag} + return FlagNotSetError{flagText: flagStatusID} } status, err := gtsClient.GetStatus(c.statusID) @@ -146,7 +146,7 @@ func (c *showCommand) showStatus(gtsClient *client.Client) error { return nil } -func (c *showCommand) showTimeline(gtsClient *client.Client) error { +func (c *ShowExecutor) showTimeline(gtsClient *client.Client) error { var ( timeline model.Timeline err error @@ -159,18 +159,18 @@ func (c *showCommand) showTimeline(gtsClient *client.Client) error { timeline, err = gtsClient.GetPublicTimeline(c.limit) case "list": if c.listID == "" { - return flagNotSetError{flagText: listIDFlag} + return FlagNotSetError{flagText: flagListID} } timeline, err = gtsClient.GetListTimeline(c.listID, c.limit) case "tag": if c.tag == "" { - return flagNotSetError{flagText: tagFlag} + return FlagNotSetError{flagText: flagTag} } timeline, err = gtsClient.GetTagTimeline(c.tag, c.limit) default: - return invalidTimelineCategoryError{category: c.timelineCategory} + return InvalidTimelineCategoryError{category: c.timelineCategory} } if err != nil { @@ -188,7 +188,7 @@ func (c *showCommand) showTimeline(gtsClient *client.Client) error { return nil } -func (c *showCommand) showList(gtsClient *client.Client) error { +func (c *ShowExecutor) showList(gtsClient *client.Client) error { if c.listID == "" { return c.showLists(gtsClient) } @@ -217,7 +217,7 @@ func (c *showCommand) showList(gtsClient *client.Client) error { return nil } -func (c *showCommand) showLists(gtsClient *client.Client) error { +func (c *ShowExecutor) showLists(gtsClient *client.Client) error { lists, err := gtsClient.GetAllLists() if err != nil { return fmt.Errorf("unable to retrieve the lists; %w", err) @@ -235,8 +235,8 @@ func (c *showCommand) showLists(gtsClient *client.Client) error { return nil } -func (c *showCommand) showFollowers(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, c.myAccount, c.accountName, c.topLevelFlags.configDir) +func (c *ShowExecutor) showFollowers(gtsClient *client.Client) error { + accountID, err := getAccountID(gtsClient, c.myAccount, c.accountName, c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("received an error while getting the account ID; %w", err) } @@ -255,8 +255,8 @@ func (c *showCommand) showFollowers(gtsClient *client.Client) error { return nil } -func (c *showCommand) showFollowing(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, c.myAccount, c.accountName, c.topLevelFlags.configDir) +func (c *ShowExecutor) showFollowing(gtsClient *client.Client) error { + accountID, err := getAccountID(gtsClient, c.myAccount, c.accountName, c.topLevelFlags.ConfigDir) if err != nil { return fmt.Errorf("received an error while getting the account ID; %w", err) } @@ -275,7 +275,7 @@ func (c *showCommand) showFollowing(gtsClient *client.Client) error { return nil } -func (c *showCommand) showBlocked(gtsClient *client.Client) error { +func (c *ShowExecutor) showBlocked(gtsClient *client.Client) error { blocked, err := gtsClient.GetBlockedAccounts(c.limit) if err != nil { return fmt.Errorf("unable to retrieve the list of blocked accounts; %w", err) diff --git a/internal/executor/switch.go b/internal/executor/switch.go new file mode 100644 index 0000000..8bd19b2 --- /dev/null +++ b/internal/executor/switch.go @@ -0,0 +1,57 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" +) + +type SwitchExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + toResourceType string + accountName string +} + +func NewSwitchExecutor(tlf TopLevelFlags, name, summary string) *SwitchExecutor { + switchExe := SwitchExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + topLevelFlags: tlf, + } + + switchExe.StringVar(&switchExe.toResourceType, flagTo, "", "the account to switch to") + switchExe.StringVar(&switchExe.accountName, flagAccountName, "", "the name of the account to switch to") + + switchExe.Usage = commandUsageFunc(name, summary, switchExe.FlagSet) + + return &switchExe +} + +func (s *SwitchExecutor) Execute() error { + funcMap := map[string]func() error{ + resourceAccount: s.switchToAccount, + } + + doFunc, ok := funcMap[s.toResourceType] + if !ok { + return UnsupportedTypeError{resourceType: s.toResourceType} + } + + return doFunc() +} + +func (s *SwitchExecutor) switchToAccount() error { + if s.accountName == "" { + return NoAccountSpecifiedError{} + } + + if err := config.UpdateCurrentAccount(s.accountName, s.topLevelFlags.ConfigDir); err != nil { + return fmt.Errorf("unable to switch account to the account; %w", err) + } + + fmt.Printf("The current account is now set to %q.\n", s.accountName) + + return nil +} diff --git a/internal/executor/update.go b/internal/executor/update.go new file mode 100644 index 0000000..44ce4ef --- /dev/null +++ b/internal/executor/update.go @@ -0,0 +1,91 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/model" +) + +type EditExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags + resourceType string + listID string + listTitle string + listRepliesPolicy string +} + +func NewEditExecutor(tlf TopLevelFlags, name, summary string) *EditExecutor { + editExe := EditExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + topLevelFlags: tlf, + } + + editExe.StringVar(&editExe.resourceType, flagType, "", "specify the type of resource to update") + editExe.StringVar(&editExe.listID, flagListID, "", "specify the ID of the list to update") + editExe.StringVar(&editExe.listTitle, flagListTitle, "", "specify the title of the list") + editExe.StringVar(&editExe.listRepliesPolicy, flagListRepliesPolicy, "", "specify the policy of the replies for this list (valid values are followed, list and none)") + + editExe.Usage = commandUsageFunc(name, summary, editExe.FlagSet) + + return &editExe +} + +func (e *EditExecutor) Execute() error { + if e.resourceType == "" { + return FlagNotSetError{flagText: flagType} + } + + funcMap := map[string]func(*client.Client) error{ + resourceList: e.updateList, + } + + doFunc, ok := funcMap[e.resourceType] + if !ok { + return UnsupportedTypeError{resourceType: e.resourceType} + } + + gtsClient, err := client.NewClientFromConfig(e.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to create the GoToSocial client; %w", err) + } + + return doFunc(gtsClient) +} + +func (e *EditExecutor) updateList(gtsClient *client.Client) error { + if e.listID == "" { + return FlagNotSetError{flagText: flagListID} + } + + list, err := gtsClient.GetList(e.listID) + if err != nil { + return fmt.Errorf("unable to get the list; %w", err) + } + + if e.listTitle != "" { + list.Title = e.listTitle + } + + if e.listRepliesPolicy != "" { + repliesPolicy, err := model.ParseListRepliesPolicy(e.listRepliesPolicy) + if err != nil { + return fmt.Errorf("unable to parse the list replies policy; %w", err) + } + + list.RepliesPolicy = repliesPolicy + } + + updatedList, err := gtsClient.UpdateList(list) + if err != nil { + return fmt.Errorf("unable to update the list; %w", err) + } + + fmt.Println("Successfully updated the list.") + fmt.Println(updatedList) + + return nil +} diff --git a/internal/executor/usage.go b/internal/executor/usage.go new file mode 100644 index 0000000..7f9275c --- /dev/null +++ b/internal/executor/usage.go @@ -0,0 +1,37 @@ +package executor + +import ( + "flag" + "fmt" + "strings" +) + +// commandUsageFunc returns the function used to print a command's help page. +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, + ) + + flagset.VisitAll(func(f *flag.Flag) { + fmt.Fprintf( + &builder, + "\n --%s\n %s", + f.Name, + f.Usage, + ) + }) + + builder.WriteString("\n") + + w := flag.CommandLine.Output() + + fmt.Fprint(w, builder.String()) + } +} diff --git a/cmd/enbas/version.go b/internal/executor/version.go similarity index 77% rename from cmd/enbas/version.go rename to internal/executor/version.go index f41c65c..07a6952 100644 --- a/cmd/enbas/version.go +++ b/internal/executor/version.go @@ -1,4 +1,4 @@ -package main +package executor import ( "flag" @@ -7,14 +7,7 @@ import ( "strings" ) -var ( - binaryVersion string - buildTime string - goVersion string - gitCommit string -) - -type versionCommand struct { +type VersionExecutor struct { *flag.FlagSet showFullVersion bool binaryVersion string @@ -23,8 +16,8 @@ type versionCommand struct { gitCommit string } -func newVersionCommand(name, summary string) *versionCommand { - command := versionCommand{ +func NewVersionExecutor(name, summary, binaryVersion, buildTime, goVersion, gitCommit string) *VersionExecutor { + command := VersionExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), binaryVersion: binaryVersion, buildTime: buildTime, @@ -40,7 +33,7 @@ func newVersionCommand(name, summary string) *versionCommand { return &command } -func (c *versionCommand) Execute() error { +func (c *VersionExecutor) Execute() error { var builder strings.Builder if c.showFullVersion { diff --git a/internal/executor/whoami.go b/internal/executor/whoami.go new file mode 100644 index 0000000..0384b06 --- /dev/null +++ b/internal/executor/whoami.go @@ -0,0 +1,36 @@ +package executor + +import ( + "flag" + "fmt" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" +) + +type WhoAmIExecutor struct { + *flag.FlagSet + + topLevelFlags TopLevelFlags +} + +func NewWhoAmIExecutor(tlf TopLevelFlags, name, summary string) *WhoAmIExecutor { + whoExe := WhoAmIExecutor{ + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + topLevelFlags: tlf, + } + + whoExe.Usage = commandUsageFunc(name, summary, whoExe.FlagSet) + + return &whoExe +} + +func (c *WhoAmIExecutor) Execute() error { + config, err := config.NewCredentialsConfigFromFile(c.topLevelFlags.ConfigDir) + if err != nil { + return fmt.Errorf("unable to load the credential config; %w", err) + } + + fmt.Printf("You are logged in as %q.\n", config.CurrentAccount) + + return nil +}