pkgsrc-Changes archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

CVS commit: pkgsrc/pkgtools/pkglint/files



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.

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")
+}



Home | Main Index | Thread Index | Old Index