Adds force-include; fixes bug in text width
3 files changed, 42 insertions(+), 26 deletions(-)

M cmd/orgchart/main.go
M cmd/orgchart/main_test.go
M cmd/orgchart/svg.go
M cmd/orgchart/main.go +17 -4
@@ 38,6 38,7 @@ func main() {
 	cssfile := goopt.String([]string{"-y"}, "", "CSS file to embed")
 	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")
+	incls := goopt.Strings([]string{"--ex-incl"}, "", "Column,Value -- Include (overrides and and or excludes)")
 	goopt.Version = ""
 	goopt.Summary = "Organization chart generator"
 	goopt.Parse(nil)

          
@@ 82,7 83,7 @@ func main() {
 		fmt.Println(err.Error())
 		os.Exit(1)
 	}
-	exc, err := makeExcludes(*ands, *ors)
+	exc, err := makeExcludes(*ands, *ors, *incls)
 	if err != nil {
 		fmt.Println(err.Error())
 		os.Exit(1)

          
@@ 233,11 234,21 @@ type Exclude struct {
 }
 
 type Excludes struct {
-	ands []Exclude
-	ors  []Exclude
+	ands  []Exclude
+	ors   []Exclude
+	incls []Exclude
 }
 
 func (ex Excludes) Match(cols []string) bool {
+	// If any INCLUDEs match, they win
+	for _, e := range ex.incls {
+		if e.idx < len(cols) {
+			if e.match == cols[e.idx] {
+				return false
+			}
+		}
+	}
+	// If any exclude ORs match, exclude
 	for _, e := range ex.ors {
 		if e.idx < len(cols) {
 			if e.match == cols[e.idx] {

          
@@ 245,6 256,7 @@ func (ex Excludes) Match(cols []string) 
 			}
 		}
 	}
+	// If all exclude ANDs match, exclude
 	matches := len(ex.ands) > 0
 	for _, e := range ex.ands {
 		if e.idx < len(cols) {

          
@@ 258,11 270,12 @@ func (ex Excludes) Match(cols []string) 
 	return matches
 }
 
-func makeExcludes(ands, ors []string) (Excludes, error) {
+func makeExcludes(ands, ors, incls []string) (Excludes, error) {
 	var err error
 	rv := Excludes{}
 	rv.ands, err = breakUp(ands)
 	rv.ors, err = breakUp(ors)
+	rv.incls, err = breakUp(incls)
 	return rv, err
 }
 

          
M cmd/orgchart/main_test.go +18 -12
@@ 61,7 61,7 @@ D1,B2
 C2,B2`
 	b := bytes.NewBufferString(s)
 	rx := make(Ruleset, 0)
-	exc, _ := makeExcludes([]string{}, []string{})
+	exc, _ := makeExcludes([]string{}, []string{}, []string{})
 	ps, er := makePeople(b, exc, []int{}, rx)
 	if er != nil {
 		assert.Fail(t, "failed to parse: %s", er.Error())

          
@@ 113,7 113,7 @@ C2B2,B2
 C3B2,B2`
 	b := bytes.NewBufferString(s)
 	rx := make(Ruleset, 0)
-	exc, _ := makeExcludes([]string{}, []string{})
+	exc, _ := makeExcludes([]string{}, []string{}, []string{})
 	ps, er := makePeople(b, exc, []int{}, rx)
 	if er != nil {
 		assert.Fail(t, "failed to parse: %s", er.Error())

          
@@ 139,7 139,7 @@ D1,C2B3
 D2,C2B3`
 	b = bytes.NewBufferString(s)
 	rx = make(Ruleset, 0)
-	exc, _ = makeExcludes([]string{}, []string{})
+	exc, _ = makeExcludes([]string{}, []string{}, []string{})
 	ps, er = makePeople(b, exc, []int{}, rx)
 	if er != nil {
 		assert.Fail(t, "failed to parse: %s", er.Error())

          
@@ 164,7 164,7 @@ W,,false,false`
 	if er != nil {
 		assert.Fail(t, er.Error())
 	}
-	exc, _ := makeExcludes([]string{}, []string{})
+	exc, _ := makeExcludes([]string{}, []string{}, []string{})
 	ps, er := makePeople(b, exc, []int{}, rx)
 	if er != nil {
 		assert.Fail(t, er.Error())

          
@@ 182,17 182,19 @@ func TestExcludes(t *testing.T) {
 	ex1, er := makeExcludes(
 		[]string{"0,TRUE", "1,TRUE"}, // AND
 		[]string{"2,TRUE", "3,TRUE"}, // OR
+		[]string{"4,TRUE"},           // INCLUDE
 	)
 	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", "", ""}))
+	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", "", "", ""}))
+	assert.True(t, ex1.Match([]string{"TRUE", "TRUE", "", "TRUE", "TRUE"}))
 }
 
 func TestExclude(t *testing.T) {

          
@@ 210,12 212,14 @@ C1B3,B3,TRUE,FALSE,
 C2B3,B3,FALSE,TRUE,
 C3B3,B3,FALSE,FALSE,
 D1,C2B3,TRUE,TRUE,TRUE
+D3,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
+		[]string{"0", "D3"},          // FORCE INCLUDE
 	)
 	if er != nil {
 		assert.Fail(t, "failed to parse: %s", er.Error())

          
@@ 242,8 246,10 @@ D2,C2B3,TRUE,TRUE,FALSE`
 				foundCount += 1
 			case "C3B3":
 				foundCount += 1
+			case "D3":
+				fountCount += 1
 			}
 		}
 	}
-	assert.Equal(t, 4, foundCount)
+	assert.Equal(t, 5, foundCount)
 }

          
M cmd/orgchart/svg.go +7 -10
@@ 11,8 11,8 @@ import (
  *  Constants & global variables                                          *
  **************************************************************************/
 const (
-	charW     = 5
-	padW      = 30
+	charW     = 6
+	padW      = 25
 	gapW      = 30
 	marginW   = 20
 	gapH      = 100

          
@@ 65,12 65,15 @@ func setBoxW(rs Ranks) {
 }
 
 func renderSVG(out io.Writer, rs Ranks, sup bool, css string) {
+	if sup {
+		rs = addSuperiors(rs)
+	}
 	setBoxH(rs)
 	setBoxW(rs)
 	c := svg.New(out)
 	bgW = boxW + gapW
 	pageWidth := rs.width() * bgW
-	h := height(rs, sup)
+	h := height(rs)
 	c.Start(pageWidth, h)
 	c.Filter("blur")
 	c.FeGaussianBlur(svg.Filterspec{In: "SourceAlpha", Result: "blur"}, 5, 5)

          
@@ 110,9 113,6 @@ func renderSVG(out io.Writer, rs Ranks, 
 
 	b := make(map[string]Box)
 	xoff := marginW
-	if sup {
-		rs = addSuperiors(rs)
-	}
 	for _, p := range rs[0] {
 		drawOrg(c, xoff, marginH/2, p, b, true, false, sup)
 		xoff += width(p)

          
@@ 245,11 245,8 @@ func width(p *Person) int {
 	return p.Width() * (boxW + gapW)
 }
 
-func height(rs Ranks, s bool) int {
+func height(rs Ranks) int {
 	high := rs.height()
-	if s {
-		high += 1
-	}
 	return (high * boxH) + ((high - 1) * (gapH / 2)) + marginH
 }