feat: add Enbas CLI schema and code generator

Summary:

- Created a custom CLI schema for Enbas which will act as the Source
  of Truth for code and document generation.
- Created a code generator which uses the schema to generate the
  executor definitions and code in the internal usage package.

Changes:

- Created the Enbas CLI schema as the Source of Truth for Enbas.
- Created the code generator that generates the executor
  definitions and code in the usage package.
- Regenerated the executor definitions using the code generator.
- Moved the custom flag value types to the new internal flag
  package.
- Created a new flag value type for the bool pointer to replace the
  flag.BoolFunc() used for the sensitive and no-color flags.
- Moved the version and build variables to the new internal version
  package to simplify the version executor.
- Created a new usage package and moved the usage functions there.
- Changed the type of the account-name flag from string to the
  internal StringSliceValue type.
This commit is contained in:
Dan Anglin 2024-08-13 14:53:26 +01:00
parent 299b134b58
commit 84091f398d
Signed by: dananglin
GPG key ID: 0C1D44CFBEE68638
48 changed files with 2386 additions and 1341 deletions

160
cmd/enbas-codegen/main.go Normal file
View file

@ -0,0 +1,160 @@
package main
import (
"embed"
"errors"
"flag"
"fmt"
"os"
"os/exec"
"strings"
"text/template"
"unicode"
)
func main() {
var (
enbasCLISchemaFilepath string
packageName string
)
flag.StringVar(&enbasCLISchemaFilepath, "path-to-enbas-cli-schema", "", "The path to the Enbas CLI schema file")
flag.StringVar(&packageName, "package", "", "The name of the internal package")
flag.Parse()
schema, err := newEnbasCLISchemaFromFile(enbasCLISchemaFilepath)
if err != nil {
fmt.Printf("ERROR: Unable to read the schema file: %v.\n", err)
}
if err := generateExecutors(schema, packageName); err != nil {
fmt.Printf("ERROR: Unable to generate the executors: %v.\n", err)
}
}
//go:embed templates/*
var executorTemplates embed.FS
var errNoPackageFlag = errors.New("the --package flag must be used")
func generateExecutors(schema enbasCLISchema, packageName string) error {
if packageName == "" {
return errNoPackageFlag
}
dirName := "templates/" + packageName
fsDir, err := executorTemplates.ReadDir(dirName)
if err != nil {
return fmt.Errorf("unable to read the template directory in the file system (FS): %w", err)
}
funcMap := template.FuncMap{
"capitalise": capitalise,
"flagFieldName": flagFieldName,
"getFlagType": schema.Flags.getType,
"getFlagDescription": schema.Flags.getDescription,
"internalFlagValue": internalFlagValue,
}
for _, obj := range fsDir {
templateFilename := obj.Name()
if !strings.HasSuffix(templateFilename, ".go.gotmpl") {
continue
}
if err := func() error {
tmpl := template.Must(template.New(templateFilename).
Funcs(funcMap).
ParseFS(executorTemplates, dirName+"/"+templateFilename),
)
output := strings.TrimSuffix(templateFilename, ".gotmpl")
file, err := os.Create(output)
if err != nil {
return fmt.Errorf("unable to create the output file: %w", err)
}
defer file.Close()
if err := tmpl.Execute(file, schema.Commands); err != nil {
return fmt.Errorf("unable to generate the code from the template: %w", err)
}
if err := runGoImports(output); err != nil {
return fmt.Errorf("unable to run goimports: %w", err)
}
return nil
}(); err != nil {
return fmt.Errorf("received an error after attempting to generate the code for %q: %w", templateFilename, err)
}
}
return nil
}
func runGoImports(path string) error {
imports := exec.Command("goimports", "-w", path)
if err := imports.Run(); err != nil {
return fmt.Errorf("received an error after running goimports: %w", err)
}
return nil
}
func capitalise(str string) string {
runes := []rune(str)
runes[0] = unicode.ToUpper(runes[0])
return string(runes)
}
func flagFieldName(flagRef enbasCLISchemaFlagReference) string {
if flagRef.FieldName != "" {
return flagRef.FieldName
}
return convertFlagToMixedCaps(flagRef.Flag)
}
func convertFlagToMixedCaps(value string) string {
var builder strings.Builder
runes := []rune(value)
numRunes := len(runes)
cursor := 0
for cursor < numRunes {
if runes[cursor] != '-' {
builder.WriteRune(runes[cursor])
cursor++
} else {
if cursor != numRunes-1 && unicode.IsLower(runes[cursor+1]) {
builder.WriteRune(unicode.ToUpper(runes[cursor+1]))
cursor += 2
} else {
cursor++
}
}
}
return builder.String()
}
func internalFlagValue(flagType string) bool {
internalFlagValues := map[string]struct{}{
"StringSliceValue": {},
"IntSliceValue": {},
"TimeDurationValue": {},
"BoolPtrValue": {},
}
_, exists := internalFlagValues[flagType]
return exists
}

View file

@ -0,0 +1,72 @@
package main
import (
"encoding/json"
"fmt"
"os"
)
type enbasCLISchema struct {
Flags enbasCLISchemaFlagMap `json:"flags"`
Commands map[string]enbasCLISchemaCommand `json:"commands"`
}
func newEnbasCLISchemaFromFile(path string) (enbasCLISchema, error) {
file, err := os.Open(path)
if err != nil {
return enbasCLISchema{}, fmt.Errorf("unable to open the schema file: %w", err)
}
defer file.Close()
var schema enbasCLISchema
if err := json.NewDecoder(file).Decode(&schema); err != nil {
return enbasCLISchema{}, fmt.Errorf("unable to decode the JSON data: %w", err)
}
return schema, nil
}
type enbasCLISchemaFlag struct {
Type string `json:"type"`
Description string `json:"description"`
}
type enbasCLISchemaFlagMap map[string]enbasCLISchemaFlag
func (e enbasCLISchemaFlagMap) getType(name string) string {
flag, ok := e[name]
if !ok {
return "UNKNOWN TYPE"
}
return flag.Type
}
func (e enbasCLISchemaFlagMap) getDescription(name string) string {
flag, ok := e[name]
if !ok {
return "UNKNOWN DESCRIPTION"
}
return flag.Description
}
type enbasCLISchemaCommand struct {
AdditionalFields []enbasCLISchemaAdditionalFields `json:"additionalFields"`
Flags []enbasCLISchemaFlagReference `json:"flags"`
Summary string `json:"summary"`
UseConfig bool `json:"useConfig"`
UsePrinter bool `json:"usePrinter"`
}
type enbasCLISchemaFlagReference struct {
Flag string `json:"flag"`
FieldName string `json:"fieldName"`
Default string `json:"default"`
}
type enbasCLISchemaAdditionalFields struct {
Name string `json:"name"`
Type string `json:"type"`
}

View file

@ -0,0 +1,109 @@
{{- /* vim: set noexpandtab : */ -}}
{{- /* vim: set tabstop=8 : */ -}}
{{- /* vim: set shiftwidth=8 : */ -}}
{{- /* vim: set softtabstop=8 : */ -}}
/*
This file is generated by the enbas-codegen
DO NOT EDIT.
*/
{{ print "" }}
package executor
{{ print "" }}
{{ print "" }}
import "fmt"
import "codeflow.dananglin.me.uk/apollo/enbas/internal/config"
import "codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
{{ print "" }}
{{ print "" }}
func Execute(
command string,
args []string,
noColor bool,
configDir string,
) error {
var (
enbasConfig *config.Config
enbasPrinter *printer.Printer
err error
)
switch command {
case "init", "version":
enbasPrinter = printer.NewPrinter(noColor, "", 0)
default:
enbasConfig, err = config.NewConfigFromFile(configDir)
if err != nil {
enbasPrinter = printer.NewPrinter(noColor, "", 0)
enbasPrinter.PrintFailure("unable to load the configuration: " + err.Error() + ".")
return err
}
enbasPrinter = printer.NewPrinter(
noColor,
enbasConfig.Integrations.Pager,
enbasConfig.LineWrapMaxWidth,
)
}
if err = execute(
command,
args,
enbasPrinter,
enbasConfig,
configDir,
); err != nil {
enbasPrinter.PrintFailure("(" + command + ") " + err.Error() + ".")
return err
}
return nil
}
{{ print "" }}
{{ print "" }}
func execute(
command string,
args []string,
enbasPrinter *printer.Printer,
enbasConfig *config.Config,
configDir string,
) error {
executorMap := map[string]Executor{
{{- range $name, $command := . -}}
{{- $new_executor_function_name := capitalise $name | printf "New%sExecutor" -}}
{{ print "" }}
{{ printf "%q" $name }}: {{ $new_executor_function_name }}(
{{- if $command.UsePrinter -}}
{{ print "" }}
enbasPrinter,
{{- end -}}
{{- if $command.UseConfig -}}
{{ print "" }}
enbasConfig,
{{- end -}}
{{- range $field := $command.AdditionalFields -}}
{{ print "" }}
{{ $field.Name }},
{{- end -}}
{{ print "" }}
),
{{- end -}}
{{ print "" }}
}
exe, ok := executorMap[command]
if !ok {
return UnknownCommandError{Command: command}
}
if err := exe.Parse(args); err != nil {
return fmt.Errorf("flag parsing error: %w", err)
}
if err := exe.Execute(); err != nil {
return fmt.Errorf("execution error: %w", err)
}
return nil
}

View file

@ -0,0 +1,109 @@
{{- /* vim: set noexpandtab : */ -}}
{{- /* vim: set tabstop=8 : */ -}}
{{- /* vim: set shiftwidth=8 : */ -}}
{{- /* vim: set softtabstop=8 : */ -}}
/*
This file is generated by the enbas-codegen
DO NOT EDIT.
*/
{{ print "" }}
package executor
{{ print "" }}
{{ print "" }}
import internalFlag "codeflow.dananglin.me.uk/apollo/enbas/internal/flag"
import "codeflow.dananglin.me.uk/apollo/enbas/internal/usage"
{{ print "" }}
{{ print "" }}
type Executor interface {
Name() string
Parse(args []string) error
Execute() error
}
{{ range $name, $command := . }}
{{- $struct_name := capitalise $name | printf "%sExecutor" -}}
{{- $new_executor_function_name := capitalise $name | printf "New%sExecutor" -}}
{{ print "" }}
// {{ $struct_name }} is the executor for the {{ $name }} command.
type {{ $struct_name }} struct {
*flag.FlagSet
{{- if $command.UsePrinter }}
printer *printer.Printer
{{- end }}
{{- if $command.UseConfig }}
config *config.Config
{{- end }}
{{- range $flag := $command.Flags -}}
{{- $flag_type := getFlagType $flag.Flag -}}
{{- if internalFlagValue $flag_type -}}
{{ print "" }}
{{ flagFieldName $flag }} internalFlag.{{ $flag_type }}
{{- else -}}
{{ print "" }}
{{ flagFieldName $flag }} {{ $flag_type }}
{{- end -}}
{{- end -}}
{{- range $field := $command.AdditionalFields -}}
{{ print "" }}
{{ $field.Name }} {{ $field.Type }}
{{- end -}}
{{ print "" }}
}
func {{ $new_executor_function_name }}(
{{- if $command.UsePrinter }}
printer *printer.Printer,
{{- end }}
{{- if $command.UseConfig }}
config *config.Config,
{{- end }}
{{- range $field := $command.AdditionalFields -}}
{{ print "" }}
{{ $field.Name }} {{ $field.Type }},
{{- end -}}
{{ print "" }}
) *{{ $struct_name }} {
exe := {{ $struct_name }}{
FlagSet: flag.NewFlagSet({{ printf "%q" $name }}, flag.ExitOnError),
{{- if $command.UsePrinter }}
printer: printer,
{{- end }}
{{- if $command.UseConfig }}
config: config,
{{- end }}
{{- range $flag := $command.Flags -}}
{{- $flag_type := getFlagType $flag.Flag -}}
{{- if internalFlagValue $flag_type -}}
{{ print "" }}
{{ flagFieldName $flag }}: internalFlag.New{{ $flag_type }}(),
{{- end -}}
{{- end -}}
{{- range $field := $command.AdditionalFields -}}
{{ print "" }}
{{ $field.Name }}: {{ $field.Name }},
{{- end -}}
{{ print "" }}
}
{{ print "" }}
exe.Usage = usage.ExecutorUsageFunc({{ printf "%q" $name }}, {{ printf "%q" $command.Summary }}, exe.FlagSet)
{{ print "" }}
{{- range $flag := $command.Flags -}}
{{- $flag_type := getFlagType $flag.Flag -}}
{{- if eq $flag_type "string" -}}
{{ print "" }}
exe.StringVar(&exe.{{ flagFieldName $flag }}, {{ printf "%q" $flag.Flag }}, {{ printf "%q" $flag.Default }}, {{ getFlagDescription $flag.Flag | printf "%q" }})
{{- else if eq $flag_type "bool" -}}
{{ print "" }}
exe.BoolVar(&exe.{{ flagFieldName $flag }}, {{ printf "%q" $flag.Flag }}, {{ $flag.Default }}, {{ getFlagDescription $flag.Flag | printf "%q" }})
{{- else if eq $flag_type "int" -}}
{{ print "" }}
exe.IntVar(&exe.{{ flagFieldName $flag }}, {{ printf "%q" $flag.Flag }}, {{ $flag.Default }}, {{ getFlagDescription $flag.Flag | printf "%q" }})
{{- else if internalFlagValue $flag_type -}}
{{ print "" }}
exe.Var(&exe.{{ flagFieldName $flag }}, {{ printf "%q" $flag.Flag }}, {{ getFlagDescription $flag.Flag | printf "%q" }})
{{- end -}}
{{- end -}}
{{ print "" }}
{{ print "" }}
return &exe
}
{{ end }}

View file

@ -0,0 +1,18 @@
{{- /* vim: set noexpandtab : */ -}}
{{- /* vim: set tabstop=8 : */ -}}
{{- /* vim: set shiftwidth=8 : */ -}}
{{- /* vim: set softtabstop=8 : */ -}}
/*
This file is generated by the enbas-codegen
DO NOT EDIT.
*/
{{ print "" }}
package usage
{{ print "" }}
var summaries = map[string]string {
{{- range $name, $command := . -}}
{{ print "" }}
{{ printf "%q" $name }}: {{ printf "%q" $command.Summary }},
{{- end -}}
{{ print "" }}
}

View file

@ -2,20 +2,11 @@ package main
import (
"flag"
"fmt"
"os"
"strconv"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/executor"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
var (
binaryVersion string //nolint:gochecknoglobals
buildTime string //nolint:gochecknoglobals
goVersion string //nolint:gochecknoglobals
gitCommit string //nolint:gochecknoglobals
internalFlag "codeflow.dananglin.me.uk/apollo/enbas/internal/flag"
"codeflow.dananglin.me.uk/apollo/enbas/internal/usage"
)
func main() {
@ -26,24 +17,14 @@ func main() {
func run() error {
var (
configDir string
noColor *bool
configDir string
noColorFlag internalFlag.BoolPtrValue
)
flag.StringVar(&configDir, "config-dir", "", "Specify your config directory")
flag.BoolFunc("no-color", "Disable ANSI colour output when displaying text on screen", func(value string) error {
boolVal, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("unable to parse %q as a boolean: %w", value, err)
}
flag.Var(&noColorFlag, "no-color", "Disable ANSI colour output when displaying text on screen")
noColor = new(bool)
*noColor = boolVal
return nil
})
flag.Usage = usageFunc(executor.CommandSummaryMap())
flag.Usage = usage.AppUsageFunc()
flag.Parse()
@ -53,176 +34,16 @@ func run() error {
return nil
}
// If NoColor is still unspecified,
// check to see if the NO_COLOR environment variable is set
if noColor == nil {
noColor = new(bool)
if os.Getenv("NO_COLOR") != "" {
*noColor = true
} else {
*noColor = false
}
var noColor bool
if noColorFlag.Value != nil {
noColor = *noColorFlag.Value
} else if os.Getenv("NO_COLOR") != "" {
noColor = true
}
command := flag.Arg(0)
args := flag.Args()[1:]
var (
enbasConfig *config.Config
enbasPrinter *printer.Printer
err error
)
switch command {
case executor.CommandInit, executor.CommandVersion:
enbasPrinter = printer.NewPrinter(*noColor, "", 0)
default:
enbasConfig, err = config.NewConfigFromFile(configDir)
if err != nil {
enbasPrinter = printer.NewPrinter(*noColor, "", 0)
enbasPrinter.PrintFailure("unable to load the configuration: " + err.Error() + ".")
return err
}
enbasPrinter = printer.NewPrinter(*noColor, enbasConfig.Integrations.Pager, enbasConfig.LineWrapMaxWidth)
}
executorMap := map[string]executor.Executor{
executor.CommandAccept: executor.NewAcceptOrRejectExecutor(
enbasPrinter,
enbasConfig,
executor.CommandAccept,
executor.CommandSummaryLookup(executor.CommandAccept),
),
executor.CommandAdd: executor.NewAddExecutor(
enbasPrinter,
enbasConfig,
executor.CommandAdd,
executor.CommandSummaryLookup(executor.CommandAdd),
),
executor.CommandBlock: executor.NewBlockOrUnblockExecutor(
enbasPrinter,
enbasConfig,
executor.CommandBlock,
executor.CommandSummaryLookup(executor.CommandBlock),
),
executor.CommandCreate: executor.NewCreateExecutor(
enbasPrinter,
enbasConfig,
executor.CommandCreate,
executor.CommandSummaryLookup(executor.CommandCreate),
),
executor.CommandDelete: executor.NewDeleteExecutor(
enbasPrinter,
enbasConfig,
executor.CommandDelete,
executor.CommandSummaryLookup(executor.CommandDelete),
),
executor.CommandEdit: executor.NewEditExecutor(
enbasPrinter,
enbasConfig,
executor.CommandEdit,
executor.CommandSummaryLookup(executor.CommandEdit),
),
executor.CommandFollow: executor.NewFollowOrUnfollowExecutor(
enbasPrinter,
enbasConfig,
executor.CommandFollow,
executor.CommandSummaryLookup(executor.CommandFollow),
),
executor.CommandInit: executor.NewInitExecutor(
enbasPrinter,
configDir,
executor.CommandInit,
executor.CommandSummaryLookup(executor.CommandInit),
),
executor.CommandLogin: executor.NewLoginExecutor(
enbasPrinter,
enbasConfig,
executor.CommandLogin,
executor.CommandSummaryLookup(executor.CommandLogin),
),
executor.CommandMute: executor.NewMuteOrUnmuteExecutor(
enbasPrinter,
enbasConfig,
executor.CommandMute,
executor.CommandSummaryLookup(executor.CommandMute),
),
executor.CommandReject: executor.NewAcceptOrRejectExecutor(
enbasPrinter,
enbasConfig,
executor.CommandReject,
executor.CommandSummaryLookup(executor.CommandReject),
),
executor.CommandRemove: executor.NewRemoveExecutor(
enbasPrinter,
enbasConfig,
executor.CommandRemove,
executor.CommandSummaryLookup(executor.CommandRemove),
),
executor.CommandSwitch: executor.NewSwitchExecutor(
enbasPrinter,
enbasConfig,
executor.CommandSwitch,
executor.CommandSummaryLookup(executor.CommandSwitch),
),
executor.CommandUnfollow: executor.NewFollowOrUnfollowExecutor(
enbasPrinter,
enbasConfig,
executor.CommandUnfollow,
executor.CommandSummaryLookup(executor.CommandUnfollow),
),
executor.CommandUnmute: executor.NewMuteOrUnmuteExecutor(
enbasPrinter,
enbasConfig,
executor.CommandUnmute,
executor.CommandSummaryLookup(executor.CommandUnmute),
),
executor.CommandUnblock: executor.NewBlockOrUnblockExecutor(
enbasPrinter,
enbasConfig,
executor.CommandUnblock,
executor.CommandSummaryLookup(executor.CommandUnblock),
),
executor.CommandShow: executor.NewShowExecutor(
enbasPrinter,
enbasConfig,
executor.CommandShow,
executor.CommandSummaryLookup(executor.CommandShow),
),
executor.CommandVersion: executor.NewVersionExecutor(
enbasPrinter,
executor.CommandVersion,
executor.CommandSummaryLookup(executor.CommandVersion),
binaryVersion,
buildTime,
goVersion,
gitCommit,
),
executor.CommandWhoami: executor.NewWhoAmIExecutor(
enbasPrinter,
enbasConfig,
executor.CommandWhoami,
executor.CommandSummaryLookup(executor.CommandWhoami),
),
}
exe, ok := executorMap[command]
if !ok {
err = executor.UnknownCommandError{Command: command}
enbasPrinter.PrintFailure(err.Error() + ".")
flag.Usage()
return err
}
if err = executor.Execute(exe, args); err != nil {
enbasPrinter.PrintFailure("(" + command + ") " + err.Error() + ".")
return err
}
return nil
return executor.Execute(command, args, noColor, configDir)
}

View file

@ -0,0 +1,40 @@
package executor
import (
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
)
func (a *AcceptExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceFollowRequest: a.acceptFollowRequest,
}
doFunc, ok := funcMap[a.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: a.resourceType}
}
gtsClient, err := client.NewClientFromFile(a.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (a *AcceptExecutor) acceptFollowRequest(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, a.accountName, a.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
if err := gtsClient.AcceptFollowRequest(accountID); err != nil {
return fmt.Errorf("unable to accept the follow request: %w", err)
}
a.printer.PrintSuccess("Successfully accepted the follow request.")
return nil
}

View file

@ -1,91 +0,0 @@
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type AcceptOrRejectExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
resourceType string
accountName string
command string
}
func NewAcceptOrRejectExecutor(enbasPrinter *printer.Printer, config *config.Config, name, summary string) *AcceptOrRejectExecutor {
acceptExe := AcceptOrRejectExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: enbasPrinter,
config: config,
command: name,
}
acceptExe.StringVar(&acceptExe.resourceType, flagType, "", "Specify the type of resource to accept or reject")
acceptExe.StringVar(&acceptExe.accountName, flagAccountName, "", "Specify the account name in full (username@domain)")
acceptExe.Usage = commandUsageFunc(name, summary, acceptExe.FlagSet)
return &acceptExe
}
func (a *AcceptOrRejectExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceFollowRequest: a.acceptOrRejectFollowRequest,
}
doFunc, ok := funcMap[a.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: a.resourceType}
}
gtsClient, err := client.NewClientFromFile(a.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (a *AcceptOrRejectExecutor) acceptOrRejectFollowRequest(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, a.accountName, a.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
switch a.command {
case CommandAccept:
return a.acceptFollowRequest(gtsClient, accountID)
case CommandReject:
return a.rejectFollowRequest(gtsClient, accountID)
default:
return nil
}
}
func (a *AcceptOrRejectExecutor) acceptFollowRequest(gtsClient *client.Client, accountID string) error {
if err := gtsClient.AcceptFollowRequest(accountID); err != nil {
return fmt.Errorf("unable to accept the follow request: %w", err)
}
a.printer.PrintSuccess("Successfully accepted the follow request.")
return nil
}
func (a *AcceptOrRejectExecutor) rejectFollowRequest(gtsClient *client.Client, accountID string) error {
if err := gtsClient.RejectFollowRequest(accountID); err != nil {
return fmt.Errorf("unable to reject the follow request: %w", err)
}
a.printer.PrintSuccess("Successfully rejected the follow request.")
return nil
}

View file

@ -5,49 +5,51 @@ import (
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
internalFlag "codeflow.dananglin.me.uk/apollo/enbas/internal/flag"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
)
func getAccountID(gtsClient *client.Client, myAccount bool, accountName, path string) (string, error) {
func getAccountID(
gtsClient *client.Client,
myAccount bool,
accountNames internalFlag.StringSliceValue,
credentialsFile string,
) (string, error) {
account, err := getAccount(gtsClient, myAccount, accountNames, credentialsFile)
if err != nil {
return "", fmt.Errorf("unable to get the account information: %w", err)
}
return account.ID, nil
}
func getAccount(
gtsClient *client.Client,
myAccount bool,
accountNames internalFlag.StringSliceValue,
credentialsFile string,
) (model.Account, error) {
var (
accountID string
err error
account model.Account
err error
)
switch {
case myAccount:
accountID, err = getMyAccountID(gtsClient, path)
account, err = getMyAccount(gtsClient, credentialsFile)
if err != nil {
return "", fmt.Errorf("unable to get your account ID: %w", err)
return account, fmt.Errorf("unable to get your account ID: %w", err)
}
case accountName != "":
accountID, err = getTheirAccountID(gtsClient, accountName)
case !accountNames.Empty():
account, err = getOtherAccount(gtsClient, accountNames)
if err != nil {
return "", fmt.Errorf("unable to get their account ID: %w", err)
return account, fmt.Errorf("unable to get the account ID: %w", err)
}
default:
return "", NoAccountSpecifiedError{}
return account, NoAccountSpecifiedError{}
}
return accountID, nil
}
func getTheirAccountID(gtsClient *client.Client, accountURI string) (string, error) {
account, err := getAccount(gtsClient, accountURI)
if err != nil {
return "", fmt.Errorf("unable to retrieve your account: %w", err)
}
return account.ID, nil
}
func getMyAccountID(gtsClient *client.Client, path string) (string, error) {
account, err := getMyAccount(gtsClient, path)
if err != nil {
return "", fmt.Errorf("received an error while getting your account details: %w", err)
}
return account.ID, nil
return account, nil
}
func getMyAccount(gtsClient *client.Client, path string) (model.Account, error) {
@ -58,7 +60,7 @@ func getMyAccount(gtsClient *client.Client, path string) (model.Account, error)
accountURI := authConfig.CurrentAccount
account, err := getAccount(gtsClient, accountURI)
account, err := gtsClient.GetAccount(accountURI)
if err != nil {
return model.Account{}, fmt.Errorf("unable to retrieve your account: %w", err)
}
@ -66,11 +68,35 @@ func getMyAccount(gtsClient *client.Client, path string) (model.Account, error)
return account, nil
}
func getAccount(gtsClient *client.Client, accountURI string) (model.Account, error) {
account, err := gtsClient.GetAccount(accountURI)
func getOtherAccount(gtsClient *client.Client, accountNames internalFlag.StringSliceValue) (model.Account, error) {
expectedNumAccountNames := 1
if !accountNames.ExpectedLength(expectedNumAccountNames) {
return model.Account{}, fmt.Errorf(
"received an unexpected number of account names: want %d",
expectedNumAccountNames,
)
}
account, err := gtsClient.GetAccount(accountNames[0])
if err != nil {
return model.Account{}, fmt.Errorf("unable to retrieve the account details: %w", err)
}
return account, nil
}
func getOtherAccounts(gtsClient *client.Client, accountNames internalFlag.StringSliceValue) ([]model.Account, error) {
numAccountNames := len(accountNames)
accounts := make([]model.Account, numAccountNames)
for ind := 0; ind < numAccountNames; ind++ {
var err error
accounts[ind], err = gtsClient.GetAccount(accountNames[ind])
if err != nil {
return nil, fmt.Errorf("unable to retrieve the account information for %s: %w", accountNames[ind], err)
}
}
return accounts, nil
}

View file

@ -2,54 +2,11 @@ package executor
import (
"errors"
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type AddExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
resourceType string
toResourceType string
listID string
statusID string
pollID string
choices MultiIntFlagValue
accountNames MultiStringFlagValue
content string
}
func NewAddExecutor(printer *printer.Printer, config *config.Config, name, summary string) *AddExecutor {
emptyArr := make([]string, 0, 3)
addExe := AddExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
accountNames: MultiStringFlagValue(emptyArr),
}
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")
addExe.StringVar(&addExe.statusID, flagStatusID, "", "The ID of the status")
addExe.StringVar(&addExe.content, flagContent, "", "The content of the resource")
addExe.StringVar(&addExe.pollID, flagPollID, "", "The ID of the poll")
addExe.Var(&addExe.accountNames, flagAccountName, "The name of the account")
addExe.Var(&addExe.choices, flagVote, "Add a vote to an option in a poll")
addExe.Usage = commandUsageFunc(name, summary, addExe.FlagSet)
return &addExe
}
func (a *AddExecutor) Execute() error {
if a.toResourceType == "" {
return FlagNotSetError{flagText: flagTo}
@ -97,28 +54,28 @@ func (a *AddExecutor) addAccountsToList(gtsClient *client.Client) error {
return FlagNotSetError{flagText: flagListID}
}
if len(a.accountNames) == 0 {
if a.accountNames.Empty() {
return NoAccountSpecifiedError{}
}
accountIDs := make([]string, len(a.accountNames))
accounts, err := getOtherAccounts(gtsClient, a.accountNames)
if err != nil {
return fmt.Errorf("unable to get the accounts: %w", err)
}
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 := make([]string, len(accounts))
relationship, err := gtsClient.GetAccountRelationship(accountID)
for ind := range accounts {
relationship, err := gtsClient.GetAccountRelationship(accounts[ind].ID)
if err != nil {
return fmt.Errorf("unable to get your relationship to %s: %w", a.accountNames[ind], err)
return fmt.Errorf("unable to get your relationship to %s: %w", accounts[ind].Acct, err)
}
if !relationship.Following {
return NotFollowingError{Account: a.accountNames[ind]}
return NotFollowingError{Account: accounts[ind].Acct}
}
accountIDs[ind] = accountID
accountIDs[ind] = accounts[ind].ID
}
if err := gtsClient.AddAccountsToList(a.listID, accountIDs); err != nil {
@ -147,11 +104,7 @@ func (a *AddExecutor) addToAccount(gtsClient *client.Client) error {
}
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.config.CredentialsFile)
accountID, err := getAccountID(gtsClient, false, a.accountNames, a.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
@ -265,7 +218,7 @@ func (a *AddExecutor) addToPoll(gtsClient *client.Client) error {
}
func (a *AddExecutor) addVoteToPoll(gtsClient *client.Client) error {
if len(a.choices) == 0 {
if a.votes.Empty() {
return errors.New("please use --" + flagVote + " to make a choice in this poll")
}
@ -278,11 +231,11 @@ func (a *AddExecutor) addVoteToPoll(gtsClient *client.Client) error {
return PollClosedError{}
}
if !poll.Multiple && len(a.choices) > 1 {
if !poll.Multiple && !a.votes.ExpectedLength(1) {
return MultipleChoiceError{}
}
if err := gtsClient.VoteInPoll(a.pollID, []int(a.choices)); err != nil {
if err := gtsClient.VoteInPoll(a.pollID, []int(a.votes)); err != nil {
return fmt.Errorf("unable to add your vote(s) to the poll: %w", err)
}

View file

@ -0,0 +1,40 @@
package executor
import (
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
)
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.NewClientFromFile(b.config.CredentialsFile)
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.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
if err := gtsClient.BlockAccount(accountID); err != nil {
return fmt.Errorf("unable to block the account: %w", err)
}
b.printer.PrintSuccess("Successfully blocked the account.")
return nil
}

View file

@ -1,95 +0,0 @@
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type BlockOrUnblockExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
resourceType string
accountName string
command string
}
func NewBlockOrUnblockExecutor(printer *printer.Printer, config *config.Config, name, summary string) *BlockOrUnblockExecutor {
blockExe := BlockOrUnblockExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
command: name,
}
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 *BlockOrUnblockExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceAccount: b.blockOrUnblockAccount,
}
doFunc, ok := funcMap[b.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: b.resourceType}
}
gtsClient, err := client.NewClientFromFile(b.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (b *BlockOrUnblockExecutor) blockOrUnblockAccount(gtsClient *client.Client) error {
if b.accountName == "" {
return FlagNotSetError{flagText: flagAccountName}
}
accountID, err := getAccountID(gtsClient, false, b.accountName, b.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
switch b.command {
case CommandBlock:
return b.blockAccount(gtsClient, accountID)
case CommandUnblock:
return b.unblockAccount(gtsClient, accountID)
default:
return nil
}
}
func (b *BlockOrUnblockExecutor) blockAccount(gtsClient *client.Client, accountID string) error {
if err := gtsClient.BlockAccount(accountID); err != nil {
return fmt.Errorf("unable to block the account: %w", err)
}
b.printer.PrintSuccess("Successfully blocked the account.")
return nil
}
func (b *BlockOrUnblockExecutor) unblockAccount(gtsClient *client.Client, accountID string) error {
if err := gtsClient.UnblockAccount(accountID); err != nil {
return fmt.Errorf("unable to unblock the account: %w", err)
}
b.printer.PrintSuccess("Successfully unblocked the account.")
return nil
}

View file

@ -0,0 +1,3 @@
package executor
//go:generate go run ../../cmd/enbas-codegen --package executor --path-to-enbas-cli-schema ../../schema/enbas_cli_schema.json

View file

@ -1,78 +0,0 @@
package executor
const (
CommandAccept string = "accept"
CommandAdd string = "add"
CommandBlock string = "block"
CommandCreate string = "create"
CommandDelete string = "delete"
CommandEdit string = "edit"
CommandFollow string = "follow"
CommandInit string = "init"
CommandLogin string = "login"
CommandMute string = "mute"
CommandReject string = "reject"
CommandRemove string = "remove"
CommandShow string = "show"
CommandSwitch string = "switch"
CommandUnblock string = "unblock"
CommandUnfollow string = "unfollow"
CommandUnmute string = "unmute"
CommandVersion string = "version"
CommandWhoami string = "whoami"
commandAcceptSummary string = "Accept a request (e.g. a follow request)"
commandAddSummary string = "Add a resource to another resource"
commandBlockSummary string = "Block a resource (e.g. an account)"
commandCreateSummary string = "Create a specific resource"
commandDeleteSummary string = "Delete a specific resource"
commandEditSummary string = "Edit a specific resource"
commandFollowSummary string = "Follow a resource (e.g. an account)"
commandInitSummary string = "Create a new configuration file in the specified configuration directory"
commandLoginSummary string = "Login to an account on GoToSocial"
commandMuteSummary string = "Mute a resource (e.g. an account)"
commandRejectSummary string = "Reject a request (e.g. a follow request)"
commandRemoveSummary string = "Remove a resource from another resource"
commandShowSummary string = "Print details about a specified resource"
commandSwitchSummary string = "Perform a switch operation (e.g. switch logged in accounts)"
commandUnblockSummary string = "Unblock a resource (e.g. an account)"
commandUnfollowSummary string = "Unfollow a resource (e.g. an account)"
commandUnmuteSummary string = "Unmute a resource (e.g. an account)"
commandVersionSummary string = "Print the application's version and build information"
commandWhoamiSummary string = "Print the account that you are currently logged in to"
)
func CommandSummaryMap() map[string]string {
return map[string]string{
CommandAccept: commandAcceptSummary,
CommandAdd: commandAddSummary,
CommandBlock: commandBlockSummary,
CommandCreate: commandCreateSummary,
CommandDelete: commandDeleteSummary,
CommandEdit: commandEditSummary,
CommandFollow: commandFollowSummary,
CommandInit: commandInitSummary,
CommandLogin: commandLoginSummary,
CommandMute: commandMuteSummary,
CommandReject: commandRejectSummary,
CommandRemove: commandRemoveSummary,
CommandShow: commandShowSummary,
CommandSwitch: commandSwitchSummary,
CommandUnblock: commandUnblockSummary,
CommandUnfollow: commandUnfollowSummary,
CommandUnmute: commandUnmuteSummary,
CommandVersion: commandVersionSummary,
CommandWhoami: commandWhoamiSummary,
}
}
func CommandSummaryLookup(command string) string {
commandMap := CommandSummaryMap()
summary, ok := commandMap[command]
if !ok {
return "This command does not have a summary"
}
return summary
}

View file

@ -1,94 +1,13 @@
package executor
import (
"flag"
"fmt"
"strconv"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
)
type CreateExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
addPoll bool
boostable bool
federated bool
likeable bool
pollAllowsMultipleChoices bool
pollHidesVoteCounts bool
replyable bool
sensitive *bool
content string
contentType string
fromFile string
inReplyTo string
language string
resourceType string
listTitle string
listRepliesPolicy string
spoilerText string
visibility string
pollExpiresIn TimeDurationFlagValue
pollOptions MultiStringFlagValue
}
func NewCreateExecutor(printer *printer.Printer, config *config.Config, name, summary string) *CreateExecutor {
createExe := CreateExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
}
createExe.StringVar(&createExe.resourceType, flagType, "", "Specify the type of resource to create")
// Flags for statuses
createExe.BoolVar(&createExe.boostable, flagEnableReposts, true, "Specify if the status can be reposted/boosted by others")
createExe.StringVar(&createExe.content, flagContent, "", "The content of the status to create")
createExe.StringVar(&createExe.contentType, flagContentType, "plain", "The type that the contents should be parsed from (valid values are plain and markdown)")
createExe.BoolVar(&createExe.federated, flagEnableFederation, true, "Specify if the status can be federated beyond the local timelines")
createExe.StringVar(&createExe.fromFile, flagFromFile, "", "The file path where to read the contents from")
createExe.StringVar(&createExe.inReplyTo, flagInReplyTo, "", "The ID of the status that you want to reply to")
createExe.StringVar(&createExe.language, flagLanguage, "", "The ISO 639 language code for this status")
createExe.BoolVar(&createExe.likeable, flagEnableLikes, true, "Specify if the status can be liked/favourited")
createExe.BoolVar(&createExe.replyable, flagEnableReplies, true, "Specify if the status can be replied to")
createExe.StringVar(&createExe.spoilerText, flagSpoilerText, "", "The text to display as the status' warning or subject")
createExe.StringVar(&createExe.visibility, flagVisibility, "", "The visibility of the posted status")
createExe.BoolFunc(flagSensitive, "Specify if the status should be marked as sensitive", func(value string) error {
boolVal, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("unable to parse %q as a boolean value: %w", value, err)
}
createExe.sensitive = new(bool)
*createExe.sensitive = boolVal
return nil
})
// Flags specifically for polls
createExe.BoolVar(&createExe.addPoll, flagAddPoll, false, "Add a poll to the status")
createExe.BoolVar(&createExe.pollAllowsMultipleChoices, flagPollAllowsMultipleChoices, false, "The poll allows viewers to make multiple choices in the poll")
createExe.BoolVar(&createExe.pollHidesVoteCounts, flagPollHidesVoteCounts, false, "The poll will hide the vote count until it is closed")
createExe.Var(&createExe.pollOptions, flagPollOption, "A poll option. Use this multiple times to set multiple options")
createExe.Var(&createExe.pollExpiresIn, flagPollExpiresIn, "The duration in which the poll is open for")
// Flags for lists
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)")
createExe.Usage = commandUsageFunc(name, summary, createExe.FlagSet)
return &createExe
}
func (c *CreateExecutor) Execute() error {
if c.resourceType == "" {
return FlagNotSetError{flagText: flagType}
@ -179,8 +98,8 @@ func (c *CreateExecutor) createStatus(gtsClient *client.Client) error {
visibility = preferences.PostingDefaultVisibility
}
if c.sensitive != nil {
sensitive = *c.sensitive
if c.sensitive.Value != nil {
sensitive = *c.sensitive.Value
} else {
sensitive = preferences.PostingDefaultSensitive
}

View file

@ -1,39 +1,11 @@
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type DeleteExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
resourceType string
listID string
}
func NewDeleteExecutor(printer *printer.Printer, config *config.Config, name, summary string) *DeleteExecutor {
deleteExe := DeleteExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
}
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}

View file

@ -1,44 +1,12 @@
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/model"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type EditExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
resourceType string
listID string
listTitle string
listRepliesPolicy string
}
func NewEditExecutor(printer *printer.Printer, config *config.Config, name, summary string) *EditExecutor {
editExe := EditExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
}
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}

View file

@ -0,0 +1,160 @@
/*
This file is generated by the enbas-codegen
DO NOT EDIT.
*/
package executor
import (
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
func Execute(
command string,
args []string,
noColor bool,
configDir string,
) error {
var (
enbasConfig *config.Config
enbasPrinter *printer.Printer
err error
)
switch command {
case "init", "version":
enbasPrinter = printer.NewPrinter(noColor, "", 0)
default:
enbasConfig, err = config.NewConfigFromFile(configDir)
if err != nil {
enbasPrinter = printer.NewPrinter(noColor, "", 0)
enbasPrinter.PrintFailure("unable to load the configuration: " + err.Error() + ".")
return err
}
enbasPrinter = printer.NewPrinter(
noColor,
enbasConfig.Integrations.Pager,
enbasConfig.LineWrapMaxWidth,
)
}
if err = execute(
command,
args,
enbasPrinter,
enbasConfig,
configDir,
); err != nil {
enbasPrinter.PrintFailure("(" + command + ") " + err.Error() + ".")
return err
}
return nil
}
func execute(
command string,
args []string,
enbasPrinter *printer.Printer,
enbasConfig *config.Config,
configDir string,
) error {
executorMap := map[string]Executor{
"accept": NewAcceptExecutor(
enbasPrinter,
enbasConfig,
),
"add": NewAddExecutor(
enbasPrinter,
enbasConfig,
),
"block": NewBlockExecutor(
enbasPrinter,
enbasConfig,
),
"create": NewCreateExecutor(
enbasPrinter,
enbasConfig,
),
"delete": NewDeleteExecutor(
enbasPrinter,
enbasConfig,
),
"edit": NewEditExecutor(
enbasPrinter,
enbasConfig,
),
"follow": NewFollowExecutor(
enbasPrinter,
enbasConfig,
),
"init": NewInitExecutor(
enbasPrinter,
configDir,
),
"login": NewLoginExecutor(
enbasPrinter,
enbasConfig,
),
"mute": NewMuteExecutor(
enbasPrinter,
enbasConfig,
),
"reject": NewRejectExecutor(
enbasPrinter,
enbasConfig,
),
"remove": NewRemoveExecutor(
enbasPrinter,
enbasConfig,
),
"show": NewShowExecutor(
enbasPrinter,
enbasConfig,
),
"switch": NewSwitchExecutor(
enbasPrinter,
enbasConfig,
),
"unblock": NewUnblockExecutor(
enbasPrinter,
enbasConfig,
),
"unfollow": NewUnfollowExecutor(
enbasPrinter,
enbasConfig,
),
"unmute": NewUnmuteExecutor(
enbasPrinter,
enbasConfig,
),
"version": NewVersionExecutor(
enbasPrinter,
),
"whoami": NewWhoamiExecutor(
enbasPrinter,
enbasConfig,
),
}
exe, ok := executorMap[command]
if !ok {
return UnknownCommandError{Command: command}
}
if err := exe.Parse(args); err != nil {
return fmt.Errorf("flag parsing error: %w", err)
}
if err := exe.Execute(); err != nil {
return fmt.Errorf("execution error: %w", err)
}
return nil
}

View file

@ -1,21 +0,0 @@
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("flag parsing error: %w", err)
}
if err := executor.Execute(); err != nil {
return fmt.Errorf("execution error: %w", err)
}
return nil
}

View file

@ -0,0 +1,641 @@
/*
This file is generated by the enbas-codegen
DO NOT EDIT.
*/
package executor
import (
"flag"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
internalFlag "codeflow.dananglin.me.uk/apollo/enbas/internal/flag"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
"codeflow.dananglin.me.uk/apollo/enbas/internal/usage"
)
type Executor interface {
Name() string
Parse(args []string) error
Execute() error
}
// AcceptExecutor is the executor for the accept command.
type AcceptExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName internalFlag.StringSliceValue
resourceType string
}
func NewAcceptExecutor(
printer *printer.Printer,
config *config.Config,
) *AcceptExecutor {
exe := AcceptExecutor{
FlagSet: flag.NewFlagSet("accept", flag.ExitOnError),
printer: printer,
config: config,
accountName: internalFlag.NewStringSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("accept", "Accepts a request (e.g. a follow request)", exe.FlagSet)
exe.Var(&exe.accountName, "account-name", "The name of the account")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// AddExecutor is the executor for the add command.
type AddExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountNames internalFlag.StringSliceValue
content string
listID string
pollID string
statusID string
toResourceType string
resourceType string
votes internalFlag.IntSliceValue
}
func NewAddExecutor(
printer *printer.Printer,
config *config.Config,
) *AddExecutor {
exe := AddExecutor{
FlagSet: flag.NewFlagSet("add", flag.ExitOnError),
printer: printer,
config: config,
accountNames: internalFlag.NewStringSliceValue(),
votes: internalFlag.NewIntSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("add", "Adds a resource to another resource", exe.FlagSet)
exe.Var(&exe.accountNames, "account-name", "The name of the account")
exe.StringVar(&exe.content, "content", "", "The content of the created resource")
exe.StringVar(&exe.listID, "list-id", "", "The ID of the list in question")
exe.StringVar(&exe.pollID, "poll-id", "", "The ID of the poll")
exe.StringVar(&exe.statusID, "status-id", "", "The ID of the status")
exe.StringVar(&exe.toResourceType, "to", "", "TBC")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
exe.Var(&exe.votes, "vote", "Add a vote to an option in a poll")
return &exe
}
// BlockExecutor is the executor for the block command.
type BlockExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName internalFlag.StringSliceValue
resourceType string
}
func NewBlockExecutor(
printer *printer.Printer,
config *config.Config,
) *BlockExecutor {
exe := BlockExecutor{
FlagSet: flag.NewFlagSet("block", flag.ExitOnError),
printer: printer,
config: config,
accountName: internalFlag.NewStringSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("block", "Blocks a resource (e.g. an account)", exe.FlagSet)
exe.Var(&exe.accountName, "account-name", "The name of the account")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// CreateExecutor is the executor for the create command.
type CreateExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
addPoll bool
content string
contentType string
federated bool
likeable bool
replyable bool
boostable bool
fromFile string
inReplyTo string
language string
listRepliesPolicy string
listTitle string
pollAllowsMultipleChoices bool
pollExpiresIn internalFlag.TimeDurationValue
pollHidesVoteCounts bool
pollOptions internalFlag.StringSliceValue
sensitive internalFlag.BoolPtrValue
spoilerText string
resourceType string
visibility string
}
func NewCreateExecutor(
printer *printer.Printer,
config *config.Config,
) *CreateExecutor {
exe := CreateExecutor{
FlagSet: flag.NewFlagSet("create", flag.ExitOnError),
printer: printer,
config: config,
pollExpiresIn: internalFlag.NewTimeDurationValue(),
pollOptions: internalFlag.NewStringSliceValue(),
sensitive: internalFlag.NewBoolPtrValue(),
}
exe.Usage = usage.ExecutorUsageFunc("create", "Creates a specific resource", exe.FlagSet)
exe.BoolVar(&exe.addPoll, "add-poll", false, "Set to true to add a poll when creating a status")
exe.StringVar(&exe.content, "content", "", "The content of the created resource")
exe.StringVar(&exe.contentType, "content-type", "plain", "The type that the contents should be parsed from (valid values are plain and markdown)")
exe.BoolVar(&exe.federated, "enable-federation", true, "Set to true to federate the status beyond the local timelines")
exe.BoolVar(&exe.likeable, "enable-likes", true, "Set to true to allow the status to be liked (favourited)")
exe.BoolVar(&exe.replyable, "enable-replies", true, "Set to true to allow viewers to reply to the status")
exe.BoolVar(&exe.boostable, "enable-reposts", true, "Set to true to allow the status to be reposted (boosted) by others")
exe.StringVar(&exe.fromFile, "from-file", "", "The file path where to read the contents from")
exe.StringVar(&exe.inReplyTo, "in-reply-to", "", "The ID of the status that you want to reply to")
exe.StringVar(&exe.language, "language", "", "The ISO 639 language code for this status")
exe.StringVar(&exe.listRepliesPolicy, "list-replies-policy", "list", "The replies policy of the list")
exe.StringVar(&exe.listTitle, "list-title", "", "The title of the list")
exe.BoolVar(&exe.pollAllowsMultipleChoices, "poll-allows-multiple-choices", false, "Set to true to allow viewers to make multiple choices in the poll")
exe.Var(&exe.pollExpiresIn, "poll-expires-in", "The duration in which the poll is open for")
exe.BoolVar(&exe.pollHidesVoteCounts, "poll-hides-vote-counts", false, "Set to true to hide the vote count until the poll is closed")
exe.Var(&exe.pollOptions, "poll-option", "A poll option. Use this multiple times to set multiple options")
exe.Var(&exe.sensitive, "sensitive", "Set to true if the status should be marked as sensitive")
exe.StringVar(&exe.spoilerText, "spoiler-text", "", "The text to display as the status' warning or subject")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
exe.StringVar(&exe.visibility, "visibility", "", "The visibility of the posted status")
return &exe
}
// DeleteExecutor is the executor for the delete command.
type DeleteExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
listID string
resourceType string
}
func NewDeleteExecutor(
printer *printer.Printer,
config *config.Config,
) *DeleteExecutor {
exe := DeleteExecutor{
FlagSet: flag.NewFlagSet("delete", flag.ExitOnError),
printer: printer,
config: config,
}
exe.Usage = usage.ExecutorUsageFunc("delete", "Deletes a specific resource", exe.FlagSet)
exe.StringVar(&exe.listID, "list-id", "", "The ID of the list in question")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// EditExecutor is the executor for the edit command.
type EditExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
listID string
listTitle string
listRepliesPolicy string
resourceType string
}
func NewEditExecutor(
printer *printer.Printer,
config *config.Config,
) *EditExecutor {
exe := EditExecutor{
FlagSet: flag.NewFlagSet("edit", flag.ExitOnError),
printer: printer,
config: config,
}
exe.Usage = usage.ExecutorUsageFunc("edit", "Edit a specific resource", exe.FlagSet)
exe.StringVar(&exe.listID, "list-id", "", "The ID of the list in question")
exe.StringVar(&exe.listTitle, "list-title", "", "The title of the list")
exe.StringVar(&exe.listRepliesPolicy, "list-replies-policy", "", "The replies policy of the list")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// FollowExecutor is the executor for the follow command.
type FollowExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName internalFlag.StringSliceValue
notify bool
showReposts bool
resourceType string
}
func NewFollowExecutor(
printer *printer.Printer,
config *config.Config,
) *FollowExecutor {
exe := FollowExecutor{
FlagSet: flag.NewFlagSet("follow", flag.ExitOnError),
printer: printer,
config: config,
accountName: internalFlag.NewStringSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("follow", "Follow a resource (e.g. an account)", exe.FlagSet)
exe.Var(&exe.accountName, "account-name", "The name of the account")
exe.BoolVar(&exe.notify, "notify", false, "Get notifications from statuses from the account you want to follow")
exe.BoolVar(&exe.showReposts, "show-reposts", true, "Show reposts from the account you want to follow")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// InitExecutor is the executor for the init command.
type InitExecutor struct {
*flag.FlagSet
printer *printer.Printer
configDir string
}
func NewInitExecutor(
printer *printer.Printer,
configDir string,
) *InitExecutor {
exe := InitExecutor{
FlagSet: flag.NewFlagSet("init", flag.ExitOnError),
printer: printer,
configDir: configDir,
}
exe.Usage = usage.ExecutorUsageFunc("init", "Creates a new configuration file in the specified configuration directory", exe.FlagSet)
return &exe
}
// LoginExecutor is the executor for the login command.
type LoginExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
instance string
}
func NewLoginExecutor(
printer *printer.Printer,
config *config.Config,
) *LoginExecutor {
exe := LoginExecutor{
FlagSet: flag.NewFlagSet("login", flag.ExitOnError),
printer: printer,
config: config,
}
exe.Usage = usage.ExecutorUsageFunc("login", "Logs into an account on GoToSocial", exe.FlagSet)
exe.StringVar(&exe.instance, "instance", "", "The instance that you want to log into")
return &exe
}
// MuteExecutor is the executor for the mute command.
type MuteExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName internalFlag.StringSliceValue
muteDuration internalFlag.TimeDurationValue
muteNotifications bool
resourceType string
}
func NewMuteExecutor(
printer *printer.Printer,
config *config.Config,
) *MuteExecutor {
exe := MuteExecutor{
FlagSet: flag.NewFlagSet("mute", flag.ExitOnError),
printer: printer,
config: config,
accountName: internalFlag.NewStringSliceValue(),
muteDuration: internalFlag.NewTimeDurationValue(),
}
exe.Usage = usage.ExecutorUsageFunc("mute", "Mutes a specific resource (e.g. an account)", exe.FlagSet)
exe.Var(&exe.accountName, "account-name", "The name of the account")
exe.Var(&exe.muteDuration, "mute-duration", "Specify how long the mute should last for. To mute indefinitely, set this to 0s")
exe.BoolVar(&exe.muteNotifications, "mute-notifications", false, "Set to true to mute notifications as well as posts")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// RejectExecutor is the executor for the reject command.
type RejectExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName internalFlag.StringSliceValue
resourceType string
}
func NewRejectExecutor(
printer *printer.Printer,
config *config.Config,
) *RejectExecutor {
exe := RejectExecutor{
FlagSet: flag.NewFlagSet("reject", flag.ExitOnError),
printer: printer,
config: config,
accountName: internalFlag.NewStringSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("reject", "Rejects a request (e.g. a follow request)", exe.FlagSet)
exe.Var(&exe.accountName, "account-name", "The name of the account")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// RemoveExecutor is the executor for the remove command.
type RemoveExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountNames internalFlag.StringSliceValue
fromResourceType string
listID string
statusID string
resourceType string
}
func NewRemoveExecutor(
printer *printer.Printer,
config *config.Config,
) *RemoveExecutor {
exe := RemoveExecutor{
FlagSet: flag.NewFlagSet("remove", flag.ExitOnError),
printer: printer,
config: config,
accountNames: internalFlag.NewStringSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("remove", "Removes a resource from another resource", exe.FlagSet)
exe.Var(&exe.accountNames, "account-name", "The name of the account")
exe.StringVar(&exe.fromResourceType, "from", "", "Specify the resource type to action the target resource from")
exe.StringVar(&exe.listID, "list-id", "", "The ID of the list in question")
exe.StringVar(&exe.statusID, "status-id", "", "The ID of the status")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// ShowExecutor is the executor for the show command.
type ShowExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName internalFlag.StringSliceValue
getAllImages bool
getAllVideos bool
attachmentIDs internalFlag.StringSliceValue
showInBrowser bool
excludeBoosts bool
excludeReplies bool
fromResourceType string
limit int
listID string
myAccount bool
onlyMedia bool
onlyPinned bool
onlyPublic bool
pollID string
showUserPreferences bool
showStatuses bool
skipAccountRelationship bool
statusID string
timelineCategory string
tag string
resourceType string
}
func NewShowExecutor(
printer *printer.Printer,
config *config.Config,
) *ShowExecutor {
exe := ShowExecutor{
FlagSet: flag.NewFlagSet("show", flag.ExitOnError),
printer: printer,
config: config,
accountName: internalFlag.NewStringSliceValue(),
attachmentIDs: internalFlag.NewStringSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("show", "Shows details about a specified resource", exe.FlagSet)
exe.Var(&exe.accountName, "account-name", "The name of the account")
exe.BoolVar(&exe.getAllImages, "all-images", false, "Set to true to show all images from a status")
exe.BoolVar(&exe.getAllVideos, "all-videos", false, "Set to true to show all videos from a status")
exe.Var(&exe.attachmentIDs, "attachment-id", "The ID of the media attachment")
exe.BoolVar(&exe.showInBrowser, "browser", false, "Set to true to view in the your favourite browser")
exe.BoolVar(&exe.excludeBoosts, "exclude-boosts", false, "Set to true to exclude statuses that are boosts of another status")
exe.BoolVar(&exe.excludeReplies, "exclude-replies", false, "Set to true to exclude statuses that are a reply to another status")
exe.StringVar(&exe.fromResourceType, "from", "", "Specify the resource type to action the target resource from")
exe.IntVar(&exe.limit, "limit", 20, "Specify the limit of items to display")
exe.StringVar(&exe.listID, "list-id", "", "The ID of the list in question")
exe.BoolVar(&exe.myAccount, "my-account", false, "Set to true to specify your account")
exe.BoolVar(&exe.onlyMedia, "only-media", false, "Set to true to show only the statuses with media attachments")
exe.BoolVar(&exe.onlyPinned, "only-pinned", false, "Set to true to show only the account's pinned statuses")
exe.BoolVar(&exe.onlyPublic, "only-public", false, "Set to true to show only the account's public posts")
exe.StringVar(&exe.pollID, "poll-id", "", "The ID of the poll")
exe.BoolVar(&exe.showUserPreferences, "show-preferences", false, "Set to true to view your posting preferences when viewing your account information")
exe.BoolVar(&exe.showStatuses, "show-statuses", false, "Set to true to view the statuses created from the account you are viewing")
exe.BoolVar(&exe.skipAccountRelationship, "skip-relationship", false, "Set to true to skip showing your relationship to the account that you are viewing")
exe.StringVar(&exe.statusID, "status-id", "", "The ID of the status")
exe.StringVar(&exe.timelineCategory, "timeline-category", "home", "The timeline category")
exe.StringVar(&exe.tag, "tag", "", "The name of the tag")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// SwitchExecutor is the executor for the switch command.
type SwitchExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName internalFlag.StringSliceValue
to string
}
func NewSwitchExecutor(
printer *printer.Printer,
config *config.Config,
) *SwitchExecutor {
exe := SwitchExecutor{
FlagSet: flag.NewFlagSet("switch", flag.ExitOnError),
printer: printer,
config: config,
accountName: internalFlag.NewStringSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("switch", "Performs a switch operation (e.g. switching between logged in accounts)", exe.FlagSet)
exe.Var(&exe.accountName, "account-name", "The name of the account")
exe.StringVar(&exe.to, "to", "", "TBC")
return &exe
}
// UnblockExecutor is the executor for the unblock command.
type UnblockExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName internalFlag.StringSliceValue
resourceType string
}
func NewUnblockExecutor(
printer *printer.Printer,
config *config.Config,
) *UnblockExecutor {
exe := UnblockExecutor{
FlagSet: flag.NewFlagSet("unblock", flag.ExitOnError),
printer: printer,
config: config,
accountName: internalFlag.NewStringSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("unblock", "Unblocks a resource (e.g. an account)", exe.FlagSet)
exe.Var(&exe.accountName, "account-name", "The name of the account")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// UnfollowExecutor is the executor for the unfollow command.
type UnfollowExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName internalFlag.StringSliceValue
resourceType string
}
func NewUnfollowExecutor(
printer *printer.Printer,
config *config.Config,
) *UnfollowExecutor {
exe := UnfollowExecutor{
FlagSet: flag.NewFlagSet("unfollow", flag.ExitOnError),
printer: printer,
config: config,
accountName: internalFlag.NewStringSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("unfollow", "Unfollows a resource (e.g. an account)", exe.FlagSet)
exe.Var(&exe.accountName, "account-name", "The name of the account")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// UnmuteExecutor is the executor for the unmute command.
type UnmuteExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName internalFlag.StringSliceValue
resourceType string
}
func NewUnmuteExecutor(
printer *printer.Printer,
config *config.Config,
) *UnmuteExecutor {
exe := UnmuteExecutor{
FlagSet: flag.NewFlagSet("unmute", flag.ExitOnError),
printer: printer,
config: config,
accountName: internalFlag.NewStringSliceValue(),
}
exe.Usage = usage.ExecutorUsageFunc("unmute", "Umutes a specific resource (e.g. an account)", exe.FlagSet)
exe.Var(&exe.accountName, "account-name", "The name of the account")
exe.StringVar(&exe.resourceType, "type", "", "The type of resource you want to action on (e.g. account, status)")
return &exe
}
// VersionExecutor is the executor for the version command.
type VersionExecutor struct {
*flag.FlagSet
printer *printer.Printer
full bool
}
func NewVersionExecutor(
printer *printer.Printer,
) *VersionExecutor {
exe := VersionExecutor{
FlagSet: flag.NewFlagSet("version", flag.ExitOnError),
printer: printer,
}
exe.Usage = usage.ExecutorUsageFunc("version", "Prints the application's version and build information", exe.FlagSet)
exe.BoolVar(&exe.full, "full", false, "Set to true to print the build information in full")
return &exe
}
// WhoamiExecutor is the executor for the whoami command.
type WhoamiExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
}
func NewWhoamiExecutor(
printer *printer.Printer,
config *config.Config,
) *WhoamiExecutor {
exe := WhoamiExecutor{
FlagSet: flag.NewFlagSet("whoami", flag.ExitOnError),
printer: printer,
config: config,
}
exe.Usage = usage.ExecutorUsageFunc("whoami", "Prints the account that you are currently logged into", exe.FlagSet)
return &exe
}

View file

@ -1,120 +1,18 @@
package executor
import (
"fmt"
"strconv"
"strings"
"time"
)
const (
flagAddPoll = "add-poll"
flagAccountName = "account-name"
flagAllImages = "all-images"
flagAllVideos = "all-videos"
flagAttachmentID = "attachment-id"
flagBrowser = "browser"
flagContentType = "content-type"
flagContent = "content"
flagEnableFederation = "enable-federation"
flagEnableLikes = "enable-likes"
flagEnableReplies = "enable-replies"
flagEnableReposts = "enable-reposts"
flagExcludeBoosts = "exclude-boosts"
flagExcludeReplies = "exclude-replies"
flagFrom = "from"
flagFromFile = "from-file"
flagFull = "full"
flagInReplyTo = "in-reply-to"
flagInstance = "instance"
flagLanguage = "language"
flagLimit = "limit"
flagListID = "list-id"
flagListTitle = "list-title"
flagListRepliesPolicy = "list-replies-policy"
flagMyAccount = "my-account"
flagMuteDuration = "mute-duration"
flagMuteNotifications = "mute-notifications"
flagNotify = "notify"
flagOnlyMedia = "only-media"
flagOnlyPinned = "only-pinned"
flagOnlyPublic = "only-public"
flagPollAllowsMultipleChoices = "poll-allows-multiple-choices"
flagPollExpiresIn = "poll-expires-in"
flagPollHidesVoteCounts = "poll-hides-vote-counts"
flagPollID = "poll-id"
flagPollOption = "poll-option"
flagSensitive = "sensitive"
flagSkipRelationship = "skip-relationship"
flagShowPreferences = "show-preferences"
flagShowReposts = "show-reposts"
flagShowStatuses = "show-statuses"
flagSpoilerText = "spoiler-text"
flagStatusID = "status-id"
flagTag = "tag"
flagTimelineCategory = "timeline-category"
flagTo = "to"
flagType = "type"
flagVisibility = "visibility"
flagVote = "vote"
flagAttachmentID = "attachment-id"
flagContent = "content"
flagFrom = "from"
flagFromFile = "from-file"
flagInstance = "instance"
flagListID = "list-id"
flagListTitle = "list-title"
flagPollID = "poll-id"
flagPollOption = "poll-option"
flagStatusID = "status-id"
flagTag = "tag"
flagTo = "to"
flagType = "type"
flagVote = "vote"
)
type MultiStringFlagValue []string
func (v *MultiStringFlagValue) String() string {
return strings.Join(*v, ", ")
}
func (v *MultiStringFlagValue) Set(value string) error {
if len(value) > 0 {
*v = append(*v, value)
}
return nil
}
type MultiIntFlagValue []int
func (v *MultiIntFlagValue) String() string {
value := "Choices: "
for ind, vote := range *v {
if ind == len(*v)-1 {
value += strconv.Itoa(vote)
} else {
value += strconv.Itoa(vote) + ", "
}
}
return value
}
func (v *MultiIntFlagValue) Set(text string) error {
value, err := strconv.Atoi(text)
if err != nil {
return fmt.Errorf("unable to parse the value to an integer: %w", err)
}
*v = append(*v, value)
return nil
}
type TimeDurationFlagValue struct {
Duration time.Duration
}
func (v TimeDurationFlagValue) String() string {
return ""
}
func (v *TimeDurationFlagValue) Set(text string) error {
duration, err := time.ParseDuration(text)
if err != nil {
return fmt.Errorf("unable to parse the value as time duration: %w", err)
}
v.Duration = duration
return nil
}

View file

@ -0,0 +1,46 @@
package executor
import (
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
)
func (f *FollowExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceAccount: f.followAccount,
}
doFunc, ok := funcMap[f.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: f.resourceType}
}
gtsClient, err := client.NewClientFromFile(f.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (f *FollowExecutor) followAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, f.accountName, f.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
form := client.FollowAccountForm{
AccountID: accountID,
ShowReposts: f.showReposts,
Notify: f.notify,
}
if err := gtsClient.FollowAccount(form); err != nil {
return fmt.Errorf("unable to follow the account: %w", err)
}
f.printer.PrintSuccess("Successfully sent the follow request.")
return nil
}

View file

@ -1,101 +0,0 @@
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type FollowOrUnfollowExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
resourceType string
accountName string
showReposts bool
notify bool
action string
}
func NewFollowOrUnfollowExecutor(printer *printer.Printer, config *config.Config, name, summary string) *FollowOrUnfollowExecutor {
command := FollowOrUnfollowExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
action: name,
}
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 (f *FollowOrUnfollowExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceAccount: f.followOrUnfollowAccount,
}
doFunc, ok := funcMap[f.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: f.resourceType}
}
gtsClient, err := client.NewClientFromFile(f.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (f *FollowOrUnfollowExecutor) followOrUnfollowAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, f.accountName, f.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
switch f.action {
case CommandFollow:
return f.followAccount(gtsClient, accountID)
case CommandUnfollow:
return f.unfollowAccount(gtsClient, accountID)
default:
return nil
}
}
func (f *FollowOrUnfollowExecutor) followAccount(gtsClient *client.Client, accountID string) error {
form := client.FollowAccountForm{
AccountID: accountID,
ShowReposts: f.showReposts,
Notify: f.notify,
}
if err := gtsClient.FollowAccount(form); err != nil {
return fmt.Errorf("unable to follow the account: %w", err)
}
f.printer.PrintSuccess("Successfully sent the follow request.")
return nil
}
func (f *FollowOrUnfollowExecutor) unfollowAccount(gtsClient *client.Client, accountID string) error {
if err := gtsClient.UnfollowAccount(accountID); err != nil {
return fmt.Errorf("unable to unfollow the account: %w", err)
}
f.printer.PrintSuccess("Successfully unfollowed the account.")
return nil
}

View file

@ -1,34 +1,12 @@
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
)
type InitExecutor struct {
*flag.FlagSet
printer *printer.Printer
configDir string
}
func NewInitExecutor(printer *printer.Printer, configDir, name, summary string) *InitExecutor {
initExe := InitExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
configDir: configDir,
}
initExe.Usage = commandUsageFunc(name, summary, initExe.FlagSet)
return &initExe
}
func (i *InitExecutor) Execute() error {
if err := utilities.EnsureDirectory(i.configDir); err != nil {
return fmt.Errorf("unable to ensure that the configuration directory is present: %w", err)

View file

@ -1,40 +1,14 @@
package executor
import (
"flag"
"fmt"
"strings"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
"codeflow.dananglin.me.uk/apollo/enbas/internal/utilities"
)
type LoginExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
instance string
}
func NewLoginExecutor(printer *printer.Printer, config *config.Config, name, summary string) *LoginExecutor {
loginExe := LoginExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
instance: "",
}
loginExe.StringVar(&loginExe.instance, flagInstance, "", "Specify the instance that you want to login to.")
loginExe.Usage = commandUsageFunc(name, summary, loginExe.FlagSet)
return &loginExe
}
func (l *LoginExecutor) Execute() error {
var err error

45
internal/executor/mute.go Normal file
View file

@ -0,0 +1,45 @@
package executor
import (
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
)
func (m *MuteExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceAccount: m.muteAccount,
}
doFunc, ok := funcMap[m.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: m.resourceType}
}
gtsClient, err := client.NewClientFromFile(m.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (m *MuteExecutor) muteAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, m.accountName, m.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
form := client.MuteAccountForm{
Notifications: m.muteNotifications,
Duration: int(m.muteDuration.Duration.Seconds()),
}
if err := gtsClient.MuteAccount(accountID, form); err != nil {
return fmt.Errorf("unable to mute the account: %w", err)
}
m.printer.PrintSuccess("Successfully muted the account.")
return nil
}

View file

@ -1,108 +0,0 @@
package executor
import (
"flag"
"fmt"
"time"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type MuteOrUnmuteExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
accountName string
command string
resourceType string
muteDuration TimeDurationFlagValue
muteNotifications bool
}
func NewMuteOrUnmuteExecutor(printer *printer.Printer, config *config.Config, name, summary string) *MuteOrUnmuteExecutor {
muteDuration := TimeDurationFlagValue{time.Duration(0 * time.Second)}
exe := MuteOrUnmuteExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
command: name,
muteDuration: muteDuration,
}
exe.StringVar(&exe.accountName, flagAccountName, "", "Specify the account name in full (username@domain)")
exe.StringVar(&exe.resourceType, flagType, "", "Specify the type of resource to mute or unmute")
exe.BoolVar(&exe.muteNotifications, flagMuteNotifications, false, "Mute notifications as well as posts")
exe.Var(&exe.muteDuration, flagMuteDuration, "Specify how long the mute should last for. To mute indefinitely, set this to 0s")
exe.Usage = commandUsageFunc(name, summary, exe.FlagSet)
return &exe
}
func (m *MuteOrUnmuteExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceAccount: m.muteOrUnmuteAccount,
}
doFunc, ok := funcMap[m.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: m.resourceType}
}
gtsClient, err := client.NewClientFromFile(m.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (m *MuteOrUnmuteExecutor) muteOrUnmuteAccount(gtsClient *client.Client) error {
if m.accountName == "" {
return FlagNotSetError{flagText: flagAccountName}
}
accountID, err := getAccountID(gtsClient, false, m.accountName, m.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
switch m.command {
case CommandMute:
return m.muteAccount(gtsClient, accountID)
case CommandUnmute:
return m.unmuteAccount(gtsClient, accountID)
default:
return nil
}
}
func (m *MuteOrUnmuteExecutor) muteAccount(gtsClient *client.Client, accountID string) error {
form := client.MuteAccountForm{
Notifications: m.muteNotifications,
Duration: int(m.muteDuration.Duration.Seconds()),
}
if err := gtsClient.MuteAccount(accountID, form); err != nil {
return fmt.Errorf("unable to mute the account: %w", err)
}
m.printer.PrintSuccess("Successfully muted the account.")
return nil
}
func (m *MuteOrUnmuteExecutor) unmuteAccount(gtsClient *client.Client, accountID string) error {
if err := gtsClient.UnmuteAccount(accountID); err != nil {
return fmt.Errorf("unable to unmute the account: %w", err)
}
m.printer.PrintSuccess("Successfully unmuted the account.")
return nil
}

View file

@ -0,0 +1,40 @@
package executor
import (
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
)
func (r *RejectExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceFollowRequest: r.rejectFollowRequest,
}
doFunc, ok := funcMap[r.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: r.resourceType}
}
gtsClient, err := client.NewClientFromFile(r.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (r *RejectExecutor) rejectFollowRequest(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, r.accountName, r.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
if err := gtsClient.RejectFollowRequest(accountID); err != nil {
return fmt.Errorf("unable to reject the follow request: %w", err)
}
r.printer.PrintSuccess("Successfully rejected the follow request.")
return nil
}

View file

@ -1,48 +1,11 @@
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type RemoveExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
resourceType string
fromResourceType string
listID string
statusID string
accountNames MultiStringFlagValue
}
func NewRemoveExecutor(printer *printer.Printer, config *config.Config, name, summary string) *RemoveExecutor {
emptyArr := make([]string, 0, 3)
removeExe := RemoveExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
accountNames: MultiStringFlagValue(emptyArr),
}
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.StringVar(&removeExe.statusID, flagStatusID, "", "The ID of the status")
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}
@ -89,19 +52,19 @@ func (r *RemoveExecutor) removeAccountsFromList(gtsClient *client.Client) error
return FlagNotSetError{flagText: flagListID}
}
if len(r.accountNames) == 0 {
if r.accountNames.Empty() {
return NoAccountSpecifiedError{}
}
accountIDs := make([]string, len(r.accountNames))
accounts, err := getOtherAccounts(gtsClient, r.accountNames)
if err != nil {
return fmt.Errorf("unable to get the accounts: %w", err)
}
for ind := range r.accountNames {
accountID, err := getTheirAccountID(gtsClient, r.accountNames[ind])
if err != nil {
return fmt.Errorf("unable to get the account ID for %s: %w", r.accountNames[ind], err)
}
accountIDs := make([]string, len(accounts))
accountIDs[ind] = accountID
for ind := range accounts {
accountIDs[ind] = accounts[ind].ID
}
if err := gtsClient.RemoveAccountsFromList(r.listID, accountIDs); err != nil {
@ -130,11 +93,7 @@ func (r *RemoveExecutor) removeFromAccount(gtsClient *client.Client) error {
}
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.config.CredentialsFile)
accountID, err := getAccountID(gtsClient, false, r.accountNames, r.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}

View file

@ -1,83 +1,15 @@
package executor
import (
"flag"
"fmt"
"path/filepath"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/media"
"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
printer *printer.Printer
config *config.Config
myAccount bool
excludeBoosts bool
excludeReplies bool
onlyMedia bool
onlyPinned bool
onlyPublic bool
showInBrowser bool
showUserPreferences bool
showStatuses bool
skipAccountRelationship bool
getAllImages bool
getAllVideos bool
resourceType string
accountName string
statusID string
timelineCategory string
listID string
tag string
pollID string
fromResourceType string
limit int
attachmentIDs MultiStringFlagValue
}
func NewShowExecutor(printer *printer.Printer, config *config.Config, name, summary string) *ShowExecutor {
showExe := ShowExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
}
showExe.BoolVar(&showExe.myAccount, flagMyAccount, false, "Set to true to lookup your account")
showExe.BoolVar(&showExe.skipAccountRelationship, flagSkipRelationship, false, "Set to true to skip showing your relationship to the specified account")
showExe.BoolVar(&showExe.showUserPreferences, flagShowPreferences, false, "Show your preferences")
showExe.BoolVar(&showExe.showInBrowser, flagBrowser, false, "Set to true to view in the browser")
showExe.BoolVar(&showExe.showStatuses, flagShowStatuses, false, "Set to true to view the statuses created from the specified account")
showExe.BoolVar(&showExe.excludeReplies, flagExcludeReplies, false, "Set to true to exclude statuses that are a reply to another status")
showExe.BoolVar(&showExe.excludeBoosts, flagExcludeBoosts, false, "Set to true to exclude statuses that are boosts of another status")
showExe.BoolVar(&showExe.onlyPinned, flagOnlyPinned, false, "Set to true to show only the account's pinned statuses")
showExe.BoolVar(&showExe.onlyMedia, flagOnlyMedia, false, "Set to true to show only the statuses with media attachments")
showExe.BoolVar(&showExe.onlyPublic, flagOnlyPublic, false, "Set to true to show only the account's public posts")
showExe.BoolVar(&showExe.getAllImages, flagAllImages, false, "Set to true to show all images from a status")
showExe.BoolVar(&showExe.getAllVideos, flagAllVideos, false, "Set to true to show all videos from a status")
showExe.StringVar(&showExe.resourceType, flagType, "", "Specify the type of resource to display")
showExe.StringVar(&showExe.accountName, flagAccountName, "", "Specify the account name in full (username@domain)")
showExe.StringVar(&showExe.statusID, flagStatusID, "", "Specify the ID of the status to display")
showExe.StringVar(&showExe.timelineCategory, flagTimelineCategory, model.TimelineCategoryHome, "Specify the timeline category to view")
showExe.StringVar(&showExe.listID, flagListID, "", "Specify the ID of the list to display")
showExe.StringVar(&showExe.tag, flagTag, "", "Specify the name of the tag to use")
showExe.StringVar(&showExe.pollID, flagPollID, "", "Specify the ID of the poll to display")
showExe.Var(&showExe.attachmentIDs, flagAttachmentID, "Specify the ID of the media attachment to display")
showExe.StringVar(&showExe.fromResourceType, flagFrom, "", "Specify the resource type to view the target resource from (e.g. status for viewing media from, etc)")
showExe.IntVar(&showExe.limit, flagLimit, 20, "Specify the limit of items to display")
showExe.Usage = commandUsageFunc(name, summary, showExe.FlagSet)
return &showExe
}
func (s *ShowExecutor) Execute() error {
if s.resourceType == "" {
return FlagNotSetError{flagText: flagType}
@ -127,25 +59,9 @@ func (s *ShowExecutor) showInstance(gtsClient *client.Client) error {
}
func (s *ShowExecutor) showAccount(gtsClient *client.Client) error {
var (
account model.Account
err error
)
if s.myAccount {
account, err = getMyAccount(gtsClient, s.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account details: %w", err)
}
} else {
if s.accountName == "" {
return FlagNotSetError{flagText: flagAccountName}
}
account, err = getAccount(gtsClient, s.accountName)
if err != nil {
return fmt.Errorf("received an error while getting the account details: %w", err)
}
account, err := getAccount(gtsClient, s.myAccount, s.accountName, s.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to get the account information: %w", err)
}
if s.showInBrowser {

View file

@ -1,61 +1,38 @@
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type SwitchExecutor struct {
*flag.FlagSet
config *config.Config
printer *printer.Printer
toResourceType string
accountName string
}
func NewSwitchExecutor(printer *printer.Printer, config *config.Config, name, summary string) *SwitchExecutor {
switchExe := SwitchExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
config: config,
printer: printer,
}
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]
doFunc, ok := funcMap[s.to]
if !ok {
return UnsupportedTypeError{resourceType: s.toResourceType}
return UnsupportedTypeError{resourceType: s.to}
}
return doFunc()
}
func (s *SwitchExecutor) switchToAccount() error {
if s.accountName == "" {
return NoAccountSpecifiedError{}
expectedNumAccountNames := 1
if !s.accountName.ExpectedLength(expectedNumAccountNames) {
return fmt.Errorf(
"found an unexpected number of --account-name flags: expected %d",
expectedNumAccountNames,
)
}
if err := config.UpdateCurrentAccount(s.accountName, s.config.CredentialsFile); err != nil {
if err := config.UpdateCurrentAccount(s.accountName[0], s.config.CredentialsFile); err != nil {
return fmt.Errorf("unable to switch account to the account: %w", err)
}
s.printer.PrintSuccess("The current account is now set to '" + s.accountName + "'.")
s.printer.PrintSuccess("The current account is now set to '" + s.accountName[0] + "'.")
return nil
}

View file

@ -0,0 +1,40 @@
package executor
import (
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
)
func (b *UnblockExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceAccount: b.unblockAccount,
}
doFunc, ok := funcMap[b.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: b.resourceType}
}
gtsClient, err := client.NewClientFromFile(b.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (b *UnblockExecutor) unblockAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, b.accountName, b.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
if err := gtsClient.UnblockAccount(accountID); err != nil {
return fmt.Errorf("unable to unblock the account: %w", err)
}
b.printer.PrintSuccess("Successfully unblocked the account.")
return nil
}

View file

@ -0,0 +1,40 @@
package executor
import (
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
)
func (f *UnfollowExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceAccount: f.unfollowAccount,
}
doFunc, ok := funcMap[f.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: f.resourceType}
}
gtsClient, err := client.NewClientFromFile(f.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (f *UnfollowExecutor) unfollowAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, f.accountName, f.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
if err := gtsClient.UnfollowAccount(accountID); err != nil {
return fmt.Errorf("unable to unfollow the account: %w", err)
}
f.printer.PrintSuccess("Successfully unfollowed the account.")
return nil
}

View file

@ -0,0 +1,40 @@
package executor
import (
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/client"
)
func (m *UnmuteExecutor) Execute() error {
funcMap := map[string]func(*client.Client) error{
resourceAccount: m.unmuteAccount,
}
doFunc, ok := funcMap[m.resourceType]
if !ok {
return UnsupportedTypeError{resourceType: m.resourceType}
}
gtsClient, err := client.NewClientFromFile(m.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to create the GoToSocial client: %w", err)
}
return doFunc(gtsClient)
}
func (m *UnmuteExecutor) unmuteAccount(gtsClient *client.Client) error {
accountID, err := getAccountID(gtsClient, false, m.accountName, m.config.CredentialsFile)
if err != nil {
return fmt.Errorf("received an error while getting the account ID: %w", err)
}
if err := gtsClient.UnmuteAccount(accountID); err != nil {
return fmt.Errorf("unable to unmute the account: %w", err)
}
m.printer.PrintSuccess("Successfully unmuted the account.")
return nil
}

View file

@ -1,51 +1,7 @@
package executor
import (
"flag"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type VersionExecutor struct {
*flag.FlagSet
printer *printer.Printer
showFullVersion bool
binaryVersion string
buildTime string
goVersion string
gitCommit string
}
func NewVersionExecutor(
printer *printer.Printer,
name,
summary,
binaryVersion,
buildTime,
goVersion,
gitCommit string,
) *VersionExecutor {
command := VersionExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
binaryVersion: binaryVersion,
buildTime: buildTime,
goVersion: goVersion,
gitCommit: gitCommit,
showFullVersion: false,
}
command.BoolVar(&command.showFullVersion, flagFull, false, "prints the full build information")
command.Usage = commandUsageFunc(name, summary, command.FlagSet)
return &command
}
func (v *VersionExecutor) Execute() error {
v.printer.PrintVersion(v.showFullVersion, v.binaryVersion, v.buildTime, v.goVersion, v.gitCommit)
v.printer.PrintVersion(v.full)
return nil
}

View file

@ -1,40 +1,18 @@
package executor
import (
"flag"
"fmt"
"codeflow.dananglin.me.uk/apollo/enbas/internal/config"
"codeflow.dananglin.me.uk/apollo/enbas/internal/printer"
)
type WhoAmIExecutor struct {
*flag.FlagSet
printer *printer.Printer
config *config.Config
}
func NewWhoAmIExecutor(printer *printer.Printer, config *config.Config, name, summary string) *WhoAmIExecutor {
whoExe := WhoAmIExecutor{
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
printer: printer,
config: config,
}
whoExe.Usage = commandUsageFunc(name, summary, whoExe.FlagSet)
return &whoExe
}
func (c *WhoAmIExecutor) Execute() error {
config, err := config.NewCredentialsConfigFromFile(c.config.CredentialsFile)
func (e *WhoamiExecutor) Execute() error {
config, err := config.NewCredentialsConfigFromFile(e.config.CredentialsFile)
if err != nil {
return fmt.Errorf("unable to load the credential config: %w", err)
}
c.printer.PrintInfo("You are logged in as '" + config.CurrentAccount + "'.\n")
e.printer.PrintInfo("You are logged in as '" + config.CurrentAccount + "'.\n")
return nil
}

View file

@ -0,0 +1,36 @@
package flag
import (
"fmt"
"strconv"
)
type BoolPtrValue struct {
Value *bool
}
func NewBoolPtrValue() BoolPtrValue {
return BoolPtrValue{
Value: nil,
}
}
func (b BoolPtrValue) String() string {
if b.Value == nil {
return "NOT SET"
}
return strconv.FormatBool(*b.Value)
}
func (b *BoolPtrValue) Set(value string) error {
boolVar, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("unable to parse %q as a boolean value: %w", value, err)
}
b.Value = new(bool)
*b.Value = boolVar
return nil
}

49
internal/flag/intslice.go Normal file
View file

@ -0,0 +1,49 @@
package flag
import (
"fmt"
"strconv"
"strings"
)
type IntSliceValue []int
func NewIntSliceValue() IntSliceValue {
arr := make([]int, 0, 3)
return IntSliceValue(arr)
}
func (v IntSliceValue) String() string {
var builder strings.Builder
for ind, value := range v {
if ind == len(v)-1 {
builder.WriteString(strconv.Itoa(value))
} else {
builder.WriteString(strconv.Itoa(value))
builder.WriteString(", ")
}
}
return builder.String()
}
func (v *IntSliceValue) Set(text string) error {
value, err := strconv.Atoi(text)
if err != nil {
return fmt.Errorf("unable to parse the value to an integer: %w", err)
}
*v = append(*v, value)
return nil
}
func (v IntSliceValue) Empty() bool {
return len(v) == 0
}
func (v IntSliceValue) ExpectedLength(expectedLength int) bool {
return len(v) == expectedLength
}

View file

@ -0,0 +1,31 @@
package flag
import "strings"
type StringSliceValue []string
func NewStringSliceValue() StringSliceValue {
arr := make([]string, 0, 3)
return StringSliceValue(arr)
}
func (v StringSliceValue) String() string {
return strings.Join(v, ", ")
}
func (v *StringSliceValue) Set(value string) error {
if len(value) > 0 {
*v = append(*v, value)
}
return nil
}
func (v StringSliceValue) Empty() bool {
return len(v) == 0
}
func (v StringSliceValue) ExpectedLength(expectedLength int) bool {
return len(v) == expectedLength
}

View file

@ -0,0 +1,31 @@
package flag
import (
"fmt"
"time"
)
type TimeDurationValue struct {
Duration time.Duration
}
func NewTimeDurationValue() TimeDurationValue {
return TimeDurationValue{
Duration: 0 * time.Second,
}
}
func (v TimeDurationValue) String() string {
return v.Duration.String()
}
func (v *TimeDurationValue) Set(text string) error {
duration, err := time.ParseDuration(text)
if err != nil {
return fmt.Errorf("unable to parse the value as time duration: %w", err)
}
v.Duration = duration
return nil
}

View file

@ -3,11 +3,13 @@ package printer
import (
"strings"
"text/tabwriter"
"codeflow.dananglin.me.uk/apollo/enbas/internal/version"
)
func (p Printer) PrintVersion(showFullVersion bool, binaryVersion, buildTime, goVersion, gitCommit string) {
func (p Printer) PrintVersion(showFullVersion bool) {
if !showFullVersion {
printToStdout("Enbas " + binaryVersion + "\n")
printToStdout("Enbas " + version.BinaryVersion + "\n")
return
}
@ -18,10 +20,10 @@ func (p Printer) PrintVersion(showFullVersion bool, binaryVersion, buildTime, go
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.Write([]byte(p.fieldFormat("Version:") + "\t" + version.BinaryVersion + "\n"))
_, _ = tableWriter.Write([]byte(p.fieldFormat("Git commit:") + "\t" + version.GitCommit + "\n"))
_, _ = tableWriter.Write([]byte(p.fieldFormat("Go version:") + "\t" + version.GoVersion + "\n"))
_, _ = tableWriter.Write([]byte(p.fieldFormat("Build date:") + "\t" + version.BuildTime + "\n"))
tableWriter.Flush()

View file

@ -1,4 +1,4 @@
package main
package usage
import (
"flag"
@ -6,9 +6,11 @@ import (
"slices"
"strings"
"text/tabwriter"
"codeflow.dananglin.me.uk/apollo/enbas/internal/version"
)
func usageFunc(summaries map[string]string) func() {
func AppUsageFunc() func() {
cmds := make([]string, len(summaries))
ind := 0
@ -24,8 +26,8 @@ func usageFunc(summaries map[string]string) func() {
builder.WriteString("SUMMARY:\n enbas - A GoToSocial client for the terminal.\n\n")
if binaryVersion != "" {
builder.WriteString("VERSION:\n " + binaryVersion + "\n\n")
if version.BinaryVersion != "" {
builder.WriteString("VERSION:\n " + version.BinaryVersion + "\n\n")
}
builder.WriteString("USAGE:\n enbas [flags]\n enbas [flags] [command]\n\nCOMMANDS:")

View file

@ -0,0 +1,3 @@
package usage
//go:generate go run ../../cmd/enbas-codegen --package usage --path-to-enbas-cli-schema ../../schema/enbas_cli_schema.json

View file

@ -1,4 +1,4 @@
package executor
package usage
import (
"flag"
@ -6,8 +6,8 @@ import (
"strings"
)
// commandUsageFunc returns the function used to print a command's help page.
func commandUsageFunc(name, summary string, flagset *flag.FlagSet) func() {
// ExecutorUsageFunc returns the function used to print a command's help page.
func ExecutorUsageFunc(name, summary string, flagset *flag.FlagSet) func() {
return func() {
var builder strings.Builder

View file

@ -0,0 +1,28 @@
/*
This file is generated by the enbas-codegen
DO NOT EDIT.
*/
package usage
var summaries = map[string]string{
"accept": "Accepts a request (e.g. a follow request)",
"add": "Adds a resource to another resource",
"block": "Blocks a resource (e.g. an account)",
"create": "Creates a specific resource",
"delete": "Deletes a specific resource",
"edit": "Edit a specific resource",
"follow": "Follow a resource (e.g. an account)",
"init": "Creates a new configuration file in the specified configuration directory",
"login": "Logs into an account on GoToSocial",
"mute": "Mutes a specific resource (e.g. an account)",
"reject": "Rejects a request (e.g. a follow request)",
"remove": "Removes a resource from another resource",
"show": "Shows details about a specified resource",
"switch": "Performs a switch operation (e.g. switching between logged in accounts)",
"unblock": "Unblocks a resource (e.g. an account)",
"unfollow": "Unfollows a resource (e.g. an account)",
"unmute": "Umutes a specific resource (e.g. an account)",
"version": "Prints the application's version and build information",
"whoami": "Prints the account that you are currently logged into",
}

View file

@ -0,0 +1,8 @@
package version
var (
BinaryVersion string //nolint:gochecknoglobals
BuildTime string //nolint:gochecknoglobals
GoVersion string //nolint:gochecknoglobals
GitCommit string //nolint:gochecknoglobals
)

View file

@ -110,10 +110,21 @@ func Clean() error {
// ldflags returns the build flags.
func ldflags() string {
ldflagsfmt := "-s -w -X main.binaryVersion=%s -X main.gitCommit=%s -X main.goVersion=%s -X main.buildTime=%s"
versionPackage := "codeflow.dananglin.me.uk/apollo/enbas/internal/version"
binaryVersionVar := versionPackage + "." + "BinaryVersion"
gitCommitVar := versionPackage + "." + "GitCommit"
goVersionVar := versionPackage + "." + "GoVersion"
buildTimeVar := versionPackage + "." + "BuildTime"
ldflagsfmt := "-s -w -X %s=%s -X %s=%s -X %s=%s -X %s=%s"
buildTime := time.Now().UTC().Format(time.RFC3339)
return fmt.Sprintf(ldflagsfmt, version(), gitCommit(), runtime.Version(), buildTime)
return fmt.Sprintf(
ldflagsfmt,
binaryVersionVar, version(),
gitCommitVar, gitCommit(),
goVersionVar, runtime.Version(),
buildTimeVar, buildTime,
)
}
// version returns the latest git tag using git describe.

View file

@ -0,0 +1,440 @@
{
"flags": {
"account-name": {
"type": "StringSliceValue",
"description": "The name of the account"
},
"all-images": {
"type": "bool",
"description": "Set to true to show all images from a status"
},
"all-videos": {
"type": "bool",
"description": "Set to true to show all videos from a status"
},
"attachment-id": {
"type": "StringSliceValue",
"description": "The ID of the media attachment"
},
"add-poll": {
"type": "bool",
"description": "Set to true to add a poll when creating a status"
},
"browser": {
"type": "bool",
"description": "Set to true to view in the your favourite browser"
},
"content": {
"type": "string",
"description": "The content of the created resource"
},
"content-type": {
"type": "string",
"description": "The type that the contents should be parsed from (valid values are plain and markdown)"
},
"enable-federation": {
"type": "bool",
"description": "Set to true to federate the status beyond the local timelines"
},
"enable-likes": {
"type": "bool",
"description": "Set to true to allow the status to be liked (favourited)"
},
"enable-replies": {
"type": "bool",
"description": "Set to true to allow viewers to reply to the status"
},
"enable-reposts": {
"type": "bool",
"description": "Set to true to allow the status to be reposted (boosted) by others"
},
"exclude-boosts": {
"type": "bool",
"description": "Set to true to exclude statuses that are boosts of another status"
},
"exclude-replies": {
"type": "bool",
"description": "Set to true to exclude statuses that are a reply to another status"
},
"from": {
"type": "string",
"description": "The resource type to action the target resource from (e.g. status)"
},
"from-file": {
"type": "string",
"description": "The file path where to read the contents from"
},
"full": {
"type": "bool",
"description": "Set to true to print the build information in full"
},
"in-reply-to": {
"type": "string",
"description": "The ID of the status that you want to reply to"
},
"instance": {
"type": "string",
"description": "The instance that you want to log into"
},
"language": {
"type": "string",
"description": "The ISO 639 language code for this status"
},
"limit": {
"type": "int",
"description": "Specify the limit of items to display"
},
"list-id": {
"type": "string",
"description": "The ID of the list in question"
},
"list-title": {
"type": "string",
"description": "The title of the list"
},
"list-replies-policy": {
"type": "string",
"description": "The replies policy of the list"
},
"mute-duration": {
"type": "TimeDurationValue",
"description": "Specify how long the mute should last for. To mute indefinitely, set this to 0s"
},
"mute-notifications": {
"type": "bool",
"description": "Set to true to mute notifications as well as posts"
},
"my-account": {
"type": "bool",
"description": "Set to true to specify your account"
},
"notify": {
"type": "bool",
"description": "Get notifications from statuses from the account you want to follow"
},
"only-media": {
"type": "bool",
"description": "Set to true to show only the statuses with media attachments"
},
"only-pinned": {
"type": "bool",
"description": "Set to true to show only the account's pinned statuses"
},
"only-public": {
"type": "bool",
"description": "Set to true to show only the account's public posts"
},
"poll-allows-multiple-choices": {
"type": "bool",
"description": "Set to true to allow viewers to make multiple choices in the poll"
},
"poll-expires-in": {
"type": "TimeDurationValue",
"description": "The duration in which the poll is open for"
},
"poll-hides-vote-counts": {
"type": "bool",
"description": "Set to true to hide the vote count until the poll is closed"
},
"poll-id": {
"type": "string",
"description": "The ID of the poll"
},
"poll-option": {
"type": "StringSliceValue",
"description": "A poll option. Use this multiple times to set multiple options"
},
"sensitive": {
"type": "BoolPtrValue",
"description": "Set to true if the status should be marked as sensitive"
},
"show-preferences": {
"type": "bool",
"description": "Set to true to view your posting preferences when viewing your account information"
},
"show-reposts": {
"type": "bool",
"description": "Show reposts from the account you want to follow"
},
"show-statuses": {
"type": "bool",
"description": "Set to true to view the statuses created from the account you are viewing"
},
"skip-relationship": {
"type": "bool",
"description": "Set to true to skip showing your relationship to the account that you are viewing"
},
"spoiler-text": {
"type": "string",
"description": "The text to display as the status' warning or subject"
},
"status-id": {
"type": "string",
"description": "The ID of the status"
},
"tag": {
"type": "string",
"description": "The name of the tag"
},
"timeline-category": {
"type": "string",
"description": "The timeline category"
},
"to": {
"type": "string",
"description": "The resource type to action the target resource to (e.g. status)"
},
"type": {
"type": "string",
"description": "The type of resource you want to action on (e.g. account, status)"
},
"visibility": {
"type": "string",
"description": "The visibility of the posted status"
},
"vote": {
"type": "IntSliceValue",
"description": "Add a vote to an option in a poll"
}
},
"commands": {
"accept": {
"additionalFields": [],
"flags": [
{ "flag": "account-name" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Accepts a request (e.g. a follow request)",
"useConfig": true,
"usePrinter": true
},
"add": {
"additionalFields": [],
"flags": [
{ "flag": "account-name", "fieldName": "accountNames" },
{ "flag": "content", "default": "" },
{ "flag": "list-id", "fieldName": "listID", "default": "" },
{ "flag": "poll-id", "fieldName": "pollID", "default": "" },
{ "flag": "status-id", "fieldName": "statusID", "default": "" },
{ "flag": "to", "fieldName": "toResourceType", "default": "" },
{ "flag": "type", "fieldName": "resourceType", "default": "" },
{ "flag": "vote", "fieldName": "votes" }
],
"summary": "Adds a resource to another resource",
"useConfig": true,
"usePrinter": true
},
"block": {
"additionalFields": [],
"flags": [
{ "flag": "account-name" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Blocks a resource (e.g. an account)",
"useConfig": true,
"usePrinter": true
},
"create": {
"additionalFields": [],
"flags": [
{ "flag": "add-poll", "default": "false" },
{ "flag": "content", "default": "" },
{ "flag": "content-type", "default": "plain" },
{ "flag": "enable-federation", "fieldName": "federated", "default": "true" },
{ "flag": "enable-likes", "fieldName": "likeable", "default": "true" },
{ "flag": "enable-replies", "fieldName": "replyable", "default": "true" },
{ "flag": "enable-reposts", "fieldName": "boostable", "default": "true" },
{ "flag": "from-file", "default": "" },
{ "flag": "in-reply-to", "default": "" },
{ "flag": "language", "default": "" },
{ "flag": "list-replies-policy", "default": "list" },
{ "flag": "list-title", "default": "" },
{ "flag": "poll-allows-multiple-choices", "default": "false" },
{ "flag": "poll-expires-in" },
{ "flag": "poll-hides-vote-counts", "default": "false" },
{ "flag": "poll-option", "fieldName": "pollOptions" },
{ "flag": "sensitive" },
{ "flag": "spoiler-text", "default": "" },
{ "flag": "type", "fieldName": "resourceType", "default": "" },
{ "flag": "visibility", "default": "" }
],
"summary": "Creates a specific resource",
"useConfig": true,
"usePrinter": true
},
"delete": {
"additionalFields": [],
"flags": [
{ "flag": "list-id", "fieldName": "listID", "default": ""},
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Deletes a specific resource",
"useConfig": true,
"usePrinter": true
},
"edit": {
"additionalFields": [],
"flags": [
{ "flag": "list-id", "fieldName": "listID", "default": ""},
{ "flag": "list-title", "default": "" },
{ "flag": "list-replies-policy", "default": "" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Edit a specific resource",
"useConfig": true,
"usePrinter": true
},
"follow": {
"additionalFields": [],
"flags": [
{ "flag": "account-name" },
{ "flag": "notify", "default": "false" },
{ "flag": "show-reposts", "default": "true" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Follow a resource (e.g. an account)",
"useConfig": true,
"usePrinter": true
},
"init": {
"additionalFields": [
{ "name": "configDir", "type": "string"}
],
"flags": [],
"summary": "Creates a new configuration file in the specified configuration directory",
"useConfig": false,
"usePrinter": true
},
"login": {
"additionalFields": [],
"flags": [
{ "flag": "instance", "default": "" }
],
"summary": "Logs into an account on GoToSocial",
"useConfig": true,
"usePrinter": true
},
"mute": {
"additionalFields": [],
"flags": [
{ "flag": "account-name" },
{ "flag": "mute-duration" },
{ "flag": "mute-notifications", "default": "false" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Mutes a specific resource (e.g. an account)",
"useConfig": true,
"usePrinter": true
},
"reject": {
"additionalFields": [],
"flags": [
{ "flag": "account-name" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Rejects a request (e.g. a follow request)",
"useConfig": true,
"usePrinter": true
},
"remove": {
"additionalFields": [],
"flags": [
{ "flag": "account-name", "fieldName": "accountNames" },
{ "flag": "from", "fieldName": "fromResourceType", "default": "" },
{ "flag": "list-id", "fieldName": "listID", "default": "" },
{ "flag": "status-id", "fieldName": "statusID", "default": "" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Removes a resource from another resource",
"useConfig": true,
"usePrinter": true
},
"show": {
"additionalFields": [],
"flags": [
{ "flag": "account-name" },
{ "flag": "all-images", "fieldName": "getAllImages", "default": "false" },
{ "flag": "all-videos", "fieldName": "getAllVideos", "default": "false" },
{ "flag": "attachment-id", "fieldName": "attachmentIDs" },
{ "flag": "browser", "fieldName": "showInBrowser", "default": "false" },
{ "flag": "exclude-boosts", "default": "false" },
{ "flag": "exclude-replies", "default": "false" },
{ "flag": "from", "fieldName": "fromResourceType", "default": "" },
{ "flag": "limit", "default": "20" },
{ "flag": "list-id", "fieldName": "listID", "default": "" },
{ "flag": "my-account", "default": "false" },
{ "flag": "only-media", "default": "false" },
{ "flag": "only-pinned", "default": "false" },
{ "flag": "only-public", "default": "false" },
{ "flag": "poll-id", "fieldName": "pollID", "default": "" },
{ "flag": "show-preferences", "fieldName": "showUserPreferences", "default": "false" },
{ "flag": "show-statuses", "default": "false" },
{ "flag": "skip-relationship", "fieldName": "skipAccountRelationship", "default": "false" },
{ "flag": "status-id", "fieldName": "statusID", "default": "" },
{ "flag": "timeline-category", "default": "home" },
{ "flag": "tag", "default": "" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Shows details about a specified resource",
"useConfig": true,
"usePrinter": true
},
"switch": {
"additionalFields": [],
"flags": [
{ "flag": "account-name" },
{ "flag": "to", "default": "" }
],
"summary": "Performs a switch operation (e.g. switching between logged in accounts)",
"useConfig": true,
"usePrinter": true
},
"unblock": {
"additionalFields": [],
"flags": [
{ "flag": "account-name" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Unblocks a resource (e.g. an account)",
"useConfig": true,
"usePrinter": true
},
"unfollow": {
"additionalFields": [],
"flags": [
{ "flag": "account-name" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Unfollows a resource (e.g. an account)",
"useConfig": true,
"usePrinter": true
},
"unmute": {
"additionalFields": [],
"flags": [
{ "flag": "account-name" },
{ "flag": "type", "fieldName": "resourceType", "default": "" }
],
"summary": "Umutes a specific resource (e.g. an account)",
"useConfig": true,
"usePrinter": true
},
"version": {
"additionalFields": [],
"flags": [
{ "flag": "full", "default": "false" }
],
"summary": "Prints the application's version and build information",
"useConfig": false,
"usePrinter": true
},
"whoami": {
"additionalFields": [],
"flags": [],
"summary": "Prints the account that you are currently logged into",
"useConfig": true,
"usePrinter": true
}
}
}