Received: by mail.netbsd.org (Postfix, from userid 605) id 2B31084D55; Sat, 23 Nov 2019 23:36:22 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by mail.netbsd.org (Postfix) with ESMTP id A6E2284D4D for ; Sat, 23 Nov 2019 23:36:21 +0000 (UTC) X-Virus-Scanned: amavisd-new at netbsd.org Received: from mail.netbsd.org ([IPv6:::1]) by localhost (mail.netbsd.org [IPv6:::1]) (amavisd-new, port 10025) with ESMTP id 81qaDGBSDdWI for ; Sat, 23 Nov 2019 23:36:20 +0000 (UTC) Received: from cvs.NetBSD.org (ivanova.netbsd.org [199.233.217.197]) by mail.netbsd.org (Postfix) with ESMTP id 4F5BF84D43 for ; Sat, 23 Nov 2019 23:36:20 +0000 (UTC) Received: by cvs.NetBSD.org (Postfix, from userid 500) id 4BF9CFA97; Sat, 23 Nov 2019 23:36:20 +0000 (UTC) Content-Transfer-Encoding: 7bit Content-Type: multipart/mixed; boundary="_----------=_157455218020620" MIME-Version: 1.0 Date: Sat, 23 Nov 2019 23:36:20 +0000 From: "Roland Illig" Subject: CVS commit: pkgsrc/pkgtools/pkglint/files To: pkgsrc-changes@NetBSD.org Reply-To: rillig@netbsd.org X-Mailer: log_accum Message-Id: <20191123233620.4BF9CFA97@cvs.NetBSD.org> Sender: pkgsrc-changes-owner@NetBSD.org List-Id: pkgsrc-changes.NetBSD.org Precedence: bulk List-Unsubscribe: This is a multi-part message in MIME format. --_----------=_157455218020620 Content-Disposition: inline Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="US-ASCII" Module Name: pkgsrc Committed By: rillig Date: Sat Nov 23 23:36:20 UTC 2019 Added Files: pkgsrc/pkgtools/pkglint/files: path.go path_test.go Log Message: pkgtools/pkglint: update to 19.3.10 Changes since 19.3.9: In diagnostics for suggested package updates, the exact line of doc/TODO is mentioned. If a suggested update has an additional comment, the brackets around that comment are not output anymore. The check for defined but not used variables has been improved for the edge case of defining a variable in the package Makefile and using it in the buildlink3.mk file of the same package, which just doesn't work. Makefile fragments in patches/ directories are now completely ignored. It was a hypothetical case anyway. Comparing PKGSRC_COMPILER using the == or != operators is now considered an error instead of a warning. The common cases can be autofixed. To generate a diff of this commit: cvs rdiff -u -r0 -r1.1 pkgsrc/pkgtools/pkglint/files/path.go \ pkgsrc/pkgtools/pkglint/files/path_test.go Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files. --_----------=_157455218020620 Content-Disposition: inline Content-Length: 18173 Content-Transfer-Encoding: binary Content-Type: text/x-diff; charset=us-ascii Added files: Index: pkgsrc/pkgtools/pkglint/files/path.go diff -u /dev/null pkgsrc/pkgtools/pkglint/files/path.go:1.1 --- /dev/null Sat Nov 23 23:36:20 2019 +++ pkgsrc/pkgtools/pkglint/files/path.go Sat Nov 23 23:36:20 2019 @@ -0,0 +1,162 @@ +package pkglint + +import ( + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" +) + +// Path is a slash-separated path in the filesystem. +// It may be absolute or relative. +// Some paths may contain placeholders like @VAR@ or ${VAR}. +// The base directory of relative paths depends on the context +// in which the path is used. +type Path string + +func NewPath(name string) Path { return Path(name) } + +func NewPathSlash(name string) Path { return Path(filepath.ToSlash(name)) } + +func (p Path) String() string { return string(p) } + +func (p Path) GoString() string { return sprintf("%q", string(p)) } + +func (p Path) Dir() Path { return Path(path.Dir(string(p))) } + +func (p Path) Base() string { return path.Base(string(p)) } + +func (p Path) Split() (dir Path, base string) { + strDir, strBase := path.Split(string(p)) + return Path(strDir), strBase +} + +func (p Path) Parts() []string { + return strings.FieldsFunc(string(p), func(r rune) bool { return r == '/' }) +} + +func (p Path) Count() int { return len(p.Parts()) } + +func (p Path) HasPrefixText(prefix string) bool { + return hasPrefix(string(p), prefix) +} + +// HasPrefixPath returns whether the path starts with the given prefix. +// The basic unit of comparison is a path component, not a character. +func (p Path) HasPrefixPath(prefix Path) bool { + return hasPrefix(string(p), string(prefix)) && + (len(p) == len(prefix) || p[len(prefix)] == '/') +} + +// TODO: Check each call whether ContainsPath is more appropriate; add tests +func (p Path) ContainsText(contained string) bool { + return contains(string(p), contained) +} + +// ContainsPath returns whether the sub path is part of the path. +// The basic unit of comparison is a path component, not a character. +// +// Note that the paths used in pkglint may contains seemingly unnecessary +// components, like "../../wip/mk/../../devel/gettext-lib". To ignore these +// components, use ContainsPathCanonical instead. +func (p Path) ContainsPath(sub Path) bool { + limit := len(p) - len(sub) + for i := 0; i <= limit; i++ { + if (i == 0 || p[i-1] == '/') && p[i:].HasPrefixPath(sub) { + return true + } + } + return sub == "." +} + +func (p Path) ContainsPathCanonical(sub Path) bool { + cleaned := cleanpath(p) + return cleaned.ContainsPath(sub) +} + +// TODO: Check each call whether HasSuffixPath is more appropriate; add tests +func (p Path) HasSuffixText(suffix string) bool { + return hasSuffix(string(p), suffix) +} + +// HasSuffixPath returns whether the path ends with the given prefix. +// The basic unit of comparison is a path component, not a character. +func (p Path) HasSuffixPath(suffix Path) bool { + return hasSuffix(string(p), string(suffix)) && + (len(p) == len(suffix) || p[len(p)-len(suffix)-1] == '/') +} + +func (p Path) HasBase(base string) bool { return p.Base() == base } + +func (p Path) TrimSuffix(suffix string) Path { + return Path(strings.TrimSuffix(string(p), suffix)) +} + +func (p Path) Replace(from, to string) Path { + return Path(strings.Replace(string(p), from, to, -1)) +} + +func (p Path) JoinClean(s Path) Path { + return Path(path.Join(string(p), string(s))) +} + +func (p Path) JoinNoClean(s Path) Path { + return Path(string(p) + "/" + string(s)) +} + +func (p Path) Clean() Path { return NewPath(path.Clean(string(p))) } + +func (p Path) IsAbs() bool { + return p.HasPrefixText("/") || filepath.IsAbs(filepath.FromSlash(string(p))) +} + +func (p Path) Rel(other Path) Path { + fp := filepath.FromSlash(p.String()) + fpOther := filepath.FromSlash(other.String()) + rel, err := filepath.Rel(fp, fpOther) + assertNil(err, "relpath from %q to %q", p, other) + return NewPath(filepath.ToSlash(rel)) +} + +func (p Path) Rename(newName Path) error { + return os.Rename(string(p), string(newName)) +} + +func (p Path) Lstat() (os.FileInfo, error) { return os.Lstat(string(p)) } + +func (p Path) Stat() (os.FileInfo, error) { return os.Stat(string(p)) } + +func (p Path) Exists() bool { + _, err := p.Lstat() + return err == nil +} + +func (p Path) IsFile() bool { + info, err := p.Lstat() + return err == nil && info.Mode().IsRegular() +} + +func (p Path) IsDir() bool { + info, err := p.Lstat() + return err == nil && info.IsDir() +} + +func (p Path) Chmod(mode os.FileMode) error { + return os.Chmod(string(p), mode) +} + +func (p Path) ReadDir() ([]os.FileInfo, error) { + return ioutil.ReadDir(string(p)) +} + +func (p Path) Open() (*os.File, error) { return os.Open(string(p)) } + +func (p Path) ReadString() (string, error) { + bytes, err := ioutil.ReadFile(string(p)) + return string(bytes), err +} + +func (p Path) WriteString(s string) error { + return ioutil.WriteFile(string(p), []byte(s), 0666) +} Index: pkgsrc/pkgtools/pkglint/files/path_test.go diff -u /dev/null pkgsrc/pkgtools/pkglint/files/path_test.go:1.1 --- /dev/null Sat Nov 23 23:36:20 2019 +++ pkgsrc/pkgtools/pkglint/files/path_test.go Sat Nov 23 23:36:20 2019 @@ -0,0 +1,535 @@ +package pkglint + +import ( + "gopkg.in/check.v1" + "io" + "os" + "runtime" + "strings" +) + +func (s *Suite) Test_NewPath(c *check.C) { + t := s.Init(c) + + t.CheckEquals(NewPath("filename"), NewPath("filename")) + t.CheckEquals(NewPath("\\"), NewPath("\\")) + c.Check(NewPath("\\"), check.Not(check.Equals), NewPath("/")) +} + +func (s *Suite) Test_NewPathSlash(c *check.C) { + t := s.Init(c) + + t.CheckEquals(NewPathSlash("filename"), NewPathSlash("filename")) + t.CheckEquals(NewPathSlash("\\"), NewPathSlash("\\")) + + t.CheckEquals( + NewPathSlash("\\"), + NewPathSlash(condStr(runtime.GOOS == "windows", "/", "\\"))) +} + +func (s *Suite) Test_Path_String(c *check.C) { + t := s.Init(c) + + for _, p := range []string{"", "filename", "a/b", "c\\d"} { + t.CheckEquals(NewPath(p).String(), p) + } +} + +func (s *Suite) Test_Path_GoString(c *check.C) { + t := s.Init(c) + + test := func(p Path, s string) { + t.CheckEquals(p.GoString(), s) + } + + test("", "\"\"") + test("filename", "\"filename\"") + test("a/b", "\"a/b\"") + test("c\\d", "\"c\\\\d\"") +} + +func (s *Suite) Test_Path_Dir(c *check.C) { + t := s.Init(c) + + test := func(p, dir Path) { + t.CheckEquals(p.Dir(), dir) + } + + test("", ".") + test("././././", ".") + test("/root", "/") + test("filename", ".") + test("dir/filename", "dir") + test("dir/filename\\with\\backslash", "dir") +} + +func (s *Suite) Test_Path_Base(c *check.C) { + t := s.Init(c) + + test := func(p Path, base string) { + t.CheckEquals(p.Base(), base) + } + + test("", ".") // That's a bit surprising + test("././././", ".") + test("/root", "root") + test("filename", "filename") + test("dir/filename", "filename") + test("dir/filename\\with\\backslash", "filename\\with\\backslash") +} + +func (s *Suite) Test_Path_Split(c *check.C) { + t := s.Init(c) + + test := func(p, dir, base string) { + actualDir, actualBase := NewPath(p).Split() + + t.CheckDeepEquals( + []string{actualDir.String(), actualBase}, + []string{dir, base}) + } + + test("", "", "") + test("././././", "././././", "") + test("/root", "/", "root") + test("filename", "", "filename") + test("dir/filename", "dir/", "filename") + test("dir/filename\\with\\backslash", "dir/", "filename\\with\\backslash") +} + +func (s *Suite) Test_Path_Parts(c *check.C) { + t := s.Init(c) + + test := func(p string, parts ...string) { + t.CheckDeepEquals(NewPath(p).Parts(), parts) + } + + test("", []string{}...) + test("././././", ".", ".", ".", ".") // No trailing "" + test("/root", "root") // No leading "" + test("filename", "filename") + test("dir/filename", "dir", "filename") + test("dir/filename\\with\\backslash", "dir", "filename\\with\\backslash") +} + +func (s *Suite) Test_Path_Count(c *check.C) { + t := s.Init(c) + + test := func(p string, count int) { + t.CheckEquals(NewPath(p).Count(), count) + } + + test("", 0) // FIXME + test("././././", 4) + test("/root", 1) // FIXME + test("filename", 1) + test("dir/filename", 2) + test("dir/filename\\with\\backslash", 2) +} + +func (s *Suite) Test_Path_HasPrefixText(c *check.C) { + t := s.Init(c) + + test := func(p, prefix string, hasPrefix bool) { + t.CheckEquals(NewPath(p).HasPrefixText(prefix), hasPrefix) + } + + test("", "", true) + test("filename", "", true) + test("", "x", false) + test("/root", "/r", true) + test("/root", "/root", true) + test("/root", "/root/", false) + test("/root", "root/", false) +} + +func (s *Suite) Test_Path_HasPrefixPath(c *check.C) { + t := s.Init(c) + + test := func(p, prefix Path, hasPrefix bool) { + t.CheckEquals(p.HasPrefixPath(prefix), hasPrefix) + } + + test("", "", true) + test("filename", "", false) + test("", "x", false) + test("/root", "/r", false) + test("/root", "/root", true) + test("/root", "/root/", false) + test("/root/", "/root", true) + test("/root/subdir", "/root", true) +} + +func (s *Suite) Test_Path_ContainsText(c *check.C) { + t := s.Init(c) + + test := func(p Path, text string, contains bool) { + t.CheckEquals(p.ContainsText(text), contains) + } + + test("", "", true) + test("filename", "", true) + test("filename", ".", false) + test("a.b", ".", true) + test("..", ".", true) + test("", "x", false) + test("/root", "/r", true) + test("/root", "/root", true) + test("/root", "/root/", false) + test("/root", "root/", false) + test("/root", "ro", true) + test("/root", "ot", true) +} + +func (s *Suite) Test_Path_ContainsPath(c *check.C) { + t := s.Init(c) + + test := func(p, sub Path, contains bool) { + t.CheckEquals(p.ContainsPath(sub), contains) + } + + test("", "", true) // It doesn't make sense to search for empty paths. + test(".", "", false) // It doesn't make sense to search for empty paths. + test("filename", ".", true) // Every path contains "." implicitly at the beginning + test("a.b", ".", true) + test("..", ".", true) + test("filename", "", false) + test("filename", "filename", true) + test("a/b/c", "a", true) + test("a/b/c", "b", true) + test("a/b/c", "c", true) + test("a/b/c", "a/b", true) + test("a/b/c", "b/c", true) + test("a/b/c", "a/b/c", true) + test("aa/b/c", "a", false) + test("a/bb/c", "b", false) + test("a/bb/c", "b/c", false) + test("mk/fetch/fetch.mk", "mk", true) + test("category/package/../../wip/mk/../..", "mk", true) +} + +func (s *Suite) Test_Path_ContainsPathCanonical(c *check.C) { + t := s.Init(c) + + test := func(p, sub Path, contains bool) { + t.CheckEquals(p.ContainsPathCanonical(sub), contains) + } + + test("", "", false) + test(".", "", false) + test("filename", "", false) + test("filename", "filename", true) + test("a/b/c", "a", true) + test("a/b/c", "b", true) + test("a/b/c", "c", true) + test("a/b/c", "a/b", true) + test("a/b/c", "b/c", true) + test("a/b/c", "a/b/c", true) + test("aa/b/c", "a", false) + test("a/bb/c", "b", false) + test("a/bb/c", "b/c", false) + test("mk/fetch/fetch.mk", "mk", true) + test("category/package/../../wip/mk", "mk", true) + test("category/package/../../wip/mk/..", "mk", true) // FIXME + test("category/package/../../wip/mk/../..", "mk", false) +} + +func (s *Suite) Test_Path_HasSuffixText(c *check.C) { + t := s.Init(c) + + test := func(p Path, suffix string, has bool) { + t.CheckEquals(p.HasSuffixText(suffix), has) + } + + test("", "", true) + test("a/bb/c", "", true) + test("a/bb/c", "c", true) + test("a/bb/c", "/c", true) + test("a/bb/c", "b/c", true) + test("aa/b/c", "bb", false) +} + +func (s *Suite) Test_Path_HasSuffixPath(c *check.C) { + t := s.Init(c) + + test := func(p, suffix Path, has bool) { + t.CheckEquals(p.HasSuffixPath(suffix), has) + } + + test("", "", true) + test("a/bb/c", "", false) + test("a/bb/c", "c", true) + test("a/bb/c", "/c", false) + test("a/bb/c", "b/c", false) + test("aa/b/c", "bb", false) +} + +func (s *Suite) Test_Path_HasBase(c *check.C) { + t := s.Init(c) + + test := func(p Path, suffix string, hasBase bool) { + t.CheckEquals(p.HasBase(suffix), hasBase) + } + + test("dir/file", "e", false) + test("dir/file", "file", true) + test("dir/file", "file.ext", false) + test("dir/file", "/file", false) + test("dir/file", "dir/file", false) +} + +func (s *Suite) Test_Path_TrimSuffix(c *check.C) { + t := s.Init(c) + + test := func(p Path, suffix string, result Path) { + t.CheckEquals(p.TrimSuffix(suffix), result) + } + + test("dir/file", "e", "dir/fil") + test("dir/file", "file", "dir/") + test("dir/file", "/file", "dir") + test("dir/file", "dir/file", "") + test("dir/file", "subdir/file", "dir/file") +} + +func (s *Suite) Test_Path_Replace(c *check.C) { + t := s.Init(c) + + test := func(p Path, from, to string, result Path) { + t.CheckEquals(p.Replace(from, to), result) + } + + test("dir/file", "dir", "other", "other/file") + test("dir/file", "r", "sk", "disk/file") + test("aaa/file", "a", "sub/", "sub/sub/sub//file") +} + +func (s *Suite) Test_Path_JoinClean(c *check.C) { + t := s.Init(c) + + test := func(p Path, suffix Path, result Path) { + t.CheckEquals(p.JoinClean(suffix), result) + } + + test("dir", "file", "dir/file") + test("dir", "///file", "dir/file") + test("dir/./../dir/", "///file", "dir/file") + test("dir", "..", ".") +} + +func (s *Suite) Test_Path_JoinNoClean(c *check.C) { + t := s.Init(c) + + test := func(p, suffix Path, result Path) { + t.CheckEquals(p.JoinNoClean(suffix), result) + } + + test("dir", "file", "dir/file") + test("dir", "///file", "dir////file") + test("dir/./../dir/", "///file", "dir/./../dir/////file") + test("dir", "..", "dir/..") +} + +func (s *Suite) Test_Path_Clean(c *check.C) { + t := s.Init(c) + + test := func(p, result Path) { + t.CheckEquals(p.Clean(), result) + } + + test("", ".") + test(".", ".") + test("./././", ".") + test("a/bb///../c", "a/c") +} + +func (s *Suite) Test_Path_IsAbs(c *check.C) { + t := s.Init(c) + + test := func(p Path, abs bool) { + t.CheckEquals(p.IsAbs(), abs) + } + + test("", false) + test(".", false) + test("a/b", false) + test("/a", true) + test("C:/", runtime.GOOS == "windows") + test("c:/", runtime.GOOS == "windows") +} + +func (s *Suite) Test_Path_Rel(c *check.C) { + t := s.Init(c) + + base := NewPath(".") + abc := NewPath("a/b/c") + defFile := NewPath("d/e/f/file") + + t.CheckEquals(abc.Rel(defFile), NewPath("../../../d/e/f/file")) + t.CheckEquals(base.Rel(base), NewPath(".")) + t.CheckEquals(abc.Rel(base), NewPath("../../../.")) +} + +func (s *Suite) Test_Path_Rename(c *check.C) { + t := s.Init(c) + + f := t.CreateFileLines("filename.old", + "line 1") + t.CheckEquals(f.IsFile(), true) + dst := NewPath(f.TrimSuffix(".old").String() + ".new") + + err := f.Rename(dst) + + assertNil(err, "Rename") + t.CheckEquals(f.IsFile(), false) + t.CheckFileLines("filename.new", + "line 1") +} + +func (s *Suite) Test_Path_Lstat(c *check.C) { + t := s.Init(c) + + testDir := func(f Path, isDir bool) { + st, err := f.Lstat() + assertNil(err, "Lstat") + t.CheckEquals(st.Mode()&os.ModeDir != 0, isDir) + } + + t.CreateFileLines("subdir/file") + t.CreateFileLines("file") + + testDir(t.File("subdir"), true) + testDir(t.File("file"), false) +} + +func (s *Suite) Test_Path_Stat(c *check.C) { + t := s.Init(c) + + testDir := func(f Path, isDir bool) { + st, err := f.Stat() + assertNil(err, "Stat") + t.CheckEquals(st.Mode()&os.ModeDir != 0, isDir) + } + + t.CreateFileLines("subdir/file") + t.CreateFileLines("file") + + testDir(t.File("subdir"), true) + testDir(t.File("file"), false) +} + +func (s *Suite) Test_Path_Exists(c *check.C) { + t := s.Init(c) + + test := func(f Path, exists bool) { + t.CheckEquals(f.Exists(), exists) + } + + t.CreateFileLines("subdir/file") + t.CreateFileLines("file") + + test(t.File("subdir"), true) + test(t.File("file"), true) + test(t.File("enoent"), false) +} + +func (s *Suite) Test_Path_IsFile(c *check.C) { + t := s.Init(c) + + t.CreateFileLines("dir/file") + + t.CheckEquals(t.File("nonexistent").IsFile(), false) + t.CheckEquals(t.File("dir").IsFile(), false) + t.CheckEquals(t.File("dir/nonexistent").IsFile(), false) + t.CheckEquals(t.File("dir/file").IsFile(), true) +} + +func (s *Suite) Test_Path_IsDir(c *check.C) { + t := s.Init(c) + + t.CreateFileLines("dir/file") + + t.CheckEquals(t.File("nonexistent").IsDir(), false) + t.CheckEquals(t.File("dir").IsDir(), true) + t.CheckEquals(t.File("dir/nonexistent").IsDir(), false) + t.CheckEquals(t.File("dir/file").IsDir(), false) +} + +func (s *Suite) Test_Path_Chmod(c *check.C) { + t := s.Init(c) + + testWritable := func(f Path, writable bool) { + lstat, err := f.Lstat() + assertNil(err, "Lstat") + t.CheckEquals(lstat.Mode().Perm()&0200 != 0, writable) + } + + f := t.CreateFileLines("file") + testWritable(f, true) + + err := f.Chmod(0444) + assertNil(err, "Chmod") + + testWritable(f, false) +} + +func (s *Suite) Test_Path_ReadDir(c *check.C) { + t := s.Init(c) + + t.CreateFileLines("subdir/file") + t.CreateFileLines("file") + t.CreateFileLines("CVS/Entries") + t.CreateFileLines(".git/info/exclude") + + infos, err := t.File(".").ReadDir() + + assertNil(err, "ReadDir") + var names []string + for _, info := range infos { + names = append(names, info.Name()) + } + + t.CheckDeepEquals(names, []string{".git", "CVS", "file", "subdir"}) +} + +func (s *Suite) Test_Path_Open(c *check.C) { + t := s.Init(c) + + t.CreateFileLines("filename", + "line 1", + "line 2") + + f, err := t.File("filename").Open() + + assertNil(err, "Open") + defer func() { assertNil(f.Close(), "Close") }() + var sb strings.Builder + n, err := io.Copy(&sb, f) + assertNil(err, "Copy") + t.CheckEquals(n, int64(14)) + t.CheckEquals(sb.String(), "line 1\nline 2\n") +} + +func (s *Suite) Test_Path_ReadString(c *check.C) { + t := s.Init(c) + + t.CreateFileLines("filename", + "line 1", + "line 2") + + text, err := t.File("filename").ReadString() + + assertNil(err, "ReadString") + t.CheckEquals(text, "line 1\nline 2\n") +} + +func (s *Suite) Test_Path_WriteString(c *check.C) { + t := s.Init(c) + + err := t.File("filename").WriteString("line 1\nline 2\n") + + assertNil(err, "WriteString") + t.CheckFileLines("filename", + "line 1", + "line 2") +} --_----------=_157455218020620--