1
- //go:build ignore
2
-
3
1
// Copyright 2018 The Go Authors. All rights reserved.
4
2
// Use of this source code is governed by a BSD-style
5
3
// license that can be found in the LICENSE file.
16
14
// There are no restrictions imposed directly by use of this structure,
17
15
// but additional checking functions, most notably Check, verify that
18
16
// a particular path, version pair is valid.
19
- //
20
- // # Escaped Paths
21
- //
22
- // Module paths appear as substrings of file system paths
23
- // (in the download cache) and of web server URLs in the proxy protocol.
24
- // In general we cannot rely on file systems to be case-sensitive,
25
- // nor can we rely on web servers, since they read from file systems.
26
- // That is, we cannot rely on the file system to keep rsc.io/QUOTE
27
- // and rsc.io/quote separate. Windows and macOS don't.
28
- // Instead, we must never require two different casings of a file path.
29
- // Because we want the download cache to match the proxy protocol,
30
- // and because we want the proxy protocol to be possible to serve
31
- // from a tree of static files (which might be stored on a case-insensitive
32
- // file system), the proxy protocol must never require two different casings
33
- // of a URL path either.
34
- //
35
- // One possibility would be to make the escaped form be the lowercase
36
- // hexadecimal encoding of the actual path bytes. This would avoid ever
37
- // needing different casings of a file path, but it would be fairly illegible
38
- // to most programmers when those paths appeared in the file system
39
- // (including in file paths in compiler errors and stack traces)
40
- // in web server logs, and so on. Instead, we want a safe escaped form that
41
- // leaves most paths unaltered.
42
- //
43
- // The safe escaped form is to replace every uppercase letter
44
- // with an exclamation mark followed by the letter's lowercase equivalent.
45
- //
46
- // For example,
47
- //
48
- // github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go.
49
- // github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy
50
- // github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus.
51
- //
52
- // Import paths that avoid upper-case letters are left unchanged.
53
- // Note that because import paths are ASCII-only and avoid various
54
- // problematic punctuation (like : < and >), the escaped form is also ASCII-only
55
- // and avoids the same problematic punctuation.
56
- //
57
- // Import paths have never allowed exclamation marks, so there is no
58
- // need to define how to escape a literal !.
59
- //
60
- // # Unicode Restrictions
61
- //
62
- // Today, paths are disallowed from using Unicode.
63
- //
64
- // Although paths are currently disallowed from using Unicode,
65
- // we would like at some point to allow Unicode letters as well, to assume that
66
- // file systems and URLs are Unicode-safe (storing UTF-8), and apply
67
- // the !-for-uppercase convention for escaping them in the file system.
68
- // But there are at least two subtle considerations.
69
- //
70
- // First, note that not all case-fold equivalent distinct runes
71
- // form an upper/lower pair.
72
- // For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin)
73
- // are three distinct runes that case-fold to each other.
74
- // When we do add Unicode letters, we must not assume that upper/lower
75
- // are the only case-equivalent pairs.
76
- // Perhaps the Kelvin symbol would be disallowed entirely, for example.
77
- // Or perhaps it would escape as "!!k", or perhaps as "(212A)".
78
- //
79
- // Second, it would be nice to allow Unicode marks as well as letters,
80
- // but marks include combining marks, and then we must deal not
81
- // only with case folding but also normalization: both U+00E9 ('é')
82
- // and U+0065 U+0301 ('e' followed by combining acute accent)
83
- // look the same on the page and are treated by some file systems
84
- // as the same path. If we do allow Unicode marks in paths, there
85
- // must be some kind of normalization to allow only one canonical
86
- // encoding of any character used in an import path.
87
17
package module
88
18
89
19
// IMPORTANT NOTE
90
20
//
91
- // This file essentially defines the set of valid import paths for the go command.
21
+ // This file essentially defines the set of valid import paths for the cue command.
92
22
// There are many subtle considerations, including Unicode ambiguity,
93
23
// security, network, and file system representations.
94
- //
95
- // This file also defines the set of valid module path and version combinations,
96
- // another topic with many subtle considerations.
97
- //
98
- // Changes to the semantics in this file require approval from rsc.
99
24
100
25
import (
26
+ "fmt"
101
27
"sort"
102
28
"strings"
103
29
@@ -106,56 +32,133 @@ import (
106
32
107
33
// A Version (for clients, a module.Version) is defined by a module path and version pair.
108
34
// These are stored in their plain (unescaped) form.
35
+ // This type is comparable.
109
36
type Version struct {
110
- // Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2".
111
- Path string
37
+ path string
38
+ version string
39
+ }
112
40
113
- // Version is usually a semantic version in canonical form.
114
- // There are three exceptions to this general rule.
115
- // First, the top-level target of a build has no specific version
116
- // and uses Version = "".
117
- // Second, during MVS calculations the version "none" is used
118
- // to represent the decision to take no version of a given module.
119
- // Third, filesystem paths found in "replace" directives are
120
- // represented by a path with an empty version.
121
- Version string `json:",omitempty"`
41
+ // Path returns the module path part of the Version,
42
+ // which always includes the major version suffix
43
+ // unless a module path, like "github.com/foo/bar@v0".
44
+ // Note that in general the path should include the major version suffix
45
+ // even though it's implied from the version. The Canonical
46
+ // method can be used to add the major version suffix if not present.
47
+ // The BasePath method can be used to obtain the path without
48
+ // the suffix.
49
+ func (m Version ) Path () string {
50
+ return m .path
51
+ }
52
+
53
+ func (m Version ) BasePath () string {
54
+ basePath , _ , ok := SplitPathVersion (m .path )
55
+ if ! ok {
56
+ panic (fmt .Errorf ("broken invariant: failed to split version in %q" , m .path ))
57
+ }
58
+ return basePath
122
59
}
123
60
124
- // String returns a representation of the Version suitable for logging
61
+ func (m Version ) Version () string {
62
+ return m .version
63
+ }
64
+
65
+ // String returns the string form of the Version:
125
66
// (Path@Version, or just Path if Version is empty).
126
67
func (m Version ) String () string {
127
- if m .Version == "" {
128
- return m .Path
68
+ if m .version == "" {
69
+ return m .path
70
+ }
71
+ return m .BasePath () + "@" + m .version
72
+ }
73
+
74
+ func MustParseVersion (s string ) Version {
75
+ v , err := ParseVersion (s )
76
+ if err != nil {
77
+ panic (err )
129
78
}
130
- return m . Path + "@" + m . Version
79
+ return v
131
80
}
132
81
133
- // CanonicalVersion returns the canonical form of the version string v.
134
- // It is the same as semver.Canonical(v) except that it preserves the special build suffix "+incompatible".
135
- func CanonicalVersion (v string ) string {
136
- cv := semver .Canonical (v )
137
- if semver .Build (v ) == "+incompatible" {
138
- cv += "+incompatible"
82
+ // ParseVersion parses a $module@$version
83
+ // string into a Version.
84
+ // The version must be canonical (i.e. it can't be
85
+ // just a major version).
86
+ func ParseVersion (s string ) (Version , error ) {
87
+ basePath , vers , ok := SplitPathVersion (s )
88
+ if ! ok {
89
+ return Version {}, fmt .Errorf ("invalid module path@version %q" , s )
90
+ }
91
+ if semver .Canonical (vers ) != vers {
92
+ return Version {}, fmt .Errorf ("module version in %q is not canonical" , s )
93
+ }
94
+ return Version {basePath + "@" + semver .Major (vers ), vers }, nil
95
+ }
96
+
97
+ func MustNewVersion (path string , vers string ) Version {
98
+ v , err := NewVersion (path , vers )
99
+ if err != nil {
100
+ panic (err )
101
+ }
102
+ return v
103
+ }
104
+
105
+ // NewVersion forms a Version from the given path and version.
106
+ // The version must be canonical or empty.
107
+ // If the path doesn't have a major version suffix, one will be added
108
+ // if the version isn't empty; if the version is empty, it's an error.
109
+ func NewVersion (path string , vers string ) (Version , error ) {
110
+ if vers != "" && vers != "none" {
111
+ if semver .Canonical (vers ) != vers {
112
+ return Version {}, fmt .Errorf ("version %q (of module %q) is not canonical" , vers , path )
113
+ }
114
+ maj := semver .Major (vers )
115
+ _ , vmaj , ok := SplitPathVersion (path )
116
+ if ok && maj != vmaj {
117
+ return Version {}, fmt .Errorf ("mismatched major version suffix in %q (version %v)" , path , vers )
118
+ }
119
+ if ! ok {
120
+ fullPath := path + "@" + maj
121
+ if _ , _ , ok := SplitPathVersion (fullPath ); ! ok {
122
+ return Version {}, fmt .Errorf ("cannot form version path from %q, version %v" , path , vers )
123
+ }
124
+ path = fullPath
125
+ }
126
+ } else {
127
+ if _ , _ , ok := SplitPathVersion (path ); ! ok {
128
+ return Version {}, fmt .Errorf ("path %q has no major version" , path )
129
+ }
130
+ }
131
+ if vers == "" {
132
+ if err := CheckPath (path ); err != nil {
133
+ return Version {}, err
134
+ }
135
+ } else {
136
+ if err := Check (path , vers ); err != nil {
137
+ return Version {}, err
138
+ }
139
139
}
140
- return cv
140
+ return Version {
141
+ path : path ,
142
+ version : vers ,
143
+ }, nil
141
144
}
142
145
143
146
// Sort sorts the list by Path, breaking ties by comparing Version fields.
144
147
// The Version fields are interpreted as semantic versions (using semver.Compare)
145
148
// optionally followed by a tie-breaking suffix introduced by a slash character,
146
- // like in "v0.0.1/go.mod ".
149
+ // like in "v0.0.1/module.cue ".
147
150
func Sort (list []Version ) {
148
151
sort .Slice (list , func (i , j int ) bool {
149
152
mi := list [i ]
150
153
mj := list [j ]
151
- if mi .Path != mj .Path {
152
- return mi .Path < mj .Path
154
+ if mi .path != mj .path {
155
+ return mi .path < mj .path
153
156
}
154
157
// To help go.sum formatting, allow version/file.
155
158
// Compare semver prefix by semver rules,
156
159
// file by string order.
157
- vi := mi .Version
158
- vj := mj .Version
160
+ vi := mi .version
161
+ vj := mj .version
159
162
var fi , fj string
160
163
if k := strings .Index (vi , "/" ); k >= 0 {
161
164
vi , fi = vi [:k ], vi [k :]
0 commit comments