From 947b1b8c46ab9a1e8959d59eab6afcdcab36733c Mon Sep 17 00:00:00 2001 From: Dan Anglin Date: Sun, 16 Jun 2024 20:17:07 +0100 Subject: [PATCH] checkpoint: created the printer and began refactoring and redesigning --- cmd/enbas/main.go | 47 ++++++--- internal/client/lists.go | 2 +- internal/executor/accept_or_reject.go | 25 ++--- internal/executor/add.go | 30 +++--- internal/executor/block_or_unblock.go | 25 ++--- internal/executor/create.go | 55 ++++++----- internal/executor/delete.go | 20 ++-- internal/executor/edit.go | 19 ++-- internal/executor/follow_or_unfollow.go | 29 +++--- internal/executor/show.go | 67 +++++++------ internal/executor/switch.go | 15 +-- internal/executor/version.go | 39 +++----- internal/executor/whoami.go | 16 +-- internal/model/list.go | 52 ---------- internal/printer/account.go | 1 + internal/printer/list.go | 45 +++++++++ internal/printer/printer.go | 126 ++++++++++++++++++++++++ internal/printer/version.go | 33 +++++++ 18 files changed, 424 insertions(+), 222 deletions(-) create mode 100644 internal/printer/account.go create mode 100644 internal/printer/list.go create mode 100644 internal/printer/printer.go create mode 100644 internal/printer/version.go diff --git a/cmd/enbas/main.go b/cmd/enbas/main.go index 9c4274b..58ffe4a 100644 --- a/cmd/enbas/main.go +++ b/cmd/enbas/main.go @@ -11,6 +11,7 @@ import ( "strconv" "codeflow.dananglin.me.uk/apollo/enbas/internal/executor" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) var ( @@ -87,39 +88,52 @@ func run() error { command := flag.Arg(0) args := flag.Args()[1:] + enbasPrinter := printer.NewPrinter( + *topLevelFlags.NoColor, + topLevelFlags.Pager, + 80, + ) + executorMap := map[string]executor.Executor{ executor.CommandAccept: executor.NewAcceptOrRejectExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandAccept, executor.CommandSummaryLookup(executor.CommandAccept), ), executor.CommandAdd: executor.NewAddExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandAdd, executor.CommandSummaryLookup(executor.CommandAdd), ), executor.CommandBlock: executor.NewBlockOrUnblockExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandBlock, executor.CommandSummaryLookup(executor.CommandBlock), ), executor.CommandCreate: executor.NewCreateExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandCreate, executor.CommandSummaryLookup(executor.CommandCreate), ), executor.CommandDelete: executor.NewDeleteExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandDelete, executor.CommandSummaryLookup(executor.CommandDelete), ), executor.CommandEdit: executor.NewEditExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandEdit, executor.CommandSummaryLookup(executor.CommandEdit), ), executor.CommandFollow: executor.NewFollowOrUnfollowExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandFollow, executor.CommandSummaryLookup(executor.CommandFollow), ), @@ -129,7 +143,8 @@ func run() error { executor.CommandSummaryLookup(executor.CommandLogin), ), executor.CommandReject: executor.NewAcceptOrRejectExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandReject, executor.CommandSummaryLookup(executor.CommandReject), ), @@ -139,26 +154,31 @@ func run() error { executor.CommandSummaryLookup(executor.CommandRemove), ), executor.CommandSwitch: executor.NewSwitchExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandSwitch, executor.CommandSummaryLookup(executor.CommandSwitch), ), executor.CommandUnfollow: executor.NewFollowOrUnfollowExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandUnfollow, executor.CommandSummaryLookup(executor.CommandUnfollow), ), executor.CommandUnblock: executor.NewBlockOrUnblockExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandUnblock, executor.CommandSummaryLookup(executor.CommandUnblock), ), executor.CommandShow: executor.NewShowExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandShow, executor.CommandSummaryLookup(executor.CommandShow), ), executor.CommandVersion: executor.NewVersionExecutor( + enbasPrinter, executor.CommandVersion, executor.CommandSummaryLookup(executor.CommandVersion), binaryVersion, @@ -167,7 +187,8 @@ func run() error { gitCommit, ), executor.CommandWhoami: executor.NewWhoAmIExecutor( - topLevelFlags, + enbasPrinter, + topLevelFlags.ConfigDir, executor.CommandWhoami, executor.CommandSummaryLookup(executor.CommandWhoami), ), diff --git a/internal/client/lists.go b/internal/client/lists.go index b46e22a..9d51ce0 100644 --- a/internal/client/lists.go +++ b/internal/client/lists.go @@ -17,7 +17,7 @@ const ( listPath string = "/api/v1/lists" ) -func (g *Client) GetAllLists() (model.Lists, error) { +func (g *Client) GetAllLists() ([]model.List, error) { url := g.Authentication.Instance + listPath var lists []model.List diff --git a/internal/executor/accept_or_reject.go b/internal/executor/accept_or_reject.go index 1fae328..7e32c69 100644 --- a/internal/executor/accept_or_reject.go +++ b/internal/executor/accept_or_reject.go @@ -9,23 +9,26 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) type AcceptOrRejectExecutor struct { *flag.FlagSet - topLevelFlags TopLevelFlags - resourceType string - accountName string - command string + printer *printer.Printer + configDir string + resourceType string + accountName string + command string } -func NewAcceptOrRejectExecutor(tlf TopLevelFlags, name, summary string) *AcceptOrRejectExecutor { +func NewAcceptOrRejectExecutor(enbasPrinter *printer.Printer, configDir, name, summary string) *AcceptOrRejectExecutor { acceptExe := AcceptOrRejectExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, - command: name, + printer: enbasPrinter, + configDir: configDir, + command: name, } acceptExe.StringVar(&acceptExe.resourceType, flagType, "", "Specify the type of resource to accept or reject") @@ -46,7 +49,7 @@ func (a *AcceptOrRejectExecutor) Execute() error { return UnsupportedTypeError{resourceType: a.resourceType} } - gtsClient, err := client.NewClientFromConfig(a.topLevelFlags.ConfigDir) + gtsClient, err := client.NewClientFromConfig(a.configDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -55,7 +58,7 @@ func (a *AcceptOrRejectExecutor) Execute() error { } func (a *AcceptOrRejectExecutor) acceptOrRejectFollowRequest(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, false, a.accountName, a.topLevelFlags.ConfigDir) + accountID, err := getAccountID(gtsClient, false, a.accountName, a.configDir) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } @@ -75,7 +78,7 @@ func (a *AcceptOrRejectExecutor) acceptFollowRequest(gtsClient *client.Client, a return fmt.Errorf("unable to accept the follow request: %w", err) } - fmt.Println("Successfully accepted the follow request.") + a.printer.PrintSuccess("Successfully accepted the follow request.") return nil } @@ -85,7 +88,7 @@ func (a *AcceptOrRejectExecutor) rejectFollowRequest(gtsClient *client.Client, a return fmt.Errorf("unable to reject the follow request: %w", err) } - fmt.Println("Successfully rejected the follow request.") + a.printer.PrintSuccess("Successfully rejected the follow request.") return nil } diff --git a/internal/executor/add.go b/internal/executor/add.go index d48926b..52488c6 100644 --- a/internal/executor/add.go +++ b/internal/executor/add.go @@ -10,12 +10,14 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) type AddExecutor struct { *flag.FlagSet - topLevelFlags TopLevelFlags + printer *printer.Printer + configDir string resourceType string toResourceType string listID string @@ -26,13 +28,15 @@ type AddExecutor struct { content string } -func NewAddExecutor(tlf TopLevelFlags, name, summary string) *AddExecutor { +func NewAddExecutor(printer *printer.Printer, configDir, name, summary string) *AddExecutor { emptyArr := make([]string, 0, 3) addExe := AddExecutor{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - accountNames: MultiStringFlagValue(emptyArr), - topLevelFlags: tlf, + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + printer: printer, + configDir: configDir, + accountNames: MultiStringFlagValue(emptyArr), } addExe.StringVar(&addExe.resourceType, flagType, "", "Specify the resource type to add (e.g. account, note)") @@ -67,7 +71,7 @@ func (a *AddExecutor) Execute() error { return UnsupportedTypeError{resourceType: a.toResourceType} } - gtsClient, err := client.NewClientFromConfig(a.topLevelFlags.ConfigDir) + gtsClient, err := client.NewClientFromConfig(a.configDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -124,7 +128,7 @@ func (a *AddExecutor) addAccountsToList(gtsClient *client.Client) error { return fmt.Errorf("unable to add the accounts to the list: %w", err) } - fmt.Println("Successfully added the account(s) to the list.") + a.printer.PrintSuccess("Successfully added the account(s) to the list.") return nil } @@ -150,7 +154,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.topLevelFlags.ConfigDir) + accountID, err := getAccountID(gtsClient, false, a.accountNames[0], a.configDir) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } @@ -166,7 +170,7 @@ func (a *AddExecutor) addNoteToAccount(gtsClient *client.Client) error { return fmt.Errorf("unable to add the private note to the account: %w", err) } - fmt.Println("Successfully added the private note to the account.") + a.printer.PrintSuccess("Successfully added the private note to the account.") return nil } @@ -196,7 +200,7 @@ func (a *AddExecutor) addStatusToBookmarks(gtsClient *client.Client) error { return fmt.Errorf("unable to add the status to your bookmarks: %w", err) } - fmt.Println("Successfully added the status to your bookmarks.") + a.printer.PrintSuccess("Successfully added the status to your bookmarks.") return nil } @@ -228,7 +232,7 @@ func (a *AddExecutor) addStarToStatus(gtsClient *client.Client) error { return fmt.Errorf("unable to add the %s to the status: %w", a.resourceType, err) } - fmt.Printf("Successfully added a %s to the status.\n", a.resourceType) + a.printer.PrintSuccess("Successfully added a " + a.resourceType + " to the status.\n") return nil } @@ -238,7 +242,7 @@ func (a *AddExecutor) addBoostToStatus(gtsClient *client.Client) error { return fmt.Errorf("unable to add the boost to the status: %w", err) } - fmt.Println("Successfully added the boost to the status.") + a.printer.PrintSuccess("Successfully added the boost to the status.") return nil } @@ -285,7 +289,7 @@ func (a *AddExecutor) addVoteToPoll(gtsClient *client.Client) error { return fmt.Errorf("unable to add your vote(s) to the poll: %w", err) } - fmt.Println("Successfully added your vote(s) to the poll.") + a.printer.PrintSuccess("Successfully added your vote(s) to the poll.") return nil } diff --git a/internal/executor/block_or_unblock.go b/internal/executor/block_or_unblock.go index e68ae94..c129bb5 100644 --- a/internal/executor/block_or_unblock.go +++ b/internal/executor/block_or_unblock.go @@ -9,23 +9,26 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) type BlockOrUnblockExecutor struct { *flag.FlagSet - topLevelFlags TopLevelFlags - resourceType string - accountName string - command string + printer *printer.Printer + configDir string + resourceType string + accountName string + command string } -func NewBlockOrUnblockExecutor(tlf TopLevelFlags, name, summary string) *BlockOrUnblockExecutor { +func NewBlockOrUnblockExecutor(printer *printer.Printer, configDir, name, summary string) *BlockOrUnblockExecutor { blockExe := BlockOrUnblockExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, - command: name, + printer: printer, + configDir: configDir, + command: name, } blockExe.StringVar(&blockExe.resourceType, flagType, "", "Specify the type of resource to block or unblock") @@ -46,7 +49,7 @@ func (b *BlockOrUnblockExecutor) Execute() error { return UnsupportedTypeError{resourceType: b.resourceType} } - gtsClient, err := client.NewClientFromConfig(b.topLevelFlags.ConfigDir) + gtsClient, err := client.NewClientFromConfig(b.configDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -55,7 +58,7 @@ func (b *BlockOrUnblockExecutor) Execute() error { } func (b *BlockOrUnblockExecutor) blockOrUnblockAccount(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, false, b.accountName, b.topLevelFlags.ConfigDir) + accountID, err := getAccountID(gtsClient, false, b.accountName, b.configDir) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } @@ -75,7 +78,7 @@ func (b *BlockOrUnblockExecutor) blockAccount(gtsClient *client.Client, accountI return fmt.Errorf("unable to block the account: %w", err) } - fmt.Println("Successfully blocked the account.") + b.printer.PrintSuccess("Successfully blocked the account.") return nil } @@ -85,7 +88,7 @@ func (b *BlockOrUnblockExecutor) unblockAccount(gtsClient *client.Client, accoun return fmt.Errorf("unable to unblock the account: %w", err) } - fmt.Println("Successfully unblocked the account.") + b.printer.PrintSuccess("Successfully unblocked the account.") return nil } diff --git a/internal/executor/create.go b/internal/executor/create.go index 910508d..0f27f5d 100644 --- a/internal/executor/create.go +++ b/internal/executor/create.go @@ -11,39 +11,42 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/model" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" ) type CreateExecutor struct { *flag.FlagSet - topLevelFlags TopLevelFlags - addPoll bool - boostable bool - federated bool - likeable bool + printer *printer.Printer + addPoll bool + boostable bool + federated bool + likeable bool pollAllowsMultipleChoices bool pollHidesVoteCounts bool - replyable bool - sensitive *bool - content string - contentType string - fromFile string - language string - resourceType string - listTitle string - listRepliesPolicy string - spoilerText string - visibility string - pollExpiresIn TimeDurationFlagValue - pollOptions MultiStringFlagValue + replyable bool + sensitive *bool + configDir string + content string + contentType string + fromFile string + language string + resourceType string + listTitle string + listRepliesPolicy string + spoilerText string + visibility string + pollExpiresIn TimeDurationFlagValue + pollOptions MultiStringFlagValue } -func NewCreateExecutor(tlf TopLevelFlags, name, summary string) *CreateExecutor { +func NewCreateExecutor(printer *printer.Printer, configDir, name, summary string) *CreateExecutor { createExe := CreateExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, + printer: printer, + configDir: configDir, } createExe.BoolVar(&createExe.boostable, flagEnableReposts, true, "Specify if the status can be reposted/boosted by others") @@ -87,7 +90,7 @@ func (c *CreateExecutor) Execute() error { return FlagNotSetError{flagText: flagType} } - gtsClient, err := client.NewClientFromConfig(c.topLevelFlags.ConfigDir) + gtsClient, err := client.NewClientFromConfig(c.configDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -125,8 +128,8 @@ func (c *CreateExecutor) createList(gtsClient *client.Client) error { return fmt.Errorf("unable to create the list: %w", err) } - fmt.Println("Successfully created the following list:") - utilities.Display(list, *c.topLevelFlags.NoColor, c.topLevelFlags.Pager) + c.printer.PrintSuccess("Successfully created the following list:") + c.printer.PrintList(list) return nil } @@ -217,13 +220,13 @@ func (c *CreateExecutor) createStatus(gtsClient *client.Client) error { form.Poll = &poll } - status, err := gtsClient.CreateStatus(form) + _, err = gtsClient.CreateStatus(form) if err != nil { return fmt.Errorf("unable to create the status: %w", err) } - fmt.Println("Successfully created the following status:") - utilities.Display(status, *c.topLevelFlags.NoColor, c.topLevelFlags.Pager) + c.printer.PrintSuccess("Successfully created the following status:") + //utilities.Display(status, *c.topLevelFlags.NoColor, c.topLevelFlags.Pager) return nil } diff --git a/internal/executor/delete.go b/internal/executor/delete.go index fec798d..7d173c6 100644 --- a/internal/executor/delete.go +++ b/internal/executor/delete.go @@ -9,20 +9,24 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) type DeleteExecutor struct { *flag.FlagSet - topLevelFlags TopLevelFlags - resourceType string - listID string + printer *printer.Printer + configDir string + resourceType string + listID string } -func NewDeleteExecutor(tlf TopLevelFlags, name, summary string) *DeleteExecutor { +func NewDeleteExecutor(printer *printer.Printer, configDir, name, summary string) *DeleteExecutor { deleteExe := DeleteExecutor{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + printer: printer, + configDir: configDir, } deleteExe.StringVar(&deleteExe.resourceType, flagType, "", "Specify the type of resource to delete") @@ -47,7 +51,7 @@ func (d *DeleteExecutor) Execute() error { return UnsupportedTypeError{resourceType: d.resourceType} } - gtsClient, err := client.NewClientFromConfig(d.topLevelFlags.ConfigDir) + gtsClient, err := client.NewClientFromConfig(d.configDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -64,7 +68,7 @@ func (d *DeleteExecutor) deleteList(gtsClient *client.Client) error { return fmt.Errorf("unable to delete the list: %w", err) } - fmt.Println("The list was successfully deleted.") + d.printer.PrintSuccess("The list was successfully deleted.") return nil } diff --git a/internal/executor/edit.go b/internal/executor/edit.go index 620a3ca..e18e2e8 100644 --- a/internal/executor/edit.go +++ b/internal/executor/edit.go @@ -10,23 +10,26 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/model" - "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) type EditExecutor struct { *flag.FlagSet - topLevelFlags TopLevelFlags + printer *printer.Printer + configDir string resourceType string listID string listTitle string listRepliesPolicy string } -func NewEditExecutor(tlf TopLevelFlags, name, summary string) *EditExecutor { +func NewEditExecutor(printer *printer.Printer, configDir, name, summary string) *EditExecutor { editExe := EditExecutor{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + printer: printer, + configDir: configDir, } editExe.StringVar(&editExe.resourceType, flagType, "", "Specify the type of resource to update") @@ -53,7 +56,7 @@ func (e *EditExecutor) Execute() error { return UnsupportedTypeError{resourceType: e.resourceType} } - gtsClient, err := client.NewClientFromConfig(e.topLevelFlags.ConfigDir) + gtsClient, err := client.NewClientFromConfig(e.configDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -89,8 +92,8 @@ func (e *EditExecutor) editList(gtsClient *client.Client) error { return fmt.Errorf("unable to update the list: %w", err) } - fmt.Println("Successfully updated the list.") - utilities.Display(updatedList, *e.topLevelFlags.NoColor, e.topLevelFlags.Pager) + e.printer.PrintSuccess("Successfully updated the list.") + e.printer.PrintList(updatedList) return nil } diff --git a/internal/executor/follow_or_unfollow.go b/internal/executor/follow_or_unfollow.go index 9ffbb78..535df04 100644 --- a/internal/executor/follow_or_unfollow.go +++ b/internal/executor/follow_or_unfollow.go @@ -9,25 +9,28 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) type FollowOrUnfollowExecutor struct { *flag.FlagSet - topLevelFlags TopLevelFlags - resourceType string - accountName string - showReposts bool - notify bool - action string + printer *printer.Printer + configDir string + resourceType string + accountName string + showReposts bool + notify bool + action string } -func NewFollowOrUnfollowExecutor(tlf TopLevelFlags, name, summary string) *FollowOrUnfollowExecutor { +func NewFollowOrUnfollowExecutor(printer *printer.Printer, configDir, name, summary string) *FollowOrUnfollowExecutor { command := FollowOrUnfollowExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, - action: name, + printer: printer, + configDir: configDir, + action: name, } command.StringVar(&command.resourceType, flagType, "", "Specify the type of resource to follow") @@ -50,7 +53,7 @@ func (f *FollowOrUnfollowExecutor) Execute() error { return UnsupportedTypeError{resourceType: f.resourceType} } - gtsClient, err := client.NewClientFromConfig(f.topLevelFlags.ConfigDir) + gtsClient, err := client.NewClientFromConfig(f.configDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -59,7 +62,7 @@ func (f *FollowOrUnfollowExecutor) Execute() error { } func (f *FollowOrUnfollowExecutor) followOrUnfollowAccount(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, false, f.accountName, f.topLevelFlags.ConfigDir) + accountID, err := getAccountID(gtsClient, false, f.accountName, f.configDir) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } @@ -85,7 +88,7 @@ func (f *FollowOrUnfollowExecutor) followAccount(gtsClient *client.Client, accou return fmt.Errorf("unable to follow the account: %w", err) } - fmt.Println("The follow request was sent successfully.") + f.printer.PrintSuccess("Successfully sent the follow request.") return nil } @@ -95,7 +98,7 @@ func (f *FollowOrUnfollowExecutor) unfollowAccount(gtsClient *client.Client, acc return fmt.Errorf("unable to unfollow the account: %w", err) } - fmt.Println("Successfully unfollowed the account.") + f.printer.PrintSuccess("Successfully unfollowed the account.") return nil } diff --git a/internal/executor/show.go b/internal/executor/show.go index e9b90d3..c842f28 100644 --- a/internal/executor/show.go +++ b/internal/executor/show.go @@ -10,16 +10,19 @@ import ( "codeflow.dananglin.me.uk/apollo/enbas/internal/client" "codeflow.dananglin.me.uk/apollo/enbas/internal/model" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" ) type ShowExecutor struct { *flag.FlagSet - topLevelFlags TopLevelFlags + + printer *printer.Printer myAccount bool skipAccountRelationship bool showUserPreferences bool showInBrowser bool + configDir string resourceType string accountName string statusID string @@ -30,10 +33,12 @@ type ShowExecutor struct { limit int } -func NewShowExecutor(tlf TopLevelFlags, name, summary string) *ShowExecutor { +func NewShowExecutor(printer *printer.Printer, configDir, name, summary string) *ShowExecutor { showExe := ShowExecutor{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + printer: printer, + configDir: configDir, } showExe.BoolVar(&showExe.myAccount, flagMyAccount, false, "Set to true to lookup your account") @@ -80,7 +85,7 @@ func (s *ShowExecutor) Execute() error { return UnsupportedTypeError{resourceType: s.resourceType} } - gtsClient, err := client.NewClientFromConfig(s.topLevelFlags.ConfigDir) + gtsClient, err := client.NewClientFromConfig(s.configDir) if err != nil { return fmt.Errorf("unable to create the GoToSocial client: %w", err) } @@ -94,7 +99,7 @@ func (s *ShowExecutor) showInstance(gtsClient *client.Client) error { return fmt.Errorf("unable to retrieve the instance details: %w", err) } - utilities.Display(instance, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(instance, false, "") return nil } @@ -106,7 +111,7 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error { ) if s.myAccount { - account, err = getMyAccount(gtsClient, s.topLevelFlags.ConfigDir) + account, err = getMyAccount(gtsClient, s.configDir) if err != nil { return fmt.Errorf("received an error while getting the account details: %w", err) } @@ -127,7 +132,7 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error { return nil } - utilities.Display(account, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(account, false, "") if !s.myAccount && !s.skipAccountRelationship { relationship, err := gtsClient.GetAccountRelationship(account.ID) @@ -135,7 +140,7 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error { return fmt.Errorf("unable to retrieve the relationship to this account: %w", err) } - utilities.Display(relationship, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(relationship, false, "") } if s.myAccount && s.showUserPreferences { @@ -144,7 +149,7 @@ func (s *ShowExecutor) showAccount(gtsClient *client.Client) error { return fmt.Errorf("unable to retrieve the user preferences: %w", err) } - utilities.Display(preferences, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(preferences, false, "") } return nil @@ -166,7 +171,7 @@ func (s *ShowExecutor) showStatus(gtsClient *client.Client) error { return nil } - utilities.Display(status, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(status, false, "") return nil } @@ -210,12 +215,12 @@ func (s *ShowExecutor) showTimeline(gtsClient *client.Client) error { } if len(timeline.Statuses) == 0 { - fmt.Println("There are no statuses in this timeline.") + s.printer.PrintInfo("There are no statuses in this timeline.\n") return nil } - utilities.Display(timeline, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(timeline, false, "") return nil } @@ -244,7 +249,7 @@ func (s *ShowExecutor) showList(gtsClient *client.Client) error { list.Accounts = accountMap } - utilities.Display(list, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + s.printer.PrintList(list) return nil } @@ -256,18 +261,18 @@ func (s *ShowExecutor) showLists(gtsClient *client.Client) error { } if len(lists) == 0 { - fmt.Println("You have no lists.") + s.printer.PrintInfo("You have no lists.\n") return nil } - utilities.Display(lists, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + s.printer.PrintLists(lists) return nil } func (s *ShowExecutor) showFollowers(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.topLevelFlags.ConfigDir) + accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.configDir) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } @@ -278,16 +283,16 @@ func (s *ShowExecutor) showFollowers(gtsClient *client.Client) error { } if len(followers.Accounts) > 0 { - utilities.Display(followers, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(followers, false, "") } else { - fmt.Println("There are no followers for this account or the list is hidden.") + s.printer.PrintInfo("There are no followers for this account (or the list is hidden).\n") } return nil } func (s *ShowExecutor) showFollowing(gtsClient *client.Client) error { - accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.topLevelFlags.ConfigDir) + accountID, err := getAccountID(gtsClient, s.myAccount, s.accountName, s.configDir) if err != nil { return fmt.Errorf("received an error while getting the account ID: %w", err) } @@ -298,9 +303,9 @@ func (s *ShowExecutor) showFollowing(gtsClient *client.Client) error { } if len(following.Accounts) > 0 { - utilities.Display(following, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(following, false, "") } else { - fmt.Println("This account is not following anyone or the list is hidden.") + s.printer.PrintInfo("This account is not following anyone or the list is hidden.\n") } return nil @@ -313,9 +318,9 @@ func (s *ShowExecutor) showBlocked(gtsClient *client.Client) error { } if len(blocked.Accounts) > 0 { - utilities.Display(blocked, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(blocked, false, "") } else { - fmt.Println("You have no blocked accounts.") + s.printer.PrintInfo("You have no blocked accounts.\n") } return nil @@ -328,9 +333,9 @@ func (s *ShowExecutor) showBookmarks(gtsClient *client.Client) error { } if len(bookmarks.Statuses) > 0 { - utilities.Display(bookmarks, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(bookmarks, false, "") } else { - fmt.Println("You have no bookmarks.") + s.printer.PrintInfo("You have no bookmarks.\n") } return nil @@ -343,9 +348,9 @@ func (s *ShowExecutor) showLiked(gtsClient *client.Client) error { } if len(liked.Statuses) > 0 { - utilities.Display(liked, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(liked, false, "") } else { - fmt.Printf("You have no %s statuses.\n", s.resourceType) + s.printer.PrintInfo("You have no " + s.resourceType + " statuses.\n") } return nil @@ -358,9 +363,9 @@ func (s *ShowExecutor) showFollowRequests(gtsClient *client.Client) error { } if len(accounts.Accounts) > 0 { - utilities.Display(accounts, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(accounts, false, "") } else { - fmt.Println("You have no follow requests.") + s.printer.PrintInfo("You have no follow requests.\n") } return nil @@ -376,7 +381,7 @@ func (s *ShowExecutor) showPoll(gtsClient *client.Client) error { return fmt.Errorf("unable to retrieve the poll: %w", err) } - utilities.Display(poll, *s.topLevelFlags.NoColor, s.topLevelFlags.Pager) + utilities.Display(poll, false, "") return nil } diff --git a/internal/executor/switch.go b/internal/executor/switch.go index 142be3f..988047d 100644 --- a/internal/executor/switch.go +++ b/internal/executor/switch.go @@ -9,20 +9,23 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/config" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) type SwitchExecutor struct { *flag.FlagSet - topLevelFlags TopLevelFlags + configDir string toResourceType string accountName string + printer *printer.Printer } -func NewSwitchExecutor(tlf TopLevelFlags, name, summary string) *SwitchExecutor { +func NewSwitchExecutor(printer *printer.Printer, configDir, name, summary string) *SwitchExecutor { switchExe := SwitchExecutor{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + printer: printer, + configDir: configDir, } switchExe.StringVar(&switchExe.toResourceType, flagTo, "", "The account to switch to") @@ -51,11 +54,11 @@ func (s *SwitchExecutor) switchToAccount() error { return NoAccountSpecifiedError{} } - if err := config.UpdateCurrentAccount(s.accountName, s.topLevelFlags.ConfigDir); err != nil { + if err := config.UpdateCurrentAccount(s.accountName, s.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) + s.printer.PrintSuccess("The current account is now set to '" + s.accountName + "'.") return nil } diff --git a/internal/executor/version.go b/internal/executor/version.go index 39c5ae6..4abdf6f 100644 --- a/internal/executor/version.go +++ b/internal/executor/version.go @@ -6,13 +6,13 @@ package executor import ( "flag" - "os" - "strings" - "text/tabwriter" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) type VersionExecutor struct { *flag.FlagSet + printer *printer.Printer showFullVersion bool binaryVersion string buildTime string @@ -20,9 +20,19 @@ type VersionExecutor struct { gitCommit string } -func NewVersionExecutor(name, summary, binaryVersion, buildTime, goVersion, gitCommit string) *VersionExecutor { +func NewVersionExecutor( + enbasPrinter *printer.Printer, + name, + summary, + binaryVersion, + buildTime, + goVersion, + gitCommit string, +) *VersionExecutor { command := VersionExecutor{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + printer: enbasPrinter, binaryVersion: binaryVersion, buildTime: buildTime, goVersion: goVersion, @@ -38,24 +48,7 @@ func NewVersionExecutor(name, summary, binaryVersion, buildTime, goVersion, gitC } func (v *VersionExecutor) Execute() error { - var builder strings.Builder - - if v.showFullVersion { - builder.WriteString("Enbas\n") - - tableWriter := tabwriter.NewWriter(&builder, 0, 8, 0, '\t', 0) - - tableWriter.Write([]byte(" Version:\t" + v.binaryVersion + "\n")) - tableWriter.Write([]byte(" Git commit:\t" + v.gitCommit + "\n")) - tableWriter.Write([]byte(" Go version:\t" + v.goVersion + "\n")) - tableWriter.Write([]byte(" Build date:\t" + v.buildTime + "\n")) - - tableWriter.Flush() - } else { - builder.WriteString("Enbas " + v.binaryVersion + "\n") - } - - os.Stdout.WriteString(builder.String()) + v.printer.PrintVersion(v.showFullVersion, v.binaryVersion, v.buildTime, v.goVersion, v.gitCommit) return nil } diff --git a/internal/executor/whoami.go b/internal/executor/whoami.go index 8b87b3c..a1673cb 100644 --- a/internal/executor/whoami.go +++ b/internal/executor/whoami.go @@ -9,18 +9,22 @@ import ( "fmt" "codeflow.dananglin.me.uk/apollo/enbas/internal/config" + "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) type WhoAmIExecutor struct { *flag.FlagSet - topLevelFlags TopLevelFlags + printer *printer.Printer + configDir string } -func NewWhoAmIExecutor(tlf TopLevelFlags, name, summary string) *WhoAmIExecutor { +func NewWhoAmIExecutor(printer *printer.Printer, configDir, name, summary string) *WhoAmIExecutor { whoExe := WhoAmIExecutor{ - FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - topLevelFlags: tlf, + FlagSet: flag.NewFlagSet(name, flag.ExitOnError), + + printer: printer, + configDir: configDir, } whoExe.Usage = commandUsageFunc(name, summary, whoExe.FlagSet) @@ -29,12 +33,12 @@ func NewWhoAmIExecutor(tlf TopLevelFlags, name, summary string) *WhoAmIExecutor } func (c *WhoAmIExecutor) Execute() error { - config, err := config.NewCredentialsConfigFromFile(c.topLevelFlags.ConfigDir) + config, err := config.NewCredentialsConfigFromFile(c.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) + c.printer.PrintInfo("You are logged in as '" + config.CurrentAccount + "'.\n") return nil } diff --git a/internal/model/list.go b/internal/model/list.go index 665d25a..bbd6a30 100644 --- a/internal/model/list.go +++ b/internal/model/list.go @@ -7,8 +7,6 @@ package model import ( "encoding/json" "fmt" - - "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" ) type ListRepliesPolicy int @@ -107,53 +105,3 @@ type List struct { Title string `json:"title"` Accounts map[string]string } - -func (l List) Display(noColor bool) string { - format := ` -%s - %s - -%s - %s - -%s - %s - -%s` - - output := fmt.Sprintf( - format, - utilities.HeaderFormat(noColor, "LIST TITLE:"), l.Title, - utilities.HeaderFormat(noColor, "LIST ID:"), l.ID, - utilities.HeaderFormat(noColor, "REPLIES POLICY:"), l.RepliesPolicy, - utilities.HeaderFormat(noColor, "ADDED ACCOUNTS:"), - ) - - if len(l.Accounts) > 0 { - for acct, name := range l.Accounts { - output += "\n • " + utilities.FullDisplayNameFormat(noColor, name, acct) - } - } else { - output += "\n None" - } - - output += "\n" - - return output -} - -type Lists []List - -func (l Lists) Display(noColor bool) string { - output := "\n" + utilities.HeaderFormat(noColor, "LISTS") - - for i := range l { - output += fmt.Sprintf( - "\n • %s (%s)", - l[i].Title, - l[i].ID, - ) - } - - return output -} diff --git a/internal/printer/account.go b/internal/printer/account.go new file mode 100644 index 0000000..b0c0dd8 --- /dev/null +++ b/internal/printer/account.go @@ -0,0 +1 @@ +package printer diff --git a/internal/printer/list.go b/internal/printer/list.go new file mode 100644 index 0000000..3e21a94 --- /dev/null +++ b/internal/printer/list.go @@ -0,0 +1,45 @@ +package printer + +import ( + "strings" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/model" +) + +func (p Printer) PrintList(list model.List) { + var builder strings.Builder + + builder.WriteString(p.headerFormat("\n" + "LIST TITLE:") + "\n") + builder.WriteString(list.Title + "\n\n") + builder.WriteString(p.headerFormat("LIST ID:") + "\n") + builder.WriteString(list.ID + "\n\n") + builder.WriteString(p.headerFormat("REPLIES POLICY:") + "\n") + builder.WriteString(list.RepliesPolicy.String() + "\n\n") + builder.WriteString(p.headerFormat("ADDED ACCOUNTS:")) + + if len(list.Accounts) > 0 { + for acct, name := range list.Accounts { + builder.WriteString("\n" + p.bullet + p.fullDisplayNameFormat(name, acct)) + } + } else { + builder.WriteString("\n" + "None") + } + + builder.WriteString("\n") + + printToStdout(builder.String()) +} + +func (p Printer) PrintLists(lists []model.List) { + var builder strings.Builder + + builder.WriteString("\n" + p.headerFormat("LISTS")) + + for i := range lists { + builder.WriteString("\n" + p.bullet + " " + lists[i].Title + " (" + lists[i].ID + ")") + } + + builder.WriteString("\n") + + printToStdout(builder.String()) +} diff --git a/internal/printer/printer.go b/internal/printer/printer.go new file mode 100644 index 0000000..a487461 --- /dev/null +++ b/internal/printer/printer.go @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: 2024 Dan Anglin +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package printer + +import ( + "os" + "regexp" + "strings" + "time" +) + +type theme struct { + reset string + boldblue string + boldmagenta string + green string +} + +type Printer struct { + theme theme + noColor bool + maxTerminalWidth int + pager string + statusSeparator string + bullet string + pollMeterSymbol string + successSymbol string + failureSymbol string + dateFormat string + dateTimeFormat string +} + +func NewPrinter( + noColor bool, + pager string, + maxTerminalWidth int, +) *Printer { + theme := theme{ + reset: "\033[0m", + boldblue: "\033[34;1m", + boldmagenta: "\033[35;1m", + green: "\033[32m", + } + + return &Printer{ + noColor: noColor, + maxTerminalWidth: maxTerminalWidth, + pager: pager, + statusSeparator: strings.Repeat("─", maxTerminalWidth), + bullet: "\u2022", + pollMeterSymbol: "\u2501", + successSymbol: "\u2714", + failureSymbol: "\u2717", + dateFormat: "02 Jan 2006", + dateTimeFormat: "02 Jan 2006, 15:04 (MST)", + theme: theme, + } +} + +func (p Printer) PrintSuccess(text string) { + success := p.theme.green + p.successSymbol + p.theme.reset + if p.noColor { + success = p.successSymbol + } + + printToStdout(success + " " + text + "\n") +} + +func (p Printer) PrintFailure(text string) { + printToStderr(p.failureSymbol + " " + text) +} + +func (p Printer) PrintInfo(text string) { + printToStdout(text) +} + +func (p Printer) headerFormat(text string) string { + if p.noColor { + return text + } + + return p.theme.boldblue + text + p.theme.reset +} + +func (p Printer) fieldFormat(text string) string { + if p.noColor { + return text + } + + return p.theme.green + text + p.theme.reset +} + +func (p Printer) fullDisplayNameFormat(displayName, acct string) string { + // use this pattern to remove all emoji strings + pattern := regexp.MustCompile(`\s:[A-Za-z0-9_]*:`) + + var builder strings.Builder + + if p.noColor { + builder.WriteString(pattern.ReplaceAllString(displayName, "")) + } else { + builder.WriteString(p.theme.boldmagenta + pattern.ReplaceAllString(displayName, "") + p.theme.reset) + } + + builder.WriteString(" (@" + acct + ")") + + return builder.String() +} + +func (p Printer) formatDate(date time.Time) string { + return date.Local().Format(p.dateFormat) +} + +func (p Printer) formatDateTime(date time.Time) string { + return date.Local().Format(p.dateTimeFormat) +} + +func printToStdout(text string) { + os.Stdout.WriteString(text) +} + +func printToStderr(text string) { + os.Stderr.WriteString(text) +} diff --git a/internal/printer/version.go b/internal/printer/version.go new file mode 100644 index 0000000..695fbb0 --- /dev/null +++ b/internal/printer/version.go @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2024 Dan Anglin +// +// SPDX-License-Identifier: GPL-3.0-or-later + +package printer + +import ( + "strings" + "text/tabwriter" +) + +func (p Printer) PrintVersion(showFullVersion bool, binaryVersion, buildTime, goVersion, gitCommit string) { + if !showFullVersion { + printToStdout("Enbas " + binaryVersion + "\n") + + return + } + + var builder strings.Builder + + builder.WriteString(p.headerFormat("Enbas") + "\n\n") + + tableWriter := tabwriter.NewWriter(&builder, 0, 4, 1, ' ', 0) + + _, _ = tableWriter.Write([]byte(p.fieldFormat("Version:") + "\t" + binaryVersion + "\n")) + _, _ = tableWriter.Write([]byte(p.fieldFormat("Git commit:") + "\t" + gitCommit + "\n")) + _, _ = tableWriter.Write([]byte(p.fieldFormat("Go version:") + "\t" + goVersion + "\n")) + _, _ = tableWriter.Write([]byte(p.fieldFormat("Build date:") + "\t" + buildTime + "\n")) + + tableWriter.Flush() + + printToStdout(builder.String()) +}