3 Commits

Author SHA1 Message Date
brent saner
8260e4fa93 v1.14.0
ADD:
* `paths.Len()`
* `paths.LenSys()`
* `paths.Segment()`
* `paths.SegmentSys()`
* `paths.Strip()`
* `paths.StripSys()`

FIX:
* Naming conflics between `<stdlib>/path` and the `path` parameters
2025-07-31 03:48:08 -04:00
brent saner
e5f7296d2e v1.13.3
ADDED:
* envs.GetEnvErr(), envs.GetEnvErrNoBlank(), envs.EnvErrNoVal
  This allows error-returned env vars for nonexistent/empty values.
2025-07-12 15:10:38 -04:00
brent saner
82f58d4fbf v1.13.2
ADDED:
* paths.RealPathJoin() and paths.RealPathJoinSys()
2025-07-09 16:27:48 -04:00
7 changed files with 364 additions and 112 deletions

View File

@@ -1,27 +1,27 @@
package envs
import (
`bytes`
`errors`
`fmt`
`io/ioutil`
`os`
`reflect`
`strings`
`sync`
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"reflect"
"strings"
"sync"
`r00t2.io/goutils/multierr`
`r00t2.io/goutils/structutils`
`r00t2.io/sysutils/errs`
`r00t2.io/sysutils/internal`
`r00t2.io/sysutils/paths`
"r00t2.io/goutils/multierr"
"r00t2.io/goutils/structutils"
"r00t2.io/sysutils/errs"
"r00t2.io/sysutils/internal"
"r00t2.io/sysutils/paths"
)
/*
DefEnv operates like Python's .get() method on dicts (maps);
if the environment variable specified by key does not exist/is not specified,
then the value specified by fallback will be returned instead
otherwise key's value is returned.
DefEnv operates like Python's .get() method on dicts (maps);
if the environment variable specified by key does not exist/is not specified,
then the value specified by fallback will be returned instead
otherwise key's value is returned.
*/
func DefEnv(key, fallback string) (value string) {
@@ -45,6 +45,54 @@ func DefEnvBlank(key, fallback string) (value string) {
return
}
// GetEnvErr returns the value of key if it exists. If it does not exist, err will be an EnvErrNoVal.
func GetEnvErr(key string) (value string, err error) {
var exists bool
if value, exists = os.LookupEnv(key); !exists {
err = &EnvErrNoVal{
VarName: key,
}
return
}
return
}
/*
GetEnvErrNoBlank behaves exactly like GetEnvErr with the
additional stipulation that the value must not be empty.
An error for a value that is non-empty but whitespace only (e.g. VARNM="\t")
can be returned if ignoreWhitespace == true.
(If it is, an EnvErrNoVal will also be returned.)
*/
func GetEnvErrNoBlank(key string, ignoreWhitespace bool) (value string, err error) {
var exists bool
var e *EnvErrNoVal = &EnvErrNoVal{
VarName: key,
WasRequiredNonEmpty: true,
IgnoreWhiteSpace: ignoreWhitespace,
}
if value, exists = os.LookupEnv(key); !exists {
err = e
return
} else {
e.WasFound = true
e.WasWhitespace = (strings.TrimSpace(value) == "") && (value != "")
if ignoreWhitespace && e.WasWhitespace {
err = e
return
}
}
return
}
// GetEnvMap returns a map of all environment variables. All values are strings.
func GetEnvMap() (envVars map[string]string) {
@@ -56,18 +104,18 @@ func GetEnvMap() (envVars map[string]string) {
}
/*
GetEnvMapNative returns a map of all environment variables, but attempts to "nativize" them.
All values are interfaces. It is up to the caller to typeswitch them to proper types.
GetEnvMapNative returns a map of all environment variables, but attempts to "nativize" them.
All values are interfaces. It is up to the caller to typeswitch them to proper types.
Note that the PATH/Path environment variable (for *Nix and Windows, respectively) will be
a []string (as per GetPathEnv). No other env vars, even if they contain os.PathListSeparator,
will be transformed to a slice or the like.
If an error occurs during parsing the path env var, it will be rendered as a string.
Note that the PATH/Path environment variable (for *Nix and Windows, respectively) will be
a []string (as per GetPathEnv). No other env vars, even if they contain os.PathListSeparator,
will be transformed to a slice or the like.
If an error occurs during parsing the path env var, it will be rendered as a string.
All number types will attempt to be their 64-bit version (i.e. int64, uint64, float64, etc.).
All number types will attempt to be their 64-bit version (i.e. int64, uint64, float64, etc.).
If a type cannot be determined for a value, its string form will be used
(as it would be found in GetEnvMap).
If a type cannot be determined for a value, its string form will be used
(as it would be found in GetEnvMap).
*/
func GetEnvMapNative() (envMap map[string]interface{}) {
@@ -79,24 +127,24 @@ func GetEnvMapNative() (envMap map[string]interface{}) {
}
/*
GetFirst gets the first instance if populated/set occurrence of varNames.
GetFirst gets the first instance if populated/set occurrence of varNames.
For example, if you have three potential env vars, FOO, FOOBAR, FOOBARBAZ,
and want to follow the logic flow of:
For example, if you have three potential env vars, FOO, FOOBAR, FOOBARBAZ,
and want to follow the logic flow of:
1.) Check if FOO is set. If not,
2.) Check if FOOBAR is set. If not,
3.) Check if FOOBARBAZ is set.
1.) Check if FOO is set. If not,
2.) Check if FOOBAR is set. If not,
3.) Check if FOOBARBAZ is set.
Then this would be specified as:
Then this would be specified as:
GetFirst([]string{"FOO", "FOOBAR", "FOOBARBAZ"})
GetFirst([]string{"FOO", "FOOBAR", "FOOBARBAZ"})
If val is "" and ok is true, this means that one of the specified variable names IS
set but is set to an empty value. If ok is false, none of the specified variables
are set.
If val is "" and ok is true, this means that one of the specified variable names IS
set but is set to an empty value. If ok is false, none of the specified variables
are set.
It is a thin wrapper around GetFirstWithRef.
It is a thin wrapper around GetFirstWithRef.
*/
func GetFirst(varNames []string) (val string, ok bool) {
@@ -106,14 +154,14 @@ func GetFirst(varNames []string) (val string, ok bool) {
}
/*
GetFirstWithRef behaves exactly like GetFirst, but with an additional returned value, idx,
which specifies the index in varNames in which a set variable was found. e.g. if:
GetFirstWithRef behaves exactly like GetFirst, but with an additional returned value, idx,
which specifies the index in varNames in which a set variable was found. e.g. if:
GetFirstWithRef([]string{"FOO", "FOOBAR", "FOOBAZ"})
GetFirstWithRef([]string{"FOO", "FOOBAR", "FOOBAZ"})
is called and FOO is not set but FOOBAR is, idx will be 1.
is called and FOO is not set but FOOBAR is, idx will be 1.
If ok is false, idx will always be -1 and should be ignored.
If ok is false, idx will always be -1 and should be ignored.
*/
func GetFirstWithRef(varNames []string) (val string, ok bool, idx int) {
@@ -148,8 +196,8 @@ func GetPathEnv() (pathList []string, err error) {
}
/*
GetPidEnvMap will only work on *NIX-like systems with procfs.
It gets the environment variables of a given process' PID.
GetPidEnvMap will only work on *NIX-like systems with procfs.
It gets the environment variables of a given process' PID.
*/
func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) {
@@ -187,10 +235,10 @@ func GetPidEnvMap(pid uint32) (envMap map[string]string, err error) {
}
/*
GetPidEnvMapNative, like GetEnvMapNative, returns a map of all environment variables, but attempts to "nativize" them.
All values are interfaces. It is up to the caller to typeswitch them to proper types.
GetPidEnvMapNative, like GetEnvMapNative, returns a map of all environment variables, but attempts to "nativize" them.
All values are interfaces. It is up to the caller to typeswitch them to proper types.
See the documentation for GetEnvMapNative for details.
See the documentation for GetEnvMapNative for details.
*/
func GetPidEnvMapNative(pid uint32) (envMap map[string]interface{}, err error) {
@@ -206,11 +254,11 @@ func GetPidEnvMapNative(pid uint32) (envMap map[string]interface{}, err error) {
}
/*
HasEnv is much like os.LookupEnv, but only returns a boolean for
if the environment variable key exists or not.
HasEnv is much like os.LookupEnv, but only returns a boolean for
if the environment variable key exists or not.
This is useful anywhere you may need to set a boolean in a func call
depending on the *presence* of an env var or not.
This is useful anywhere you may need to set a boolean in a func call
depending on the *presence* of an env var or not.
*/
func HasEnv(key string) (envIsSet bool) {
@@ -220,28 +268,28 @@ func HasEnv(key string) (envIsSet bool) {
}
/*
Interpolate takes one of:
Interpolate takes one of:
- a string (pointer only)
- a struct (pointer only)
- a map (applied to both keys *and* values)
- a slice
- a string (pointer only)
- a struct (pointer only)
- a map (applied to both keys *and* values)
- a slice
and performs variable substitution on strings from environment variables.
and performs variable substitution on strings from environment variables.
It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and,
if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%).
It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and,
if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%).
For structs, the tag name used can be changed by setting the StructTagInterpolate
variable in this submodule; the default is `envsub`.
If the tag value is "-", the field will be skipped.
For map fields within structs etc., the default is to apply interpolation to both keys and values.
All other tag value(s) are ignored.
For structs, the tag name used can be changed by setting the StructTagInterpolate
variable in this submodule; the default is `envsub`.
If the tag value is "-", the field will be skipped.
For map fields within structs etc., the default is to apply interpolation to both keys and values.
All other tag value(s) are ignored.
For maps and slices, Interpolate will recurse into values (e.g. [][]string will work as expected).
For maps and slices, Interpolate will recurse into values (e.g. [][]string will work as expected).
If s is nil, no interpolation will be performed. No error will be returned.
If s is not a valid/supported type, no interpolation will be performed. No error will be returned.
If s is nil, no interpolation will be performed. No error will be returned.
If s is not a valid/supported type, no interpolation will be performed. No error will be returned.
*/
func Interpolate[T any](s T) (err error) {
@@ -293,16 +341,16 @@ func Interpolate[T any](s T) (err error) {
}
/*
InterpolateString takes (a pointer to) a struct or string and performs variable substitution on it
from environment variables.
InterpolateString takes (a pointer to) a struct or string and performs variable substitution on it
from environment variables.
It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and,
if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%).
It supports both UNIX/Linux/POSIX syntax formats (e.g. $VARNAME, ${VARNAME}) and,
if on Windows, it *additionally* supports the EXPAND_SZ format (e.g. %VARNAME%).
If s is nil, nothing will be done and err will be ErrNilPtr.
If s is nil, nothing will be done and err will be ErrNilPtr.
This is a standalone function that is much more performant than Interpolate
at the cost of rigidity.
This is a standalone function that is much more performant than Interpolate
at the cost of rigidity.
*/
func InterpolateString(s *string) (err error) {

27
envs/funcs_enverrnoval.go Normal file
View File

@@ -0,0 +1,27 @@
package envs
import (
"strings"
)
// Error conforms to a stdlib error interface.
func (e *EnvErrNoVal) Error() (errStr string) {
var sb *strings.Builder = new(strings.Builder)
sb.WriteString("the variable '")
sb.WriteString(e.VarName)
sb.WriteString("' was ")
if sb.WasFound {
sb.WriteString("found")
} else {
sb.WriteString("not found")
}
if e.WasRequiredNonEmpty && e.WasFound {
sb.WriteString(" but is empty and was required to be non-empty")
}
errStr = sb.String()
return
}

20
envs/types.go Normal file
View File

@@ -0,0 +1,20 @@
package envs
type (
/*
EnvErrNoVal is an error containing the variable that does not exist
(and information surrounding the errored state).
*/
EnvErrNoVal struct {
// VarName is the variable name/key name originally specified in the function call.
VarName string `json:"var" toml:"VariableName" yaml:"Variable Name/Key" xml:"key,attr"`
// WasFound is only used for GetEnvErrNoBlank(). It is true if the variable was found/populated.
WasFound bool `json:"found" toml:"Found" yaml:"Found" xml:"found,attr"`
// WasRequiredNonEmpty indicates that this error was returned in a context where a variable was required to be non-empty (e.g. via GetEnvErrNoBlank()) but was empty.
WasRequiredNonEmpty bool `json:"reqd_non_empty" toml:"RequiredNonEmpty" yaml:"Required Non-Empty" xml:"reqNonEmpty,attr"`
// IgnoreWhitespace is true if the value was found but its evaluation was done against a whitestripped version.
IgnoreWhitespace bool `json:"ignore_ws" toml:"IgnoreWhitespace" yaml:"Ignore Whitespace" xml:"ignoreWhitespace,attr"`
// WasWhitespace is true if the value was whitespace-only.
WasWhitespace bool `json:"was_ws" toml:"WasWhitespace" yaml:"Was Whitespace Only" xml:"wasWhitespace,attr"`
}
)

8
go.mod
View File

@@ -6,11 +6,11 @@ require (
github.com/davecgh/go-spew v1.1.1
github.com/djherbis/times v1.6.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/shirou/gopsutil/v4 v4.25.5
golang.org/x/sync v0.15.0
golang.org/x/sys v0.33.0
github.com/shirou/gopsutil/v4 v4.25.6
golang.org/x/sync v0.16.0
golang.org/x/sys v0.34.0
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8
r00t2.io/goutils v1.8.1
r00t2.io/goutils v1.9.0
)
require (

8
go.sum
View File

@@ -21,6 +21,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc=
github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs=
github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
@@ -31,6 +33,8 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -38,10 +42,14 @@ golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8 h1:FW42yWB1sGClqswyHIB68wo0+oPrav1IuQ+Tdy8Qp8E=
honnef.co/go/augeas v0.0.0-20161110001225-ca62e35ed6b8/go.mod h1:44w9OfBSQ9l3o59rc2w3AnABtE44bmtNnRMNC7z+oKE=
r00t2.io/goutils v1.8.1 h1:TQcUycPKsYn0QI4uCqb56utmvu/vVSxlblBg98iXStg=
r00t2.io/goutils v1.8.1/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/goutils v1.9.0 h1:iEwa9LinCzabpTD03/2oUrFE3QinxszTzL48pBV9cD4=
r00t2.io/goutils v1.9.0/go.mod h1:9ObJI9S71wDLTOahwoOPs19DhZVYrOh4LEHmQ8SW4Lk=
r00t2.io/sysutils v1.1.1/go.mod h1:Wlfi1rrJpoKBOjWiYM9rw2FaiZqraD6VpXyiHgoDo/o=

View File

@@ -4,6 +4,10 @@ import (
"io/fs"
)
const (
GenericSeparator rune = '/'
)
// Mostly just for reference.
const (
// ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular

View File

@@ -23,6 +23,7 @@ import (
"errors"
"fmt"
"io/fs"
"math"
"os"
"os/user"
"path"
@@ -42,7 +43,7 @@ import (
ExpandHome will take a tilde(~)-prefixed path and resolve it to the actual path in-place.
"Nested" user paths (~someuser/somechroot/~someotheruser) are not supported as home directories are expected to be absolute paths.
*/
func ExpandHome(path *string) (err error) {
func ExpandHome(p *string) (err error) {
var unameSplit []string
var uname string
@@ -51,10 +52,10 @@ func ExpandHome(path *string) (err error) {
// Props to this guy.
// https://stackoverflow.com/a/43578461/733214
if len(*path) == 0 {
if len(*p) == 0 {
err = errors.New("empty path")
return
} else if (*path)[0] != '~' {
} else if (*p)[0] != '~' {
return
}
@@ -70,7 +71,7 @@ func ExpandHome(path *string) (err error) {
}
*/
// K but do it smarter.
unameSplit = strings.SplitN(*path, string(os.PathSeparator), 2)
unameSplit = strings.SplitN(*p, string(os.PathSeparator), 2)
if len(unameSplit) != 2 {
unameSplit = append(unameSplit, "")
}
@@ -86,7 +87,7 @@ func ExpandHome(path *string) (err error) {
}
}
*path = filepath.Join(u.HomeDir, unameSplit[1])
*p = filepath.Join(u.HomeDir, unameSplit[1])
return
}
@@ -108,9 +109,9 @@ potentially returning an inaccurate result.
This is a thin wrapper around GetFirstWithRef.
*/
func GetFirst(paths []string) (content []byte, isDir, ok bool) {
func GetFirst(p []string) (content []byte, isDir, ok bool) {
content, isDir, ok, _ = GetFirstWithRef(paths)
content, isDir, ok, _ = GetFirstWithRef(p)
return
}
@@ -119,12 +120,12 @@ func GetFirst(paths []string) (content []byte, isDir, ok bool) {
GetFirstWithRef is the file equivalent of envs.GetFirstWithRef.
It behaves exactly like GetFirst, but with an additional returned value, idx,
which specifies the index in paths in which a path was found.
which specifies the index in p in which a path was found.
As always, results are not guaranteed due to permissions, etc.
potentially returning an inaccurate result.
*/
func GetFirstWithRef(paths []string) (content []byte, isDir, ok bool, idx int) {
func GetFirstWithRef(p []string) (content []byte, isDir, ok bool, idx int) {
var locPaths []string
var exists bool
@@ -133,11 +134,11 @@ func GetFirstWithRef(paths []string) (content []byte, isDir, ok bool, idx int) {
idx = -1
// We have to be a little less cavalier about this.
if paths == nil {
if p == nil {
return
}
locPaths = make([]string, len(paths))
locPaths = paths[:] // Create an explicit copy so we don't modify paths.
locPaths = make([]string, len(p))
locPaths = p[:] // Create an explicit copy so we don't modify p.
for i, p := range locPaths {
if exists, stat, err = RealPathExistsStat(&p); err != nil {
err = nil
@@ -160,6 +161,30 @@ func GetFirstWithRef(paths []string) (content []byte, isDir, ok bool, idx int) {
return
}
/*
Len returns the number of path segments in p, as split with the same param signature to Segment.
See Segment for details on abs and strict.
*/
func Len(p string, abs, strict bool) (segments int) {
segments = len(Segment(p, abs, strict))
return
}
/*
LenSys returns the number of path segments in p, as split with the same param signature to SegmentSys.
See Segment for details on abs and strict.
*/
func LenSys(p string, abs, strict bool) (segments int) {
segments = len(SegmentSys(p, abs, strict))
return
}
/*
MakeDirIfNotExist will create a directory at a given path if it doesn't exist.
@@ -167,11 +192,11 @@ See also the documentation for RealPath.
This is a bit more sane option than os.MkdirAll as it will normalize paths a little better.
*/
func MakeDirIfNotExist(path string) (err error) {
func MakeDirIfNotExist(p string) (err error) {
var stat os.FileInfo
var exists bool
var locPath string = path
var locPath string = p
if exists, stat, err = RealPathExistsStat(&locPath); err != nil {
if !exists {
@@ -208,14 +233,16 @@ It is recommended to check err (if not nil) for an invalid path error. If this i
path syntax/string itself is not supported on the runtime OS. This can be done via:
if errors.Is(err, fs.ErrInvalid) {...}
*/
func RealPath(path *string) (err error) {
if err = ExpandHome(path); err != nil {
RealPath is simply a wrapper around ExpandHome(path) and filepath.Abs(*path).
*/
func RealPath(p *string) (err error) {
if err = ExpandHome(p); err != nil {
return
}
if *path, err = filepath.Abs(*path); err != nil {
if *p, err = filepath.Abs(*p); err != nil {
return
}
@@ -225,25 +252,25 @@ func RealPath(path *string) (err error) {
/*
RealPathJoin combines RealPath with (path).Join.
If dst is nil, then rootPath will be updated with the new value.
If dst is nil, then p will be updated with the new value.
You probably don't want that.
*/
func RealPathJoin(rootPath, dst *string, subPaths ...string) (err error) {
func RealPathJoin(p, dst *string, subPaths ...string) (err error) {
var newPath string
var realDst *string
if err = RealPath(rootPath); err != nil {
if err = RealPath(p); err != nil {
return
}
if dst == nil {
realDst = rootPath
realDst = p
} else {
realDst = dst
}
newPath = path.Join(append([]string{*rootPath}, subPaths...)...)
newPath = path.Join(append([]string{*p}, subPaths...)...)
if err = RealPath(&newPath); err != nil {
return
}
@@ -259,22 +286,22 @@ RealPathJoinSys combines RealPath with (path/filepath).Join.
If dst is nil, then path will be updated with the new value.
You probably don't want that.
*/
func RealPathJoinSys(path, dst *string, subPaths ...string) (err error) {
func RealPathJoinSys(p, dst *string, subPaths ...string) (err error) {
var newPath string
var realDst *string
if err = RealPath(path); err != nil {
if err = RealPath(p); err != nil {
return
}
if dst == nil {
realDst = path
realDst = p
} else {
realDst = dst
}
newPath = filepath.Join(append([]string{*path}, subPaths...)...)
newPath = filepath.Join(append([]string{*p}, subPaths...)...)
if err = RealPath(&newPath); err != nil {
return
}
@@ -300,13 +327,13 @@ See also the documentation for RealPath.
In those cases, it may be preferable to use RealPathExistsStat and checking stat for nil.
*/
func RealPathExists(path *string) (exists bool, err error) {
func RealPathExists(p *string) (exists bool, err error) {
if err = RealPath(path); err != nil {
if err = RealPath(p); err != nil {
return
}
if _, err = os.Stat(*path); err != nil {
if _, err = os.Stat(*p); err != nil {
if errors.Is(err, fs.ErrNotExist) {
err = nil
}
@@ -325,13 +352,13 @@ for the path (assuming it exists).
If stat is nil, it is highly recommended to check err via the methods suggested
in the documentation for RealPath and RealPathExists.
*/
func RealPathExistsStat(path *string) (exists bool, stat os.FileInfo, err error) {
func RealPathExistsStat(p *string) (exists bool, stat os.FileInfo, err error) {
if exists, err = RealPathExists(path); err != nil {
if exists, err = RealPathExists(p); err != nil {
return
}
if stat, err = os.Stat(*path); err != nil {
if stat, err = os.Stat(*p); err != nil {
return
}
@@ -498,6 +525,124 @@ func SearchFsPathsAsync(matcher FsSearchCriteriaAsync) {
return
}
/*
Segment returns path p's segments as a slice of strings, using GenericSeparator as a separator.
If abs is true, the placeholder leading prefix(es) (if any) of GenericSeparator will be kept in-place;
otherwise it/they will be trimmed out.
e.g.:
abs == true: //foo/bar/baz => []string{"", "", "foo", "bar", "baz"}
abs == false: /foo/bar/baz => []string{"foo", "bar", "baz"}
If strict is true, any trailing GenericSeparator will be kept in-place;
otherwise they will be trimmed out.
e.g. (assuming abs == false):
strict == true: /foo/bar/baz// => []string{"foo", "bar", "baz", "", ""}
strict == false: /foo/bar/baz/ => []string{"foo", "bar", "baz"}
It is recommended to call RealPath for path's ptr first for normalization.
*/
func Segment(p string, abs, strict bool) (segments []string) {
if !abs {
p = strings.TrimLeft(p, string(GenericSeparator))
}
if !strict {
p = strings.TrimRight(p, string(GenericSeparator))
}
segments = strings.Split(p, string(GenericSeparator))
return
}
// SegmentSys is exactly like Segment, except using os.PathSeparator instead of GenericSeparator.
func SegmentSys(p string, abs, strict bool) (segments []string) {
if !abs {
p = strings.TrimLeft(p, string(os.PathSeparator))
}
if !strict {
p = strings.TrimRight(p, string(os.PathSeparator))
}
segments = strings.Split(p, string(os.PathSeparator))
return
}
/*
Strip is like Segment but trims out the leading n number of segments and reassembles the path using path.Join.
n may be negative, in which case the *trailing* n number of segments will be trimmed out.
(i.e. n == -1, p == `foo/bar/baz/quux` would be `foo/bar/baz`, not `bar/baz/quux`)
If you require more traditional slicing (e.g. with interval),
you may want to use path.Join with a sliced result of Segment instead.
e.g.: *only* the *last* n segments: path.Join(Segment(p, ...)[Len(p, ...)-n:]...)
If n == 0 or int(math.Abs(float64(n))) >= len(Segment(p, ...)), no transformation will be done.
e.g.
n == 2: foo/bar/baz/foobar/quux => baz/foobar/quux
n == -2: foo/bar/baz/foobar/quux => foo/bar/baz
*/
func Strip(p string, abs, strict bool, n int) (slicedPath string) {
var pLen int
var absN int
var segments []string
segments = Segment(p, abs, strict)
pLen = len(segments)
absN = int(math.Abs(float64(n)))
if n == 0 || absN >= pLen {
slicedPath = p
return
}
if n > 0 {
segments = segments[n:]
} else {
segments = segments[:pLen-absN]
}
slicedPath = path.Join(segments...)
return
}
// StripSys is exactly like Strip but using (path/filepath).Join and SegmentSys.
func StripSys(p string, abs, strict bool, n int) (slicedPath string) {
var pLen int
var absN int
var segments []string
segments = SegmentSys(p, abs, strict)
pLen = len(segments)
absN = int(math.Abs(float64(n)))
if n == 0 || absN >= pLen {
slicedPath = p
return
}
if n > 0 {
segments = segments[n:]
} else {
segments = segments[:pLen-absN]
}
slicedPath = filepath.Join(segments...)
return
}
/*
filterTimes checks a times.Timespec of a file using:
- an age specified by the caller