1// package fileutils provides utililty methods to copy and move files and directories.
2package fileutils
3
4import (
5	"fmt"
6	"io"
7	"io/ioutil"
8	"os"
9	"path/filepath"
10	"runtime"
11	"strings"
12)
13
14// https://golang.org/cmd/go/#hdr-File_types
15var goFileTypes = []string{
16	".go",
17	".c", ".h",
18	".cc", ".cpp", ".cxx", ".hh", ".hpp", ".hxx",
19	".m",
20	".s", ".S",
21	".swig", ".swigcxx",
22	".syso",
23}
24
25var licenseFiles = []string{
26	"LICENSE", "LICENCE", "UNLICENSE", "COPYING", "COPYRIGHT",
27}
28
29func ShouldSkip(path string, info os.FileInfo, tests, all bool) bool {
30	name := filepath.Base(path)
31
32	relevantFile := false
33	for _, ext := range goFileTypes {
34		if strings.HasSuffix(name, ext) {
35			relevantFile = true
36			break
37		}
38	}
39
40	testdata := false
41	for _, n := range strings.Split(filepath.Dir(path), string(filepath.Separator)) {
42		if n == "testdata" || n == "_testdata" {
43			testdata = true
44		}
45	}
46
47	skip := false
48	switch {
49	case all && !(name == ".git" && info.IsDir()) && name != ".bzr" && name != ".hg":
50		skip = false
51
52	// Include all files in a testdata folder
53	case tests && testdata:
54		skip = false
55
56	// https://golang.org/cmd/go/#hdr-Description_of_package_lists
57	case strings.HasPrefix(name, "."):
58		skip = true
59	case strings.HasPrefix(name, "_") && name != "_testdata":
60		skip = true
61
62	case !tests && name == "_testdata" && info.IsDir():
63		skip = true
64	case !tests && name == "testdata" && info.IsDir():
65		skip = true
66	case !tests && strings.HasSuffix(name, "_test.go") && !info.IsDir():
67		skip = true
68
69	case !relevantFile && !info.IsDir():
70		skip = true
71	}
72
73	return skip
74}
75
76// Copypath copies the contents of src to dst, excluding any file that is not
77// relevant to the Go compiler.
78func Copypath(dst string, src string, tests, all bool) error {
79	err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
80		if err != nil {
81			return err
82		}
83
84		skip := ShouldSkip(path, info, tests, all)
85
86		if skip {
87			if info.IsDir() {
88				return filepath.SkipDir
89			}
90			return nil
91		}
92
93		if info.IsDir() {
94			return nil
95		}
96
97		dst := filepath.Join(dst, path[len(src):])
98
99		if info.Mode()&os.ModeSymlink != 0 {
100			return Copylink(dst, path)
101		}
102
103		return Copyfile(dst, path)
104	})
105	if err != nil {
106		// if there was an error during copying, remove the partial copy.
107		RemoveAll(dst)
108	}
109	return err
110}
111
112func Copyfile(dst, src string) error {
113	err := mkdir(filepath.Dir(dst))
114	if err != nil {
115		return fmt.Errorf("copyfile: mkdirall: %v", err)
116	}
117	r, err := os.Open(src)
118	if err != nil {
119		return fmt.Errorf("copyfile: open(%q): %v", src, err)
120	}
121	defer r.Close()
122	w, err := os.Create(dst)
123	if err != nil {
124		return fmt.Errorf("copyfile: create(%q): %v", dst, err)
125	}
126	defer w.Close()
127	_, err = io.Copy(w, r)
128	return err
129}
130
131func Copylink(dst, src string) error {
132	target, err := os.Readlink(src)
133	if err != nil {
134		return fmt.Errorf("copylink: readlink: %v", err)
135	}
136	if err := mkdir(filepath.Dir(dst)); err != nil {
137		return fmt.Errorf("copylink: mkdirall: %v", err)
138	}
139	if err := os.Symlink(target, dst); err != nil {
140		return fmt.Errorf("copylink: symlink: %v", err)
141	}
142	return nil
143}
144
145// RemoveAll removes path and any children it contains. Unlike os.RemoveAll it
146// deletes read only files on Windows.
147func RemoveAll(path string) error {
148	if runtime.GOOS == "windows" {
149		// Simple case: if Remove works, we're done.
150		err := os.Remove(path)
151		if err == nil || os.IsNotExist(err) {
152			return nil
153		}
154		// make sure all files are writable so we can delete them
155		filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
156			if err != nil {
157				// walk gave us some error, give it back.
158				return err
159			}
160			mode := info.Mode()
161			if mode|0200 == mode {
162				return nil
163			}
164			return os.Chmod(path, mode|0200)
165		})
166	}
167	return os.RemoveAll(path)
168}
169
170// CopyLicense copies the license file from folder src to folder dst.
171func CopyLicense(dst, src string) error {
172	files, err := ioutil.ReadDir(src)
173	if err != nil {
174		return err
175	}
176	for _, f := range files {
177		if f.IsDir() {
178			continue
179		}
180		for _, candidate := range licenseFiles {
181			if strings.ToLower(candidate) == strings.TrimSuffix(
182				strings.TrimSuffix(strings.ToLower(f.Name()), ".md"), ".txt") {
183				if err := Copyfile(filepath.Join(dst, f.Name()),
184					filepath.Join(src, f.Name())); err != nil {
185					return err
186				}
187			}
188		}
189	}
190	return nil
191}
192
193func mkdir(path string) error {
194	return os.MkdirAll(path, 0755)
195}
196