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 &gt; Console Output" tooltip="Failed &gt; 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  }