markbates / pkger

Embed static files in Go binaries (replacement for gobuffalo/packr)
MIT License
1.19k stars 60 forks source link

Suggestion: add ioutil.ReadFile equivalent #63

Closed twpayne closed 3 years ago

twpayne commented 4 years ago

Go has a very useful ioutil.ReadFile function which slurps an entire file into memory.

It would be very helpful to have an equivalent both at the top level and as part of the Pkger interface.

moqmar commented 4 years ago

I'd even go as far as to add a MustOpen and MustReadFile function - it would make it so much easier to work with pkger... Here's an example, to give a bit of context regarding this issue:

f, err := pkger.Open("/example.txt") // can't use a wrapper function or pkger doesn't pack it!
if err != nil {
  panic(err) // we know that this file exists, so this is just to be sure and to get error messages that make sense
}
b, err := ioutil.ReadAll(f)
if err != nil {
  panic(err) // this will hopefully only possibly panic during development as the file is already in memory otherwise
}
s := string(b)

Let's say I have to load 3 files at once, that gets really complex, and it's a very common pattern. The shortest way I could think of would be to create a wrapper function like this:

func MustReadAll(f io.Reader, err error) []byte {
  if err != nil {
    panic(err)
  }
  b, err := ioutil.ReadAll(f)
  if err != nil {
    panic(err)
  }
  return b
}
//...
f, err := pkger.Open("/example.txt")
s1 := string(MustReadAll(f, err))
f, err = pkger.Open("/example2.txt")
s2 := string(MustReadAll(f, err))
f, err = pkger.Open("/example3.txt")
s3 := string(MustReadAll(f, err))

That's a lot of boilerplate code for a very simple example - as we're not using the filesystem in a production build, panicking seems like a fine way to handle errors in pkger to me (at least if it's obvious thanks to the Must prefix). A way simpler approach would be this one:

s1 := string(pkger.MustReadFile("/example.txt"))
s2 := string(pkger.MustReadFile("/example2.txt"))
s3 := string(pkger.MustReadFile("/example3.txt"))

Or, if I want to catch the errors:

b1, err1 := pkger.ReadFile("/example.txt")
b2, err2 := pkger.ReadFile("/example2.txt")
b3, err3 := pkger.ReadFile("/example3.txt")
if err1 != nil || err2 != nil || err3 != nil {
  log.Fatalf("Couldn't load at least one of the example files: %v; %v; %v\n", err1, err2, err3)
}
s1 := string(b1)
s2 := string(b2)
s3 := string(b3)
markbates commented 4 years ago

@twpayne PRs are welcome. There was one once, however, to make it useful it needs to be added to all of the implementations, and be a required part of the Pkger interface. The alternatives are you have to pass the Pkger implementation into the function to read the file, which is also cumbersome amd cumbersome.

@moqmar I’m sorry, but there won’t be any Must functions or panicking in this application.

It must be remembered that Pkger mimics the std library and Go doesn’t have those functions for working with files.

xeoncross commented 4 years ago

Remember, you can use pkger.Include in init() of your package to pre-include all the needed files, then you can make a helper function that uses pkger.Open() with variables and it will work fine.

func load(filename string) (string, error) {
    f, err := pkger.Open(filename)
    if err != nil {
        return "", err
    }

    b, err := ioutil.ReadAll(f)
    if err != nil {
        return "", err
    }

    return string(b), nil
}

@markbates you should probably mention this trick in the README

radcool commented 4 years ago

Thanks for the tip @Xeoncross! It proved useful to me!

twpayne commented 3 years ago

I assume that this package is now superseded by Go's embed, so closing this.