├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── cmd └── resources │ └── cmd.go ├── go.mod ├── go.sum ├── live └── live.go ├── resources.go ├── resources_test.go └── testdata ├── generated └── generated.go ├── patrick.txt ├── query.sql ├── sub-a ├── sub-b │ └── patrick2.txt └── test2.txt └── test.txt /.gitignore: -------------------------------------------------------------------------------- 1 | cmd/resources/resources 2 | tags 3 | testdata/resources 4 | testdata/generated 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: go 3 | 4 | go: 5 | - 1.8.x 6 | - 1.9 7 | - master 8 | 9 | script: 10 | - go generate 11 | - go test -v ./... 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 omeid 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Heads up! 2 | > This library is in maintenance mode. Please use the official embed library going forward. 3 | > https://pkg.go.dev/embed 4 | 5 | 6 | 7 | # Resources [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/omeid/go-resources) [![Build Status](https://travis-ci.org/omeid/go-resources.svg?branch=master)](https://travis-ci.org/omeid/go-resources) [![Go Report Card](https://goreportcard.com/badge/github.com/omeid/go-resources?bust=true)](https://goreportcard.com/report/github.com/omeid/go-resources) 8 | Unfancy resources embedding with Go. 9 | 10 | - No blings. 11 | - No runtime dependency. 12 | - Idiomatic Library First design. 13 | 14 | ### Dude, Why? 15 | 16 | Yes, there is quite a lot of projects that handles resource embedding 17 | but they come with more bling than you ever need and you often end up 18 | with having dependencies for your end project. Not this time. 19 | 20 | ### Installing 21 | 22 | Just go get it! 23 | 24 | ```sh 25 | $ go get github.com/omeid/go-resources/cmd/resources 26 | ``` 27 | 28 | ### Usage 29 | 30 | ``` 31 | $ resources -h 32 | Usage resources: 33 | -declare 34 | whether to declare the -var (default false) 35 | -fmt 36 | run output through gofmt, this is slow for huge files (default false) 37 | -output filename 38 | filename to write the output to 39 | -package name 40 | name of the package to generate (default "main") 41 | -tag tag 42 | tag to use for the generated package (default no tag) 43 | -trim prefix 44 | path prefix to remove from the resulting file path in the virtual filesystem 45 | -var name 46 | name of the variable to assign the virtual filesystem to (default "FS") 47 | -width number 48 | number of content bytes per line in generetated file (default 12) 49 | ``` 50 | 51 | ### Optimization 52 | 53 | Generating resources result in a very high number of lines of code, 1MB 54 | of resources result about 5MB of code at over 87,000 lines of code. This 55 | is caused by the chosen representation of the file contents within the 56 | generated file. 57 | 58 | Instead of a (binary) string, `resources` transforms each file into an 59 | actual byte slice. For example, a file with content `Hello, world!` will 60 | be represented as follows: 61 | 62 | ``` go 63 | FS = &FileSystem{ 64 | "/hello.txt": File{ 65 | data: []byte{ 66 | 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 67 | 0x21, 68 | }, 69 | fi: FileInfo{ 70 | name: "hello.txt", 71 | size: 13, 72 | modTime: time.Unix(0, 1504640959536230658), 73 | isDir: false, 74 | }, 75 | }, 76 | } 77 | ``` 78 | 79 | While this seems wasteful, the compiled binary is not really affected. 80 | _If you add 1MB of resources, your binary will increase 1MB as well_. 81 | 82 | However, compiling this many lines of code takes time and slows down the 83 | compiler. To avoid recompiling the resources every time and leverage the 84 | compiler cache, generate your resources into a standalone package and 85 | then import it, this will allow for faster iteration as you don't have 86 | to wait for the resources to be compiled with every change. 87 | 88 | ``` sh 89 | mkdir -p assets 90 | resources -declare -var=FS -package=assets -output=assets/assets.go your/files/here 91 | ``` 92 | 93 | ``` go 94 | package main 95 | 96 | import "importpath/to/assets" 97 | 98 | func main() { 99 | data, err := assets.FS.Open("your/files/here") 100 | // ... 101 | } 102 | ``` 103 | 104 | ##### "Live" development of resources 105 | 106 | For fast iteration and improvement of your resources, you can work 107 | around the compile with the following technique: 108 | 109 | First, create a normal `main.go`: 110 | 111 | ```go 112 | package main 113 | 114 | import "net/http" 115 | 116 | var Assets http.FileSystem 117 | 118 | func main() { 119 | if Assets == nil { 120 | panic("No Assets. Have you generated the resources?") 121 | } 122 | 123 | // use Assets here 124 | } 125 | ``` 126 | 127 | Then, add a second file in the same package (`main` here), with the 128 | following content: 129 | 130 | ```go 131 | // +build !embed 132 | 133 | package main 134 | 135 | import ( 136 | "net/http" 137 | 138 | "github.com/omeid/go-resources/live" 139 | ) 140 | 141 | var Assets = live.Dir("./public") 142 | ``` 143 | 144 | Now when you build or run your project, you will have files directly 145 | served from `./public` directory. 146 | 147 | To create a *production build*, i.e. one with the embedded files, build 148 | the resouces with `-tag=embed` and add the `embed` tag to `go build`: 149 | 150 | ```sh 151 | $ resources -output=public_resources.go -var=Assets -tag=embed public/* 152 | $ go build -tags=embed 153 | ``` 154 | 155 | Now your resources should be embedded with your program! 156 | Of course, you may use any `var` or `tag` name you please. 157 | 158 | ### Go Generate 159 | 160 | There is a few reasons to avoid resource embedding in `go generate`. 161 | 162 | First `go generate` is for generating Go source code from your code, 163 | generally the resources you want to embed aren't effected by the Go 164 | source directly and as such generating resources are slightly out of the 165 | scope of `go generate`. 166 | 167 | Second, you're unnecessarily slowing down code iterations by blocking 168 | `go generate` for resource generation. 169 | 170 | # Resources, The Library [![GoDoc](https://godoc.org/github.com/omeid/go-resources?status.svg)](https://godoc.org/github.com/omeid/go-resources) 171 | 172 | The resource generator is written as a library and isn't bound to 173 | filesystem by the way of accepting files in the form 174 | 175 | ```go 176 | type File interface { 177 | io.Reader 178 | Stat() (os.FileInfo, error) 179 | } 180 | ``` 181 | 182 | along with a helper method that adds files from filesystem. 183 | 184 | This allows to integrate `resources` with ease in your workflow when the 185 | when the provided command doesn't fit well, for an example see the [Gonzo 186 | binding](https://github.com/go-gonzo/resources/blob/master/resources.go) 187 | `resources`. 188 | 189 | Please refer to the [GoDoc](https://godoc.org/github.com/omeid/go-resources) 190 | for complete documentation. 191 | 192 | ### Strings 193 | 194 | The generated FileSystem also implements an `String(string) (string, bool)` method that allows you to read the content of a file as string, to use that 195 | instead of defining your file Assets variable as simply an http.FileSystem, do the following: 196 | 197 | ```go 198 | type Resources interface { 199 | http.FileSystem 200 | String(string) (string, bool) 201 | } 202 | 203 | var Assets Resources 204 | ``` 205 | 206 | Now you can call `Assets.String(someFile)` and get the content as string with a boolean value indicating whatever the file was found or not. 207 | 208 | --- 209 | 210 | ### Contributing 211 | 212 | Please consider opening an issue first, or just send a pull request. :) 213 | 214 | ### Credits 215 | 216 | See [Contributors](https://github.com/omeid/go-resources/graphs/contributors). 217 | 218 | ### LICENSE 219 | 220 | [MIT](LICENSE). 221 | -------------------------------------------------------------------------------- /cmd/resources/cmd.go: -------------------------------------------------------------------------------- 1 | // Unfancy resources embedding with Go. 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "log" 8 | "path/filepath" 9 | "time" 10 | 11 | "github.com/omeid/go-resources" 12 | ) 13 | 14 | var ( 15 | pkg = "main" 16 | varName = "FS" 17 | tag = "" 18 | declare = false 19 | out = "" 20 | trimPath = "" 21 | width = resources.BlockWidth 22 | gofmt = false 23 | ) 24 | 25 | type nope struct{} 26 | 27 | func main() { 28 | flag.StringVar(&pkg, "package", pkg, "`name` of the package to generate") 29 | flag.StringVar(&varName, "var", varName, "`name` of the variable to assign the virtual filesystem to") 30 | flag.StringVar(&tag, "tag", tag, "`tag` to use for the generated package (default no tag)") 31 | flag.BoolVar(&declare, "declare", declare, "whether to declare the -var (default false)") 32 | flag.StringVar(&out, "output", out, "`filename` to write the output to") 33 | flag.StringVar(&trimPath, "trim", trimPath, "path `prefix` to remove from the resulting file path in the virtual filesystem") 34 | flag.IntVar(&width, "width", width, "`number` of content bytes per line in generetated file") 35 | flag.BoolVar(&gofmt, "fmt", gofmt, "run output through gofmt, this is slow for huge files (default false)") 36 | flag.Parse() 37 | 38 | if out == "" { 39 | flag.PrintDefaults() 40 | log.Fatal("-output is required.") 41 | } 42 | 43 | config := resources.Config{ 44 | Pkg: pkg, 45 | Var: varName, 46 | Tag: tag, 47 | Declare: declare, 48 | Format: gofmt, 49 | } 50 | resources.BlockWidth = width 51 | 52 | res := resources.New() 53 | res.Config = config 54 | 55 | files := make(map[string]nope) 56 | 57 | for _, g := range flag.Args() { 58 | matches, err := filepath.Glob(g) 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | for _, m := range matches { 63 | files[m] = nope{} 64 | } 65 | } 66 | 67 | t0 := time.Now() 68 | 69 | var wrote int 70 | for file := range files { 71 | n, err := res.AddFiles(trimPath, file) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | wrote += n 77 | } 78 | 79 | if err := res.Write(out); err != nil { 80 | log.Fatal(err) 81 | } 82 | 83 | log.Printf("Finished in %v. Wrote %d resources to %s", time.Since(t0), wrote, out) 84 | } 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/omeid/go-resources 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeid/go-resources/8e53f3c874c547886eeea92cf90dfd24ced5beed/go.sum -------------------------------------------------------------------------------- /live/live.go: -------------------------------------------------------------------------------- 1 | // Package live implements a live implementation of go-resources http.FileSystem compatible FileSystem. 2 | package live 3 | 4 | import ( 5 | "io/ioutil" 6 | "net/http" 7 | "path/filepath" 8 | "runtime" 9 | ) 10 | 11 | // Resources describes an instance of the go-resources which is an extension of 12 | // http.FileSystem 13 | type Resources interface { 14 | http.FileSystem 15 | String(string) (string, bool) 16 | } 17 | 18 | // Dir returns an Resources implementation that servers the files from the 19 | // provided dir location, it will expand the path relative to the caller. 20 | func Dir(dir string) Resources { 21 | 22 | _, filename, _, ok := runtime.Caller(1) 23 | 24 | if !ok { 25 | panic("failed to get Caller details") 26 | } 27 | 28 | dir = filepath.Join(filepath.Dir(filename), dir) 29 | return &resources{http.Dir(dir)} 30 | } 31 | 32 | type resources struct { 33 | http.FileSystem 34 | } 35 | 36 | func (r *resources) String(name string) (string, bool) { 37 | 38 | file, err := r.Open(name) 39 | if err != nil { 40 | return "", false 41 | } 42 | 43 | content, err := ioutil.ReadAll(file) 44 | 45 | if err != nil { 46 | return "", false 47 | } 48 | 49 | return string(content), true 50 | } 51 | -------------------------------------------------------------------------------- /resources.go: -------------------------------------------------------------------------------- 1 | // Package resources provides unfancy resources embedding with Go. 2 | package resources 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "go/format" 8 | "io" 9 | "log" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | "text/template" 14 | ) 15 | 16 | // File mimicks the os.File and http.File interface. 17 | type File interface { 18 | io.Reader 19 | Stat() (os.FileInfo, error) 20 | } 21 | 22 | // New creates a new Package. 23 | func New() *Package { 24 | return &Package{ 25 | Config: Config{ 26 | Pkg: "resources", 27 | Var: "FS", 28 | Declare: true, 29 | }, 30 | Files: make(map[string]File), 31 | } 32 | } 33 | 34 | // Config defines some details about the output file 35 | type Config struct { 36 | Pkg string // Pkg holds the package name 37 | Var string // Var holds the variable name for the virtual filesystem 38 | Tag string // Tag may hold an optional build tag, unless empty 39 | Declare bool // Declare controls if the Var should be declared as well 40 | Format bool // Format controls, whether gofmt should be applied to the output 41 | } 42 | 43 | // A Package describes a collection of files and how they should be tranformed 44 | // to an output. 45 | type Package struct { 46 | Config 47 | Files map[string]File 48 | } 49 | 50 | // Add a file to the package at the give path. 51 | func (p *Package) Add(path string, file File) error { 52 | path = filepath.ToSlash(path) 53 | p.Files[path] = file 54 | return nil 55 | } 56 | 57 | // AddFile is a helper function that adds a file into the package. 58 | func (p *Package) AddFile(path string, file string) error { 59 | f, err := os.Open(file) 60 | if err != nil { 61 | return err 62 | } 63 | return p.Add(path, f) 64 | } 65 | 66 | // AddFiles is a helper function that recursively adds files to the package. 67 | func (p *Package) AddFiles(trimPath string, path string) (int, error) { 68 | var n int 69 | err := filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error { 70 | if err != nil { 71 | return err 72 | } 73 | 74 | f, err := os.Open(filePath) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | n++ 80 | return p.Add(strings.TrimPrefix(filePath, trimPath), f) 81 | }) 82 | return n, err 83 | } 84 | 85 | // Build compiles the package and writes it into an io.Writer. 86 | func (p *Package) Build(out io.Writer) error { 87 | return pkg.Execute(out, p) 88 | } 89 | 90 | // Write builds the package (via Build) and writes the output the the file 91 | // given by the path argument. 92 | func (p *Package) Write(path string) error { 93 | f, err := os.Create(path) 94 | if err != nil { 95 | return err 96 | } 97 | defer func() { 98 | err := f.Close() 99 | if err != nil { 100 | log.Panicf("Failed to close file: %s", err) 101 | } 102 | }() 103 | 104 | if !p.Format { 105 | return p.Build(f) 106 | } 107 | 108 | buf := &bytes.Buffer{} 109 | if e := p.Build(buf); e != nil { 110 | return e 111 | } 112 | 113 | fmted, e := format.Source(buf.Bytes()) 114 | if e != nil { 115 | return e 116 | } 117 | _, e = f.Write(fmted) 118 | return e 119 | } 120 | 121 | var ( 122 | // Template 123 | pkg *template.Template 124 | 125 | // BlockWidth allows to adjust the number of bytes per line in the generated file 126 | BlockWidth = 12 127 | ) 128 | 129 | func reader(input io.Reader, indent int) (string, error) { 130 | var ( 131 | buff bytes.Buffer 132 | err error 133 | curblock = 0 134 | linebreak = "\n" + strings.Repeat("\t", indent) 135 | ) 136 | 137 | b := make([]byte, BlockWidth) 138 | 139 | for n, e := input.Read(b); e == nil; n, e = input.Read(b) { 140 | for i := 0; i < n; i++ { 141 | _, e = fmt.Fprintf(&buff, "0x%02x,", b[i]) 142 | if e != nil { 143 | err = e 144 | break 145 | } 146 | curblock++ 147 | if curblock < BlockWidth { 148 | buff.WriteRune(' ') 149 | continue 150 | } 151 | buff.WriteString(linebreak) 152 | curblock = 0 153 | } 154 | } 155 | 156 | return buff.String(), err 157 | } 158 | 159 | func init() { 160 | pkg = template.Must(template.New("file").Funcs(template.FuncMap{"reader": reader}).Parse(fileTemplate)) 161 | pkg = template.Must(pkg.New("pkg").Parse(pkgTemplate)) 162 | } 163 | 164 | const fileTemplate = `File{ 165 | data: []byte{ 166 | {{ reader . 5 }} 167 | }, 168 | fi: FileInfo{ 169 | name: "{{ .Stat.Name }}", 170 | size: {{ .Stat.Size }}, 171 | modTime: time.Unix(0, {{ .Stat.ModTime.UnixNano }}), 172 | isDir: {{ .Stat.IsDir }}, 173 | }, 174 | }` 175 | 176 | const pkgTemplate = `{{ if .Tag }}// +build {{ .Tag }} 177 | 178 | {{ end }}// Package {{ .Pkg }} is generated by github.com/omeid/go-resources 179 | package {{ .Pkg }} 180 | 181 | import ( 182 | "bytes" 183 | "errors" 184 | "net/http" 185 | "os" 186 | "path/filepath" 187 | "strings" 188 | "time" 189 | ) 190 | 191 | // FileSystem is an http.FileSystem implementation. 192 | type FileSystem struct { 193 | files map[string]File 194 | } 195 | 196 | // String returns the content of the file as string. 197 | func (fs *FileSystem) String(name string) (string, bool) { 198 | if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 || 199 | strings.Contains(name, "\x00") { 200 | return "", false 201 | } 202 | 203 | file, ok := fs.files[name] 204 | 205 | if !ok { 206 | return "", false 207 | } 208 | 209 | return string(file.data), true 210 | } 211 | 212 | // Open implements http.FileSystem.Open 213 | func (fs *FileSystem) Open(name string) (http.File, error) { 214 | if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 || 215 | strings.Contains(name, "\x00") { 216 | return nil, errors.New("http: invalid character in file path") 217 | } 218 | file, ok := fs.files[name] 219 | if !ok { 220 | files := []os.FileInfo{} 221 | for path, file := range fs.files { 222 | if strings.HasPrefix(path, name) { 223 | fi := file.fi 224 | files = append(files, &fi) 225 | } 226 | } 227 | 228 | if len(files) == 0 { 229 | return nil, os.ErrNotExist 230 | } 231 | 232 | //We have a directory. 233 | return &File{ 234 | fi: FileInfo{ 235 | isDir: true, 236 | files: files, 237 | }}, nil 238 | } 239 | file.Reader = bytes.NewReader(file.data) 240 | return &file, nil 241 | } 242 | 243 | // File implements http.File 244 | type File struct { 245 | *bytes.Reader 246 | data []byte 247 | fi FileInfo 248 | } 249 | 250 | // Close is a noop-closer. 251 | func (f *File) Close() error { 252 | return nil 253 | } 254 | 255 | // Readdir implements http.File.Readdir 256 | func (f *File) Readdir(_ int) ([]os.FileInfo, error) { 257 | return nil, os.ErrNotExist 258 | } 259 | 260 | // Stat implements http.Stat.Readdir 261 | func (f *File) Stat() (os.FileInfo, error) { 262 | return &f.fi, nil 263 | } 264 | 265 | // FileInfo implements the os.FileInfo interface. 266 | type FileInfo struct { 267 | name string 268 | size int64 269 | mode os.FileMode 270 | modTime time.Time 271 | isDir bool 272 | sys interface{} 273 | 274 | files []os.FileInfo 275 | } 276 | 277 | // Name implements os.FileInfo.Name 278 | func (f *FileInfo) Name() string { 279 | return f.name 280 | } 281 | 282 | // Size implements os.FileInfo.Size 283 | func (f *FileInfo) Size() int64 { 284 | return f.size 285 | } 286 | 287 | // Mode implements os.FileInfo.Mode 288 | func (f *FileInfo) Mode() os.FileMode { 289 | return f.mode 290 | } 291 | 292 | // ModTime implements os.FileInfo.ModTime 293 | func (f *FileInfo) ModTime() time.Time { 294 | return f.modTime 295 | } 296 | 297 | // IsDir implements os.FileInfo.IsDir 298 | func (f *FileInfo) IsDir() bool { 299 | return f.isDir 300 | } 301 | 302 | // Readdir implements os.FileInfo.Readdir 303 | func (f *FileInfo) Readdir(_ int) ([]os.FileInfo, error) { 304 | return f.files, nil 305 | } 306 | 307 | // Sys returns the underlying value. 308 | func (f *FileInfo) Sys() interface{} { 309 | return f.sys 310 | } 311 | 312 | {{ if .Declare }}var {{ .Var }} *FileSystem{{ end }} 313 | 314 | func init() { 315 | {{ .Var }} = &FileSystem{ 316 | files: map[string]File{ 317 | {{range $path, $file := .Files }}"/{{ $path }}": {{ template "file" $file }},{{ end }} 318 | }, 319 | } 320 | } 321 | ` 322 | -------------------------------------------------------------------------------- /resources_test.go: -------------------------------------------------------------------------------- 1 | package resources 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/omeid/go-resources/testdata/generated" 9 | ) 10 | 11 | //go:generate go build -o testdata/resources github.com/omeid/go-resources/cmd/resources 12 | //go:generate testdata/resources -package generated -declare -output testdata/generated/store_prod.go testdata/*.txt testdata/*.sql testdata/sub-a 13 | 14 | func TestGenerated(t *testing.T) { 15 | for _, tt := range []struct { 16 | name string 17 | snippet string 18 | }{ 19 | {name: "test.txt", snippet: "this is test.txt"}, 20 | {name: "patrick.txt", snippet: "no, this is patrick!"}, 21 | {name: "query.sql", snippet: `drop table "files";`}, 22 | {name: "sub-a/test2.txt", snippet: "this is test2.txt"}, 23 | {name: "sub-a/sub-b/patrick2.txt", snippet: "no, this is patrick2!"}, 24 | } { 25 | t.Run(tt.name, func(t *testing.T) { 26 | f, err := generated.FS.Open("/testdata/" + tt.name) 27 | 28 | if err != nil { 29 | t.Fatalf("expected no error opening file, got %v", err) 30 | } 31 | defer f.Close() 32 | 33 | content, err := ioutil.ReadAll(f) 34 | if err != nil { 35 | t.Fatalf("expected no error reading file, got %v", err) 36 | } 37 | 38 | if !strings.Contains(string(content), tt.snippet) { 39 | t.Errorf("expected to find snippet %q in file", tt.snippet) 40 | } 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /testdata/generated/generated.go: -------------------------------------------------------------------------------- 1 | package generated 2 | -------------------------------------------------------------------------------- /testdata/patrick.txt: -------------------------------------------------------------------------------- 1 | is this is test.txt? 2 | no, this is patrick! 3 | -------------------------------------------------------------------------------- /testdata/query.sql: -------------------------------------------------------------------------------- 1 | create table if not exist "files"; 2 | drop table "files"; 3 | -------------------------------------------------------------------------------- /testdata/sub-a/sub-b/patrick2.txt: -------------------------------------------------------------------------------- 1 | is this is test.txt? 2 | no, this is patrick2! 3 | -------------------------------------------------------------------------------- /testdata/sub-a/test2.txt: -------------------------------------------------------------------------------- 1 | this is test2.txt 2 | -------------------------------------------------------------------------------- /testdata/test.txt: -------------------------------------------------------------------------------- 1 | this is test.txt 2 | --------------------------------------------------------------------------------