github.com/bndr/gojenkins@v1.1.0/build_history.go (about) 1 package gojenkins 2 3 import ( 4 "io" 5 "strconv" 6 "strings" 7 8 "golang.org/x/net/html" 9 ) 10 11 // Parse jenkins ajax response in order find the current jenkins build history 12 func parseBuildHistory(d io.Reader) []*History { 13 z := html.NewTokenizer(d) 14 depth := 0 15 buildRowCellDepth := -1 16 builds := make([]*History, 0) 17 isInsideDisplayName := false 18 var curBuild *History 19 for { 20 tt := z.Next() 21 switch tt { 22 case html.ErrorToken: 23 if z.Err() == io.EOF { 24 return builds 25 } 26 case html.SelfClosingTagToken: 27 tn, hasAttr := z.TagName() 28 // fmt.Println("START__", string(tn), hasAttr) 29 if hasAttr { 30 a := attr(z) 31 // <img src="/static/f2881562/images/16x16/red.png" alt="Failed > Console Output" tooltip="Failed > Console Output" style="width: 16px; height: 16px; " class="icon-red icon-sm" /> 32 if string(tn) == "img" { 33 if hasCSSClass(a, "icon-sm") && buildRowCellDepth > -1 { 34 if alt, found := a["alt"]; found { 35 curBuild.BuildStatus = strings.Fields(alt)[0] 36 } 37 } 38 } 39 } 40 case html.StartTagToken: 41 depth++ 42 tn, hasAttr := z.TagName() 43 // fmt.Println("START__", string(tn), hasAttr) 44 if hasAttr { 45 a := attr(z) 46 // <td class="build-row-cell"> 47 if string(tn) == "td" { 48 if hasCSSClass(a, "build-row-cell") { 49 buildRowCellDepth = depth 50 curBuild = &History{} 51 builds = append(builds, curBuild) 52 } 53 } 54 // <a update-parent-class=".build-row" href="/job/appscode/job/43/job/build-binary/227/" class="tip model-link inside build-link display-name">#227</a> 55 if string(tn) == "a" { 56 if hasCSSClass(a, "display-name") && buildRowCellDepth > -1 { 57 if href, found := a["href"]; found { 58 parts := strings.Split(href, "/") 59 if num, err := strconv.Atoi(parts[len(parts)-2]); err == nil { 60 curBuild.BuildNumber = num 61 isInsideDisplayName = true 62 } 63 } 64 } 65 } 66 // <div time="1469024602546" class="pane build-details"> ... </div> 67 if string(tn) == "div" { 68 if hasCSSClass(a, "build-details") && buildRowCellDepth > -1 { 69 if t, found := a["time"]; found { 70 if msec, err := strconv.ParseInt(t, 10, 0); err == nil { 71 curBuild.BuildTimestamp = msec / 1000 72 } 73 } 74 } 75 } 76 } 77 case html.TextToken: 78 if isInsideDisplayName { 79 curBuild.BuildDisplayName = z.Token().Data 80 isInsideDisplayName = false 81 } 82 case html.EndTagToken: 83 tn, _ := z.TagName() 84 if string(tn) == "td" && depth == buildRowCellDepth { 85 buildRowCellDepth = -1 86 curBuild = nil 87 } 88 depth-- 89 } 90 } 91 } 92 93 func attr(z *html.Tokenizer) map[string]string { 94 a := make(map[string]string) 95 for { 96 k, v, more := z.TagAttr() 97 if k != nil && v != nil { 98 a[string(k)] = string(v) 99 } 100 if !more { 101 break 102 } 103 } 104 return a 105 } 106 107 func hasCSSClass(a map[string]string, className string) bool { 108 if classes, found := a["class"]; found { 109 for _, class := range strings.Fields(classes) { 110 if class == className { 111 return true 112 } 113 } 114 } 115 return false 116 }