diff --git a/web/scraping.go b/web/scraping.go
index 19b5571..d01b5c7 100644
--- a/web/scraping.go
+++ b/web/scraping.go
@@ -193,6 +193,10 @@ func getFilterResult(filters []Filter, filter *Filter, watch *Watch, web *Web, d
{
getFilterResultSubstring(filter)
}
+ case filter.Type == "subset":
+ {
+ getFilterResultSubset(filter)
+ }
case filter.Type == "contains":
{
getFilterResultContains(filter)
@@ -694,10 +698,11 @@ func getFilterResultSubstring(filter *Filter) {
if hasFrom && err != nil {
filter.Log("Could not parse left side of: '", substring, "'")
return
- } else if from < 0 {
- from = len(asRunes) + from
}
if from < 0 {
+ from = len(asRunes) + from
+ }
+ if from < 0 || from > len(asRunes) {
filter.Log("Out of bounds:", from_to)
continue
}
@@ -712,13 +717,15 @@ func getFilterResultSubstring(filter *Filter) {
if hasTo && err != nil {
filter.Log("Could not parse right side of: '", substring, "'")
return
- } else if to < 0 {
- to = len(asRunes) + to
}
if to < 0 {
+ to = len(asRunes) + to
+ }
+ if to < 0 || to > len(asRunes) {
filter.Log("Out of bounds:", from_to)
continue
}
+
if hasFrom && hasTo {
_, err := sb.WriteString(string(asRunes[from:to]))
if err != nil {
@@ -735,6 +742,10 @@ func getFilterResultSubstring(filter *Filter) {
filter.Log("Could not parse: '", substring, "'")
return
}
+ if pos < 0 || pos >= int64(len(asRunes)) {
+ filter.Log("Out of bounds:", pos)
+ continue
+ }
sb.WriteRune(asRunes[pos])
}
}
@@ -743,6 +754,91 @@ func getFilterResultSubstring(filter *Filter) {
}
}
+// getFilterResultSubset performs a subset selection on all the results of its parents
+func getFilterResultSubset(filter *Filter) {
+ numResults := 0
+ for _, parent := range filter.Parents {
+ numResults += len(parent.Results)
+ }
+
+ results := make([]string, 0, numResults)
+
+ for _, parent := range filter.Parents {
+ for _, result := range parent.Results {
+ results = append(results, result)
+ }
+ }
+
+ substrings := strings.Split(filter.Var1, ",")
+ for _, substring := range substrings {
+ if strings.Contains(substring, ":") {
+ from_to := strings.Split(substring, ":")
+ if len(from_to) != 2 {
+ filter.Log("Missing value in range: '", substring, "'")
+ return
+ }
+ fromStr := from_to[0]
+ var hasFrom bool = true
+ if fromStr == "" {
+ hasFrom = false
+ }
+ from64, err := strconv.ParseInt(fromStr, 10, 32)
+ var from = int(from64)
+ if hasFrom && err != nil {
+ filter.Log("Could not parse left side of: '", substring, "'")
+ return
+ }
+ if from < 0 {
+ from = len(results) + from
+ }
+ if from < 0 || from > len(results) {
+ filter.Log("Out of bounds:", from_to)
+ continue
+ }
+
+ toStr := from_to[1]
+ var hasTo bool = true
+ if toStr == "" {
+ hasTo = false
+ }
+ to64, err := strconv.ParseInt(toStr, 10, 32)
+ var to = int(to64)
+ if hasTo && err != nil {
+ filter.Log("Could not parse right side of: '", substring, "'")
+ return
+ }
+ if to < 0 {
+ to = len(results) + to
+ }
+ if to < 0 || to > len(results) {
+ filter.Log("Out of bounds:", from_to)
+ continue
+ }
+ if hasFrom && hasTo {
+ filter.Results = append(filter.Results, results[from:to]...)
+ if err != nil {
+ filter.Log("Could not substring: ", err)
+ }
+ } else if hasFrom {
+ filter.Results = append(filter.Results, results[from:]...)
+ } else if hasTo {
+ filter.Results = append(filter.Results, results[:to]...)
+ }
+ } else {
+ pos, err := strconv.ParseInt(substring, 10, 32)
+ if err != nil || pos < 0 {
+ filter.Log("Could not parse: '", substring, "'")
+ return
+ }
+ if pos < 0 || pos >= int64(numResults) {
+ filter.Log("Out of bounds:", pos)
+ continue
+ }
+ filter.Results = append(filter.Results, results[pos])
+ }
+ }
+}
+
// getFilterResultContains performs a regex contains on all the results of its parents
func getFilterResultContains(filter *Filter) {
r, err := regexp.Compile(filter.Var1)
@@ -1318,7 +1414,7 @@ func getFilterResultDisableSchedules(filter *Filter, web *Web, debug bool) {
return
}
- web.db.Model(&Filter{}).Where("watch_id = ?", filter.WatchID).Update("Var2", "no")
+ web.db.Model(&Filter{}).Where("watch_id = ? AND type = 'cron'", filter.WatchID).Update("Var2", "no")
}
// getFilterResultEcho is a debug filter type, used to bootstrap some tests
diff --git a/web/scraping_test.go b/web/scraping_test.go
index e1237a2..3fb4e7d 100644
--- a/web/scraping_test.go
+++ b/web/scraping_test.go
@@ -436,8 +436,11 @@ func TestFilterSubstringOutOfBounds(t *testing.T) {
Input string
Query string
}{
+ {"01234", "0:8"},
{"01234", ":-6"},
{"01234", "-6:"},
+ {"01234", "-1"},
+ {"01234", "6"},
}
for _, test := range tests {
@@ -459,6 +462,79 @@ func TestFilterSubstringOutOfBounds(t *testing.T) {
}
}
+func TestFilterSubset(t *testing.T) {
+ var tests = []struct {
+ Input []string
+ Query string
+ Want []string
+ }{
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "0", []string{"zero"}},
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "6", []string{"six"}},
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "-1", []string{}},
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "7", []string{}},
+
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "0,3,6", []string{"zero", "three", "six"}},
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "0,2:5,6", []string{"zero", "two", "three", "four", "six"}},
+
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "3:6", []string{"three", "four", "five"}},
+
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "0:7", []string{"zero", "one", "two", "three", "four", "five", "six"}},
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, ":7", []string{"zero", "one", "two", "three", "four", "five", "six"}},
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "0:", []string{"zero", "one", "two", "three", "four", "five", "six"}},
+ }
+
+ for _, test := range tests {
+ testname := fmt.Sprintf("%s %s", test.Input, test.Query)
+ t.Run(testname, func(t *testing.T) {
+ filter := Filter{
+ Parents: []*Filter{
+ {Results: test.Input},
+ },
+ Var1: test.Query,
+ }
+ getFilterResultSubset(
+ &filter,
+ )
+ if !DeepEqualStringSlice(filter.Results, test.Want) {
+ t.Errorf("Got %s, want %s", filter.Results, test.Want)
+ }
+ })
+ }
+}
+
+func TestFilterSubsetOutOfBounds(t *testing.T) {
+ var tests = []struct {
+ Input []string
+ Query string
+ }{
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "9"},
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "-10"},
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "-10:9"},
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "-10:"},
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, ":9"},
+
+ {[]string{"zero", "one", "two", "three", "four", "five", "six"}, "0,1,2,8,4"},
+ }
+
+ for _, test := range tests {
+ testname := fmt.Sprintf("%s %s", test.Input, test.Query)
+ t.Run(testname, func(t *testing.T) {
+ filter := Filter{
+ Parents: []*Filter{
+ {Results: test.Input},
+ },
+ Var1: test.Query,
+ }
+ getFilterResultSubset(
+ &filter,
+ )
+ if len(filter.Logs) == 0 {
+ t.Errorf("No log message, expected one for OoB")
+ }
+ })
+ }
+}
+
func TestFilterContains(t *testing.T) {
var tests = []struct {
Input []string
diff --git a/web/static/edit.js b/web/static/edit.js
index eeaef78..0700395 100644
--- a/web/static/edit.js
+++ b/web/static/edit.js
@@ -245,6 +245,25 @@ function onTypeChange(node) {
var2Div.appendChild(var2Input);
break;
}
+ case "subset": {
+ var var1Input = document.createElement("input");
+ var1Input.name = "var1";
+ var1Input.id = "var1Input";
+ var1Input.value = var1Value;
+ var1Input.classList.add("form-control");
+ var1Label.innerHTML = "Subset";
+ var1Input.placeholder = ":20,25-40,45,47,49,-20:";
+ var1Div.appendChild(var1Input);
+ var var2Input = document.createElement("input");
+ var2Input.name = "var2";
+ var2Input.id = "var2Input";
+ var2Input.value = var2Value;
+ var2Input.classList.add("form-control");
+ var2Input.disabled = true;
+ var2Label.innerHTML = "-";
+ var2Div.appendChild(var2Input);
+ break;
+ }
case "contains": {
var var1Input = document.createElement("input");
var1Input.name = "var1";
diff --git a/web/static/edit.ts b/web/static/edit.ts
index 487c9e6..d21ace1 100644
--- a/web/static/edit.ts
+++ b/web/static/edit.ts
@@ -224,6 +224,26 @@ function onTypeChange(node: DiagramNode | null = null){
var2Div.appendChild(var2Input);
break;
}
+ case "subset": {
+ let var1Input = document.createElement("input");
+ var1Input.name = "var1";
+ var1Input.id = "var1Input";
+ var1Input.value = var1Value;
+ var1Input.classList.add("form-control")
+ var1Label.innerHTML = "Subset";
+ var1Input.placeholder = ":20,25-40,45,47,49,-20:";
+ var1Div.appendChild(var1Input);
+
+ let var2Input = document.createElement("input");
+ var2Input.name = "var2";
+ var2Input.id = "var2Input";
+ var2Input.value = var2Value;
+ var2Input.classList.add("form-control")
+ var2Input.disabled = true;
+ var2Label.innerHTML = "-";
+ var2Div.appendChild(var2Input);
+ break;
+ }
case "contains": {
let var1Input = document.createElement("input");
var1Input.name = "var1";
diff --git a/web/templates/watch/edit.html b/web/templates/watch/edit.html
index 8a705fc..a08499e 100644
--- a/web/templates/watch/edit.html
+++ b/web/templates/watch/edit.html
@@ -83,6 +83,7 @@ GoWatch Edit {{ .Watch.Name }}
+