blob: 0898e5ffc0ad6e6b6fb937059cd84d3403ca644e [file] [log] [blame]
Marcel van Lohuizenbc4d65d2018-12-10 15:40:02 +01001// Copyright 2018 The CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package load
16
17import (
18 "io"
19 "io/ioutil"
20 "os"
21 "path/filepath"
22 "strings"
23)
24
25// TODO: remove this file if we know we don't need it.
26
27// A fileSystem specifies the supporting context for a build.
28type fileSystem struct {
29 // By default, Import uses the operating system's file system calls
30 // to read directories and files. To read from other sources,
31 // callers can set the following functions. They all have default
32 // behaviors that use the local file system, so clients need only set
33 // the functions whose behaviors they wish to change.
34
35 // JoinPath joins the sequence of path fragments into a single path.
36 // If JoinPath is nil, Import uses filepath.Join.
37 JoinPath func(elem ...string) string
38
39 // SplitPathList splits the path list into a slice of individual paths.
40 // If SplitPathList is nil, Import uses filepath.SplitList.
41 SplitPathList func(list string) []string
42
43 // IsAbsPath reports whether path is an absolute path.
44 // If IsAbsPath is nil, Import uses filepath.IsAbs.
45 IsAbsPath func(path string) bool
46
47 // IsDir reports whether the path names a directory.
48 // If IsDir is nil, Import calls os.Stat and uses the result's IsDir method.
49 IsDir func(path string) bool
50
51 // HasSubdir reports whether dir is a subdirectory of
52 // (perhaps multiple levels below) root.
53 // If so, HasSubdir sets rel to a slash-separated path that
54 // can be joined to root to produce a path equivalent to dir.
55 // If HasSubdir is nil, Import uses an implementation built on
56 // filepath.EvalSymlinks.
57 HasSubdir func(root, dir string) (rel string, ok bool)
58
59 // ReadDir returns a slice of os.FileInfo, sorted by Name,
60 // describing the content of the named directory.
61 // If ReadDir is nil, Import uses ioutil.ReadDir.
62 ReadDir func(dir string) ([]os.FileInfo, error)
63
64 // OpenFile opens a file (not a directory) for reading.
65 // If OpenFile is nil, Import uses os.Open.
66 OpenFile func(path string) (io.ReadCloser, error)
67}
68
69// JoinPath calls ctxt.JoinPath (if not nil) or else filepath.Join.
70func (ctxt *fileSystem) joinPath(elem ...string) string {
71 if f := ctxt.JoinPath; f != nil {
72 return f(elem...)
73 }
74 return filepath.Join(elem...)
75}
76
77// splitPathList calls ctxt.SplitPathList (if not nil) or else filepath.SplitList.
78func (ctxt *fileSystem) splitPathList(s string) []string {
79 if f := ctxt.SplitPathList; f != nil {
80 return f(s)
81 }
82 return filepath.SplitList(s)
83}
84
85// isAbsPath calls ctxt.IsAbsPath (if not nil) or else filepath.IsAbs.
86func (ctxt *fileSystem) isAbsPath(path string) bool {
87 if f := ctxt.IsAbsPath; f != nil {
88 return f(path)
89 }
90 return filepath.IsAbs(path)
91}
92
93// isDir calls ctxt.IsDir (if not nil) or else uses os.Stat.
94func (ctxt *fileSystem) isDir(path string) bool {
95 if f := ctxt.IsDir; f != nil {
96 return f(path)
97 }
98 fi, err := os.Stat(path)
99 return err == nil && fi.IsDir()
100}
101
102// hasSubdir calls ctxt.HasSubdir (if not nil) or else uses
103// the local file system to answer the question.
104func (ctxt *fileSystem) hasSubdir(root, dir string) (rel string, ok bool) {
105 if f := ctxt.HasSubdir; f != nil {
106 return f(root, dir)
107 }
108
109 // Try using paths we received.
110 if rel, ok = hasSubdir(root, dir); ok {
111 return
112 }
113
114 // Try expanding symlinks and comparing
115 // expanded against unexpanded and
116 // expanded against expanded.
117 rootSym, _ := filepath.EvalSymlinks(root)
118 dirSym, _ := filepath.EvalSymlinks(dir)
119
120 if rel, ok = hasSubdir(rootSym, dir); ok {
121 return
122 }
123 if rel, ok = hasSubdir(root, dirSym); ok {
124 return
125 }
126 return hasSubdir(rootSym, dirSym)
127}
128
129func hasSubdir(root, dir string) (rel string, ok bool) {
130 const sep = string(filepath.Separator)
131 root = filepath.Clean(root)
132 if !strings.HasSuffix(root, sep) {
133 root += sep
134 }
135 dir = filepath.Clean(dir)
136 if !strings.HasPrefix(dir, root) {
137 return "", false
138 }
139 return filepath.ToSlash(dir[len(root):]), true
140}
141
142// ReadDir calls ctxt.ReadDir (if not nil) or else ioutil.ReadDir.
143func (ctxt *fileSystem) readDir(path string) ([]os.FileInfo, error) {
144 if f := ctxt.ReadDir; f != nil {
145 return f(path)
146 }
147 return ioutil.ReadDir(path)
148}
149
150// openFile calls ctxt.OpenFile (if not nil) or else os.Open.
151func (ctxt *fileSystem) openFile(path string) (io.ReadCloser, error) {
152 if fn := ctxt.OpenFile; fn != nil {
153 return fn(path)
154 }
155
156 f, err := os.Open(path)
157 if err != nil {
158 return nil, err // nil interface
159 }
160 return f, nil
161}
162
163// isFile determines whether path is a file by trying to open it.
164// It reuses openFile instead of adding another function to the
165// list in Context.
166func (ctxt *fileSystem) isFile(path string) bool {
167 f, err := ctxt.openFile(path)
168 if err != nil {
169 return false
170 }
171 f.Close()
172 return true
173}