mirror of
https://github.com/vbatts/sl-feeds.git
synced 2025-07-14 12:09:10 +00:00
changelog: a slackware ChangeLog parser
and ability to export to feeds Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
This commit is contained in:
parent
f522293398
commit
8e97e3d16f
14 changed files with 1750 additions and 535 deletions
51
changelog/feeds.go
Normal file
51
changelog/feeds.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
package changelog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/feeds"
|
||||
)
|
||||
|
||||
// ToFeed produces a github.com/gorilla/feeds.Feed that can be written to Atom or Rss
|
||||
func ToFeed(link string, entries []Entry) (*feeds.Feed, error) {
|
||||
var newestEntryTime time.Time
|
||||
var oldestEntryTime time.Time
|
||||
|
||||
for _, e := range entries {
|
||||
if e.Date.After(newestEntryTime) {
|
||||
newestEntryTime = e.Date
|
||||
}
|
||||
if e.Date.Before(oldestEntryTime) {
|
||||
oldestEntryTime = e.Date
|
||||
}
|
||||
}
|
||||
|
||||
feed := &feeds.Feed{
|
||||
Title: "",
|
||||
Link: &feeds.Link{Href: link},
|
||||
Description: "Generated ChangeLog.txt feeds by sl-feeds (github.com/vbatts/sl-feeds)",
|
||||
Created: oldestEntryTime,
|
||||
Updated: newestEntryTime,
|
||||
}
|
||||
feed.Items = make([]*feeds.Item, len(entries))
|
||||
for i, e := range entries {
|
||||
feed.Items[i] = &feeds.Item{
|
||||
Created: e.Date,
|
||||
Link: &feeds.Link{Href: ""},
|
||||
Description: e.ToChangeLog(),
|
||||
}
|
||||
|
||||
updateWord := "updates"
|
||||
if len(e.Updates) == 1 {
|
||||
updateWord = "update"
|
||||
}
|
||||
if e.SecurityFix() {
|
||||
feed.Items[i].Title = fmt.Sprintf("%d %s. Including a %s!", len(e.Updates), updateWord, securityFixStr)
|
||||
} else {
|
||||
feed.Items[i].Title = fmt.Sprintf("%d %s.", len(e.Updates), updateWord)
|
||||
}
|
||||
}
|
||||
|
||||
return feed, nil
|
||||
}
|
38
changelog/feeds_test.go
Normal file
38
changelog/feeds_test.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package changelog
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFeed(t *testing.T) {
|
||||
fh, err := os.Open("testdata/ChangeLog.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
e, err := Parse(fh)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
f, err := ToFeed("http://slackware.osuosl.org/slackware64-current/ChangeLog.txt", e)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rss, err := f.ToRss()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
//println(rss)
|
||||
if len(rss) == 0 {
|
||||
t.Error("rss output is empty")
|
||||
}
|
||||
|
||||
if err := f.WriteRss(ioutil.Discard); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
124
changelog/parse.go
Normal file
124
changelog/parse.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package changelog
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
dividerStr = `+--------------------------+`
|
||||
securityFixStr = `(* Security fix *)`
|
||||
dayPat = `^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s.*\d{4}$`
|
||||
updatePat = `^([a-z].*/.*): (Added|Rebuilt|Removed|Updated|Upgraded)\.$`
|
||||
)
|
||||
|
||||
var (
|
||||
dayReg = regexp.MustCompile(dayPat)
|
||||
updateReg = regexp.MustCompile(updatePat)
|
||||
)
|
||||
|
||||
// Parse takes in a slackware ChangeLog.txt and returns its collections of Entries
|
||||
func Parse(r io.Reader) ([]Entry, error) {
|
||||
buf := bufio.NewReader(r)
|
||||
entries := []Entry{}
|
||||
curEntry := Entry{}
|
||||
var curUpdate *Update
|
||||
for {
|
||||
line, err := buf.ReadString('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
isEOF := err == io.EOF
|
||||
trimmedline := strings.TrimSuffix(line, "\n")
|
||||
|
||||
if trimmedline == dividerStr {
|
||||
if curUpdate != nil {
|
||||
curEntry.Updates = append(curEntry.Updates, *curUpdate)
|
||||
curUpdate = nil
|
||||
}
|
||||
entries = append(entries, curEntry)
|
||||
if isEOF {
|
||||
break
|
||||
}
|
||||
curEntry = Entry{}
|
||||
} else if dayReg.MatchString(trimmedline) {
|
||||
// this date means it is the beginning of an entry
|
||||
t, err := time.Parse(time.UnixDate, trimmedline)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
curEntry.Date = t
|
||||
} else if updateReg.MatchString(trimmedline) {
|
||||
// match on whether this is an update line
|
||||
if curUpdate != nil {
|
||||
curEntry.Updates = append(curEntry.Updates, *curUpdate)
|
||||
curUpdate = nil
|
||||
}
|
||||
m := updateReg.FindStringSubmatch(trimmedline)
|
||||
curUpdate = &Update{
|
||||
Name: m[1],
|
||||
Action: m[2],
|
||||
}
|
||||
} else if curUpdate != nil && strings.HasPrefix(trimmedline, " ") {
|
||||
curUpdate.Comment = curUpdate.Comment + line
|
||||
} else {
|
||||
// Everything else is a comment on the Entry
|
||||
curEntry.Comment = curEntry.Comment + line
|
||||
}
|
||||
|
||||
if isEOF {
|
||||
break
|
||||
}
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// Entry is an section of updates (or release comments) in a ChangeLog.txt
|
||||
type Entry struct {
|
||||
Date time.Time
|
||||
Comment string
|
||||
Updates []Update
|
||||
}
|
||||
|
||||
// SecurityFix is whether an update in this ChangeLog Entry includes a SecurityFix
|
||||
func (e Entry) SecurityFix() bool {
|
||||
for _, u := range e.Updates {
|
||||
if u.SecurityFix() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ToChangeLog reformats the struct as the text for ChangeLog.txt output
|
||||
func (e Entry) ToChangeLog() string {
|
||||
str := e.Date.Format(time.UnixDate) + "\n"
|
||||
if strings.Trim(e.Comment, " \n") != "" {
|
||||
str = str + e.Comment
|
||||
}
|
||||
for _, u := range e.Updates {
|
||||
str = str + u.ToChangeLog()
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Update is a package or component that is updated in a ChangeLog Entry
|
||||
type Update struct {
|
||||
Name string
|
||||
Action string
|
||||
Comment string
|
||||
}
|
||||
|
||||
// SecurityFix that this update is a security fix (that the comment includes `(* Security fix *)`)
|
||||
func (u Update) SecurityFix() bool {
|
||||
return strings.Contains(u.Comment, securityFixStr)
|
||||
}
|
||||
|
||||
// ToChangeLog reformats the struct as the text for ChangeLog.txt output
|
||||
func (u Update) ToChangeLog() string {
|
||||
return fmt.Sprintf("%s: %s.\n%s", u.Name, u.Action, u.Comment)
|
||||
}
|
61
changelog/parse_test.go
Normal file
61
changelog/parse_test.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package changelog
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
fh, err := os.Open("testdata/ChangeLog.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
e, err := Parse(fh)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Make sure we got all the entries
|
||||
expectedLen := 52
|
||||
if len(e) != expectedLen {
|
||||
t.Errorf("expected %d entries; got %d", expectedLen, len(e))
|
||||
}
|
||||
|
||||
// Make sure we got as many security fix entries as expected
|
||||
expectedSec := 34
|
||||
secCount := 0
|
||||
for i := range e {
|
||||
if e[i].SecurityFix() {
|
||||
secCount++
|
||||
}
|
||||
}
|
||||
if secCount != expectedSec {
|
||||
t.Errorf("expected %d security fix entries; got %d", expectedSec, secCount)
|
||||
}
|
||||
|
||||
// Make sure we got as many individual updates as expected
|
||||
expectedUp := 597
|
||||
upCount := 0
|
||||
for i := range e {
|
||||
upCount += len(e[i].Updates)
|
||||
}
|
||||
if upCount != expectedUp {
|
||||
t.Errorf("expected %d updates across the entries; got %d", expectedUp, upCount)
|
||||
}
|
||||
|
||||
// Make sure the top comment of an entry is working
|
||||
foundWorkmanComment := false
|
||||
expectedComment := "Thanks to Robby Workman for most of these updates."
|
||||
for i := range e {
|
||||
foundWorkmanComment = strings.Contains(e[i].Comment, expectedComment)
|
||||
if foundWorkmanComment {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundWorkmanComment {
|
||||
t.Errorf("expected to find an Entry with comment %q", expectedComment)
|
||||
}
|
||||
}
|
1430
changelog/testdata/ChangeLog.txt
vendored
Normal file
1430
changelog/testdata/ChangeLog.txt
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue