mirror of
https://github.com/therootcompany/golib.git
synced 2026-03-29 12:35:10 +00:00
feat(path/winpath): add sync script and patches from Go stdlib
- Add sync.sh to download and apply patches from Go standard library - Add patches/*.diff for transforming internal/filepathlite to winpath - Backup Nextron's original diff for reference - Add .gitignore for orig/ directory - Add README with usage examples and attribution
This commit is contained in:
parent
d779cd44c1
commit
da4cd3aa48
5
path/winpath/.gitignore
vendored
Normal file
5
path/winpath/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Ignore downloaded original files
|
||||
orig/
|
||||
*.orig
|
||||
*.tmp
|
||||
*.tmp.d
|
||||
84
path/winpath/README.md
Normal file
84
path/winpath/README.md
Normal file
@ -0,0 +1,84 @@
|
||||
# path/winpath
|
||||
|
||||
[](https://pkg.go.dev/github.com/therootcompany/golib/path/winpath)
|
||||
|
||||
Windows-style path manipulation that works on any platform.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/therootcompany/golib/path/winpath
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
import winpath "github.com/therootcompany/golib/path/winpath"
|
||||
|
||||
// Clean a Windows path (from any platform)
|
||||
clean := winpath.Clean(`C:\foo\..\bar`) // C:\bar
|
||||
|
||||
// Join path elements
|
||||
joined := winpath.Join(`C:\`, "foo", "bar.txt") // C:\foo\bar.txt
|
||||
|
||||
// Split path into directory and file
|
||||
dir, file := winpath.Split(`C:\foo\bar.txt`) // C:\foo\, bar.txt
|
||||
|
||||
// Get file extension
|
||||
ext := winpath.Ext(`C:\foo\bar.txt`) // .txt
|
||||
|
||||
// Get base name
|
||||
base := winpath.Base(`C:\foo\bar.txt`) // bar.txt
|
||||
|
||||
// Get directory
|
||||
d := winpath.Dir(`C:\foo\bar.txt`) // C:\foo
|
||||
|
||||
// Check if path is absolute
|
||||
winpath.IsAbs(`C:\foo`) // true
|
||||
winpath.IsAbs(`foo\bar`) // false
|
||||
winpath.IsAbs(`\foo`) // false (rooted but no drive)
|
||||
winpath.IsAbs(`\\server\share`) // true (UNC)
|
||||
|
||||
// Get volume name
|
||||
vol := winpath.VolumeName(`C:\foo\bar`) // C:
|
||||
len := winpath.VolumeNameLen(`C:\foo`) // 2
|
||||
|
||||
// Convert separators
|
||||
fwd := winpath.ToSlash(`C:\foo\bar`) // C:/foo/bar
|
||||
bck := winpath.FromSlash(`C:/foo/bar`) // C:\foo\bar
|
||||
|
||||
// Constants
|
||||
winpath.Separator // '\'
|
||||
winpath.ListSeparator // ';'
|
||||
winpath.IsPathSeparator('\\') // true
|
||||
winpath.IsPathSeparator('/') // true
|
||||
```
|
||||
|
||||
## Comparison with stdlib
|
||||
|
||||
| Feature | `path/filepath` | `path/winpath` |
|
||||
|---------|-----------------|----------------|
|
||||
| Platform aware | Yes (uses OS) | No (always Windows) |
|
||||
| Use alongside `filepath` | N/A | ✅ Works together |
|
||||
|
||||
Use `path/filepath` for native OS paths on any platform.
|
||||
Use `path/winpath` when you need to handle Windows paths on non-Windows platforms (e.g., parsing config files, cross-platform tools).
|
||||
|
||||
## Attribution
|
||||
|
||||
This package is derived from the Go standard library's `internal/filepathlite` and `path/filepath` packages, adapted from [NextronSystems/universalpath](https://github.com/NextronSystems/universalpath).
|
||||
|
||||
### License
|
||||
|
||||
The Go Authors. See the [Go license](https://golang.org/LICENSE).
|
||||
|
||||
## Syncing from Go stdlib
|
||||
|
||||
To update from newer Go versions:
|
||||
|
||||
```bash
|
||||
cd path/winpath
|
||||
GO_VERSION=go1.22.0 ./sync.sh
|
||||
```
|
||||
|
||||
Run `./sync.sh diff` to regenerate patches after manual edits.
|
||||
234
path/winpath/patches/path_lite.diff
Normal file
234
path/winpath/patches/path_lite.diff
Normal file
@ -0,0 +1,234 @@
|
||||
--- orig/path_lite.go 2026-03-28 16:17:06
|
||||
+++ path_lite.go 2026-03-28 16:17:03
|
||||
@@ -2,25 +2,18 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
-// Package filepathlite implements a subset of path/filepath,
|
||||
-// only using packages which may be imported by "os".
|
||||
-//
|
||||
-// Tests for these functions are in path/filepath.
|
||||
-package filepathlite
|
||||
+// Package windows provides Windows-style path manipulation
|
||||
+// that works on any platform.
|
||||
+package winpath
|
||||
|
||||
import (
|
||||
"errors"
|
||||
- "internal/stringslite"
|
||||
- "io/fs"
|
||||
"slices"
|
||||
+ "strings"
|
||||
)
|
||||
|
||||
var errInvalidPath = errors.New("invalid path")
|
||||
|
||||
-// A lazybuf is a lazily constructed path buffer.
|
||||
-// It supports append, reading previously appended bytes,
|
||||
-// and retrieving the final string. It does not allocate a buffer
|
||||
-// to hold the output until that output diverges from s.
|
||||
type lazybuf struct {
|
||||
path string
|
||||
buf []byte
|
||||
@@ -61,25 +54,18 @@
|
||||
return b.volAndPath[:b.volLen] + string(b.buf[:b.w])
|
||||
}
|
||||
|
||||
-// Clean is filepath.Clean.
|
||||
func Clean(path string) string {
|
||||
originalPath := path
|
||||
volLen := volumeNameLen(path)
|
||||
path = path[volLen:]
|
||||
if path == "" {
|
||||
if volLen > 1 && IsPathSeparator(originalPath[0]) && IsPathSeparator(originalPath[1]) {
|
||||
- // should be UNC
|
||||
return FromSlash(originalPath)
|
||||
}
|
||||
return originalPath + "."
|
||||
}
|
||||
rooted := IsPathSeparator(path[0])
|
||||
|
||||
- // Invariants:
|
||||
- // reading from path; r is index of next byte to process.
|
||||
- // writing to buf; w is index of next byte to write.
|
||||
- // dotdot is index in buf where .. must stop, either because
|
||||
- // it is the leading slash or it is a leading ../../.. prefix.
|
||||
n := len(path)
|
||||
out := lazybuf{path: path, volAndPath: originalPath, volLen: volLen}
|
||||
r, dotdot := 0, 0
|
||||
@@ -91,23 +77,18 @@
|
||||
for r < n {
|
||||
switch {
|
||||
case IsPathSeparator(path[r]):
|
||||
- // empty path element
|
||||
r++
|
||||
case path[r] == '.' && (r+1 == n || IsPathSeparator(path[r+1])):
|
||||
- // . element
|
||||
r++
|
||||
case path[r] == '.' && path[r+1] == '.' && (r+2 == n || IsPathSeparator(path[r+2])):
|
||||
- // .. element: remove to last separator
|
||||
r += 2
|
||||
switch {
|
||||
case out.w > dotdot:
|
||||
- // can backtrack
|
||||
out.w--
|
||||
for out.w > dotdot && !IsPathSeparator(out.index(out.w)) {
|
||||
out.w--
|
||||
}
|
||||
case !rooted:
|
||||
- // cannot backtrack, but not rooted, so append .. element.
|
||||
if out.w > 0 {
|
||||
out.append(Separator)
|
||||
}
|
||||
@@ -116,63 +97,23 @@
|
||||
dotdot = out.w
|
||||
}
|
||||
default:
|
||||
- // real path element.
|
||||
- // add slash if needed
|
||||
if rooted && out.w != 1 || !rooted && out.w != 0 {
|
||||
out.append(Separator)
|
||||
}
|
||||
- // copy element
|
||||
for ; r < n && !IsPathSeparator(path[r]); r++ {
|
||||
out.append(path[r])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- // Turn empty string into "."
|
||||
if out.w == 0 {
|
||||
out.append('.')
|
||||
}
|
||||
|
||||
- postClean(&out) // avoid creating absolute paths on Windows
|
||||
+ postClean(&out)
|
||||
return FromSlash(out.string())
|
||||
}
|
||||
|
||||
-// IsLocal is filepath.IsLocal.
|
||||
-func IsLocal(path string) bool {
|
||||
- return isLocal(path)
|
||||
-}
|
||||
-
|
||||
-func unixIsLocal(path string) bool {
|
||||
- if IsAbs(path) || path == "" {
|
||||
- return false
|
||||
- }
|
||||
- hasDots := false
|
||||
- for p := path; p != ""; {
|
||||
- var part string
|
||||
- part, p, _ = stringslite.Cut(p, "/")
|
||||
- if part == "." || part == ".." {
|
||||
- hasDots = true
|
||||
- break
|
||||
- }
|
||||
- }
|
||||
- if hasDots {
|
||||
- path = Clean(path)
|
||||
- }
|
||||
- if path == ".." || stringslite.HasPrefix(path, "../") {
|
||||
- return false
|
||||
- }
|
||||
- return true
|
||||
-}
|
||||
-
|
||||
-// Localize is filepath.Localize.
|
||||
-func Localize(path string) (string, error) {
|
||||
- if !fs.ValidPath(path) {
|
||||
- return "", errInvalidPath
|
||||
- }
|
||||
- return localize(path)
|
||||
-}
|
||||
-
|
||||
-// ToSlash is filepath.ToSlash.
|
||||
func ToSlash(path string) string {
|
||||
if Separator == '/' {
|
||||
return path
|
||||
@@ -180,7 +121,6 @@
|
||||
return replaceStringByte(path, Separator, '/')
|
||||
}
|
||||
|
||||
-// FromSlash is filepath.FromSlash.
|
||||
func FromSlash(path string) string {
|
||||
if Separator == '/' {
|
||||
return path
|
||||
@@ -189,7 +129,7 @@
|
||||
}
|
||||
|
||||
func replaceStringByte(s string, old, new byte) string {
|
||||
- if stringslite.IndexByte(s, old) == -1 {
|
||||
+ if strings.IndexByte(s, old) == -1 {
|
||||
return s
|
||||
}
|
||||
n := []byte(s)
|
||||
@@ -201,7 +141,6 @@
|
||||
return string(n)
|
||||
}
|
||||
|
||||
-// Split is filepath.Split.
|
||||
func Split(path string) (dir, file string) {
|
||||
vol := VolumeName(path)
|
||||
i := len(path) - 1
|
||||
@@ -211,7 +150,6 @@
|
||||
return path[:i+1], path[i+1:]
|
||||
}
|
||||
|
||||
-// Ext is filepath.Ext.
|
||||
func Ext(path string) string {
|
||||
for i := len(path) - 1; i >= 0 && !IsPathSeparator(path[i]); i-- {
|
||||
if path[i] == '.' {
|
||||
@@ -221,18 +159,14 @@
|
||||
return ""
|
||||
}
|
||||
|
||||
-// Base is filepath.Base.
|
||||
func Base(path string) string {
|
||||
if path == "" {
|
||||
return "."
|
||||
}
|
||||
- // Strip trailing slashes.
|
||||
for len(path) > 0 && IsPathSeparator(path[len(path)-1]) {
|
||||
path = path[0 : len(path)-1]
|
||||
}
|
||||
- // Throw away volume name
|
||||
path = path[len(VolumeName(path)):]
|
||||
- // Find the last element
|
||||
i := len(path) - 1
|
||||
for i >= 0 && !IsPathSeparator(path[i]) {
|
||||
i--
|
||||
@@ -240,14 +174,12 @@
|
||||
if i >= 0 {
|
||||
path = path[i+1:]
|
||||
}
|
||||
- // If empty now, it had only slashes.
|
||||
if path == "" {
|
||||
return string(Separator)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
-// Dir is filepath.Dir.
|
||||
func Dir(path string) string {
|
||||
vol := VolumeName(path)
|
||||
i := len(path) - 1
|
||||
@@ -256,19 +188,15 @@
|
||||
}
|
||||
dir := Clean(path[len(vol) : i+1])
|
||||
if dir == "." && len(vol) > 2 {
|
||||
- // must be UNC
|
||||
return vol
|
||||
}
|
||||
return vol + dir
|
||||
}
|
||||
|
||||
-// VolumeName is filepath.VolumeName.
|
||||
func VolumeName(path string) string {
|
||||
return FromSlash(path[:volumeNameLen(path)])
|
||||
}
|
||||
|
||||
-// VolumeNameLen returns the length of the leading volume name on Windows.
|
||||
-// It returns 0 elsewhere.
|
||||
func VolumeNameLen(path string) int {
|
||||
return volumeNameLen(path)
|
||||
}
|
||||
301
path/winpath/patches/path_lite_windowsspecific.diff
Normal file
301
path/winpath/patches/path_lite_windowsspecific.diff
Normal file
@ -0,0 +1,301 @@
|
||||
--- orig/path_lite_windowsspecific.go 2026-03-28 16:15:19
|
||||
+++ path_lite_windowsspecific.go 2026-03-28 16:14:52
|
||||
@@ -2,15 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
-package filepathlite
|
||||
+package winpath
|
||||
|
||||
-import (
|
||||
- "internal/bytealg"
|
||||
- "internal/stringslite"
|
||||
- "internal/syscall/windows"
|
||||
- "syscall"
|
||||
-)
|
||||
-
|
||||
const (
|
||||
Separator = '\\' // OS-specific path separator
|
||||
ListSeparator = ';' // OS-specific path list separator
|
||||
@@ -20,159 +13,6 @@
|
||||
return c == '\\' || c == '/'
|
||||
}
|
||||
|
||||
-func isLocal(path string) bool {
|
||||
- if path == "" {
|
||||
- return false
|
||||
- }
|
||||
- if IsPathSeparator(path[0]) {
|
||||
- // Path rooted in the current drive.
|
||||
- return false
|
||||
- }
|
||||
- if stringslite.IndexByte(path, ':') >= 0 {
|
||||
- // Colons are only valid when marking a drive letter ("C:foo").
|
||||
- // Rejecting any path with a colon is conservative but safe.
|
||||
- return false
|
||||
- }
|
||||
- hasDots := false // contains . or .. path elements
|
||||
- for p := path; p != ""; {
|
||||
- var part string
|
||||
- part, p, _ = cutPath(p)
|
||||
- if part == "." || part == ".." {
|
||||
- hasDots = true
|
||||
- }
|
||||
- if isReservedName(part) {
|
||||
- return false
|
||||
- }
|
||||
- }
|
||||
- if hasDots {
|
||||
- path = Clean(path)
|
||||
- }
|
||||
- if path == ".." || stringslite.HasPrefix(path, `..\`) {
|
||||
- return false
|
||||
- }
|
||||
- return true
|
||||
-}
|
||||
-
|
||||
-func localize(path string) (string, error) {
|
||||
- for i := 0; i < len(path); i++ {
|
||||
- switch path[i] {
|
||||
- case ':', '\\', 0:
|
||||
- return "", errInvalidPath
|
||||
- }
|
||||
- }
|
||||
- containsSlash := false
|
||||
- for p := path; p != ""; {
|
||||
- // Find the next path element.
|
||||
- var element string
|
||||
- i := bytealg.IndexByteString(p, '/')
|
||||
- if i < 0 {
|
||||
- element = p
|
||||
- p = ""
|
||||
- } else {
|
||||
- containsSlash = true
|
||||
- element = p[:i]
|
||||
- p = p[i+1:]
|
||||
- }
|
||||
- if isReservedName(element) {
|
||||
- return "", errInvalidPath
|
||||
- }
|
||||
- }
|
||||
- if containsSlash {
|
||||
- // We can't depend on strings, so substitute \ for / manually.
|
||||
- buf := []byte(path)
|
||||
- for i, b := range buf {
|
||||
- if b == '/' {
|
||||
- buf[i] = '\\'
|
||||
- }
|
||||
- }
|
||||
- path = string(buf)
|
||||
- }
|
||||
- return path, nil
|
||||
-}
|
||||
-
|
||||
-// isReservedName reports if name is a Windows reserved device name.
|
||||
-// It does not detect names with an extension, which are also reserved on some Windows versions.
|
||||
-//
|
||||
-// For details, search for PRN in
|
||||
-// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file.
|
||||
-func isReservedName(name string) bool {
|
||||
- // Device names can have arbitrary trailing characters following a dot or colon.
|
||||
- base := name
|
||||
- for i := 0; i < len(base); i++ {
|
||||
- switch base[i] {
|
||||
- case ':', '.':
|
||||
- base = base[:i]
|
||||
- }
|
||||
- }
|
||||
- // Trailing spaces in the last path element are ignored.
|
||||
- for len(base) > 0 && base[len(base)-1] == ' ' {
|
||||
- base = base[:len(base)-1]
|
||||
- }
|
||||
- if !isReservedBaseName(base) {
|
||||
- return false
|
||||
- }
|
||||
- if len(base) == len(name) {
|
||||
- return true
|
||||
- }
|
||||
- // The path element is a reserved name with an extension.
|
||||
- // Since Windows 11, reserved names with extensions are no
|
||||
- // longer reserved. For example, "CON.txt" is a valid file
|
||||
- // name. Use RtlIsDosDeviceName_U to see if the name is reserved.
|
||||
- p, err := syscall.UTF16PtrFromString(name)
|
||||
- if err != nil {
|
||||
- return false
|
||||
- }
|
||||
- return windows.RtlIsDosDeviceName_U(p) > 0
|
||||
-}
|
||||
-
|
||||
-func isReservedBaseName(name string) bool {
|
||||
- if len(name) == 3 {
|
||||
- switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) {
|
||||
- case "CON", "PRN", "AUX", "NUL":
|
||||
- return true
|
||||
- }
|
||||
- }
|
||||
- if len(name) >= 4 {
|
||||
- switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) {
|
||||
- case "COM", "LPT":
|
||||
- if len(name) == 4 && '1' <= name[3] && name[3] <= '9' {
|
||||
- return true
|
||||
- }
|
||||
- // Superscript ¹, ², and ³ are considered numbers as well.
|
||||
- switch name[3:] {
|
||||
- case "\u00b2", "\u00b3", "\u00b9":
|
||||
- return true
|
||||
- }
|
||||
- return false
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- // Passing CONIN$ or CONOUT$ to CreateFile opens a console handle.
|
||||
- // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#consoles
|
||||
- //
|
||||
- // While CONIN$ and CONOUT$ aren't documented as being files,
|
||||
- // they behave the same as CON. For example, ./CONIN$ also opens the console input.
|
||||
- if len(name) == 6 && name[5] == '$' && equalFold(name, "CONIN$") {
|
||||
- return true
|
||||
- }
|
||||
- if len(name) == 7 && name[6] == '$' && equalFold(name, "CONOUT$") {
|
||||
- return true
|
||||
- }
|
||||
- return false
|
||||
-}
|
||||
-
|
||||
-func equalFold(a, b string) bool {
|
||||
- if len(a) != len(b) {
|
||||
- return false
|
||||
- }
|
||||
- for i := 0; i < len(a); i++ {
|
||||
- if toUpper(a[i]) != toUpper(b[i]) {
|
||||
- return false
|
||||
- }
|
||||
- }
|
||||
- return true
|
||||
-}
|
||||
-
|
||||
func toUpper(c byte) byte {
|
||||
if 'a' <= c && c <= 'z' {
|
||||
return c - ('a' - 'A')
|
||||
@@ -180,13 +20,11 @@
|
||||
return c
|
||||
}
|
||||
|
||||
-// IsAbs reports whether the path is absolute.
|
||||
func IsAbs(path string) (b bool) {
|
||||
l := volumeNameLen(path)
|
||||
if l == 0 {
|
||||
return false
|
||||
}
|
||||
- // If the volume name starts with a double slash, this is an absolute path.
|
||||
if IsPathSeparator(path[0]) && IsPathSeparator(path[1]) {
|
||||
return true
|
||||
}
|
||||
@@ -197,46 +35,21 @@
|
||||
return IsPathSeparator(path[0])
|
||||
}
|
||||
|
||||
-// volumeNameLen returns length of the leading volume name on Windows.
|
||||
-// It returns 0 elsewhere.
|
||||
-//
|
||||
-// See:
|
||||
-// https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats
|
||||
-// https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html
|
||||
func volumeNameLen(path string) int {
|
||||
switch {
|
||||
case len(path) >= 2 && path[1] == ':':
|
||||
- // Path starts with a drive letter.
|
||||
- //
|
||||
- // Not all Windows functions necessarily enforce the requirement that
|
||||
- // drive letters be in the set A-Z, and we don't try to here.
|
||||
- //
|
||||
- // We don't handle the case of a path starting with a non-ASCII character,
|
||||
- // in which case the "drive letter" might be multiple bytes long.
|
||||
return 2
|
||||
|
||||
case len(path) == 0 || !IsPathSeparator(path[0]):
|
||||
- // Path does not have a volume component.
|
||||
return 0
|
||||
|
||||
case pathHasPrefixFold(path, `\\.\UNC`):
|
||||
- // We're going to treat the UNC host and share as part of the volume
|
||||
- // prefix for historical reasons, but this isn't really principled;
|
||||
- // Windows's own GetFullPathName will happily remove the first
|
||||
- // component of the path in this space, converting
|
||||
- // \\.\unc\a\b\..\c into \\.\unc\a\c.
|
||||
return uncLen(path, len(`\\.\UNC\`))
|
||||
|
||||
case pathHasPrefixFold(path, `\\.`) ||
|
||||
pathHasPrefixFold(path, `\\?`) || pathHasPrefixFold(path, `\??`):
|
||||
- // Path starts with \\.\, and is a Local Device path; or
|
||||
- // path starts with \\?\ or \??\ and is a Root Local Device path.
|
||||
- //
|
||||
- // We treat the next component after the \\.\ prefix as
|
||||
- // part of the volume name, which means Clean(`\\?\c:\`)
|
||||
- // won't remove the trailing \. (See #64028.)
|
||||
if len(path) == 3 {
|
||||
- return 3 // exactly \\.
|
||||
+ return 3
|
||||
}
|
||||
_, rest, ok := cutPath(path[4:])
|
||||
if !ok {
|
||||
@@ -245,15 +58,11 @@
|
||||
return len(path) - len(rest) - 1
|
||||
|
||||
case len(path) >= 2 && IsPathSeparator(path[1]):
|
||||
- // Path starts with \\, and is a UNC path.
|
||||
return uncLen(path, 2)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
-// pathHasPrefixFold tests whether the path s begins with prefix,
|
||||
-// ignoring case and treating all path separators as equivalent.
|
||||
-// If s is longer than prefix, then s[len(prefix)] must be a path separator.
|
||||
func pathHasPrefixFold(s, prefix string) bool {
|
||||
if len(s) < len(prefix) {
|
||||
return false
|
||||
@@ -273,9 +82,6 @@
|
||||
return true
|
||||
}
|
||||
|
||||
-// uncLen returns the length of the volume prefix of a UNC path.
|
||||
-// prefixLen is the prefix prior to the start of the UNC host;
|
||||
-// for example, for "//host/share", the prefixLen is len("//")==2.
|
||||
func uncLen(path string, prefixLen int) int {
|
||||
count := 0
|
||||
for i := prefixLen; i < len(path); i++ {
|
||||
@@ -289,7 +95,6 @@
|
||||
return len(path)
|
||||
}
|
||||
|
||||
-// cutPath slices path around the first path separator.
|
||||
func cutPath(path string) (before, after string, found bool) {
|
||||
for i := range path {
|
||||
if IsPathSeparator(path[i]) {
|
||||
@@ -299,15 +104,10 @@
|
||||
return path, "", false
|
||||
}
|
||||
|
||||
-// postClean adjusts the results of Clean to avoid turning a relative path
|
||||
-// into an absolute or rooted one.
|
||||
func postClean(out *lazybuf) {
|
||||
if out.volLen != 0 || out.buf == nil {
|
||||
return
|
||||
}
|
||||
- // If a ':' appears in the path element at the start of a path,
|
||||
- // insert a .\ at the beginning to avoid converting relative paths
|
||||
- // like a/../c: into c:.
|
||||
for _, c := range out.buf {
|
||||
if IsPathSeparator(c) {
|
||||
break
|
||||
@@ -317,9 +117,6 @@
|
||||
return
|
||||
}
|
||||
}
|
||||
- // If a path begins with \??\, insert a \. at the beginning
|
||||
- // to avoid converting paths like \a\..\??\c:\x into \??\c:\x
|
||||
- // (equivalent to c:\x).
|
||||
if len(out.buf) >= 3 && IsPathSeparator(out.buf[0]) && out.buf[1] == '?' && out.buf[2] == '?' {
|
||||
out.prepend(Separator, '.')
|
||||
}
|
||||
116
path/winpath/patches/path_windowsspecific.diff
Normal file
116
path/winpath/patches/path_windowsspecific.diff
Normal file
@ -0,0 +1,116 @@
|
||||
--- orig/path_windowsspecific.go 2026-03-28 16:15:19
|
||||
+++ path_windowsspecific.go 2026-03-28 16:14:52
|
||||
@@ -2,103 +2,28 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
-package filepath
|
||||
+package winpath
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
- "syscall"
|
||||
)
|
||||
|
||||
-// HasPrefix exists for historical compatibility and should not be used.
|
||||
-//
|
||||
-// Deprecated: HasPrefix does not respect path boundaries and
|
||||
-// does not ignore case when required.
|
||||
-func HasPrefix(p, prefix string) bool {
|
||||
- if strings.HasPrefix(p, prefix) {
|
||||
- return true
|
||||
- }
|
||||
- return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix))
|
||||
-}
|
||||
-
|
||||
-func splitList(path string) []string {
|
||||
- // The same implementation is used in LookPath in os/exec;
|
||||
- // consider changing os/exec when changing this.
|
||||
-
|
||||
- if path == "" {
|
||||
- return []string{}
|
||||
- }
|
||||
-
|
||||
- // Split path, respecting but preserving quotes.
|
||||
- list := []string{}
|
||||
- start := 0
|
||||
- quo := false
|
||||
- for i := 0; i < len(path); i++ {
|
||||
- switch c := path[i]; {
|
||||
- case c == '"':
|
||||
- quo = !quo
|
||||
- case c == ListSeparator && !quo:
|
||||
- list = append(list, path[start:i])
|
||||
- start = i + 1
|
||||
- }
|
||||
- }
|
||||
- list = append(list, path[start:])
|
||||
-
|
||||
- // Remove quotes.
|
||||
- for i, s := range list {
|
||||
- list[i] = strings.ReplaceAll(s, `"`, ``)
|
||||
- }
|
||||
-
|
||||
- return list
|
||||
-}
|
||||
-
|
||||
-func abs(path string) (string, error) {
|
||||
- if path == "" {
|
||||
- // syscall.FullPath returns an error on empty path, because it's not a valid path.
|
||||
- // To implement Abs behavior of returning working directory on empty string input,
|
||||
- // special-case empty path by changing it to "." path. See golang.org/issue/24441.
|
||||
- path = "."
|
||||
- }
|
||||
- fullPath, err := syscall.FullPath(path)
|
||||
- if err != nil {
|
||||
- return "", err
|
||||
- }
|
||||
- return Clean(fullPath), nil
|
||||
-}
|
||||
-
|
||||
-func join(elem []string) string {
|
||||
+func Join(elem ...string) string {
|
||||
var b strings.Builder
|
||||
var lastChar byte
|
||||
for _, e := range elem {
|
||||
switch {
|
||||
case b.Len() == 0:
|
||||
- // Add the first non-empty path element unchanged.
|
||||
case os.IsPathSeparator(lastChar):
|
||||
- // If the path ends in a slash, strip any leading slashes from the next
|
||||
- // path element to avoid creating a UNC path (any path starting with "\\")
|
||||
- // from non-UNC elements.
|
||||
- //
|
||||
- // The correct behavior for Join when the first element is an incomplete UNC
|
||||
- // path (for example, "\\") is underspecified. We currently join subsequent
|
||||
- // elements so Join("\\", "host", "share") produces "\\host\share".
|
||||
for len(e) > 0 && os.IsPathSeparator(e[0]) {
|
||||
e = e[1:]
|
||||
}
|
||||
- // If the path is \ and the next path element is ??,
|
||||
- // add an extra .\ to create \.\?? rather than \??\
|
||||
- // (a Root Local Device path).
|
||||
if b.Len() == 1 && strings.HasPrefix(e, "??") && (len(e) == len("??") || os.IsPathSeparator(e[2])) {
|
||||
b.WriteString(`.\`)
|
||||
}
|
||||
case lastChar == ':':
|
||||
- // If the path ends in a colon, keep the path relative to the current directory
|
||||
- // on a drive and don't add a separator. Preserve leading slashes in the next
|
||||
- // path element, which may make the path absolute.
|
||||
- //
|
||||
- // Join(`C:`, `f`) = `C:f`
|
||||
- // Join(`C:`, `\f`) = `C:\f`
|
||||
default:
|
||||
- // In all other cases, add a separator between elements.
|
||||
b.WriteByte('\\')
|
||||
lastChar = '\\'
|
||||
}
|
||||
@@ -112,7 +37,3 @@
|
||||
}
|
||||
return Clean(b.String())
|
||||
}
|
||||
-
|
||||
-func sameWord(a, b string) bool {
|
||||
- return strings.EqualFold(a, b)
|
||||
-}
|
||||
341
path/winpath/patches/windows.diff.nextron.bak
Normal file
341
path/winpath/patches/windows.diff.nextron.bak
Normal file
@ -0,0 +1,341 @@
|
||||
diff --git a/windows/path_lite.go b/windows/path_lite.go
|
||||
index 4a37298..2ebb0d1 100644
|
||||
--- a/windows/path_lite.go
|
||||
+++ b/windows/path_lite.go
|
||||
@@ -2,17 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
-// Package filepathlite implements a subset of path/filepath,
|
||||
-// only using packages which may be imported by "os".
|
||||
-//
|
||||
-// Tests for these functions are in path/filepath.
|
||||
-package filepathlite
|
||||
+package windows
|
||||
|
||||
import (
|
||||
"errors"
|
||||
- "internal/stringslite"
|
||||
- "io/fs"
|
||||
"slices"
|
||||
+ "strings"
|
||||
)
|
||||
|
||||
var errInvalidPath = errors.New("invalid path")
|
||||
@@ -137,41 +132,6 @@ func Clean(path string) string {
|
||||
return FromSlash(out.string())
|
||||
}
|
||||
|
||||
-// IsLocal is filepath.IsLocal.
|
||||
-func IsLocal(path string) bool {
|
||||
- return isLocal(path)
|
||||
-}
|
||||
-
|
||||
-func unixIsLocal(path string) bool {
|
||||
- if IsAbs(path) || path == "" {
|
||||
- return false
|
||||
- }
|
||||
- hasDots := false
|
||||
- for p := path; p != ""; {
|
||||
- var part string
|
||||
- part, p, _ = stringslite.Cut(p, "/")
|
||||
- if part == "." || part == ".." {
|
||||
- hasDots = true
|
||||
- break
|
||||
- }
|
||||
- }
|
||||
- if hasDots {
|
||||
- path = Clean(path)
|
||||
- }
|
||||
- if path == ".." || stringslite.HasPrefix(path, "../") {
|
||||
- return false
|
||||
- }
|
||||
- return true
|
||||
-}
|
||||
-
|
||||
-// Localize is filepath.Localize.
|
||||
-func Localize(path string) (string, error) {
|
||||
- if !fs.ValidPath(path) {
|
||||
- return "", errInvalidPath
|
||||
- }
|
||||
- return localize(path)
|
||||
-}
|
||||
-
|
||||
// ToSlash is filepath.ToSlash.
|
||||
func ToSlash(path string) string {
|
||||
if Separator == '/' {
|
||||
@@ -189,7 +149,7 @@ func FromSlash(path string) string {
|
||||
}
|
||||
|
||||
func replaceStringByte(s string, old, new byte) string {
|
||||
- if stringslite.IndexByte(s, old) == -1 {
|
||||
+ if strings.IndexByte(s, old) == -1 {
|
||||
return s
|
||||
}
|
||||
n := []byte(s)
|
||||
diff --git a/windows/path_lite_windowsspecific.go b/windows/path_lite_windowsspecific.go
|
||||
index 011baa9..23ba2b6 100644
|
||||
--- a/windows/path_lite_windowsspecific.go
|
||||
+++ b/windows/path_lite_windowsspecific.go
|
||||
@@ -2,14 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
-package filepathlite
|
||||
-
|
||||
-import (
|
||||
- "internal/bytealg"
|
||||
- "internal/stringslite"
|
||||
- "internal/syscall/windows"
|
||||
- "syscall"
|
||||
-)
|
||||
+package windows
|
||||
|
||||
const (
|
||||
Separator = '\\' // OS-specific path separator
|
||||
@@ -20,159 +13,6 @@ func IsPathSeparator(c uint8) bool {
|
||||
return c == '\\' || c == '/'
|
||||
}
|
||||
|
||||
-func isLocal(path string) bool {
|
||||
- if path == "" {
|
||||
- return false
|
||||
- }
|
||||
- if IsPathSeparator(path[0]) {
|
||||
- // Path rooted in the current drive.
|
||||
- return false
|
||||
- }
|
||||
- if stringslite.IndexByte(path, ':') >= 0 {
|
||||
- // Colons are only valid when marking a drive letter ("C:foo").
|
||||
- // Rejecting any path with a colon is conservative but safe.
|
||||
- return false
|
||||
- }
|
||||
- hasDots := false // contains . or .. path elements
|
||||
- for p := path; p != ""; {
|
||||
- var part string
|
||||
- part, p, _ = cutPath(p)
|
||||
- if part == "." || part == ".." {
|
||||
- hasDots = true
|
||||
- }
|
||||
- if isReservedName(part) {
|
||||
- return false
|
||||
- }
|
||||
- }
|
||||
- if hasDots {
|
||||
- path = Clean(path)
|
||||
- }
|
||||
- if path == ".." || stringslite.HasPrefix(path, `..\`) {
|
||||
- return false
|
||||
- }
|
||||
- return true
|
||||
-}
|
||||
-
|
||||
-func localize(path string) (string, error) {
|
||||
- for i := 0; i < len(path); i++ {
|
||||
- switch path[i] {
|
||||
- case ':', '\\', 0:
|
||||
- return "", errInvalidPath
|
||||
- }
|
||||
- }
|
||||
- containsSlash := false
|
||||
- for p := path; p != ""; {
|
||||
- // Find the next path element.
|
||||
- var element string
|
||||
- i := bytealg.IndexByteString(p, '/')
|
||||
- if i < 0 {
|
||||
- element = p
|
||||
- p = ""
|
||||
- } else {
|
||||
- containsSlash = true
|
||||
- element = p[:i]
|
||||
- p = p[i+1:]
|
||||
- }
|
||||
- if isReservedName(element) {
|
||||
- return "", errInvalidPath
|
||||
- }
|
||||
- }
|
||||
- if containsSlash {
|
||||
- // We can't depend on strings, so substitute \ for / manually.
|
||||
- buf := []byte(path)
|
||||
- for i, b := range buf {
|
||||
- if b == '/' {
|
||||
- buf[i] = '\\'
|
||||
- }
|
||||
- }
|
||||
- path = string(buf)
|
||||
- }
|
||||
- return path, nil
|
||||
-}
|
||||
-
|
||||
-// isReservedName reports if name is a Windows reserved device name.
|
||||
-// It does not detect names with an extension, which are also reserved on some Windows versions.
|
||||
-//
|
||||
-// For details, search for PRN in
|
||||
-// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file.
|
||||
-func isReservedName(name string) bool {
|
||||
- // Device names can have arbitrary trailing characters following a dot or colon.
|
||||
- base := name
|
||||
- for i := 0; i < len(base); i++ {
|
||||
- switch base[i] {
|
||||
- case ':', '.':
|
||||
- base = base[:i]
|
||||
- }
|
||||
- }
|
||||
- // Trailing spaces in the last path element are ignored.
|
||||
- for len(base) > 0 && base[len(base)-1] == ' ' {
|
||||
- base = base[:len(base)-1]
|
||||
- }
|
||||
- if !isReservedBaseName(base) {
|
||||
- return false
|
||||
- }
|
||||
- if len(base) == len(name) {
|
||||
- return true
|
||||
- }
|
||||
- // The path element is a reserved name with an extension.
|
||||
- // Since Windows 11, reserved names with extensions are no
|
||||
- // longer reserved. For example, "CON.txt" is a valid file
|
||||
- // name. Use RtlIsDosDeviceName_U to see if the name is reserved.
|
||||
- p, err := syscall.UTF16PtrFromString(name)
|
||||
- if err != nil {
|
||||
- return false
|
||||
- }
|
||||
- return windows.RtlIsDosDeviceName_U(p) > 0
|
||||
-}
|
||||
-
|
||||
-func isReservedBaseName(name string) bool {
|
||||
- if len(name) == 3 {
|
||||
- switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) {
|
||||
- case "CON", "PRN", "AUX", "NUL":
|
||||
- return true
|
||||
- }
|
||||
- }
|
||||
- if len(name) >= 4 {
|
||||
- switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) {
|
||||
- case "COM", "LPT":
|
||||
- if len(name) == 4 && '1' <= name[3] && name[3] <= '9' {
|
||||
- return true
|
||||
- }
|
||||
- // Superscript ¹, ², and ³ are considered numbers as well.
|
||||
- switch name[3:] {
|
||||
- case "\u00b2", "\u00b3", "\u00b9":
|
||||
- return true
|
||||
- }
|
||||
- return false
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- // Passing CONIN$ or CONOUT$ to CreateFile opens a console handle.
|
||||
- // https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea#consoles
|
||||
- //
|
||||
- // While CONIN$ and CONOUT$ aren't documented as being files,
|
||||
- // they behave the same as CON. For example, ./CONIN$ also opens the console input.
|
||||
- if len(name) == 6 && name[5] == '$' && equalFold(name, "CONIN$") {
|
||||
- return true
|
||||
- }
|
||||
- if len(name) == 7 && name[6] == '$' && equalFold(name, "CONOUT$") {
|
||||
- return true
|
||||
- }
|
||||
- return false
|
||||
-}
|
||||
-
|
||||
-func equalFold(a, b string) bool {
|
||||
- if len(a) != len(b) {
|
||||
- return false
|
||||
- }
|
||||
- for i := 0; i < len(a); i++ {
|
||||
- if toUpper(a[i]) != toUpper(b[i]) {
|
||||
- return false
|
||||
- }
|
||||
- }
|
||||
- return true
|
||||
-}
|
||||
-
|
||||
func toUpper(c byte) byte {
|
||||
if 'a' <= c && c <= 'z' {
|
||||
return c - ('a' - 'A')
|
||||
diff --git a/windows/path_windowsspecific.go b/windows/path_windowsspecific.go
|
||||
index d0eb42c..40b9315 100644
|
||||
--- a/windows/path_windowsspecific.go
|
||||
+++ b/windows/path_windowsspecific.go
|
||||
@@ -2,71 +2,14 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
-package filepath
|
||||
+package windows
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
- "syscall"
|
||||
)
|
||||
|
||||
-// HasPrefix exists for historical compatibility and should not be used.
|
||||
-//
|
||||
-// Deprecated: HasPrefix does not respect path boundaries and
|
||||
-// does not ignore case when required.
|
||||
-func HasPrefix(p, prefix string) bool {
|
||||
- if strings.HasPrefix(p, prefix) {
|
||||
- return true
|
||||
- }
|
||||
- return strings.HasPrefix(strings.ToLower(p), strings.ToLower(prefix))
|
||||
-}
|
||||
-
|
||||
-func splitList(path string) []string {
|
||||
- // The same implementation is used in LookPath in os/exec;
|
||||
- // consider changing os/exec when changing this.
|
||||
-
|
||||
- if path == "" {
|
||||
- return []string{}
|
||||
- }
|
||||
-
|
||||
- // Split path, respecting but preserving quotes.
|
||||
- list := []string{}
|
||||
- start := 0
|
||||
- quo := false
|
||||
- for i := 0; i < len(path); i++ {
|
||||
- switch c := path[i]; {
|
||||
- case c == '"':
|
||||
- quo = !quo
|
||||
- case c == ListSeparator && !quo:
|
||||
- list = append(list, path[start:i])
|
||||
- start = i + 1
|
||||
- }
|
||||
- }
|
||||
- list = append(list, path[start:])
|
||||
-
|
||||
- // Remove quotes.
|
||||
- for i, s := range list {
|
||||
- list[i] = strings.ReplaceAll(s, `"`, ``)
|
||||
- }
|
||||
-
|
||||
- return list
|
||||
-}
|
||||
-
|
||||
-func abs(path string) (string, error) {
|
||||
- if path == "" {
|
||||
- // syscall.FullPath returns an error on empty path, because it's not a valid path.
|
||||
- // To implement Abs behavior of returning working directory on empty string input,
|
||||
- // special-case empty path by changing it to "." path. See golang.org/issue/24441.
|
||||
- path = "."
|
||||
- }
|
||||
- fullPath, err := syscall.FullPath(path)
|
||||
- if err != nil {
|
||||
- return "", err
|
||||
- }
|
||||
- return Clean(fullPath), nil
|
||||
-}
|
||||
-
|
||||
-func join(elem []string) string {
|
||||
+func Join(elem ...string) string {
|
||||
var b strings.Builder
|
||||
var lastChar byte
|
||||
for _, e := range elem {
|
||||
@@ -112,7 +55,3 @@ func join(elem []string) string {
|
||||
}
|
||||
return Clean(b.String())
|
||||
}
|
||||
-
|
||||
-func sameWord(a, b string) bool {
|
||||
- return strings.EqualFold(a, b)
|
||||
-}
|
||||
174
path/winpath/sync.sh
Executable file
174
path/winpath/sync.sh
Executable file
@ -0,0 +1,174 @@
|
||||
#!/bin/sh
|
||||
# Sync winpath package from Go standard library
|
||||
# Downloads source files to orig/ and applies patches from patches/
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ORIG_DIR="$SCRIPT_DIR/orig"
|
||||
PATCHES_DIR="$SCRIPT_DIR/patches"
|
||||
|
||||
# Go version to sync from (override with GO_VERSION env)
|
||||
GO_VERSION="${GO_VERSION:-go1.26.1}"
|
||||
GO_BASE_URL="https://raw.githubusercontent.com/golang/go/${GO_VERSION}/src"
|
||||
|
||||
# Source files to download
|
||||
# Format: "local_file::source_path"
|
||||
SOURCES="
|
||||
path_lite.go::internal/filepathlite/path.go
|
||||
path_lite_windowsspecific.go::internal/filepathlite/path_windows.go
|
||||
path_windowsspecific.go::path/filepath/path_windows.go
|
||||
"
|
||||
|
||||
download() {
|
||||
local dest="$1"
|
||||
local src="$2"
|
||||
local url="$GO_BASE_URL/$src"
|
||||
|
||||
printf " Downloading %s... " "$dest"
|
||||
if curl -fsSL "$url" -o "$dest.tmp"; then
|
||||
mv "$dest.tmp" "$dest"
|
||||
echo "OK"
|
||||
else
|
||||
rm -f "$dest.tmp"
|
||||
echo "FAILED"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
show_agent_instructions() {
|
||||
cat <<'EOF'
|
||||
|
||||
AGENT: PATCH APPLICATION FAILED
|
||||
AGENT:
|
||||
AGENT: The Go source files have diverged from the patches.
|
||||
AGENT:
|
||||
AGENT: To manually update:
|
||||
AGENT: 1. Run: ./sync.sh download
|
||||
AGENT: This downloads original files to orig/
|
||||
AGENT: 2. Compare orig/*.go with current *.go
|
||||
AGENT: 3. Update patches/*.diff files
|
||||
AGENT: 4. Run: ./sync.sh
|
||||
AGENT:
|
||||
AGENT: Key transformations for winpath:
|
||||
AGENT: - Package name: 'package winpath'
|
||||
AGENT: - Replace internal/stringslite with strings
|
||||
AGENT: - Remove internal/* imports
|
||||
AGENT: - Remove IsLocal, Localize, isLocal, localize, isReservedName functions
|
||||
AGENT: - Change join(elem []string) to Join(elem ...string)
|
||||
AGENT: - Export: Clean, Join, Split, Ext, Base, Dir, IsAbs, VolumeName, ToSlash, FromSlash
|
||||
EOF
|
||||
}
|
||||
|
||||
cmd_download() {
|
||||
echo "Downloading source files to orig/..."
|
||||
mkdir -p "$ORIG_DIR"
|
||||
for entry in $SOURCES; do
|
||||
local_file="${entry%%::*}"
|
||||
source_path="${entry#*::}"
|
||||
if ! download "$ORIG_DIR/$local_file" "$source_path"; then
|
||||
echo ""
|
||||
echo "ERROR: Failed to download $source_path"
|
||||
echo "AGENT: Check if Go version $GO_VERSION exists in golang/go repository"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
echo "Download complete. Original files in: $ORIG_DIR/"
|
||||
}
|
||||
|
||||
cmd_patch() {
|
||||
if ! test -d "$PATCHES_DIR"; then
|
||||
echo "ERROR: No patches directory found"
|
||||
echo "AGENT: Create patches/ directory with .diff files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy original files to working directory first
|
||||
for entry in $SOURCES; do
|
||||
local_file="${entry%%::*}"
|
||||
if test -f "$ORIG_DIR/$local_file"; then
|
||||
cp "$ORIG_DIR/$local_file" "$SCRIPT_DIR/$local_file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Applying patches..."
|
||||
patch_failed=""
|
||||
for patch in "$PATCHES_DIR"/*.diff; do
|
||||
if test -f "$patch"; then
|
||||
patch_name=$(basename "$patch")
|
||||
printf " Applying %s... " "$patch_name"
|
||||
if patch -d "$SCRIPT_DIR" -p1 <"$patch" 2>/dev/null; then
|
||||
echo "OK"
|
||||
else
|
||||
echo "FAILED"
|
||||
patch_failed=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
if test -n "$patch_failed"; then
|
||||
show_agent_instructions
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_verify() {
|
||||
printf "Verifying build... "
|
||||
if ! go build . 2>&1; then
|
||||
echo "FAILED"
|
||||
show_agent_instructions
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! go vet . 2>&1; then
|
||||
echo "FAILED (vet)"
|
||||
show_agent_instructions
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf "Running tests... "
|
||||
if ! go test -v . 2>&1; then
|
||||
echo "FAILED (tests)"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_diff() {
|
||||
echo "Generating diffs..."
|
||||
mkdir -p "$PATCHES_DIR"
|
||||
for entry in $SOURCES; do
|
||||
local_file="${entry%%::*}"
|
||||
if test -f "$ORIG_DIR/$local_file" && test -f "$SCRIPT_DIR/$local_file"; then
|
||||
diff -u "$ORIG_DIR/$local_file" "$SCRIPT_DIR/$local_file" >"$PATCHES_DIR/${local_file%.go}.diff" 2>/dev/null || true
|
||||
echo " Created patches/${local_file%.go}.diff"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
echo "Diffs created in: $PATCHES_DIR/"
|
||||
}
|
||||
|
||||
main() {
|
||||
case "${1:-}" in
|
||||
download)
|
||||
cmd_download
|
||||
;;
|
||||
diff)
|
||||
cmd_download
|
||||
cmd_diff
|
||||
;;
|
||||
*)
|
||||
# Default: download, patch, and verify
|
||||
cmd_download
|
||||
if test -d "$PATCHES_DIR"; then
|
||||
cmd_patch
|
||||
fi
|
||||
cmd_verify
|
||||
echo ""
|
||||
echo "Sync complete: path/winpath/"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
x
Reference in New Issue
Block a user