package builder import ( "context" "os" "path/filepath" "regexp" "strings" "sync" "text/template" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) var DebugLog func(string, ...interface{}) func init() { DebugLog = func(string, ...interface{}) {} } var invalidFilePattern = regexp.MustCompile(`(_test|-packr).go$`) // Builder scans folders/files looking for `packr.NewBox` and then compiling // the required static files into `-packr.go` files so they can // be built into Go binaries. type Builder struct { context.Context RootPath string IgnoredBoxes []string IgnoredFolders []string pkgs map[string]pkg moot *sync.Mutex Compress bool } // Run the builder. func (b *Builder) Run() error { wg := &errgroup.Group{} root, err := filepath.EvalSymlinks(b.RootPath) if err != nil { return errors.WithStack(err) } err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if info == nil { return filepath.SkipDir } base := strings.ToLower(filepath.Base(path)) if strings.HasPrefix(base, "_") { return filepath.SkipDir } for _, f := range b.IgnoredFolders { if strings.ToLower(f) == base { return filepath.SkipDir } } if !info.IsDir() { wg.Go(func() error { return b.process(path) }) } return nil }) if err != nil { return errors.WithStack(err) } if err := wg.Wait(); err != nil { return errors.WithStack(err) } return b.dump() } func (b *Builder) dump() error { for _, p := range b.pkgs { name := filepath.Join(p.Dir, "a_"+p.Name+"-packr.go") f, err := os.Create(name) defer f.Close() if err != nil { return errors.WithStack(err) } t, err := template.New("").Parse(tmpl) if err != nil { return errors.WithStack(err) } err = t.Execute(f, p) if err != nil { return errors.WithStack(err) } } return nil } func (b *Builder) process(path string) error { ext := filepath.Ext(path) if ext != ".go" || invalidFilePattern.MatchString(path) { return nil } v := newVisitor(path) if err := v.Run(); err != nil { return errors.WithStack(err) } pk := pkg{ Dir: filepath.Dir(path), Boxes: []box{}, Name: v.Package, } for _, n := range v.Boxes { var ignored bool for _, i := range b.IgnoredBoxes { if n == i { // this is an ignored box ignored = true break } } if ignored { continue } bx := &box{ Name: n, Files: []file{}, compress: b.Compress, } DebugLog("building box %s\n", bx.Name) p := filepath.Join(pk.Dir, bx.Name) if err := bx.Walk(p); err != nil { return errors.WithStack(err) } if len(bx.Files) > 0 { pk.Boxes = append(pk.Boxes, *bx) } DebugLog("built box %s with %q\n", bx.Name, bx.Files) } if len(pk.Boxes) > 0 { b.addPkg(pk) } return nil } func (b *Builder) addPkg(p pkg) { b.moot.Lock() defer b.moot.Unlock() if _, ok := b.pkgs[p.Name]; !ok { b.pkgs[p.Name] = p return } pp := b.pkgs[p.Name] pp.Boxes = append(pp.Boxes, p.Boxes...) b.pkgs[p.Name] = pp } // New Builder with a given context and path func New(ctx context.Context, path string) *Builder { return &Builder{ Context: ctx, RootPath: path, IgnoredBoxes: []string{}, IgnoredFolders: []string{"vendor", ".git", "node_modules", ".idea"}, pkgs: map[string]pkg{}, moot: &sync.Mutex{}, } }