-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from skuid/feature-PLIN-1851
Feature plin 1851
- Loading branch information
Showing
13 changed files
with
1,067 additions
and
275 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ redis | |
coverage.out | ||
|
||
# Build artifacts | ||
warden | ||
condparse | ||
|
||
# build/test files | ||
env.sh | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,77 @@ | ||
#condparse | ||
# condparse | ||
|
||
This is mostly intended to be used as a library for parsing, manipulating, and serializing a condition logic string. | ||
|
||
For example, given a condition logic string like so `1 OR 2 AND (3 AND 4)`, this will allow you to remove any leaves by value and still get a valid condition logic string. | ||
|
||
# Usage | ||
|
||
## Parse | ||
|
||
To parse an existing condition logic string, call `Parse(string) error` | ||
|
||
```go | ||
import { | ||
"github.com/skuid/condparse/condparse" | ||
} | ||
|
||
logic := "1 OR 2 AND (3 OR 4)" | ||
|
||
tree, err := condparse.Parse(logic) | ||
|
||
if err != nil { | ||
fmt.Print("Error: %v", err) | ||
} | ||
|
||
fmt.Printf("Built a binary expression tree that looks like this: %v", tree) | ||
``` | ||
|
||
## Node.Eval | ||
|
||
This will take a tree and write it to any `io.Writer` | ||
|
||
```go | ||
var b strings.Builder | ||
|
||
err := tree.Eval(&b) | ||
|
||
if err != nil { | ||
fmt.Print("Error: %v", err) | ||
} | ||
|
||
fmt.Printf("Serialized the tree into condition logic: %s", b.String()) | ||
``` | ||
|
||
## Node.Remove | ||
|
||
This can be called multiple times to remove leafs from the tree by value. It will hoist any remaining expressions where needed and ignore any leafs it does not contain. | ||
|
||
```go | ||
logic := "1 OR (5 AND (1 OR 1)) AND (1 AND 2 OR (56 AND 1)) OR 4" | ||
|
||
tree, _ := condparse.Parse(logic) | ||
|
||
n := tree.Remove(1) | ||
n = tree.Remove(8) | ||
|
||
var b strings.Builder | ||
n.Eval(&b) | ||
fmt.Println(b.String()) | ||
tree.Remove(8) | ||
|
||
var b strings.Builder | ||
tree.Eval(&b) | ||
fmt.Println(b.String()) | ||
// 5 AND (2 OR 56 OR 4) | ||
``` | ||
|
||
# Errors | ||
|
||
`Parse` will throw `ParseError` errors mostly. These errors contain: | ||
- **Position**: The location in the logic string the error occurred | ||
- **Logic**: The logic string that the parser was tryign to parse | ||
- **Reason**: The reason it failed to parse the logic at that location | ||
|
||
`Eval` will throw `SerializeError` errors. They contain: | ||
- **Op**: The operation that failed to serialize. Leaf values are unlikely to fail | ||
- **Reason**: The reason it could not serialize that operation, which is typically due to missing nodes. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
package condparse | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
type testCase struct { | ||
desc string | ||
exprTree Node | ||
logic string | ||
} | ||
|
||
var cases []testCase | ||
|
||
func init() { | ||
cases = []testCase{ | ||
{ | ||
"Should serialize a single leaf", | ||
&Leaf{1}, | ||
"1", | ||
}, | ||
{ | ||
"Should parse a very simple tree with just one operation and two leafs", | ||
&Op{ | ||
Left: &Leaf{1}, | ||
Val: "AND", | ||
Right: &Leaf{2}, | ||
}, | ||
"1 AND 2", | ||
}, | ||
{ | ||
"Should parse a more complex tree with left only operations (no parens)", | ||
&Op{ | ||
Left: &Op{ | ||
Left: &Leaf{1}, | ||
Val: "OR", | ||
Right: &Leaf{2}, | ||
}, | ||
Val: "AND", | ||
Right: &Leaf{3}, | ||
}, | ||
"1 OR 2 AND 3", | ||
}, | ||
{ | ||
"Should parse a more complex tree with left and right operations, including parens", | ||
&Op{ | ||
Left: &Op{ | ||
Left: &Leaf{1}, | ||
Val: "OR", | ||
Right: &Leaf{2}, | ||
}, | ||
Val: "AND", | ||
Right: &Op{ | ||
Left: &Leaf{3}, | ||
Val: "OR", | ||
Right: &Leaf{4}, | ||
}, | ||
}, | ||
"1 OR 2 AND (3 OR 4)", | ||
}, | ||
{ | ||
"Should complex trees with more depth", | ||
&Op{ | ||
Left: &Op{ | ||
Left: &Leaf{1}, | ||
Val: "OR", | ||
Right: &Op{ | ||
Left: &Leaf{5}, | ||
Val: "AND", | ||
Right: &Op{ | ||
Left: &Leaf{7}, | ||
Val: "OR", | ||
Right: &Leaf{8}, | ||
}, | ||
}, | ||
}, | ||
Val: "AND", | ||
Right: &Op{ | ||
Left: &Op{ | ||
Left: &Op{ | ||
Left: &Leaf{3}, | ||
Val: "AND", | ||
Right: &Leaf{2}, | ||
}, | ||
Val: "OR", | ||
Right: &Op{ | ||
Left: &Leaf{56}, | ||
Val: "AND", | ||
Right: &Leaf{1000}, | ||
}, | ||
}, | ||
Val: "OR", | ||
Right: &Leaf{4}, | ||
}, | ||
}, | ||
"1 OR (5 AND (7 OR 8)) AND (3 AND 2 OR (56 AND 1000) OR 4)", | ||
}, | ||
} | ||
} | ||
|
||
func TestParse(t *testing.T) { | ||
for _, c := range cases { | ||
t.Run(c.desc, func(t *testing.T) { | ||
assert := assert.New(t) | ||
actual, err := Parse(c.logic) | ||
|
||
assert.NoError(err, "Should not have an error") | ||
deepEql(assert, c.exprTree, actual) | ||
}) | ||
} | ||
} | ||
|
||
func TestSerialize(t *testing.T) { | ||
for _, c := range cases { | ||
t.Run(c.desc, func(t *testing.T) { | ||
assert := assert.New(t) | ||
var b strings.Builder | ||
c.exprTree.Eval(&b) | ||
|
||
actual := b.String() | ||
assert.Equal(c.logic, actual) | ||
}) | ||
} | ||
} | ||
|
||
func deepEql(assert *assert.Assertions, expected Node, actual Node) { | ||
switch e := expected.(type) { | ||
case *Leaf: | ||
a, isLeaf := actual.(*Leaf) | ||
assert.True(isLeaf, "Expected was a leaf, actual was not! expected %v, actual %v", expected, actual) | ||
if e != nil && a != nil { | ||
assert.Equal(e.Val, a.Val, "Expected leaf values to match") | ||
} else if e != nil || a != nil { | ||
assert.Fail(fmt.Sprintf("One was nil and the other had a value. Expected %v, Actual %v", e, a)) | ||
} | ||
case *Op: | ||
a, isOp := actual.(*Op) | ||
assert.True(isOp, "Expected was an Operation, actual was not! expected %v, actual %v", expected, actual) | ||
if e != nil && a != nil { | ||
assert.Equal(e.Val, a.Val, "Expected operation to be the same") | ||
deepEql(assert, e.Left, a.Left) | ||
deepEql(assert, e.Right, a.Right) | ||
} else if e != nil || a != nil { | ||
assert.Fail(fmt.Sprintf("One was nil and the other had a value. Expected %v, Actual %v", e, a)) | ||
} | ||
default: | ||
assert.Fail("Node was neither a leaf or an op.") | ||
} | ||
} |
Oops, something went wrong.