From 2be8e5c6a64e7f549e2a058c31639c6eecd687c8 Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Mon, 15 Jul 2024 18:17:02 -0500 Subject: [PATCH] feat(cmd): Add `--url` flag to load a pattern URL --- cmd/cmd.go | 11 +++++++++-- docs/cli-of-life.md | 3 ++- internal/config/config.go | 1 + internal/config/flags.go | 4 +++- internal/pattern/pattern.go | 39 +++++++++++++++++++++++++++++++++++++ 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index 1276438..2038c1a 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -51,9 +51,16 @@ func run(cmd *cobra.Command, _ []string) error { pat := pattern.Pattern{ Rule: rule, } - if conf.File != "" { + format := pattern.Format(conf.FileFormat) + switch { + case conf.File != "": var err error - if pat, err = pattern.UnmarshalFile(conf.File, pattern.Format(conf.FileFormat)); err != nil { + if pat, err = pattern.UnmarshalFile(conf.File, format); err != nil { + return err + } + case conf.URL != "": + var err error + if pat, err = pattern.UnmarshalURL(context.Background(), conf.URL, format); err != nil { return err } } diff --git a/docs/cli-of-life.md b/docs/cli-of-life.md index 707222d..3016c22 100644 --- a/docs/cli-of-life.md +++ b/docs/cli-of-life.md @@ -10,12 +10,13 @@ cli-of-life [flags] ``` --completion string Output command-line completion code for the specified shell (one of: bash, zsh, fish, powershell) - -f, --file string Loads a pattern file on startup + -f, --file string Load a pattern file --file-format string File format (one of: auto, rle, plaintext) (default "auto") --height uint Board height. Will be ignored if a larger pattern is loaded. (default 600) -h, --help help for cli-of-life --play Play on startup --rule-string string Rule string to use. This will be ignored if a pattern file is loaded. (default "B3/S23") + --url string Load a pattern URL --width uint Board width. Will be ignored if a larger pattern is loaded. (default 600) ``` diff --git a/internal/config/config.go b/internal/config/config.go index 76aa366..99d25b4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,6 +4,7 @@ import "github.com/gabe565/cli-of-life/internal/pattern" type Config struct { File string + URL string FileFormat string RuleString string Play bool diff --git a/internal/config/flags.go b/internal/config/flags.go index 7bcc47c..4f4398d 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -9,6 +9,7 @@ import ( const ( FileFlag = "file" + URLFlag = "url" FileFormatFlag = "file-format" RuleStringFlag = "rule-string" PlayFlag = "play" @@ -18,7 +19,8 @@ const ( ) func (c *Config) RegisterFlags(fs *pflag.FlagSet) { - fs.StringVarP(&c.File, FileFlag, "f", c.File, "Loads a pattern file on startup") + fs.StringVarP(&c.File, FileFlag, "f", c.File, "Load a pattern file") + fs.StringVar(&c.URL, URLFlag, c.URL, "Load a pattern URL") fs.StringVar(&c.FileFormat, FileFormatFlag, c.FileFormat, "File format (one of: "+strings.Join(pattern.FormatStrings(), ", ")+")") fs.StringVar(&c.RuleString, RuleStringFlag, c.RuleString, "Rule string to use. This will be ignored if a pattern file is loaded.") fs.BoolVar(&c.Play, PlayFlag, c.Play, "Play on startup") diff --git a/internal/pattern/pattern.go b/internal/pattern/pattern.go index d1237a6..4c3bb4c 100644 --- a/internal/pattern/pattern.go +++ b/internal/pattern/pattern.go @@ -2,10 +2,13 @@ package pattern import ( "bytes" + "context" "errors" "fmt" "io" + "net/http" "os" + "path" "path/filepath" ) @@ -67,6 +70,42 @@ func UnmarshalFile(path string, format Format) (Pattern, error) { } } +var ErrResponse = errors.New("received an error response") + +func UnmarshalURL(ctx context.Context, url string, format Format) (Pattern, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return Pattern{}, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return Pattern{}, err + } + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + if resp.StatusCode != http.StatusOK { + return Pattern{}, ErrResponse + } + + ext := path.Ext(url) + switch { + case format == FormatRLE, ext == ExtRLE: + return UnmarshalRLE(resp.Body) + case format == FormatPlaintext, ext == ExtPlaintext: + return UnmarshalPlaintext(resp.Body) + default: + pattern, err := Unmarshal(resp.Body) + if err != nil { + err = fmt.Errorf("%w: %s", err, url) + } + return pattern, err + } +} + func Unmarshal(r io.Reader) (Pattern, error) { buf, err := io.ReadAll(r) if err != nil {