From 3abc23e7d86b3fcfec4bba2889ecc982153743a3 Mon Sep 17 00:00:00 2001 From: Stephen Soltesz Date: Fri, 19 Jun 2020 16:20:47 +0000 Subject: [PATCH] Add flagx.File type (#124) --- flagx/file.go | 42 +++++++++++++++++++++++++++++ flagx/file_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 flagx/file.go create mode 100644 flagx/file_test.go diff --git a/flagx/file.go b/flagx/file.go new file mode 100644 index 0000000..fd7b7ec --- /dev/null +++ b/flagx/file.go @@ -0,0 +1,42 @@ +package flagx + +import ( + "io/ioutil" +) + +// File is a new flag type. For a given filename, File reads and saves the file +// content to Bytes and the original filename to Name. Errors opening or +// reading the file are handled during flag parsing. +type File struct { + Bytes []byte + Name string +} + +// Get retrieves the bytes read from the file. +func (fb *File) Get() []byte { + return fb.Bytes +} + +// Content retrieves the bytes read from the file as a string. +func (fb *File) Content() string { + return string(fb.Bytes) +} + +// Set accepts a file name. On success, the file content is saved to Bytes, and +// the original file name to Name. +func (fb *File) Set(s string) error { + b, err := ioutil.ReadFile(s) + if err != nil { + return err + } + fb.Name = s + fb.Bytes = b + return nil +} + +// String reports the original file Name. NOTE: String is typically used by the +// flag help text and to report flag values. To return the file content as a +// string, see File.Content(). +func (fb *File) String() string { + return fb.Name +} diff --git a/flagx/file_test.go b/flagx/file_test.go new file mode 100644 index 0000000..f48290d --- /dev/null +++ b/flagx/file_test.go @@ -0,0 +1,67 @@ +package flagx_test + +import ( + "flag" + "io/ioutil" + "os" + "testing" + + "github.com/m-lab/go/rtx" + + "github.com/m-lab/go/flagx" +) + +func TestFile(t *testing.T) { + tests := []struct { + name string + content string + wantErr bool + }{ + { + name: "okay", + content: "1234567890abcdef", + }, + { + name: "error-bad-filename", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var fname string + var f *os.File + var err error + + if !tt.wantErr { + f, err = ioutil.TempFile("", "filebytes-*") + rtx.Must(err, "Failed to create tempfile") + defer os.Remove(f.Name()) + f.WriteString(tt.content) + fname = f.Name() + f.Close() + } else { + fname = "this-is-not-a-file" + } + + fb := flagx.File{} + if err := fb.Set(fname); (err != nil) != tt.wantErr { + t.Errorf("File.Set() error = %v, wantErr %v", err, tt.wantErr) + } + if !tt.wantErr && tt.content != string(fb.Get()) { + t.Errorf("File.Get() want = %q, got %q", tt.content, string(fb.Get())) + } + if !tt.wantErr && tt.content != fb.Content() { + t.Errorf("File.Get() want = %q, got %q", tt.content, fb.Content()) + } + if !tt.wantErr && fname != fb.String() { + t.Errorf("File.String() want = %q, got %q", fname, fb.String()) + } + }) + } +} + +// Successful compilation of this function means that File implements the +// flag.Value interface. The function need not be called. +func assertFlagValue(b flagx.File) { + func(in flag.Value) {}(&b) +}