diff --git a/cmd/enbas/main.go b/cmd/enbas/main.go index 60ac439..cd7eb09 100644 --- a/cmd/enbas/main.go +++ b/cmd/enbas/main.go @@ -10,6 +10,7 @@ import ( "os" "strconv" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/executor" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" ) @@ -29,22 +30,11 @@ func main() { func run() error { var ( - configDir string - cacheDir string - pager string - imageViewer string - videoPlayer string - maxTerminalWidth int - noColor *bool + configDir string + noColor *bool ) flag.StringVar(&configDir, "config-dir", "", "Specify your config directory") - flag.StringVar(&cacheDir, "cache-dir", "", "Specify your cache directory") - flag.StringVar(&pager, "pager", "", "Specify your preferred pager to page through long outputs. This is disabled by default.") - flag.StringVar(&imageViewer, "image-viewer", "", "Specify your favourite image viewer.") - flag.StringVar(&videoPlayer, "video-player", "", "Specify your favourite video player.") - flag.IntVar(&maxTerminalWidth, "max-terminal-width", 80, "Specify the maximum terminal width when displaying resources on screen.") - flag.BoolFunc("no-color", "Disable ANSI colour output when displaying text on screen", func(value string) error { boolVal, err := strconv.ParseBool(value) if err != nil { @@ -80,7 +70,15 @@ func run() error { command := flag.Arg(0) args := flag.Args()[1:] - printer := printer.NewPrinter(*noColor, pager, maxTerminalWidth) + config, err := config.NewConfigFromFile(configDir) + if err != nil { + printer := printer.NewPrinter(*noColor, "", 80) + printer.PrintFailure("unable to load the configuration: " + err.Error() + ".") + + return err + } + + printer := printer.NewPrinter(*noColor, config.Integrations.Pager, config.LineWrapMaxWidth) executorMap := map[string]executor.Executor{ executor.CommandAccept: executor.NewAcceptOrRejectExecutor( @@ -175,10 +173,8 @@ func run() error { ), executor.CommandShow: executor.NewShowExecutor( printer, + &config, configDir, - cacheDir, - imageViewer, - videoPlayer, executor.CommandShow, executor.CommandSummaryLookup(executor.CommandShow), ), diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..1b40503 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,103 @@ +package config + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" +) + +const ( + configFileName = "config.json" +) + +type Config struct { + CacheDirectory string `json:"cacheDirectory"` + LineWrapMaxWidth int `json:"lineWrapMaxWidth"` + HTTP HTTPConfig `json:"http"` + Integrations Integrations `json:"integrations"` +} + +type HTTPConfig struct { + Timeout int `json:"timeout"` + MediaTimeout int `json:"mediaTimeout"` +} + +type Integrations struct { + Browser string `json:"browser"` + Editor string `json:"editor"` + Pager string `json:"pager"` + ImageViewer string `json:"imageViewer"` + VideoPlayer string `json:"videoPlayer"` +} + +func NewConfigFromFile(configDir string) (Config, error) { + path := configFile(configDir) + + fileExists, err := utilities.FileExists(path) + if err != nil { + return Config{}, fmt.Errorf("unable to check if the config file exists: %w", err) + } + + if !fileExists { + if err := saveDefaultConfigToFile(path); err != nil { + return Config{}, fmt.Errorf("unable to save the default config to file: %w", err) + } + } + + file, err := os.Open(path) + if err != nil { + return Config{}, fmt.Errorf("unable to open %s: %w", path, err) + } + defer file.Close() + + var config Config + + if err := json.NewDecoder(file).Decode(&config); err != nil { + return Config{}, fmt.Errorf("unable to decode the JSON data: %w", err) + } + + return config, nil +} + +func configFile(configDir string) string { + return filepath.Join(utilities.CalculateConfigDir(configDir), configFileName) +} + +func saveDefaultConfigToFile(path string) error { + file, err := os.Create(path) + if err != nil { + return fmt.Errorf("unable to create the file at %s: %w", path, err) + } + defer file.Close() + + config := defaultConfig() + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + + if err := encoder.Encode(config); err != nil { + return fmt.Errorf("unable to save the JSON data to the config file: %w", err) + } + + return nil +} + +func defaultConfig() Config { + return Config{ + CacheDirectory: "", + HTTP: HTTPConfig{ + Timeout: 5, + MediaTimeout: 30, + }, + LineWrapMaxWidth: 80, + Integrations: Integrations{ + Browser: "", + Editor: "", + Pager: "", + ImageViewer: "", + VideoPlayer: "", + }, + } +} diff --git a/internal/config/credentials.go b/internal/config/credentials.go index 47f6bef..ace8bd7 100644 --- a/internal/config/credentials.go +++ b/internal/config/credentials.go @@ -109,7 +109,7 @@ func NewCredentialsConfigFromFile(configDir string) (CredentialsConfig, error) { file, err := os.Open(path) if err != nil { - return CredentialsConfig{}, fmt.Errorf("unable to open %s, %w", path, err) + return CredentialsConfig{}, fmt.Errorf("unable to open %s: %w", path, err) } defer file.Close() @@ -127,9 +127,8 @@ func saveCredentialsConfigFile(authConfig CredentialsConfig, configDir string) e file, err := os.Create(path) if err != nil { - return fmt.Errorf("unable to open %s: %w", path, err) + return fmt.Errorf("unable to create the file at %s: %w", path, err) } - defer file.Close() encoder := json.NewEncoder(file) diff --git a/internal/executor/errors.go b/internal/executor/errors.go index 3e5f571..3094371 100644 --- a/internal/executor/errors.go +++ b/internal/executor/errors.go @@ -102,3 +102,11 @@ type NotFollowingError struct { func (e NotFollowingError) Error() string { return "you are not following " + e.Account } + +type UnknownMediaAttachmentError struct { + AttachmentID string +} + +func (e UnknownMediaAttachmentError) Error() string { + return "unknown media attachment '" + e.AttachmentID + "'" +} diff --git a/internal/executor/show.go b/internal/executor/show.go index 2f09a5f..dbb2604 100644 --- a/internal/executor/show.go +++ b/internal/executor/show.go @@ -11,6 +11,7 @@ import ( "strings" "codeflow.dananglin.me.uk/apollo/enbas/internal/client" + "codeflow.dananglin.me.uk/apollo/enbas/internal/config" "codeflow.dananglin.me.uk/apollo/enbas/internal/model" "codeflow.dananglin.me.uk/apollo/enbas/internal/printer" "codeflow.dananglin.me.uk/apollo/enbas/internal/utilities" @@ -20,12 +21,12 @@ type ShowExecutor struct { *flag.FlagSet printer *printer.Printer + config *config.Config myAccount bool skipAccountRelationship bool showUserPreferences bool showInBrowser bool configDir string - cacheRoot string resourceType string accountName string statusID string @@ -34,21 +35,17 @@ type ShowExecutor struct { tag string pollID string fromResourceType string - imageViewer string - videoPlayer string limit int attachmentIDs MultiStringFlagValue } -func NewShowExecutor(printer *printer.Printer, configDir, cacheRoot, imageViewer, videoPlayer, name, summary string) *ShowExecutor { +func NewShowExecutor(printer *printer.Printer, config *config.Config, configDir, name, summary string) *ShowExecutor { showExe := ShowExecutor{ FlagSet: flag.NewFlagSet(name, flag.ExitOnError), - printer: printer, - configDir: configDir, - cacheRoot: cacheRoot, - imageViewer: imageViewer, - videoPlayer: videoPlayer, + printer: printer, + config: config, + configDir: configDir, } showExe.BoolVar(&showExe.myAccount, flagMyAccount, false, "Set to true to lookup your account") @@ -471,7 +468,7 @@ func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error { } cacheDir := filepath.Join( - utilities.CalculateCacheDir(s.cacheRoot, utilities.GetFQDN(gtsClient.Authentication.Instance)), + utilities.CalculateCacheDir(s.config.CacheDirectory, utilities.GetFQDN(gtsClient.Authentication.Instance)), "media", ) @@ -498,7 +495,7 @@ func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error { for _, attachmentID := range s.attachmentIDs { mediaObj, ok := attachmentsHashMap[attachmentID] if !ok { - return fmt.Errorf("unknown media attachment: %s", attachmentID) + return UnknownMediaAttachmentError{AttachmentID: attachmentID} } split := strings.Split(mediaObj.url, "/") @@ -533,13 +530,13 @@ func (s *ShowExecutor) showMediaFromStatus(gtsClient *client.Client) error { } if len(imageFiles) > 0 { - if err := utilities.OpenMedia(s.imageViewer, imageFiles); err != nil { + if err := utilities.OpenMedia(s.config.Integrations.ImageViewer, imageFiles); err != nil { return fmt.Errorf("unable to open the image viewer: %w", err) } } if len(videoFiles) > 0 { - if err := utilities.OpenMedia(s.videoPlayer, videoFiles); err != nil { + if err := utilities.OpenMedia(s.config.Integrations.VideoPlayer, videoFiles); err != nil { return fmt.Errorf("unable to open the video player: %w", err) } } diff --git a/internal/utilities/directories.go b/internal/utilities/directories.go index 8a5d871..afa9233 100644 --- a/internal/utilities/directories.go +++ b/internal/utilities/directories.go @@ -20,7 +20,7 @@ func CalculateConfigDir(configDir string) string { configRoot, err := os.UserConfigDir() if err != nil { - configRoot = "." + return filepath.Join(os.Getenv("HOME"), "."+internal.ApplicationName, "config") } return filepath.Join(configRoot, internal.ApplicationName) @@ -33,7 +33,7 @@ func CalculateCacheDir(cacheDir, instanceFQDN string) string { cacheRoot, err := os.UserCacheDir() if err != nil { - cacheRoot = "." + return filepath.Join(os.Getenv("HOME"), "."+internal.ApplicationName, "cache") } return filepath.Join(cacheRoot, internal.ApplicationName, instanceFQDN)