Skip to content

proposal: embed: read file contents directly from FS during fs.WalkDir #45815

@colin-sitehost

Description

@colin-sitehost

background

Currently there is no ergonomic way to walk an embed.FS and interact with file contents without handling a number of errors that are guaranteed to be nil.

//go:embed static
var static embed.FS

func main() {
        fs.WalkDir(static, ".", func(path string, d fs.DirEntry, err error) error {
                if err != nil {
                        return err
                }
                f, err := static.Open(path)
                if err != nil {
                        return err
                }
                b, err := io.ReadAll(f)
                if err != nil {
                        return err
                }
                fmt.Println(string(b)) // or something more interesting
                return nil
        })
}

Each fs.DirEntry, when using an embed.FS, is actually an embed.file that contains all the data that I want to use in io.Copy, however it is gated behind the data.Open call.

I understand that for a general file system this type of feature may be dangerous or unnecessary, as sometimes things change between stat and open, however for embed.FS these are invariants.

proposal

I can envision a new interface that gives direct access to the raw data:

package embed

func Walk(fsys FS, root string, fn func(path, data string))

I am not sold on this exact syntax, and it would need to document that it is just for ripping through file data, but think that there exists something for this problem space.

//go:embed static
var static embed.FS

func main() {
        embed.Walk(static, ".", func(_, data string) {
                fmt.Println(data) // or something more interesting
        })
}

alternatives

With sufficient testing, this could be implemented in an extended standard library as syntax sugar, but the amount of errors that would be ignored (or panicked) and data buffering scares me:

package x

func EmbedWalk(fsys embed.FS, root string, fn func(path string, data []byte)) {
        fs.WalkDir(fsys, root, func(path string, d DirEntry, _ error) error) {
                if !d.IsDir() {
                        f, _ := fsys.Open(path)
                        b, _ := io.ReadAll(f)
                        fn(path, b)
                }
                return nil
        }
}

Since there likely exist other cases that would benefit from this (immutable, in memory, or loops where you intend to read every file), it may be useful to create a more generic interface in the io/fs package instead of embed. This will require more exported symbols and may not be worth the hassle.

package fs

func WalkRead(fsys ReadFileFS, fn WalkReadFunc) error

type WalkReadFunc func(path string, data []byte, err error) error

Currently, the embed.file.Sys method always returns nil, it may be considered a breaking change to start providing a value, but if that could be something useful like embed.openFile/embed.openDir, you could abuse it like so:

        fs.WalkDir(data, ".", func(path string, d fs.DirEntry, err error) error {
                if err != nil {
                        return err
                }
                io.Copy(os.Stdout, d.(interface{ Sys() interface{} }).Sys().(io.Reader)) // or something more interesting
                return nil
        })

Using the existing interface, I could just panic, but this feels like bad form, and my goal is to simplify the interface:

        fs.WalkDir(data, ".", func(path string, d fs.DirEntry, err error) error {
                if err != nil {
                        return err
                }
                f, err := data.Open(path)
                if err != nil {
                        panic(err)
                }
                io.Copy(os.Stdout, f) // or something more interesting
                return nil
        })

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions