1fd4e5da5Sopenharmony_ci// Copyright 2019 The Go Authors. 2fd4e5da5Sopenharmony_ci// 3fd4e5da5Sopenharmony_ci// Licensed under the Apache License, Version 2.0 (the "License"); 4fd4e5da5Sopenharmony_ci// you may not use this file except in compliance with the License. 5fd4e5da5Sopenharmony_ci// You may obtain a copy of the License at 6fd4e5da5Sopenharmony_ci// 7fd4e5da5Sopenharmony_ci// http://www.apache.org/licenses/LICENSE-2.0 8fd4e5da5Sopenharmony_ci// 9fd4e5da5Sopenharmony_ci// Unless required by applicable law or agreed to in writing, software 10fd4e5da5Sopenharmony_ci// distributed under the License is distributed on an "AS IS" BASIS, 11fd4e5da5Sopenharmony_ci// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12fd4e5da5Sopenharmony_ci// See the License for the specific language governing permissions and 13fd4e5da5Sopenharmony_ci// limitations under the License. 14fd4e5da5Sopenharmony_ci 15fd4e5da5Sopenharmony_cipackage span 16fd4e5da5Sopenharmony_ci 17fd4e5da5Sopenharmony_ciimport ( 18fd4e5da5Sopenharmony_ci "fmt" 19fd4e5da5Sopenharmony_ci "net/url" 20fd4e5da5Sopenharmony_ci "os" 21fd4e5da5Sopenharmony_ci "path" 22fd4e5da5Sopenharmony_ci "path/filepath" 23fd4e5da5Sopenharmony_ci "runtime" 24fd4e5da5Sopenharmony_ci "strings" 25fd4e5da5Sopenharmony_ci "unicode" 26fd4e5da5Sopenharmony_ci) 27fd4e5da5Sopenharmony_ci 28fd4e5da5Sopenharmony_ciconst fileScheme = "file" 29fd4e5da5Sopenharmony_ci 30fd4e5da5Sopenharmony_ci// URI represents the full URI for a file. 31fd4e5da5Sopenharmony_citype URI string 32fd4e5da5Sopenharmony_ci 33fd4e5da5Sopenharmony_ci// Filename returns the file path for the given URI. 34fd4e5da5Sopenharmony_ci// It is an error to call this on a URI that is not a valid filename. 35fd4e5da5Sopenharmony_cifunc (uri URI) Filename() string { 36fd4e5da5Sopenharmony_ci filename, err := filename(uri) 37fd4e5da5Sopenharmony_ci if err != nil { 38fd4e5da5Sopenharmony_ci panic(err) 39fd4e5da5Sopenharmony_ci } 40fd4e5da5Sopenharmony_ci return filepath.FromSlash(filename) 41fd4e5da5Sopenharmony_ci} 42fd4e5da5Sopenharmony_ci 43fd4e5da5Sopenharmony_cifunc filename(uri URI) (string, error) { 44fd4e5da5Sopenharmony_ci if uri == "" { 45fd4e5da5Sopenharmony_ci return "", nil 46fd4e5da5Sopenharmony_ci } 47fd4e5da5Sopenharmony_ci u, err := url.ParseRequestURI(string(uri)) 48fd4e5da5Sopenharmony_ci if err != nil { 49fd4e5da5Sopenharmony_ci return "", err 50fd4e5da5Sopenharmony_ci } 51fd4e5da5Sopenharmony_ci if u.Scheme != fileScheme { 52fd4e5da5Sopenharmony_ci return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri) 53fd4e5da5Sopenharmony_ci } 54fd4e5da5Sopenharmony_ci if isWindowsDriveURI(u.Path) { 55fd4e5da5Sopenharmony_ci u.Path = u.Path[1:] 56fd4e5da5Sopenharmony_ci } 57fd4e5da5Sopenharmony_ci return u.Path, nil 58fd4e5da5Sopenharmony_ci} 59fd4e5da5Sopenharmony_ci 60fd4e5da5Sopenharmony_ci// NewURI returns a span URI for the string. 61fd4e5da5Sopenharmony_ci// It will attempt to detect if the string is a file path or uri. 62fd4e5da5Sopenharmony_cifunc NewURI(s string) URI { 63fd4e5da5Sopenharmony_ci if u, err := url.PathUnescape(s); err == nil { 64fd4e5da5Sopenharmony_ci s = u 65fd4e5da5Sopenharmony_ci } 66fd4e5da5Sopenharmony_ci if strings.HasPrefix(s, fileScheme+"://") { 67fd4e5da5Sopenharmony_ci return URI(s) 68fd4e5da5Sopenharmony_ci } 69fd4e5da5Sopenharmony_ci return FileURI(s) 70fd4e5da5Sopenharmony_ci} 71fd4e5da5Sopenharmony_ci 72fd4e5da5Sopenharmony_cifunc CompareURI(a, b URI) int { 73fd4e5da5Sopenharmony_ci if equalURI(a, b) { 74fd4e5da5Sopenharmony_ci return 0 75fd4e5da5Sopenharmony_ci } 76fd4e5da5Sopenharmony_ci if a < b { 77fd4e5da5Sopenharmony_ci return -1 78fd4e5da5Sopenharmony_ci } 79fd4e5da5Sopenharmony_ci return 1 80fd4e5da5Sopenharmony_ci} 81fd4e5da5Sopenharmony_ci 82fd4e5da5Sopenharmony_cifunc equalURI(a, b URI) bool { 83fd4e5da5Sopenharmony_ci if a == b { 84fd4e5da5Sopenharmony_ci return true 85fd4e5da5Sopenharmony_ci } 86fd4e5da5Sopenharmony_ci // If we have the same URI basename, we may still have the same file URIs. 87fd4e5da5Sopenharmony_ci if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) { 88fd4e5da5Sopenharmony_ci return false 89fd4e5da5Sopenharmony_ci } 90fd4e5da5Sopenharmony_ci fa, err := filename(a) 91fd4e5da5Sopenharmony_ci if err != nil { 92fd4e5da5Sopenharmony_ci return false 93fd4e5da5Sopenharmony_ci } 94fd4e5da5Sopenharmony_ci fb, err := filename(b) 95fd4e5da5Sopenharmony_ci if err != nil { 96fd4e5da5Sopenharmony_ci return false 97fd4e5da5Sopenharmony_ci } 98fd4e5da5Sopenharmony_ci // Stat the files to check if they are equal. 99fd4e5da5Sopenharmony_ci infoa, err := os.Stat(filepath.FromSlash(fa)) 100fd4e5da5Sopenharmony_ci if err != nil { 101fd4e5da5Sopenharmony_ci return false 102fd4e5da5Sopenharmony_ci } 103fd4e5da5Sopenharmony_ci infob, err := os.Stat(filepath.FromSlash(fb)) 104fd4e5da5Sopenharmony_ci if err != nil { 105fd4e5da5Sopenharmony_ci return false 106fd4e5da5Sopenharmony_ci } 107fd4e5da5Sopenharmony_ci return os.SameFile(infoa, infob) 108fd4e5da5Sopenharmony_ci} 109fd4e5da5Sopenharmony_ci 110fd4e5da5Sopenharmony_ci// FileURI returns a span URI for the supplied file path. 111fd4e5da5Sopenharmony_ci// It will always have the file scheme. 112fd4e5da5Sopenharmony_cifunc FileURI(path string) URI { 113fd4e5da5Sopenharmony_ci if path == "" { 114fd4e5da5Sopenharmony_ci return "" 115fd4e5da5Sopenharmony_ci } 116fd4e5da5Sopenharmony_ci // Handle standard library paths that contain the literal "$GOROOT". 117fd4e5da5Sopenharmony_ci // TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT. 118fd4e5da5Sopenharmony_ci const prefix = "$GOROOT" 119fd4e5da5Sopenharmony_ci if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) { 120fd4e5da5Sopenharmony_ci suffix := path[len(prefix):] 121fd4e5da5Sopenharmony_ci path = runtime.GOROOT() + suffix 122fd4e5da5Sopenharmony_ci } 123fd4e5da5Sopenharmony_ci if !isWindowsDrivePath(path) { 124fd4e5da5Sopenharmony_ci if abs, err := filepath.Abs(path); err == nil { 125fd4e5da5Sopenharmony_ci path = abs 126fd4e5da5Sopenharmony_ci } 127fd4e5da5Sopenharmony_ci } 128fd4e5da5Sopenharmony_ci // Check the file path again, in case it became absolute. 129fd4e5da5Sopenharmony_ci if isWindowsDrivePath(path) { 130fd4e5da5Sopenharmony_ci path = "/" + path 131fd4e5da5Sopenharmony_ci } 132fd4e5da5Sopenharmony_ci path = filepath.ToSlash(path) 133fd4e5da5Sopenharmony_ci u := url.URL{ 134fd4e5da5Sopenharmony_ci Scheme: fileScheme, 135fd4e5da5Sopenharmony_ci Path: path, 136fd4e5da5Sopenharmony_ci } 137fd4e5da5Sopenharmony_ci uri := u.String() 138fd4e5da5Sopenharmony_ci if unescaped, err := url.PathUnescape(uri); err == nil { 139fd4e5da5Sopenharmony_ci uri = unescaped 140fd4e5da5Sopenharmony_ci } 141fd4e5da5Sopenharmony_ci return URI(uri) 142fd4e5da5Sopenharmony_ci} 143fd4e5da5Sopenharmony_ci 144fd4e5da5Sopenharmony_ci// isWindowsDrivePath returns true if the file path is of the form used by 145fd4e5da5Sopenharmony_ci// Windows. We check if the path begins with a drive letter, followed by a ":". 146fd4e5da5Sopenharmony_cifunc isWindowsDrivePath(path string) bool { 147fd4e5da5Sopenharmony_ci if len(path) < 4 { 148fd4e5da5Sopenharmony_ci return false 149fd4e5da5Sopenharmony_ci } 150fd4e5da5Sopenharmony_ci return unicode.IsLetter(rune(path[0])) && path[1] == ':' 151fd4e5da5Sopenharmony_ci} 152fd4e5da5Sopenharmony_ci 153fd4e5da5Sopenharmony_ci// isWindowsDriveURI returns true if the file URI is of the format used by 154fd4e5da5Sopenharmony_ci// Windows URIs. The url.Parse package does not specially handle Windows paths 155fd4e5da5Sopenharmony_ci// (see https://golang.org/issue/6027). We check if the URI path has 156fd4e5da5Sopenharmony_ci// a drive prefix (e.g. "/C:"). If so, we trim the leading "/". 157fd4e5da5Sopenharmony_cifunc isWindowsDriveURI(uri string) bool { 158fd4e5da5Sopenharmony_ci if len(uri) < 4 { 159fd4e5da5Sopenharmony_ci return false 160fd4e5da5Sopenharmony_ci } 161fd4e5da5Sopenharmony_ci return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':' 162fd4e5da5Sopenharmony_ci} 163