@@ 36,7 36,8 @@ func main() {
sup := goopt.Flag([]string{"-s"}, []string{}, "Display superior; only used if results are filtered", "")
hil := goopt.Strings([]string{"-c"}, "", "Column,ValueMatch,ClassName -- sets a CSS class based on a regexp match in a column")
cssfile := goopt.String([]string{"-y"}, "", "CSS file to embed")
- //lin := goopt.Strings([]string{"-l"}, "", "Box stroke. Format: <col#>,<regexp>.")
+ ands := goopt.Strings([]string{"--ex-and"}, "", "Column,Value -- Exclude (and -- all ands must match")
+ ors := goopt.Strings([]string{"--ex-or"}, "", "Column,Value -- Exclude (or -- any ors may match")
goopt.Version = ""
goopt.Summary = "Organization chart generator"
goopt.Parse(nil)
@@ 81,7 82,12 @@ func main() {
fmt.Println(err.Error())
os.Exit(1)
}
- ps, err := makePeople(fin, cols, rules)
+ exc, err := makeExcludes(*ands, *ors)
+ if err != nil {
+ fmt.Println(err.Error())
+ os.Exit(1)
+ }
+ ps, err := makePeople(fin, exc, cols, rules)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
@@ 140,7 146,7 @@ func matches(p *Person, re *regexp.Regex
// Parses a CSV data stream, producing a map of people indexed by the person
// name. Returns an error if a non-EOF read error is encountered
-func makePeople(f io.Reader, cols []int, rs Ruleset) (map[string]*Person, error) {
+func makePeople(f io.Reader, exc Excludes, cols []int, rs Ruleset) (map[string]*Person, error) {
r := csv.NewReader(f)
r.LazyQuotes = true
r.TrailingComma = true
@@ 157,7 163,7 @@ func makePeople(f io.Reader, cols []int,
header = false
continue
}
- if l[0] == "" {
+ if l[0] == "" || exc.Match(l) {
continue
}
name := l[0]
@@ 220,3 226,58 @@ func addRecursively(p *Person, rs Ranks,
}
return rs
}
+
+type Exclude struct {
+ idx int
+ match string
+}
+
+type Excludes struct {
+ ands []Exclude
+ ors []Exclude
+}
+
+func (ex Excludes) Match(cols []string) bool {
+ for _, e := range ex.ors {
+ if e.idx < len(cols) {
+ if e.match == cols[e.idx] {
+ return true
+ }
+ }
+ }
+ matches := len(ex.ands) > 0
+ for _, e := range ex.ands {
+ if e.idx < len(cols) {
+ if e.match != cols[e.idx] {
+ matches = false
+ }
+ } else {
+ return false
+ }
+ }
+ return matches
+}
+
+func makeExcludes(ands, ors []string) (Excludes, error) {
+ var err error
+ rv := Excludes{}
+ rv.ands, err = breakUp(ands)
+ rv.ors, err = breakUp(ors)
+ return rv, err
+}
+
+func breakUp(input []string) ([]Exclude, error) {
+ output := make([]Exclude, 0)
+ for _, a := range input {
+ cs := strings.Split(a, ",")
+ if len(cs) != 2 {
+ return output, errors.New("Malformed exclude argument " + a)
+ }
+ idx, err := strconv.Atoi(cs[0])
+ if err != nil {
+ return output, errors.New("Malformed exclude argument " + a)
+ }
+ output = append(output, Exclude{idx, cs[1]})
+ }
+ return output, nil
+}
@@ 50,8 50,9 @@ func makeData() (map[string]*Person, *Pe
return ps, a
}
-func testMakePeople(t *testing.T) {
- s := `C1,B1
+func TestMakePeople(t *testing.T) {
+ s := `H1,H2
+C1,B1
E1,D1
B1,A
A,
@@ 60,7 61,8 @@ D1,B2
C2,B2`
b := bytes.NewBufferString(s)
rx := make(Ruleset, 0)
- ps, er := makePeople(b, []int{}, rx)
+ exc, _ := makeExcludes([]string{}, []string{})
+ ps, er := makePeople(b, exc, []int{}, rx)
if er != nil {
assert.Fail(t, "failed to parse: %s", er.Error())
}
@@ 95,7 97,7 @@ C2,B2`
}
func TestDims(t *testing.T) {
- s := `
+ s := `H1,H2
X,
A,X
B1,A
@@ 111,7 113,8 @@ C2B2,B2
C3B2,B2`
b := bytes.NewBufferString(s)
rx := make(Ruleset, 0)
- ps, er := makePeople(b, []int{}, rx)
+ exc, _ := makeExcludes([]string{}, []string{})
+ ps, er := makePeople(b, exc, []int{}, rx)
if er != nil {
assert.Fail(t, "failed to parse: %s", er.Error())
}
@@ 119,7 122,8 @@ C3B2,B2`
assert.Equal(t, 3, rs.width())
- s = `X,
+ s = `H1,H2
+X,
A,X
B1,A
B2,A
@@ 135,7 139,8 @@ D1,C2B3
D2,C2B3`
b = bytes.NewBufferString(s)
rx = make(Ruleset, 0)
- ps, er = makePeople(b, []int{}, rx)
+ exc, _ = makeExcludes([]string{}, []string{})
+ ps, er = makePeople(b, exc, []int{}, rx)
if er != nil {
assert.Fail(t, "failed to parse: %s", er.Error())
}
@@ 159,7 164,8 @@ W,,false,false`
if er != nil {
assert.Fail(t, er.Error())
}
- ps, er := makePeople(b, []int{}, rx)
+ exc, _ := makeExcludes([]string{}, []string{})
+ ps, er := makePeople(b, exc, []int{}, rx)
if er != nil {
assert.Fail(t, er.Error())
}
@@ 171,3 177,73 @@ W,,false,false`
assert.Equal(t, ps["Z"].Class, "A B")
assert.Equal(t, ps["W"].Class, "")
}
+
+func TestExcludes(t *testing.T) {
+ ex1, er := makeExcludes(
+ []string{"0,TRUE", "1,TRUE"}, // AND
+ []string{"2,TRUE", "3,TRUE"}, // OR
+ )
+ if er != nil {
+ assert.Fail(t, "failed to parse: %s", er.Error())
+ }
+ assert.True(t, ex1.Match([]string{"", "", "", "TRUE"}))
+ assert.True(t, ex1.Match([]string{"", "", "TRUE", ""}))
+ assert.True(t, ex1.Match([]string{"TRUE", "TRUE", "", ""}))
+ assert.True(t, ex1.Match([]string{"TRUE", "TRUE", "", "TRUE"}))
+ assert.False(t, ex1.Match([]string{"", "", "", ""}))
+ assert.False(t, ex1.Match([]string{"TRUE", "", "", ""}))
+ assert.False(t, ex1.Match([]string{"", "TRUE", "", ""}))
+}
+
+func TestExclude(t *testing.T) {
+ s := `H1,H2,X1,X2,X3
+X,,,,
+A,X,,,
+B1,A,,,
+B2,A,,,
+B3,A,,,
+C1B1,B1,FALSE,FALSE,TRUE
+C2B1,B1,,,TRUE
+C3B1,B1,TRUE,FALSE,TRUE
+C1B2,B2,TRUE,TRUE,
+C1B3,B3,TRUE,FALSE,
+C2B3,B3,FALSE,TRUE,
+C3B3,B3,FALSE,FALSE,
+D1,C2B3,TRUE,TRUE,TRUE
+D2,C2B3,TRUE,TRUE,FALSE`
+ b := bytes.NewBufferString(s)
+ rx := make(Ruleset, 0)
+ ex1, er := makeExcludes(
+ []string{"2,TRUE", "3,TRUE"}, // AND
+ []string{"4,TRUE"}, // OR
+ )
+ if er != nil {
+ assert.Fail(t, "failed to parse: %s", er.Error())
+ }
+ ps, er := makePeople(b, ex1, []int{}, rx)
+ if er != nil {
+ assert.Fail(t, "failed to parse: %s", er.Error())
+ }
+ rs := rank(ps)
+ var foundCount int
+ for _, ps := range rs {
+ for _, p := range ps {
+ assert.NotEqual(t, "C1B1", p.Name)
+ assert.NotEqual(t, "C1B1", p.Name)
+ assert.NotEqual(t, "C3B1", p.Name)
+ assert.NotEqual(t, "C1B2", p.Name)
+ assert.NotEqual(t, "D1", p.Name)
+ switch p.Name {
+ case "B3":
+ foundCount += 1
+ case "C1B3":
+ foundCount += 1
+ case "C2B3":
+ foundCount += 1
+ case "C3B3":
+ foundCount += 1
+ }
+ }
+ }
+ assert.Equal(t, 4, foundCount)
+}