/ errata.go
errata.go
  1  package main
  2  
  3  import (
  4  	"bufio"
  5  	"fmt"
  6  	"io"
  7  	"net/http"
  8  	"regexp"
  9  	"sort"
 10  	"strconv"
 11  	"strings"
 12  	"time"
 13  
 14  	"golang.org/x/net/html"
 15  )
 16  
 17  // Erratum are our individual chunks of errata
 18  type Erratum struct {
 19  	ID    int
 20  	Date  time.Time
 21  	Desc  string
 22  	Patch string
 23  	Link  string
 24  	Sig   []string
 25  }
 26  
 27  // Fetch pulls down and parses the information for a given Erratum
 28  func (e *Erratum) Fetch() error {
 29  	resp, err := http.Get(e.Link)
 30  	if err != nil {
 31  		return err
 32  	}
 33  
 34  	defer resp.Body.Close()
 35  
 36  	scanner := bufio.NewScanner(resp.Body)
 37  
 38  	// First two lines are our signature
 39  	// 3rd line is date
 40  	// everything from the date down to ^Index is our description
 41  	count := 0
 42  	var descr []string
 43  	var patch []string
 44  	re := regexp.MustCompile(`^Index:`)
 45  	matched := false
 46  	for scanner.Scan() {
 47  		s := scanner.Text()
 48  		if count < 2 {
 49  			e.Sig = append(e.Sig, s)
 50  		}
 51  
 52  		if count == 3 {
 53  			parts := strings.Split(s, ",")
 54  			if len(parts) == 3 {
 55  				d := fmt.Sprintf("%s,%s",
 56  					strings.Trim(parts[1], " "),
 57  					strings.Replace(parts[2], ":", "", -1))
 58  				e.Date, err = time.Parse("January 2, 2006", d)
 59  				if err != nil {
 60  					return err
 61  				}
 62  			}
 63  		}
 64  
 65  		if count > 3 {
 66  			if re.MatchString(s) {
 67  				matched = true
 68  			}
 69  
 70  			if !matched {
 71  				descr = append(descr, s)
 72  			} else {
 73  				patch = append(patch, s)
 74  			}
 75  
 76  		}
 77  
 78  		count = count + 1
 79  	}
 80  
 81  	if err := scanner.Err(); err != nil {
 82  		return err
 83  	}
 84  
 85  	e.Desc = strings.Join(descr, "\n")
 86  	e.Patch = strings.Join(patch, "\n")
 87  
 88  	return nil //fmt.Errorf("crap")
 89  }
 90  
 91  // Errata is a collection of Errata
 92  type Errata struct {
 93  	List   []Erratum
 94  	Length int
 95  }
 96  
 97  // By is the type of a "less" function that defines the ordering of its Planet arguments.
 98  type By func(p1, p2 *Erratum) bool
 99  
100  // Sort is a method on the function type, By, that sorts the argument slice according to the function.
101  func (by By) Sort(errata []Erratum) {
102  	es := &errataSorter{
103  		errata: errata,
104  		by:     by,
105  	}
106  	sort.Sort(es)
107  }
108  
109  // errataSorter joins a By function and a slice of Errata to be sorted.
110  type errataSorter struct {
111  	errata []Erratum
112  	by     func(p1, p2 *Erratum) bool // Closure used in the Less method.
113  }
114  
115  // Len is part of sort.Interface.
116  func (s *errataSorter) Len() int {
117  	return len(s.errata)
118  }
119  
120  // Swap is part of sort.Interface.
121  func (s *errataSorter) Swap(i, j int) {
122  	s.errata[i], s.errata[j] = s.errata[j], s.errata[i]
123  }
124  
125  // Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
126  func (s *errataSorter) Less(i, j int) bool {
127  	return s.by(&s.errata[i], &s.errata[j])
128  }
129  
130  // ParseErrata does the actual parsing of html
131  func ParseErrata(body io.Reader, baseURL string) (*Errata, error) {
132  	var data []Erratum
133  	var errata = &Errata{}
134  	doc, err := html.Parse(body)
135  	if err != nil {
136  		return nil, err
137  	}
138  	var f func(*html.Node)
139  	f = func(node *html.Node) {
140  		if node.Type == html.ElementNode && node.Data == "a" {
141  			if node.FirstChild != nil {
142  				var e Erratum
143  				var err error
144  
145  				for _, a := range node.Attr {
146  					if a.Key == "href" {
147  						parts := strings.Split(a.Val, "_")
148  
149  						if len(parts) >= 2 && a.Val != "" {
150  							e.Link = fmt.Sprintf("%s%s", baseURL, a.Val)
151  							e.ID, err = strconv.Atoi(parts[0])
152  							if err != nil {
153  								// Not a link we care about
154  								break
155  							}
156  							data = append(data, e)
157  						}
158  					}
159  				}
160  				return
161  			}
162  		}
163  		for child := node.FirstChild; child != nil; child = child.NextSibling {
164  			f(child)
165  		}
166  	}
167  	f(doc)
168  
169  	byID := func(p1, p2 *Erratum) bool {
170  		return p1.ID < p2.ID
171  	}
172  
173  	By(byID).Sort(data)
174  	errata.List = data
175  
176  	return errata, nil
177  }
178  
179  // ParseRemoteErrata grabs all of the OpenBSD errata from an html page
180  func ParseRemoteErrata(s string) (*Errata, error) {
181  	resp, err := http.Get(s)
182  	if err != nil {
183  		return nil, err
184  	}
185  
186  	defer resp.Body.Close()
187  
188  	return ParseErrata(resp.Body, s)
189  }
190  
191  // PrintErrata pretty prints an errata
192  //func PrintErrata(e *Erratum) string {
193  //	return fmt.Sprintf("New OpenBSD Errata: %03d\n%s: %s\n%s",
194  //		e.ID,
195  //		e.Date.String(),
196  //		e.Desc,
197  //		e.Link,
198  //	)
199  //}
200  
201  // PrintErrataMD pretty prints an errata in markdown
202  func PrintErrataMD(e *Erratum) string {
203  	return fmt.Sprintf("# OpenBSD Errata %03d ( _%s_ )\n<pre>%s</pre>\n[A source code patch exists which remedies this problem.](%s)",
204  		e.ID,
205  		e.Date.Format("January 2, 2006"),
206  		e.Desc,
207  		e.Link,
208  	)
209  }