blob: 1e002390818900a28f1fd2b9ff24fb85ea8c962b [file] [log] [blame]
Serge Bazanski31dd6162018-10-25 14:20:50 +02001package builder
2
3import (
4 "context"
5 "os"
6 "path/filepath"
7 "regexp"
8 "strings"
9 "sync"
10 "text/template"
11
12 "github.com/pkg/errors"
13 "golang.org/x/sync/errgroup"
14)
15
16var DebugLog func(string, ...interface{})
17
18func init() {
19 DebugLog = func(string, ...interface{}) {}
20}
21
22var invalidFilePattern = regexp.MustCompile(`(_test|-packr).go$`)
23
24// Builder scans folders/files looking for `packr.NewBox` and then compiling
25// the required static files into `<package-name>-packr.go` files so they can
26// be built into Go binaries.
27type Builder struct {
28 context.Context
29 RootPath string
30 IgnoredBoxes []string
31 IgnoredFolders []string
32 pkgs map[string]pkg
33 moot *sync.Mutex
34 Compress bool
35}
36
37// Run the builder.
38func (b *Builder) Run() error {
39 wg := &errgroup.Group{}
40 root, err := filepath.EvalSymlinks(b.RootPath)
41 if err != nil {
42 return errors.WithStack(err)
43 }
44 err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
45 if info == nil {
46 return filepath.SkipDir
47 }
48
49 base := strings.ToLower(filepath.Base(path))
50 if strings.HasPrefix(base, "_") {
51 return filepath.SkipDir
52 }
53 for _, f := range b.IgnoredFolders {
54 if strings.ToLower(f) == base {
55 if info.IsDir() {
56 return filepath.SkipDir
57 } else {
58 return nil
59 }
60 }
61 }
62 if !info.IsDir() {
63 wg.Go(func() error {
64 return b.process(path)
65 })
66 }
67 return nil
68 })
69 if err != nil {
70 return errors.WithStack(err)
71 }
72 if err := wg.Wait(); err != nil {
73 return errors.WithStack(err)
74 }
75 return b.dump()
76}
77
78func (b *Builder) dump() error {
79 for _, p := range b.pkgs {
80 name := filepath.Join(p.Dir, "a_"+p.Name+"-packr.go")
81 f, err := os.Create(name)
82 defer f.Close()
83 if err != nil {
84 return errors.WithStack(err)
85 }
86 t, err := template.New("").Parse(tmpl)
87
88 if err != nil {
89 return errors.WithStack(err)
90 }
91 err = t.Execute(f, p)
92 if err != nil {
93 return errors.WithStack(err)
94 }
95 }
96 return nil
97}
98
99func (b *Builder) process(path string) error {
100 ext := filepath.Ext(path)
101 if ext != ".go" || invalidFilePattern.MatchString(path) {
102 return nil
103 }
104
105 v := newVisitor(path)
106 if err := v.Run(); err != nil {
107 return errors.WithStack(err)
108 }
109
110 pk := pkg{
111 Dir: filepath.Dir(path),
112 Boxes: []box{},
113 Name: v.Package,
114 }
115
116 for _, n := range v.Boxes {
117 var ignored bool
118 for _, i := range b.IgnoredBoxes {
119 if n == i {
120 // this is an ignored box
121 ignored = true
122 break
123 }
124 }
125 if ignored {
126 continue
127 }
128 bx := &box{
129 Name: n,
130 Files: []file{},
131 compress: b.Compress,
132 }
133 DebugLog("building box %s\n", bx.Name)
134 p := filepath.Join(pk.Dir, bx.Name)
135 if err := bx.Walk(p); err != nil {
136 return errors.WithStack(err)
137 }
138 if len(bx.Files) > 0 {
139 pk.Boxes = append(pk.Boxes, *bx)
140 }
141 DebugLog("built box %s with %q\n", bx.Name, bx.Files)
142 }
143
144 if len(pk.Boxes) > 0 {
145 b.addPkg(pk)
146 }
147 return nil
148}
149
150func (b *Builder) addPkg(p pkg) {
151 b.moot.Lock()
152 defer b.moot.Unlock()
153 if _, ok := b.pkgs[p.Name]; !ok {
154 b.pkgs[p.Name] = p
155 return
156 }
157 pp := b.pkgs[p.Name]
158 pp.Boxes = append(pp.Boxes, p.Boxes...)
159 b.pkgs[p.Name] = pp
160}
161
162// New Builder with a given context and path
163func New(ctx context.Context, path string) *Builder {
164 return &Builder{
165 Context: ctx,
166 RootPath: path,
167 IgnoredBoxes: []string{},
168 IgnoredFolders: []string{"vendor", ".git", "node_modules", ".idea"},
169 pkgs: map[string]pkg{},
170 moot: &sync.Mutex{},
171 }
172}