Skip to content

Commit 27fab87

Browse files
committed
add UBool unit type to attributes, and converter for common boolean stting specifications
the converter looks for common booleanish strings like 'yes' , 'off' 'disabled' 'True' etc, and converts them to 'true' or 'false' . these are then string-compared to the values the check returns for these attributes. use the Ubool attribute type on check_tasksched attributes: enabled, has_run and hidden. it is not used anywhere else
1 parent f331f6a commit 27fab87

4 files changed

Lines changed: 85 additions & 4 deletions

File tree

pkg/snclient/check_tasksched.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,18 @@ func (l *CheckTasksched) Build() *CheckData {
6060
{name: "application", description: "Name of the application that the task is associated with"},
6161
{name: "comment", description: "Comment or description for the work item"},
6262
{name: "creator", description: "Creator of the work item"},
63-
{name: "enabled", description: "Flag whether this job is enabled (true/false)"},
63+
{name: "enabled", description: "Flag whether this job is enabled (true/false)", unit: UBool},
6464
{name: "exit_code", description: "The last jobs exit code"},
6565
{name: "exit_string", description: "The last jobs exit code as string"},
6666
{name: "folder", description: "Task folder"},
6767
{name: "uri", description: "Fully qualified path to the task, includes folder and the task title"},
6868
{name: "uri_clean", description: "Remove the leading backslash from the URI, only for tasks directly saved at root and not for ones saved inside folders."},
69-
{name: "has_run", description: "True if this task has ever been executed"},
69+
{name: "has_run", description: "True if this task has ever been executed", unit: UBool},
7070
{name: "max_run_time", description: "Maximum length of time the task can run", unit: UDuration},
7171
{name: "most_recent_run_time", description: "Most recent time the work item began running", unit: UDate},
7272
{name: "priority", description: "Task priority"},
7373
{name: "title", description: "Task title"},
74-
{name: "hidden", description: "Indicates that the task will not be visible in the UI (true/false)"},
74+
{name: "hidden", description: "Indicates that the task will not be visible in the UI (true/false)", unit: UBool},
7575
{name: "missed_runs", description: "Number of times the registered task has missed a scheduled run"},
7676
{name: "task_status", description: "Task status as string"},
7777
{name: "next_run_time", description: "Time when the registered task is next scheduled to run", unit: UDate},

pkg/snclient/checkdata.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const (
8282
UDate
8383
UTimestamp
8484
UPercent
85+
UBool
8586
)
8687

8788
type CheckAttribute struct {

pkg/snclient/condition.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ import (
1616
)
1717

1818
var (
19+
// ^ and $ are used to capture the whole string
20+
// (\-?\d+\.\d+|\-?\d+) -> capture group one, two options separated by | first is a decimal with dot , second is a direct number
21+
// \s* -> any number of whitespace between the second group
22+
// (\D+) -> capture group two, one or more non-digit characters
1923
reConditionValueUnit = regexp.MustCompile(`^(\-?\d+\.\d+|\-?\d+)\s*(\D+)$`)
2024
reCuddleKeyword = regexp.MustCompile(`^([A-Za-z_]+)([!=><~]+)(.*)$`)
2125
reCuddleOperator = regexp.MustCompile(`^([!=><~]+)(.*?)$`)
@@ -819,10 +823,25 @@ func (c *Condition) expandDateKeyword(str string) bool {
819823
func (c *Condition) expandUnitByType(str string) error {
820824
// valid units might be "today", "thisweek", "thismonth", "thisyear" and ":utc" variants
821825
unit := c.getUnit(c.keyword)
822-
if unit == UDate || unit == UTimestamp {
826+
827+
switch unit {
828+
case UDate, UTimestamp:
823829
if done := c.expandDateKeyword(str); done {
824830
return nil
825831
}
832+
case UBool:
833+
// boolean units are not in the form of '<number> <unit>'
834+
// need to handle them before regex condition checks.
835+
newVal, oldVal, err := utils.ParseAndReplaceBoolAttributes(str)
836+
if err != nil {
837+
return fmt.Errorf("invalid boolean value: %s", err.Error())
838+
}
839+
c.original = oldVal
840+
c.value = newVal
841+
c.unit = ""
842+
843+
return nil
844+
default:
826845
}
827846

828847
match := reConditionValueUnit.FindStringSubmatch(str)
@@ -868,6 +887,8 @@ func (c *Condition) expandUnitByType(str string) error {
868887
c.unit = "s"
869888

870889
return nil
890+
case UBool:
891+
return nil // handled before the regex check
871892
case UPercent:
872893
return nil
873894
case UNone:

pkg/utils/utils.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,65 @@ func ExpandDuration(val string) (res float64, err error) {
8181
return 0, fmt.Errorf("expandDuration: cannot parse duration, unknown format in %s", val)
8282
}
8383

84+
var truthyValues = []string{"1", "true", "enabled", "yes", "y", "on", "t"}
85+
var falseyValues = []string{"0", "false", "disabled", "no", "n", "off", "f"}
86+
87+
var (
88+
truthyRegex, truthyReplacements = replacePatterns(truthyValues, "true")
89+
falseyRegex, falseyReplacements = replacePatterns(falseyValues, "false")
90+
)
91+
92+
// replacePatterns builds a regex that matches any of the given words as whole words,
93+
// and returns the pattern and a replacement map (lowercase match → replacement).
94+
func replacePatterns(words []string, replacement string) (regex *regexp.Regexp, replacements map[string]string) {
95+
escaped := make([]string, len(words))
96+
replacementMap := make(map[string]string)
97+
for i, word := range words {
98+
lowercase := strings.ToLower(word)
99+
escaped[i] = regexp.QuoteMeta(lowercase)
100+
replacementMap[lowercase] = replacement
101+
}
102+
pattern := `\b(` + strings.Join(escaped, "|") + `)\b`
103+
104+
return regexp.MustCompile(pattern), replacementMap
105+
}
106+
107+
// ParseAndReplaceBoolAttributes replaces all known truthy/falsey tokens in val
108+
// with "true"/"false" (case‑insensitive whole‑word match).
109+
// Returns the modified string, the original string, and an error if any
110+
// unrecognized boolean‑like token is found (optional).
111+
func ParseAndReplaceBoolAttributes(val string) (newVal, oldVal string, err error) {
112+
oldVal = val
113+
foundValues := 0
114+
115+
newVal = truthyRegex.ReplaceAllStringFunc(val, func(match string) string {
116+
if replacement, ok := truthyReplacements[strings.ToLower(match)]; ok {
117+
foundValues++
118+
119+
return replacement
120+
}
121+
122+
return ""
123+
})
124+
125+
newVal = falseyRegex.ReplaceAllStringFunc(newVal, func(match string) string {
126+
if replacement, ok := falseyReplacements[strings.ToLower(match)]; ok {
127+
foundValues++
128+
129+
return replacement
130+
}
131+
132+
return ""
133+
})
134+
135+
if foundValues == 0 {
136+
err = fmt.Errorf("ParseAndReplaceBoolAttributes: did not find any truthy or falsely values in boolean attribute value: '%s' , lowercase needs to be one of falsely: '%s' or truthy: '%s' values",
137+
val, strings.Join(falseyValues, " "), strings.Join(truthyValues, " "))
138+
}
139+
140+
return newVal, oldVal, err
141+
}
142+
84143
// returns time/duration in target unit with given precision
85144
func TimeUnitF(num uint64, targetUnit string, precision int) float64 {
86145
for _, factor := range TimeFactors {

0 commit comments

Comments
 (0)