go_sysutils/exec_extra/funcs.go
2024-06-17 04:33:30 -04:00

356 lines
9.4 KiB
Go

/*
SysUtils - a library to assist with various system-related functions
Copyright (C) 2020 Brent Saner
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package exec_extra
import (
`fmt`
`os/exec`
`reflect`
`r00t2.io/goutils/bitmask`
`r00t2.io/goutils/structutils`
`r00t2.io/sysutils/errs`
)
/*
ExecCmdReturn runs cmd and alsom returns the exitStatus.
A non-zero exit status is not treated as an error.
*/
func ExecCmdReturn(cmd *exec.Cmd) (exitStatus int, err error) {
// https://stackoverflow.com/a/55055100/733214
err = cmd.Run()
exitErr, _ := err.(*exec.ExitError)
exitStatus = exitErr.ExitCode()
return
}
/*
GetCmdFromStruct takes a pointer to a struct and returns a slice of
strings compatible with os/exec.Cmd.
The tag name used can be changed by setting the CmdArgsTag variable in this module;
the default is `cmdarg`.
Tag value format:
<tag>:"<option>=<value>[,<option>[=<value>],<option>[=<value>]...]"
e.g.
cmdarg:"short=l,long=list"
cmdarg:"short=l"
cmdarg:"long=list"
If the tag value is "-", the field will be explicitly skipped.
(This is the default behavior for struct fields not tagged with `cmdarg`.)
If the field is nil, it will be skipped.
If a cmdarg tag is specified but has no `short` or `long` option value, the field will be skipped entirely.
If a field's value is nil, it will be skipped.
Otherwise if a field's value is the zero-value, it will be skipped.
Aside from the 'short' and 'long' tag valued-options, see the comment for each CmdArgOpt* constant
for their corresponding tag option and the CmdArgs* variables as well for their corresponding tag option.
Each struct field can be one of the following types:
* string
* *string
* slice (with elements of supported types)
* array (with elements of supported types)
* map (with keys and values of supported types; see the CmdArgsDictSep variable for the separator to use)
* struct (with fields of supported types)
* int/int8/int16/int32/int64
* uint/uint8/uint16/uint32/uint64
* float32/float64
Struct fields, slice/array elements, etc. are processed in order.
Maps, because ordering is non-deterministic, may have unpredictable ordering.
If s is nil, nothing will be done.
If s is not a pointer to a struct, nothing will be done.
*/
func GetCmdFromStruct[T any](s T, defaultOpts ...cmdArgOpt) (cmdSlice []string, err error) {
var tmpSlice []string
var ptrVal reflect.Value
var ptrType reflect.Type
var ptrKind reflect.Kind
var argFlags *cmdArgFlag
var opts *bitmask.MaskBit = bitmask.NewMaskBit()
var sVal reflect.Value = reflect.ValueOf(s)
var sType reflect.Type = sVal.Type()
var kind reflect.Kind = sType.Kind()
if kind != reflect.Ptr {
return
}
if sVal.IsNil() || sVal.IsZero() || !sVal.IsValid() {
return
}
ptrVal = sVal.Elem()
ptrType = ptrVal.Type()
ptrKind = ptrType.Kind()
if ptrKind != reflect.Struct {
return
}
tmpSlice = make([]string, 0)
if defaultOpts != nil && len(defaultOpts) != 0 {
for _, o := range defaultOpts {
opts.AddFlag(o.BitMask())
}
}
argFlags = &cmdArgFlag{
defaults: new(bitmask.MaskBit),
fieldOpts: new(bitmask.MaskBit),
boolMap: nil,
strMap: nil,
shortFlag: "",
longFlag: "",
field: nil,
value: &ptrVal,
argSlice: &tmpSlice,
}
*argFlags.defaults = *opts
*argFlags.fieldOpts = *opts
err = getCmdStruct(argFlags)
cmdSlice = tmpSlice
return
}
// getCmdStruct iterates over each field of reflect.Value struct v, and is called by GetCmdFromStruct.
func getCmdStruct(argFlags *cmdArgFlag) (err error) {
var t reflect.Type
var kind reflect.Kind
var fieldArgFlag *cmdArgFlag
if argFlags == nil {
return
}
if argFlags.value == nil {
return
}
t = argFlags.value.Type()
kind = t.Kind()
if kind != reflect.Struct {
err = errs.ErrBadType
return
}
for i := 0; i < argFlags.value.NumField(); i++ {
fieldArgFlag = new(cmdArgFlag)
*fieldArgFlag = *argFlags
fieldArgFlag.field = new(reflect.StructField)
fieldArgFlag.value = new(reflect.Value)
*fieldArgFlag.field = t.Field(i)
*fieldArgFlag.value = argFlags.value.Field(i)
if err = getCmdStructField(fieldArgFlag); err != nil {
return
}
}
return
}
// getCmdStructField parses an individual struct field.
func getCmdStructField(argFlags *cmdArgFlag) (err error) {
if argFlags == nil || argFlags.field == nil || argFlags.value == nil {
return
}
argFlags.boolMap = structutils.TagToBoolMap(*argFlags.field, CmdArgsTag, structutils.TagMapTrim)
if argFlags.boolMap["-"] {
return
}
argFlags.strMap = structutils.TagToStringMap(*argFlags.field, CmdArgsTag, structutils.TagMapTrim)
if argFlags.strMap == nil {
return
}
for key, val := range argFlags.strMap {
switch key {
case "short":
argFlags.shortFlag = val
case "long":
argFlags.longFlag = val
}
}
fmt.Println(argFlags.field.Name + ":")
fmt.Printf("BEFORE: %d\t%d\n", argFlags.defaults.Value(), argFlags.fieldOpts.Value())
argFlags.fieldOpts = parseCmdArgOpts(argFlags.fieldOpts, argFlags.defaults, *argFlags.field)
fmt.Printf("AFTER: %d\t%d\n\n", argFlags.defaults.Value(), argFlags.fieldOpts.Value())
/*
if v.Kind() == reflect.Ptr {
if v.IsNil() {
return
}
err = getCmdStructField(field, v.Elem(), current, defaults, tmpSlice)
} else {
err = getCmdValue(v, opts, tagVals, tmpSlice)
}
*/
return
}
// getCmdValue is a dispatcher for a reflect value.
func getCmdValue(v reflect.Value, opts *bitmask.MaskBit, flagVals map[string]string, tmpSlice *[]string) (err error) {
/*
var kind reflect.Kind = v.Kind()
switch kind {
case reflect.Ptr:
if v.IsNil() || v.IsZero() || !v.IsValid() {
return
}
v = v.Elem()
if err = getCmdValue(v, opts, tmpSlice); err != nil {
return
}
case reflect.String:
if err = getCmdString(v, opts, tmpSlice); err != nil {
return
}
return
case reflect.Slice, reflect.Array:
if err = getCmdSlice(v); err != nil {
}
case reflect.Map:
if err = getCmdMap(v); err != nil {
return
}
case reflect.Struct:
if err = getCmdStruct(v); err != nil {
return
}
}
*/
return
}
// parseCmdArgOpts returns a parsed, combined, etc. set of options into a single OR'd bitmask.
func parseCmdArgOpts(current *bitmask.MaskBit, defaults *bitmask.MaskBit, field reflect.StructField) (opts *bitmask.MaskBit) {
var tagOpts *bitmask.MaskBit = tagOptsToMask(field)
opts = defaults.Copy()
fmt.Printf(
"PARSE BEFORE:\n\tOPTS:\t%d\n\tCURRENT:\t%d\n\tDEFAULTS:\t%d\n\tTAGOPTS:\t%d\n",
opts.Value(),
)
for _, b := range []*bitmask.MaskBit{
current,
tagOpts,
} {
if b == nil {
continue
}
if b.HasFlag(CmdArgOptPreferShort.BitMask()) && !b.HasFlag(CmdArgOptPreferLong.BitMask()) {
opts.AddFlag(CmdArgOptPreferShort.BitMask())
opts.ClearFlag(CmdArgOptPreferLong.BitMask())
} else {
opts.AddFlag(CmdArgOptPreferLong.BitMask())
opts.ClearFlag(CmdArgOptPreferShort.BitMask())
}
if b.HasFlag(CmdArgOptShortEquals.BitMask()) && !b.HasFlag(CmdArgOptShortNoEquals.BitMask()) {
opts.AddFlag(CmdArgOptShortEquals.BitMask())
opts.ClearFlag(CmdArgOptShortNoEquals.BitMask())
} else {
opts.AddFlag(CmdArgOptShortNoEquals.BitMask())
opts.ClearFlag(CmdArgOptShortEquals.BitMask())
}
if b.HasFlag(CmdArgOptLongNoEquals.BitMask()) && !b.HasFlag(CmdArgOptLongEquals.BitMask()) {
opts.AddFlag(CmdArgOptLongNoEquals.BitMask())
opts.ClearFlag(CmdArgOptLongEquals.BitMask())
} else {
opts.AddFlag(CmdArgOptLongEquals.BitMask())
opts.ClearFlag(CmdArgOptLongNoEquals.BitMask())
}
if b.HasFlag(CmdArgOptForcePosix.BitMask()) && !b.HasFlag(CmdArgOptForceNoPosix.BitMask()) {
opts.AddFlag(CmdArgOptForcePosix.BitMask())
opts.ClearFlag(CmdArgOptForceNoPosix.BitMask())
} else {
opts.AddFlag(CmdArgOptForceNoPosix.BitMask())
opts.ClearFlag(CmdArgOptForcePosix.BitMask())
}
}
fmt.Printf("PARSE AFTER: %d\n", opts.Value())
return
}
// tagOptsToMask returns a bitmask.MaskBit from a struct field's tags.
func tagOptsToMask(field reflect.StructField) (b *bitmask.MaskBit) {
var o cmdArgOpt
var tagOpts map[string]bool = structutils.TagToBoolMap(field, CmdArgsTag, structutils.TagMapTrim)
b = bitmask.NewMaskBit()
// First round, these are normally disabled.
for k, v := range tagOpts {
switch k {
case "prefer_short":
o = CmdArgOptPreferShort
case "short_equals":
o = CmdArgOptShortEquals
case "no_long_equals":
o = CmdArgOptLongNoEquals
case "force_posix":
o = CmdArgOptForcePosix
}
if v {
b.AddFlag(o.BitMask())
} else {
b.ClearFlag(o.BitMask())
}
}
// Second round, these override the above.
for k, v := range tagOpts {
switch k {
case "prefer_long":
o = CmdArgOptPreferShort
case "no_short_equals":
o = CmdArgOptShortEquals
case "long_equals":
o = CmdArgOptLongNoEquals
case "force_no_posix":
o = CmdArgOptForcePosix
}
// Since these are meant to disable, we flip things around.
if v {
b.ClearFlag(o.BitMask())
} else {
b.AddFlag(o.BitMask())
}
}
return
}