v1.14.1
FIXED: * `envs/funcs.go:78:3: unknown field IgnoreWhiteSpace in struct literal of type EnvErrNoVal, but does have IgnoreWhitespace` * `envs/funcs_enverrnoval.go:15:8: sb.WasFound undefined (type *strings.Builder has no field or method WasFound)`
This commit is contained in:
298
pdsh/funcs_dshgrp.go
Normal file
298
pdsh/funcs_dshgrp.go
Normal file
@@ -0,0 +1,298 @@
|
||||
package pdsh
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"r00t2.io/sysutils/paths"
|
||||
)
|
||||
|
||||
/*
|
||||
ParseDshPtrn parses ptrn using the DSH group pattern ptrn as according to `HOSTLIST EXPRESSSIONS` in pdsh(1).
|
||||
`#include` directives are explicitly skipped; this only parses actual generation pattern strings.
|
||||
*/
|
||||
func ParseDshPtrn(ptrn string) (hostList []string, err error) {
|
||||
|
||||
var r rune
|
||||
var pos int
|
||||
var s string
|
||||
var inToken bool
|
||||
var tokStr string
|
||||
var tok dshGrpToken
|
||||
var strBuf *bytes.Buffer = new(bytes.Buffer)
|
||||
var tokBuf *bytes.Buffer = new(bytes.Buffer)
|
||||
var parser *dshGrpGenerator = &dshGrpGenerator{
|
||||
tokens: make([]dshGrpToken, 0),
|
||||
tokenized: make([]string, 0),
|
||||
text: ptrn,
|
||||
}
|
||||
|
||||
s = strings.TrimSpace(ptrn)
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(s, "#") {
|
||||
return
|
||||
}
|
||||
// A quick sanity check. The end-state from the state machine below will catch any weird bracket issues beyond this.
|
||||
if strings.Count(s, "[") != strings.Count(s, "]") {
|
||||
err = ErrInvalidDshGrpSyntax
|
||||
return
|
||||
}
|
||||
|
||||
// Now the hacky bits. We read until we get to a start-token ('['), end-token (']'), or a pattern separator (',') that is *outside* a range token.
|
||||
for pos, r = range s {
|
||||
switch r {
|
||||
case '[':
|
||||
if inToken {
|
||||
// Nested [...[
|
||||
err = &PtrnParseErr{
|
||||
pos: uint(pos),
|
||||
ptrn: ptrn,
|
||||
r: r,
|
||||
err: ErrInvalidDshGrpSyntax,
|
||||
}
|
||||
return
|
||||
}
|
||||
parser.tokenized = append(parser.tokenized, strBuf.String())
|
||||
strBuf.Reset()
|
||||
inToken = true
|
||||
case ']':
|
||||
if !inToken {
|
||||
// Nested ]...]
|
||||
err = &PtrnParseErr{
|
||||
pos: uint(pos),
|
||||
ptrn: ptrn,
|
||||
r: r,
|
||||
err: ErrInvalidDshGrpSyntax,
|
||||
}
|
||||
return
|
||||
}
|
||||
tokStr = tokBuf.String()
|
||||
if tok, err = parseDshGrpToken(tokStr); err != nil {
|
||||
err = &PtrnParseErr{
|
||||
pos: uint(pos),
|
||||
ptrn: ptrn,
|
||||
r: r,
|
||||
err: err,
|
||||
}
|
||||
return
|
||||
}
|
||||
parser.tokens = append(parser.tokens, tok)
|
||||
tokBuf.Reset()
|
||||
inToken = false
|
||||
default:
|
||||
if inToken {
|
||||
// If it isn't between '0' and '9', isn't '-', and isn't ','...
|
||||
if !(0x30 <= r && r <= 0x39) && (r != 0x2d) && (r != 0x2c) {
|
||||
// It's not a valid token. (The actual syntax is validated in parseDshGrpToken and parseDshGrpSubtoken)
|
||||
err = &PtrnParseErr{
|
||||
pos: uint(pos),
|
||||
ptrn: ptrn,
|
||||
r: r,
|
||||
err: ErrInvalidDshGrpSyntax,
|
||||
}
|
||||
return
|
||||
}
|
||||
tokBuf.WriteRune(r)
|
||||
} else {
|
||||
if strings.TrimSpace(string(r)) == "" || r == '#' {
|
||||
// Whitespace is "invalid" (treat it as the end of the pattern).
|
||||
// Same for end-of-line octothorpes.
|
||||
if tokBuf.Len() > 0 {
|
||||
// This should never happen.
|
||||
err = &PtrnParseErr{
|
||||
pos: uint(pos),
|
||||
ptrn: ptrn,
|
||||
r: r,
|
||||
err: ErrInvalidDshGrpSyntax,
|
||||
}
|
||||
return
|
||||
}
|
||||
if strBuf.Len() > 0 {
|
||||
parser.tokenized = append(parser.tokenized, strBuf.String())
|
||||
}
|
||||
break
|
||||
}
|
||||
// Otherwise we just check for valid DNS chars.
|
||||
if !(0x30 <= r && r <= 0x39) && // '0'-'9'
|
||||
(r != 0x2d) && // '-'
|
||||
(r != 0x2e) && // '.'
|
||||
!(0x41 <= r && r <= 0x5a) && // 'A' through 'Z' (inclusive)
|
||||
!(0x6a <= r && r <= 0x7a) { // 'a' through 'z' (inclusive)
|
||||
err = &PtrnParseErr{
|
||||
pos: uint(pos),
|
||||
ptrn: ptrn,
|
||||
r: r,
|
||||
err: ErrInvalidDshGrpPtrn,
|
||||
}
|
||||
return
|
||||
}
|
||||
// (Probably) valid(-ish), so add it.
|
||||
strBuf.WriteRune(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the token never closed, it's also invalid.
|
||||
if inToken {
|
||||
err = ErrInvalidDshGrpSyntax
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// parseDshGrpToken parses a token string into a dshGrpToken.
|
||||
func parseDshGrpToken(tokenStr string) (token dshGrpToken, err error) {
|
||||
|
||||
var s string
|
||||
var st []string
|
||||
var sub dshGrpSubtoken
|
||||
|
||||
s = strings.TrimSpace(tokenStr)
|
||||
st = strings.Split(s, ",")
|
||||
token = dshGrpToken{
|
||||
token: tokenStr,
|
||||
subtokens: make([]dshGrpSubtoken, 0, len(st)),
|
||||
}
|
||||
for _, s = range st {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
continue
|
||||
}
|
||||
if sub, err = parseDshGrpSubtoken(s); err != nil {
|
||||
return
|
||||
}
|
||||
token.subtokens = append(token.subtokens, sub)
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// parseDshGrpSubtoken parses a subtoken string into a dshGrpSubtoken.
|
||||
func parseDshGrpSubtoken(subTokenStr string) (subtoken dshGrpSubtoken, err error) {
|
||||
|
||||
var u64 uint64
|
||||
var vals []string
|
||||
var endPad string
|
||||
var startPad string
|
||||
var st dshGrpSubtoken
|
||||
var matches map[string][]string
|
||||
|
||||
if matches = dshGrpSubTokenPtrn.MapString(subTokenStr, false, false, true); matches == nil || len(matches) == 0 {
|
||||
err = ErrInvalidDshGrpPtrn
|
||||
return
|
||||
}
|
||||
if vals = matches["start_pad"]; vals != nil && len(vals) == 1 {
|
||||
startPad = vals[0]
|
||||
}
|
||||
/*
|
||||
Due to a... particular quirk in the regex that I'm too tired to fix,
|
||||
the start_pad may be e.g. "0" (or "00", etc.) and start may be "" if the range starts *at* 0
|
||||
(or 00, 000, etc.).
|
||||
*/
|
||||
if vals = matches["start"]; vals != nil && len(vals) == 1 {
|
||||
if u64, err = strconv.ParseUint(vals[0], 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
st.start = uint(u64)
|
||||
} else if startPad != "" {
|
||||
// Yeah, regex bug. So we remove one 0 from startPad, and set st.start to 0.
|
||||
st.start = 0 // This is implicit, though.
|
||||
startPad = startPad[:len(startPad)-1]
|
||||
}
|
||||
if vals = matches["end_pad"]; vals != nil && len(vals) == 1 {
|
||||
endPad = vals[0]
|
||||
}
|
||||
if vals = matches["end"]; vals != nil && len(vals) == 1 {
|
||||
if u64, err = strconv.ParseUint(vals[0], 10, 64); err != nil {
|
||||
return
|
||||
}
|
||||
st.end = uint(u64)
|
||||
}
|
||||
|
||||
if startPad != "" && endPad != "" {
|
||||
// We set the pad to the largest.
|
||||
if len(startPad) > len(endPad) {
|
||||
st.pad = startPad
|
||||
} else {
|
||||
st.pad = endPad
|
||||
}
|
||||
} else if startPad != "" {
|
||||
st.pad = startPad
|
||||
} else if endPad != "" {
|
||||
st.pad = endPad
|
||||
}
|
||||
|
||||
subtoken = st
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
getDshGrpIncludes parses fpath for `#include ...` directives. It skips any entries in which
|
||||
`len(paths.SegmentSys(p) == []string{p}`, as these are inherently included by the dir read.
|
||||
|
||||
It is assumed that fpath is a cleaned, absolute filepath.
|
||||
*/
|
||||
func getDshGrpIncludes(fpath string) (includes []string, err error) {
|
||||
|
||||
var f *os.File
|
||||
var line string
|
||||
var exists bool
|
||||
var inclpath string
|
||||
var subIncl []string
|
||||
var segs []string
|
||||
var scanner *bufio.Scanner
|
||||
var matches map[string][]string
|
||||
|
||||
if f, err = os.Open(fpath); err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
scanner = bufio.NewScanner(f)
|
||||
for scanner.Scan() {
|
||||
line = strings.TrimSpace(scanner.Text())
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if !dshGrpInclPtrn.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
matches = dshGrpInclPtrn.MapString(line, false, false, true)
|
||||
if matches == nil {
|
||||
err = ErrInvalidDshGrpSyntax
|
||||
return
|
||||
}
|
||||
if matches["incl"] == nil || len(matches["incl"]) == 0 {
|
||||
err = ErrInvalidDshGrpSyntax
|
||||
return
|
||||
}
|
||||
inclpath = matches["incl"][0]
|
||||
segs = paths.SegmentSys(inclpath, false, false)
|
||||
if segs == nil || len(segs) == 0 || (len(segs) == 1 && segs[0] == inclpath) {
|
||||
continue
|
||||
}
|
||||
|
||||
if exists, err = paths.RealPathExists(&inclpath); err != nil {
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
includes = append(includes, inclpath)
|
||||
if subIncl, err = getDshGrpIncludes(inclpath); err != nil {
|
||||
return
|
||||
}
|
||||
if subIncl != nil && len(subIncl) > 0 {
|
||||
includes = append(includes, subIncl...)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user