Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / golang.org / x / tools@v0.0.0-20201105173854-bc9fc8d8c4bc / godoc / vfs / zipfs / zipfs.go
1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Package zipfs file provides an implementation of the FileSystem
6 // interface based on the contents of a .zip file.
7 //
8 // Assumptions:
9 //
10 //  - The file paths stored in the zip file must use a slash ('/') as path
11 //    separator; and they must be relative (i.e., they must not start with
12 //    a '/' - this is usually the case if the file was created w/o special
13 //    options).
14 //  - The zip file system treats the file paths found in the zip internally
15 //    like absolute paths w/o a leading '/'; i.e., the paths are considered
16 //    relative to the root of the file system.
17 //  - All path arguments to file system methods must be absolute paths.
18 package zipfs // import "golang.org/x/tools/godoc/vfs/zipfs"
19
20 import (
21         "archive/zip"
22         "fmt"
23         "go/build"
24         "io"
25         "os"
26         "path"
27         "path/filepath"
28         "sort"
29         "strings"
30         "time"
31
32         "golang.org/x/tools/godoc/vfs"
33 )
34
35 // zipFI is the zip-file based implementation of FileInfo
36 type zipFI struct {
37         name string    // directory-local name
38         file *zip.File // nil for a directory
39 }
40
41 func (fi zipFI) Name() string {
42         return fi.name
43 }
44
45 func (fi zipFI) Size() int64 {
46         if f := fi.file; f != nil {
47                 return int64(f.UncompressedSize)
48         }
49         return 0 // directory
50 }
51
52 func (fi zipFI) ModTime() time.Time {
53         if f := fi.file; f != nil {
54                 return f.ModTime()
55         }
56         return time.Time{} // directory has no modified time entry
57 }
58
59 func (fi zipFI) Mode() os.FileMode {
60         if fi.file == nil {
61                 // Unix directories typically are executable, hence 555.
62                 return os.ModeDir | 0555
63         }
64         return 0444
65 }
66
67 func (fi zipFI) IsDir() bool {
68         return fi.file == nil
69 }
70
71 func (fi zipFI) Sys() interface{} {
72         return nil
73 }
74
75 // zipFS is the zip-file based implementation of FileSystem
76 type zipFS struct {
77         *zip.ReadCloser
78         list zipList
79         name string
80 }
81
82 func (fs *zipFS) String() string {
83         return "zip(" + fs.name + ")"
84 }
85
86 func (fs *zipFS) RootType(abspath string) vfs.RootType {
87         var t vfs.RootType
88         switch {
89         case exists(path.Join(vfs.GOROOT, abspath)):
90                 t = vfs.RootTypeGoRoot
91         case isGoPath(abspath):
92                 t = vfs.RootTypeGoPath
93         }
94         return t
95 }
96
97 func isGoPath(abspath string) bool {
98         for _, p := range filepath.SplitList(build.Default.GOPATH) {
99                 if exists(path.Join(p, abspath)) {
100                         return true
101                 }
102         }
103         return false
104 }
105
106 func exists(path string) bool {
107         _, err := os.Stat(path)
108         return err == nil
109 }
110
111 func (fs *zipFS) Close() error {
112         fs.list = nil
113         return fs.ReadCloser.Close()
114 }
115
116 func zipPath(name string) (string, error) {
117         name = path.Clean(name)
118         if !path.IsAbs(name) {
119                 return "", fmt.Errorf("stat: not an absolute path: %s", name)
120         }
121         return name[1:], nil // strip leading '/'
122 }
123
124 func isRoot(abspath string) bool {
125         return path.Clean(abspath) == "/"
126 }
127
128 func (fs *zipFS) stat(abspath string) (int, zipFI, error) {
129         if isRoot(abspath) {
130                 return 0, zipFI{
131                         name: "",
132                         file: nil,
133                 }, nil
134         }
135         zippath, err := zipPath(abspath)
136         if err != nil {
137                 return 0, zipFI{}, err
138         }
139         i, exact := fs.list.lookup(zippath)
140         if i < 0 {
141                 // zippath has leading '/' stripped - print it explicitly
142                 return -1, zipFI{}, &os.PathError{Path: "/" + zippath, Err: os.ErrNotExist}
143         }
144         _, name := path.Split(zippath)
145         var file *zip.File
146         if exact {
147                 file = fs.list[i] // exact match found - must be a file
148         }
149         return i, zipFI{name, file}, nil
150 }
151
152 func (fs *zipFS) Open(abspath string) (vfs.ReadSeekCloser, error) {
153         _, fi, err := fs.stat(abspath)
154         if err != nil {
155                 return nil, err
156         }
157         if fi.IsDir() {
158                 return nil, fmt.Errorf("Open: %s is a directory", abspath)
159         }
160         r, err := fi.file.Open()
161         if err != nil {
162                 return nil, err
163         }
164         return &zipSeek{fi.file, r}, nil
165 }
166
167 type zipSeek struct {
168         file *zip.File
169         io.ReadCloser
170 }
171
172 func (f *zipSeek) Seek(offset int64, whence int) (int64, error) {
173         if whence == 0 && offset == 0 {
174                 r, err := f.file.Open()
175                 if err != nil {
176                         return 0, err
177                 }
178                 f.Close()
179                 f.ReadCloser = r
180                 return 0, nil
181         }
182         return 0, fmt.Errorf("unsupported Seek in %s", f.file.Name)
183 }
184
185 func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) {
186         _, fi, err := fs.stat(abspath)
187         return fi, err
188 }
189
190 func (fs *zipFS) Stat(abspath string) (os.FileInfo, error) {
191         _, fi, err := fs.stat(abspath)
192         return fi, err
193 }
194
195 func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) {
196         i, fi, err := fs.stat(abspath)
197         if err != nil {
198                 return nil, err
199         }
200         if !fi.IsDir() {
201                 return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath)
202         }
203
204         var list []os.FileInfo
205
206         // make dirname the prefix that file names must start with to be considered
207         // in this directory. we must special case the root directory because, per
208         // the spec of this package, zip file entries MUST NOT start with /, so we
209         // should not append /, as we would in every other case.
210         var dirname string
211         if isRoot(abspath) {
212                 dirname = ""
213         } else {
214                 zippath, err := zipPath(abspath)
215                 if err != nil {
216                         return nil, err
217                 }
218                 dirname = zippath + "/"
219         }
220         prevname := ""
221         for _, e := range fs.list[i:] {
222                 if !strings.HasPrefix(e.Name, dirname) {
223                         break // not in the same directory anymore
224                 }
225                 name := e.Name[len(dirname):] // local name
226                 file := e
227                 if i := strings.IndexRune(name, '/'); i >= 0 {
228                         // We infer directories from files in subdirectories.
229                         // If we have x/y, return a directory entry for x.
230                         name = name[0:i] // keep local directory name only
231                         file = nil
232                 }
233                 // If we have x/y and x/z, don't return two directory entries for x.
234                 // TODO(gri): It should be possible to do this more efficiently
235                 // by determining the (fs.list) range of local directory entries
236                 // (via two binary searches).
237                 if name != prevname {
238                         list = append(list, zipFI{name, file})
239                         prevname = name
240                 }
241         }
242
243         return list, nil
244 }
245
246 func New(rc *zip.ReadCloser, name string) vfs.FileSystem {
247         list := make(zipList, len(rc.File))
248         copy(list, rc.File) // sort a copy of rc.File
249         sort.Sort(list)
250         return &zipFS{rc, list, name}
251 }
252
253 type zipList []*zip.File
254
255 // zipList implements sort.Interface
256 func (z zipList) Len() int           { return len(z) }
257 func (z zipList) Less(i, j int) bool { return z[i].Name < z[j].Name }
258 func (z zipList) Swap(i, j int)      { z[i], z[j] = z[j], z[i] }
259
260 // lookup returns the smallest index of an entry with an exact match
261 // for name, or an inexact match starting with name/. If there is no
262 // such entry, the result is -1, false.
263 func (z zipList) lookup(name string) (index int, exact bool) {
264         // look for exact match first (name comes before name/ in z)
265         i := sort.Search(len(z), func(i int) bool {
266                 return name <= z[i].Name
267         })
268         if i >= len(z) {
269                 return -1, false
270         }
271         // 0 <= i < len(z)
272         if z[i].Name == name {
273                 return i, true
274         }
275
276         // look for inexact match (must be in z[i:], if present)
277         z = z[i:]
278         name += "/"
279         j := sort.Search(len(z), func(i int) bool {
280                 return name <= z[i].Name
281         })
282         if j >= len(z) {
283                 return -1, false
284         }
285         // 0 <= j < len(z)
286         if strings.HasPrefix(z[j].Name, name) {
287                 return i + j, false
288         }
289
290         return -1, false
291 }