Annotation of pkgsrc/pkgtools/pkglint/files/util.go, Revision 1.18
1.1 rillig 1: package main
2:
3: import (
4: "fmt"
5: "io/ioutil"
1.13 rillig 6: "netbsd.org/pkglint/regex"
7: "netbsd.org/pkglint/trace"
1.1 rillig 8: "os"
9: "path"
10: "path/filepath"
11: "regexp"
12: "strconv"
13: "strings"
1.5 rillig 14: "time"
1.1 rillig 15: )
16:
17: // Short names for commonly used functions.
1.13 rillig 18: func contains(s, substr string) bool {
19: return strings.Contains(s, substr)
20: }
21: func hasPrefix(s, prefix string) bool {
22: return strings.HasPrefix(s, prefix)
23: }
24: func hasSuffix(s, suffix string) bool {
25: return strings.HasSuffix(s, suffix)
26: }
1.15 rillig 27: func matches(s string, re regex.Pattern) bool {
1.13 rillig 28: return regex.Matches(s, re)
29: }
1.15 rillig 30: func match1(s string, re regex.Pattern) (matched bool, m1 string) {
1.13 rillig 31: return regex.Match1(s, re)
32: }
1.15 rillig 33: func match2(s string, re regex.Pattern) (matched bool, m1, m2 string) {
1.13 rillig 34: return regex.Match2(s, re)
35: }
1.15 rillig 36: func match3(s string, re regex.Pattern) (matched bool, m1, m2, m3 string) {
1.13 rillig 37: return regex.Match3(s, re)
38: }
1.15 rillig 39: func match4(s string, re regex.Pattern) (matched bool, m1, m2, m3, m4 string) {
1.13 rillig 40: return regex.Match4(s, re)
41: }
1.1 rillig 42:
43: func ifelseStr(cond bool, a, b string) string {
44: if cond {
45: return a
46: }
47: return b
48: }
49:
1.12 rillig 50: func imax(a, b int) int {
51: if a > b {
52: return a
53: }
54: return b
55: }
56:
1.15 rillig 57: func mustMatch(s string, re regex.Pattern) []string {
1.13 rillig 58: if m := regex.Match(s, re); m != nil {
1.1 rillig 59: return m
60: }
1.5 rillig 61: panic(fmt.Sprintf("mustMatch %q %q", s, re))
1.1 rillig 62: }
63:
64: func isEmptyDir(fname string) bool {
65: dirents, err := ioutil.ReadDir(fname)
66: if err != nil || hasSuffix(fname, "/CVS") {
67: return true
68: }
69: for _, dirent := range dirents {
70: name := dirent.Name()
1.17 rillig 71: if isIgnoredFilename(name) {
1.1 rillig 72: continue
73: }
74: if dirent.IsDir() && isEmptyDir(fname+"/"+name) {
75: continue
76: }
77: return false
78: }
79: return true
80: }
81:
82: func getSubdirs(fname string) []string {
83: dirents, err := ioutil.ReadDir(fname)
84: if err != nil {
1.7 rillig 85: NewLineWhole(fname).Fatalf("Cannot be read: %s", err)
1.1 rillig 86: }
87:
88: var subdirs []string
89: for _, dirent := range dirents {
90: name := dirent.Name()
1.17 rillig 91: if dirent.IsDir() && !isIgnoredFilename(name) && !isEmptyDir(fname+"/"+name) {
1.1 rillig 92: subdirs = append(subdirs, name)
93: }
94: }
95: return subdirs
96: }
97:
1.17 rillig 98: func isIgnoredFilename(fileName string) bool {
99: switch fileName {
100: case ".", "..", "CVS", ".svn", ".git", ".hg":
101: return true
102: }
103: return false
104: }
105:
1.1 rillig 106: // Checks whether a file is already committed to the CVS repository.
107: func isCommitted(fname string) bool {
1.10 rillig 108: lines := loadCvsEntries(fname)
109: needle := "/" + path.Base(fname) + "/"
1.1 rillig 110: for _, line := range lines {
1.16 rillig 111: if hasPrefix(line.Text, needle) {
1.1 rillig 112: return true
113: }
114: }
115: return false
116: }
117:
1.8 rillig 118: func isLocallyModified(fname string) bool {
1.10 rillig 119: lines := loadCvsEntries(fname)
120: needle := "/" + path.Base(fname) + "/"
1.8 rillig 121: for _, line := range lines {
1.16 rillig 122: if hasPrefix(line.Text, needle) {
123: cvsModTime, err := time.Parse(time.ANSIC, strings.Split(line.Text, "/")[3])
1.8 rillig 124: if err != nil {
125: return false
126: }
127: st, err := os.Stat(fname)
128: if err != nil {
129: return false
130: }
131:
132: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724290(v=vs.85).aspx
1.13 rillig 133: // (System Services > Windows System Information > Time > About Time > File Times)
1.8 rillig 134: delta := cvsModTime.Unix() - st.ModTime().Unix()
1.13 rillig 135: if trace.Tracing {
136: trace.Stepf("cvs.time=%v fs.time=%v delta=%v", cvsModTime, st.ModTime(), delta)
1.8 rillig 137: }
138: return !(-2 <= delta && delta <= 2)
139: }
140: }
141: return false
142: }
143:
1.16 rillig 144: func loadCvsEntries(fname string) []Line {
1.10 rillig 145: dir := path.Dir(fname)
146: if dir == G.CvsEntriesDir {
147: return G.CvsEntriesLines
148: }
149:
150: lines, err := readLines(dir+"/CVS/Entries", false)
151: if err != nil {
152: return nil
153: }
154: G.CvsEntriesDir = dir
155: G.CvsEntriesLines = lines
156: return lines
157: }
158:
1.1 rillig 159: // Returns the number of columns that a string occupies when printed with
160: // a tabulator size of 8.
161: func tabLength(s string) int {
162: length := 0
163: for _, r := range s {
164: if r == '\t' {
165: length = length - length%8 + 8
166: } else {
167: length++
168: }
169: }
170: return length
171: }
172:
173: func varnameBase(varname string) string {
1.5 rillig 174: dot := strings.IndexByte(varname, '.')
175: if dot != -1 {
176: return varname[:dot]
177: }
178: return varname
1.1 rillig 179: }
180: func varnameCanon(varname string) string {
1.5 rillig 181: dot := strings.IndexByte(varname, '.')
182: if dot != -1 {
183: return varname[:dot] + ".*"
1.1 rillig 184: }
1.5 rillig 185: return varname
1.1 rillig 186: }
187: func varnameParam(varname string) string {
1.5 rillig 188: dot := strings.IndexByte(varname, '.')
189: if dot != -1 {
190: return varname[dot+1:]
191: }
192: return ""
1.1 rillig 193: }
194:
1.14 rillig 195: func defineVar(mkline MkLine, varname string) {
1.5 rillig 196: if G.Mk != nil {
197: G.Mk.DefineVar(mkline, varname)
1.1 rillig 198: }
1.5 rillig 199: if G.Pkg != nil {
200: G.Pkg.defineVar(mkline, varname)
1.1 rillig 201: }
202: }
203: func varIsDefined(varname string) bool {
204: varcanon := varnameCanon(varname)
1.5 rillig 205: if G.Mk != nil && (G.Mk.vardef[varname] != nil || G.Mk.vardef[varcanon] != nil) {
1.1 rillig 206: return true
207: }
1.5 rillig 208: if G.Pkg != nil && (G.Pkg.vardef[varname] != nil || G.Pkg.vardef[varcanon] != nil) {
1.1 rillig 209: return true
210: }
211: return false
212: }
213:
214: func varIsUsed(varname string) bool {
215: varcanon := varnameCanon(varname)
1.5 rillig 216: if G.Mk != nil && (G.Mk.varuse[varname] != nil || G.Mk.varuse[varcanon] != nil) {
1.1 rillig 217: return true
218: }
1.5 rillig 219: if G.Pkg != nil && (G.Pkg.varuse[varname] != nil || G.Pkg.varuse[varcanon] != nil) {
1.1 rillig 220: return true
221: }
222: return false
223: }
224:
225: func splitOnSpace(s string) []string {
1.13 rillig 226: return regex.Compile(`\s+`).Split(s, -1)
1.1 rillig 227: }
228:
229: func fileExists(fname string) bool {
230: st, err := os.Stat(fname)
231: return err == nil && st.Mode().IsRegular()
232: }
233:
234: func dirExists(fname string) bool {
235: st, err := os.Stat(fname)
236: return err == nil && st.Mode().IsDir()
237: }
238:
239: // Useful in combination with regex.Find*Index
240: func negToZero(i int) int {
241: if i >= 0 {
242: return i
243: }
244: return 0
245: }
246:
1.5 rillig 247: func toInt(s string, def int) int {
248: if n, err := strconv.Atoi(s); err == nil {
249: return n
250: }
251: return def
1.1 rillig 252: }
253:
254: func dirglob(dirname string) []string {
255: fis, err := ioutil.ReadDir(dirname)
256: if err != nil {
257: return nil
258: }
1.18 ! rillig 259: var fnames []string
! 260: for _, fi := range fis {
! 261: if !(isIgnoredFilename(fi.Name())) {
! 262: fnames = append(fnames, dirname+"/"+fi.Name())
! 263: }
1.1 rillig 264: }
265: return fnames
266: }
267:
1.12 rillig 268: // Emulates make(1)'s :S substitution operator.
1.8 rillig 269: func mkopSubst(s string, left bool, from string, right bool, to string, flags string) string {
1.13 rillig 270: if trace.Tracing {
271: defer trace.Call(s, left, from, right, to, flags)()
1.8 rillig 272: }
1.15 rillig 273: re := regex.Pattern(ifelseStr(left, "^", "") + regexp.QuoteMeta(from) + ifelseStr(right, "$", ""))
1.1 rillig 274: done := false
1.8 rillig 275: gflag := contains(flags, "g")
1.13 rillig 276: return regex.Compile(re).ReplaceAllStringFunc(s, func(match string) string {
1.8 rillig 277: if gflag || !done {
278: done = !gflag
1.1 rillig 279: return to
280: }
281: return match
282: })
283: }
284:
285: func relpath(from, to string) string {
286: absFrom, err1 := filepath.Abs(from)
287: absTo, err2 := filepath.Abs(to)
288: rel, err3 := filepath.Rel(absFrom, absTo)
289: if err1 != nil || err2 != nil || err3 != nil {
1.13 rillig 290: trace.Stepf("relpath.panic", from, to, err1, err2, err3)
291: panic("relpath")
1.1 rillig 292: }
293: result := filepath.ToSlash(rel)
1.13 rillig 294: if trace.Tracing {
295: trace.Stepf("relpath from %q to %q = %q", from, to, result)
1.5 rillig 296: }
1.1 rillig 297: return result
298: }
299:
300: func abspath(fname string) string {
301: abs, err := filepath.Abs(fname)
302: if err != nil {
1.7 rillig 303: NewLineWhole(fname).Fatalf("Cannot determine absolute path.")
1.1 rillig 304: }
305: return filepath.ToSlash(abs)
306: }
307:
308: // Differs from path.Clean in that only "../../" is replaced, not "../".
309: // Also, the initial directory is always kept.
310: // This is to provide the package path as context in recursive invocations of pkglint.
311: func cleanpath(fname string) string {
312: tmp := fname
313: for len(tmp) > 2 && hasPrefix(tmp, "./") {
314: tmp = tmp[2:]
315: }
316: for contains(tmp, "/./") {
317: tmp = strings.Replace(tmp, "/./", "/", -1)
318: }
319: for contains(tmp, "//") {
320: tmp = strings.Replace(tmp, "//", "/", -1)
321: }
322: tmp = reReplaceRepeatedly(tmp, `/[^.][^/]*/[^.][^/]*/\.\./\.\./`, "/")
323: tmp = strings.TrimSuffix(tmp, "/")
324: return tmp
325: }
326:
327: func containsVarRef(s string) bool {
328: return contains(s, "${")
329: }
330:
1.15 rillig 331: func reReplaceRepeatedly(from string, re regex.Pattern, to string) string {
1.13 rillig 332: replaced := regex.Compile(re).ReplaceAllString(from, to)
1.1 rillig 333: if replaced != from {
334: return reReplaceRepeatedly(replaced, re, to)
335: }
336: return replaced
337: }
338:
339: func hasAlnumPrefix(s string) bool {
340: if s == "" {
341: return false
342: }
343: b := s[0]
344: return '0' <= b && b <= '9' || 'A' <= b && b <= 'Z' || b == '_' || 'a' <= b && b <= 'z'
345: }
1.17 rillig 346:
347: // Once remembers with which arguments its FirstTime method has been called
348: // and only returns true on each first call.
349: type Once struct {
350: seen map[string]bool
351: }
352:
353: func (o *Once) FirstTime(what string) bool {
354: if o.seen == nil {
355: o.seen = make(map[string]bool)
356: }
357: if _, ok := o.seen[what]; ok {
358: return false
359: }
360: o.seen[what] = true
361: return true
362: }
CVSweb <webmaster@jp.NetBSD.org>