# HG changeset patch # User Sean E. Russell # Date 1462043220 14400 # Sat Apr 30 15:07:00 2016 -0400 # Node ID 35a6a483396323c3caadcc9d5e03820cf13572cd # Parent 3f5d27f78a808454ee0f5f3fce5e075b9b6adda3 Add exclusion support diff --git a/cmd/orgchart/main.go b/cmd/orgchart/main.go --- a/cmd/orgchart/main.go +++ b/cmd/orgchart/main.go @@ -36,7 +36,8 @@ 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: ,.") + 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 @@ 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 @@ // 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 @@ header = false continue } - if l[0] == "" { + if l[0] == "" || exc.Match(l) { continue } name := l[0] @@ -220,3 +226,58 @@ } 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 +} diff --git a/cmd/orgchart/main_test.go b/cmd/orgchart/main_test.go --- a/cmd/orgchart/main_test.go +++ b/cmd/orgchart/main_test.go @@ -50,8 +50,9 @@ 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 @@ 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 @@ } func TestDims(t *testing.T) { - s := ` + s := `H1,H2 X, A,X B1,A @@ -111,7 +113,8 @@ 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 @@ assert.Equal(t, 3, rs.width()) - s = `X, + s = `H1,H2 +X, A,X B1,A B2,A @@ -135,7 +139,8 @@ 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 @@ 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 @@ 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) +} diff --git a/cmd/orgchart/svg_test.go b/cmd/orgchart/svg_test.go --- a/cmd/orgchart/svg_test.go +++ b/cmd/orgchart/svg_test.go @@ -28,9 +28,10 @@ if er != nil { assert.Fail(t, er.Error()) } + exc, _ := makeExcludes([]string{}, []string{}) for i, tst := range tests { b := bytes.NewBufferString(h + tst) - ps, er = makePeople(b, []int{2, 3, 4}, rx) + ps, er = makePeople(b, exc, []int{2, 3, 4}, rx) if er != nil { assert.Fail(t, er.Error()) } diff --git a/todo.txt b/todo.txt --- a/todo.txt +++ b/todo.txt @@ -1,4 +1,3 @@ Dotted-line reports Admin associations -Exclude Pictures