1package main
2
3import (
4	"bytes"
5	"fmt"
6	"go/build"
7	"io/ioutil"
8	"os"
9	"path/filepath"
10	"runtime"
11	"strings"
12	"sync"
13	"unicode/utf8"
14)
15
16// our own readdir, which skips the files it cannot lstat
17func readdir_lstat(name string) ([]os.FileInfo, error) {
18	f, err := os.Open(name)
19	if err != nil {
20		return nil, err
21	}
22	defer f.Close()
23
24	names, err := f.Readdirnames(-1)
25	if err != nil {
26		return nil, err
27	}
28
29	out := make([]os.FileInfo, 0, len(names))
30	for _, lname := range names {
31		s, err := os.Lstat(filepath.Join(name, lname))
32		if err != nil {
33			continue
34		}
35		out = append(out, s)
36	}
37	return out, nil
38}
39
40// our other readdir function, only opens and reads
41func readdir(dirname string) []os.FileInfo {
42	f, err := os.Open(dirname)
43	if err != nil {
44		return nil
45	}
46	fi, err := f.Readdir(-1)
47	f.Close()
48	if err != nil {
49		panic(err)
50	}
51	return fi
52}
53
54// returns truncated 'data' and amount of bytes skipped (for cursor pos adjustment)
55func filter_out_shebang(data []byte) ([]byte, int) {
56	if len(data) > 2 && data[0] == '#' && data[1] == '!' {
57		newline := bytes.Index(data, []byte("\n"))
58		if newline != -1 && len(data) > newline+1 {
59			return data[newline+1:], newline + 1
60		}
61	}
62	return data, 0
63}
64
65func file_exists(filename string) bool {
66	_, err := os.Stat(filename)
67	if err != nil {
68		return false
69	}
70	return true
71}
72
73func is_dir(path string) bool {
74	fi, err := os.Stat(path)
75	return err == nil && fi.IsDir()
76}
77
78func char_to_byte_offset(s []byte, offset_c int) (offset_b int) {
79	for offset_b = 0; offset_c > 0 && offset_b < len(s); offset_b++ {
80		if utf8.RuneStart(s[offset_b]) {
81			offset_c--
82		}
83	}
84	return offset_b
85}
86
87func xdg_home_dir() string {
88	xdghome := os.Getenv("XDG_CONFIG_HOME")
89	if xdghome == "" {
90		xdghome = filepath.Join(os.Getenv("HOME"), ".config")
91	}
92	return xdghome
93}
94
95func has_prefix(s, prefix string, ignorecase bool) bool {
96	if ignorecase {
97		s = strings.ToLower(s)
98		prefix = strings.ToLower(prefix)
99	}
100	return strings.HasPrefix(s, prefix)
101}
102
103func find_bzl_project_root(libpath, path string) (string, error) {
104	if libpath == "" {
105		return "", fmt.Errorf("could not find project root, libpath is empty")
106	}
107
108	pathMap := map[string]struct{}{}
109	for _, lp := range strings.Split(libpath, ":") {
110		lp := strings.TrimSpace(lp)
111		pathMap[filepath.Clean(lp)] = struct{}{}
112	}
113
114	path = filepath.Dir(path)
115	if path == "" {
116		return "", fmt.Errorf("project root is blank")
117	}
118
119	start := path
120	for path != "/" {
121		if _, ok := pathMap[filepath.Clean(path)]; ok {
122			return path, nil
123		}
124		path = filepath.Dir(path)
125	}
126	return "", fmt.Errorf("could not find project root in %q or its parents", start)
127}
128
129// Code taken directly from `gb`, I hope author doesn't mind.
130func find_gb_project_root(path string) (string, error) {
131	path = filepath.Dir(path)
132	if path == "" {
133		return "", fmt.Errorf("project root is blank")
134	}
135	start := path
136	for path != "/" {
137		root := filepath.Join(path, "src")
138		if _, err := os.Stat(root); err != nil {
139			if os.IsNotExist(err) {
140				path = filepath.Dir(path)
141				continue
142			}
143			return "", err
144		}
145		path, err := filepath.EvalSymlinks(path)
146		if err != nil {
147			return "", err
148		}
149		return path, nil
150	}
151	return "", fmt.Errorf("could not find project root in %q or its parents", start)
152}
153
154// vendorlessImportPath returns the devendorized version of the provided import path.
155// e.g. "foo/bar/vendor/a/b" => "a/b"
156func vendorlessImportPath(ipath string, currentPackagePath string) (string, bool) {
157	split := strings.Split(ipath, "vendor/")
158	// no vendor in path
159	if len(split) == 1 {
160		return ipath, true
161	}
162	// this import path does not belong to the current package
163	if currentPackagePath != "" && !strings.Contains(currentPackagePath, split[0]) {
164		if split[0] != currentPackagePath+"/" {
165			return "", false
166		}
167	}
168	// Devendorize for use in import statement.
169	if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 {
170		return ipath[i+len("/vendor/"):], true
171	}
172	if strings.HasPrefix(ipath, "vendor/") {
173		return ipath[len("vendor/"):], true
174	}
175	return ipath, true
176}
177
178func internalImportPath(ipath string, currentPackagePath string) (string, bool) {
179	split := strings.Split(ipath, "/internal")
180	// no vendor in path
181	// if len(split) == 1 {
182	// 	return "", false
183	// }
184	// this import path does not belong to the current package
185	if currentPackagePath != "" && !strings.Contains(currentPackagePath, split[0]) {
186		if split[0] != currentPackagePath+"/" {
187			return "", false
188		}
189	}
190	return ipath, true
191}
192
193//-------------------------------------------------------------------------
194// print_backtrace
195//
196// a nicer backtrace printer than the default one
197//-------------------------------------------------------------------------
198
199var g_backtrace_mutex sync.Mutex
200
201func print_backtrace(err interface{}) {
202	g_backtrace_mutex.Lock()
203	defer g_backtrace_mutex.Unlock()
204	fmt.Printf("panic: %v\n", err)
205	i := 2
206	for {
207		pc, file, line, ok := runtime.Caller(i)
208		if !ok {
209			break
210		}
211		f := runtime.FuncForPC(pc)
212		fmt.Printf("%d(%s): %s:%d\n", i-1, f.Name(), file, line)
213		i++
214	}
215	fmt.Println("")
216}
217
218//-------------------------------------------------------------------------
219// File reader goroutine
220//
221// It's a bad idea to block multiple goroutines on file I/O. Creates many
222// threads which fight for HDD. Therefore only single goroutine should read HDD
223// at the same time.
224//-------------------------------------------------------------------------
225
226type file_read_request struct {
227	filename string
228	out      chan file_read_response
229}
230
231type file_read_response struct {
232	data  []byte
233	error error
234}
235
236type file_reader_type struct {
237	in chan file_read_request
238}
239
240func new_file_reader() *file_reader_type {
241	this := new(file_reader_type)
242	this.in = make(chan file_read_request)
243	go func() {
244		var rsp file_read_response
245		for {
246			req := <-this.in
247			rsp.data, rsp.error = ioutil.ReadFile(req.filename)
248			req.out <- rsp
249		}
250	}()
251	return this
252}
253
254func (this *file_reader_type) read_file(filename string) ([]byte, error) {
255	req := file_read_request{
256		filename,
257		make(chan file_read_response),
258	}
259	this.in <- req
260	rsp := <-req.out
261	return rsp.data, rsp.error
262}
263
264var file_reader = new_file_reader()
265
266//-------------------------------------------------------------------------
267// copy of the build.Context without func fields
268//-------------------------------------------------------------------------
269
270type go_build_context struct {
271	GOARCH        string
272	GOOS          string
273	GOROOT        string
274	GOPATH        string
275	CgoEnabled    bool
276	UseAllFiles   bool
277	Compiler      string
278	BuildTags     []string
279	ReleaseTags   []string
280	InstallSuffix string
281}
282
283func pack_build_context(ctx *build.Context) go_build_context {
284	return go_build_context{
285		GOARCH:        ctx.GOARCH,
286		GOOS:          ctx.GOOS,
287		GOROOT:        ctx.GOROOT,
288		GOPATH:        ctx.GOPATH,
289		CgoEnabled:    ctx.CgoEnabled,
290		UseAllFiles:   ctx.UseAllFiles,
291		Compiler:      ctx.Compiler,
292		BuildTags:     ctx.BuildTags,
293		ReleaseTags:   ctx.ReleaseTags,
294		InstallSuffix: ctx.InstallSuffix,
295	}
296}
297
298func unpack_build_context(ctx *go_build_context) package_lookup_context {
299	return package_lookup_context{
300		Context: build.Context{
301			GOARCH:        ctx.GOARCH,
302			GOOS:          ctx.GOOS,
303			GOROOT:        ctx.GOROOT,
304			GOPATH:        ctx.GOPATH,
305			CgoEnabled:    ctx.CgoEnabled,
306			UseAllFiles:   ctx.UseAllFiles,
307			Compiler:      ctx.Compiler,
308			BuildTags:     ctx.BuildTags,
309			ReleaseTags:   ctx.ReleaseTags,
310			InstallSuffix: ctx.InstallSuffix,
311		},
312	}
313}
314