1package module
2
3import (
4 "fmt"
5 "strings"
6 "unicode/utf8"
7
8 "cuelang.org/go/internal/mod/semver"
9)
10
11// EscapePath returns the escaped form of the given module path
12// (without the major version suffix).
13// It fails if the module path is invalid.
14func EscapePath(path string) (escaped string, err error) {
15 if err := CheckPathWithoutVersion(path); err != nil {
16 return "", err
17 }
18 // Technically there's no need to escape capital letters because CheckPath
19 // doesn't allow them, but let's be defensive.
20 return escapeString(path)
21}
22
23// EscapeVersion returns the escaped form of the given module version.
24// Versions must be in (possibly non-canonical) semver form and must be valid file names
25// and not contain exclamation marks.
26func EscapeVersion(v string) (escaped string, err error) {
27 if !semver.IsValid(v) {
28 return "", &InvalidVersionError{
29 Version: v,
30 Err: fmt.Errorf("version is not in semver syntax"),
31 }
32 }
33 if err := checkElem(v, filePath); err != nil || strings.Contains(v, "!") {
34 return "", &InvalidVersionError{
35 Version: v,
36 Err: fmt.Errorf("disallowed version string"),
37 }
38 }
39 return escapeString(v)
40}
41
42func escapeString(s string) (escaped string, err error) {
43 haveUpper := false
44 for _, r := range s {
45 if r == '!' || r >= utf8.RuneSelf {
46 // This should be disallowed by CheckPath, but diagnose anyway.
47 // The correctness of the escaping loop below depends on it.
48 return "", fmt.Errorf("internal error: inconsistency in EscapePath")
49 }
50 if 'A' <= r && r <= 'Z' {
51 haveUpper = true
52 }
53 }
54
55 if !haveUpper {
56 return s, nil
57 }
58
59 var buf []byte
60 for _, r := range s {
61 if 'A' <= r && r <= 'Z' {
62 buf = append(buf, '!', byte(r+'a'-'A'))
63 } else {
64 buf = append(buf, byte(r))
65 }
66 }
67 return string(buf), nil
68}