path.go

v1.4.2
Doc Versions Source
1
package server
2
3
import "strings"
4
5
// ModuleName extracts the top-level module name from a URL path.
6
// Strips any @version suffix (e.g., "/curator@v1.0.0/..." → "curator").
7
// For multi-segment paths, returns the first segment only.
8
// Use ResolveModulePath for paths that may contain multi-segment module names.
9
func ModuleName(path string) string {
10
	path = strings.TrimPrefix(path, "/")
11
	if i := strings.Index(path, "/"); i != -1 {
12
		path = path[:i]
13
	}
14
	if mod, _, ok := strings.Cut(path, "@"); ok {
15
		return mod
16
	}
17
	return path
18
}
19
20
// ResolveModulePath finds the longest path prefix that matches a known module.
21
// Returns the module name and the remaining subpath.
22
// The resolver function returns true if the name is a known module.
23
// Handles @version in the path (e.g., "/projects/revizor@v1.0.0/sub/pkg").
24
func ResolveModulePath(urlPath string, resolver func(name string) bool) (modName, version, subpath string) {
25
	path := strings.TrimPrefix(urlPath, "/")
26
	if path == "" {
27
		return "", "", ""
28
	}
29
30
	segments := strings.Split(path, "/")
31
32
	// Find @version in any segment.
33
	versionIdx := -1
34
	for i, seg := range segments {
35
		if _, ver, ok := strings.Cut(seg, "@"); ok && ver != "" {
36
			versionIdx = i
37
			segments[i] = strings.SplitN(seg, "@", 2)[0]
38
			version = ver
39
			break
40
		}
41
	}
42
43
	// Try progressively longer prefixes (longest match wins).
44
	bestMatch := -1
45
	for i := len(segments); i >= 1; i-- {
46
		// Don't try prefixes beyond the @version segment.
47
		if versionIdx >= 0 && i > versionIdx+1 {
48
			continue
49
		}
50
		candidate := strings.Join(segments[:i], "/")
51
		if resolver(candidate) {
52
			bestMatch = i
53
			break
54
		}
55
	}
56
57
	if bestMatch < 0 {
58
		// No module found. Fall back to first segment (for go-import meta tags).
59
		first := segments[0]
60
		rest := ""
61
		startIdx := 1
62
		if versionIdx == 0 {
63
			startIdx = 1
64
		}
65
		if startIdx < len(segments) {
66
			rest = strings.Join(segments[startIdx:], "/")
67
		}
68
		return first, version, rest
69
	}
70
71
	modName = strings.Join(segments[:bestMatch], "/")
72
	if bestMatch < len(segments) {
73
		subpath = strings.Join(segments[bestMatch:], "/")
74
	}
75
	return modName, version, subpath
76
}
77
78
// ParseProxyPath parses "/<module>/@v/<file>" paths.
79
// Returns the full module path and file component (e.g. "list", "v1.0.0.info").
80
func ParseProxyPath(urlPath string) (modPath, file string, ok bool) {
81
	path := strings.TrimPrefix(urlPath, "/")
82
83
	idx := strings.Index(path, "/@v/")
84
	if idx == -1 {
85
		return "", "", false
86
	}
87
88
	modPath = path[:idx]
89
	file = path[idx+len("/@v/"):]
90
91
	if modPath == "" || file == "" {
92
		return "", "", false
93
	}
94
95
	return modPath, file, true
96
}
97
98
// ParseDocPath parses documentation URLs: "/<module>@<version>/<subpath>".
99
// Returns the module name, version (empty if not specified), and sub-path.
100
// For multi-segment module paths, use ResolveModulePath instead.
101
func ParseDocPath(urlPath string) (modName, version, subpath string) {
102
	path := strings.TrimPrefix(urlPath, "/")
103
104
	// Split on first "/" to get the first segment (which may contain @version).
105
	first, rest, _ := strings.Cut(path, "/")
106
107
	// Check for @version in first segment.
108
	if mod, ver, ok := strings.Cut(first, "@"); ok {
109
		return mod, ver, rest
110
	}
111
112
	return first, "", rest
113
}
114
115
// ParseLatestPath parses "/<module>/@latest" paths.
116
// Returns the escaped module path if the URL matches, or empty string if not.
117
func ParseLatestPath(urlPath string) (escapedModPath string, ok bool) {
118
	path := strings.TrimPrefix(urlPath, "/")
119
120
	if !strings.HasSuffix(path, "/@latest") {
121
		return "", false
122
	}
123
124
	modPath := strings.TrimSuffix(path, "/@latest")
125
	if modPath == "" {
126
		return "", false
127
	}
128
129
	return modPath, true
130
}
131
132
// StorageKey returns the S3 key for a proxy artifact.
133
func StorageKey(modName, file string) string {
134
	return modName + "/@v/" + file
135
}
136

Source Files