2019-02-12 11:13:49 -07:00
|
|
|
// Copyright 2019 The Go Authors. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package span
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"net/url"
|
2019-04-17 15:54:50 -06:00
|
|
|
"os"
|
2019-06-11 09:17:26 -06:00
|
|
|
"path"
|
2019-02-12 11:13:49 -07:00
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
|
|
|
"unicode"
|
|
|
|
)
|
|
|
|
|
|
|
|
const fileScheme = "file"
|
|
|
|
|
|
|
|
// URI represents the full URI for a file.
|
|
|
|
type URI string
|
|
|
|
|
2020-02-13 11:46:49 -07:00
|
|
|
func (uri URI) IsFile() bool {
|
|
|
|
return strings.HasPrefix(string(uri), "file://")
|
|
|
|
}
|
|
|
|
|
2019-06-06 11:51:00 -06:00
|
|
|
// Filename returns the file path for the given URI.
|
|
|
|
// It is an error to call this on a URI that is not a valid filename.
|
|
|
|
func (uri URI) Filename() string {
|
2019-02-12 11:13:49 -07:00
|
|
|
filename, err := filename(uri)
|
|
|
|
if err != nil {
|
2019-06-06 11:51:00 -06:00
|
|
|
panic(err)
|
2019-02-12 11:13:49 -07:00
|
|
|
}
|
2019-06-06 11:51:00 -06:00
|
|
|
return filepath.FromSlash(filename)
|
2019-02-12 11:13:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func filename(uri URI) (string, error) {
|
2019-06-11 09:17:26 -06:00
|
|
|
if uri == "" {
|
|
|
|
return "", nil
|
|
|
|
}
|
2019-02-12 11:13:49 -07:00
|
|
|
u, err := url.ParseRequestURI(string(uri))
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if u.Scheme != fileScheme {
|
2019-06-11 18:14:39 -06:00
|
|
|
return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri)
|
2019-02-12 11:13:49 -07:00
|
|
|
}
|
2020-01-30 12:34:39 -07:00
|
|
|
// If the URI is a Windows URI, we trim the leading "/" and lowercase
|
|
|
|
// the drive letter, which will never be case sensitive.
|
|
|
|
if isWindowsDriveURIPath(u.Path) {
|
|
|
|
u.Path = strings.ToUpper(string(u.Path[1])) + u.Path[2:]
|
2019-02-12 11:13:49 -07:00
|
|
|
}
|
|
|
|
return u.Path, nil
|
|
|
|
}
|
|
|
|
|
2020-02-12 14:36:46 -07:00
|
|
|
func URIFromURI(s string) URI {
|
|
|
|
if !strings.HasPrefix(s, "file:///") {
|
2019-02-12 11:13:49 -07:00
|
|
|
return URI(s)
|
|
|
|
}
|
2020-02-18 12:26:05 -07:00
|
|
|
|
|
|
|
// Even though the input is a URI, it may not be in canonical form. VS Code
|
|
|
|
// in particular over-escapes :, @, etc. Unescape and re-encode to canonicalize.
|
|
|
|
path, err := url.PathUnescape(s[len("file://"):])
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
2020-02-12 14:36:46 -07:00
|
|
|
}
|
2020-02-18 12:26:05 -07:00
|
|
|
|
2020-02-12 14:36:46 -07:00
|
|
|
// File URIs from Windows may have lowercase drive letters.
|
|
|
|
// Since drive letters are guaranteed to be case insensitive,
|
|
|
|
// we change them to uppercase to remain consistent.
|
|
|
|
// For example, file:///c:/x/y/z becomes file:///C:/x/y/z.
|
|
|
|
if isWindowsDriveURIPath(path) {
|
|
|
|
path = path[:1] + strings.ToUpper(string(path[1])) + path[2:]
|
|
|
|
}
|
2020-02-18 12:26:05 -07:00
|
|
|
u := url.URL{Scheme: fileScheme, Path: path}
|
|
|
|
return URI(u.String())
|
2019-02-12 11:13:49 -07:00
|
|
|
}
|
|
|
|
|
2019-04-17 15:54:50 -06:00
|
|
|
func CompareURI(a, b URI) int {
|
2019-06-11 09:17:26 -06:00
|
|
|
if equalURI(a, b) {
|
2019-04-17 15:54:50 -06:00
|
|
|
return 0
|
|
|
|
}
|
2019-06-11 09:17:26 -06:00
|
|
|
if a < b {
|
|
|
|
return -1
|
|
|
|
}
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
func equalURI(a, b URI) bool {
|
|
|
|
if a == b {
|
|
|
|
return true
|
|
|
|
}
|
2019-04-17 15:54:50 -06:00
|
|
|
// If we have the same URI basename, we may still have the same file URIs.
|
2019-06-11 09:17:26 -06:00
|
|
|
if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
fa, err := filename(a)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
fb, err := filename(b)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
// Stat the files to check if they are equal.
|
|
|
|
infoa, err := os.Stat(filepath.FromSlash(fa))
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
infob, err := os.Stat(filepath.FromSlash(fb))
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return os.SameFile(infoa, infob)
|
2019-04-17 15:54:50 -06:00
|
|
|
}
|
|
|
|
|
2020-02-12 14:36:46 -07:00
|
|
|
// URIFromPath returns a span URI for the supplied file path.
|
2019-02-12 11:13:49 -07:00
|
|
|
// It will always have the file scheme.
|
2020-02-12 14:36:46 -07:00
|
|
|
func URIFromPath(path string) URI {
|
2019-06-11 09:17:26 -06:00
|
|
|
if path == "" {
|
|
|
|
return ""
|
|
|
|
}
|
2019-02-12 11:13:49 -07:00
|
|
|
// Handle standard library paths that contain the literal "$GOROOT".
|
|
|
|
// TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT.
|
|
|
|
const prefix = "$GOROOT"
|
|
|
|
if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) {
|
|
|
|
suffix := path[len(prefix):]
|
|
|
|
path = runtime.GOROOT() + suffix
|
|
|
|
}
|
|
|
|
if !isWindowsDrivePath(path) {
|
|
|
|
if abs, err := filepath.Abs(path); err == nil {
|
|
|
|
path = abs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Check the file path again, in case it became absolute.
|
|
|
|
if isWindowsDrivePath(path) {
|
2020-01-30 12:34:39 -07:00
|
|
|
path = "/" + strings.ToUpper(string(path[0])) + path[1:]
|
2019-02-12 11:13:49 -07:00
|
|
|
}
|
|
|
|
path = filepath.ToSlash(path)
|
|
|
|
u := url.URL{
|
|
|
|
Scheme: fileScheme,
|
|
|
|
Path: path,
|
|
|
|
}
|
2020-02-03 16:07:33 -07:00
|
|
|
return URI(u.String())
|
2019-02-12 11:13:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// isWindowsDrivePath returns true if the file path is of the form used by
|
|
|
|
// Windows. We check if the path begins with a drive letter, followed by a ":".
|
2020-01-30 12:34:39 -07:00
|
|
|
// For example: C:/x/y/z.
|
2019-02-12 11:13:49 -07:00
|
|
|
func isWindowsDrivePath(path string) bool {
|
2020-01-30 12:34:39 -07:00
|
|
|
if len(path) < 3 {
|
2019-02-12 11:13:49 -07:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return unicode.IsLetter(rune(path[0])) && path[1] == ':'
|
|
|
|
}
|
|
|
|
|
|
|
|
// isWindowsDriveURI returns true if the file URI is of the format used by
|
|
|
|
// Windows URIs. The url.Parse package does not specially handle Windows paths
|
2020-01-30 12:34:39 -07:00
|
|
|
// (see golang/go#6027). We check if the URI path has a drive prefix (e.g. "/C:").
|
|
|
|
func isWindowsDriveURIPath(uri string) bool {
|
2019-02-12 11:13:49 -07:00
|
|
|
if len(uri) < 4 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
|
|
|
|
}
|