forked from cadey/xesite
support RSS feed generation
This commit is contained in:
parent
a59e48d948
commit
b89387f6bb
|
@ -0,0 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Hash is a simple wrapper around the MD5 algorithm implementation in the
|
||||
// Go standard library. It takes in data and a salt and returns the hashed
|
||||
// representation.
|
||||
func Hash(data string, salt string) string {
|
||||
output := md5.Sum([]byte(data + salt))
|
||||
return fmt.Sprintf("%x", output)
|
||||
}
|
|
@ -13,7 +13,9 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/Xe/asarfs"
|
||||
"github.com/Xe/ln"
|
||||
"github.com/gernest/front"
|
||||
"github.com/gorilla/feeds"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
|
@ -155,12 +157,73 @@ func main() {
|
|||
port = "9090"
|
||||
}
|
||||
|
||||
http.HandleFunc("/blog.rss", createFeed)
|
||||
http.HandleFunc("/blog.atom", createAtom)
|
||||
|
||||
n := negroni.Classic()
|
||||
n.UseHandler(http.DefaultServeMux)
|
||||
|
||||
log.Fatal(http.ListenAndServe(":"+port, n))
|
||||
}
|
||||
|
||||
var bootTime = time.Now()
|
||||
|
||||
var feed = &feeds.Feed{
|
||||
Title: "Christine Dodrill's Blog",
|
||||
Link: &feeds.Link{Href: "https://christine.website/blog"},
|
||||
Description: "My blog posts and rants about various technology things.",
|
||||
Author: &feeds.Author{Name: "Christine Dodrill", Email: "me@christine.website"},
|
||||
Created: bootTime,
|
||||
Copyright: "This work is copyright Christine Dodrill. My viewpoints are my own and not the view of any employer past, current or future.",
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, item := range posts {
|
||||
itime, _ := time.Parse("2006-01-02", item.Date)
|
||||
feed.Items = append(feed.Items, &feeds.Item{
|
||||
Title: item.Title,
|
||||
Link: &feeds.Link{Href: "https://christine.website/" + item.Link},
|
||||
Description: item.Summary,
|
||||
Created: itime,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// IncrediblySecureSalt *******
|
||||
const IncrediblySecureSalt = "hunter2"
|
||||
|
||||
func createFeed(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/rss+xml")
|
||||
w.Header().Set("ETag", Hash(bootTime.String(), IncrediblySecureSalt))
|
||||
|
||||
err := feed.WriteRss(w)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
ln.Error(err, ln.F{
|
||||
"remote_addr": r.RemoteAddr,
|
||||
"action": "generating_rss",
|
||||
"uri": r.RequestURI,
|
||||
"host": r.Host,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createAtom(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/atom+xml")
|
||||
w.Header().Set("ETag", Hash(bootTime.String(), IncrediblySecureSalt))
|
||||
|
||||
err := feed.WriteAtom(w)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
ln.Error(err, ln.F{
|
||||
"remote_addr": r.RemoteAddr,
|
||||
"action": "generating_rss",
|
||||
"uri": r.RequestURI,
|
||||
"host": r.Host,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func writeBlogPosts(w http.ResponseWriter, r *http.Request) {
|
||||
p := []interface{}{}
|
||||
for _, post := range posts {
|
||||
|
|
|
@ -6,3 +6,6 @@ b68094ba95c055dfda888baa8947dfe44c20b1ac github.com/Xe/asarfs
|
|||
5e4d0891fe789f2da0c2d5afada3b6a1ede6d64c layeh.com/asar
|
||||
3f7ce7b928e14ff890b067e5bbbc80af73690a9c github.com/urfave/negroni
|
||||
f3687a5cd8e600f93e02174f5c0b91b56d54e8d0 github.com/Xe/gopreload
|
||||
49bd2f58881c34d534aa97bd64bdbdf37be0df91 github.com/Xe/ln
|
||||
441264de03a8117ed530ae8e049d8f601a33a099 github.com/gorilla/feeds
|
||||
ff09b135c25aae272398c51a07235b90a75aa4f0 github.com/pkg/errors
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package ln
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Filter interface for defining chain filters
|
||||
type Filter interface {
|
||||
Apply(Event) bool
|
||||
Run()
|
||||
Close()
|
||||
}
|
||||
|
||||
// FilterFunc allows simple functions to implement the Filter interface
|
||||
type FilterFunc func(e Event) bool
|
||||
|
||||
// Apply implements the Filter interface
|
||||
func (ff FilterFunc) Apply(e Event) bool {
|
||||
return ff(e)
|
||||
}
|
||||
|
||||
// Run implements the Filter interface
|
||||
func (ff FilterFunc) Run() {}
|
||||
|
||||
// Close implements the Filter interface
|
||||
func (ff FilterFunc) Close() {}
|
||||
|
||||
// WriterFilter implements a filter, which arbitrarily writes to an io.Writer
|
||||
type WriterFilter struct {
|
||||
sync.Mutex
|
||||
Out io.Writer
|
||||
Formatter Formatter
|
||||
}
|
||||
|
||||
// NewWriterFilter creates a filter to add to the chain
|
||||
func NewWriterFilter(out io.Writer, format Formatter) *WriterFilter {
|
||||
if format == nil {
|
||||
format = DefaultFormatter
|
||||
}
|
||||
return &WriterFilter{
|
||||
Out: out,
|
||||
Formatter: format,
|
||||
}
|
||||
}
|
||||
|
||||
// Apply implements the Filter interface
|
||||
func (w *WriterFilter) Apply(e Event) bool {
|
||||
output, err := w.Formatter.Format(e)
|
||||
if err == nil {
|
||||
w.Lock()
|
||||
w.Out.Write(output)
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Run implements the Filter interface
|
||||
func (w *WriterFilter) Run() {}
|
||||
|
||||
// Close implements the Filter interface
|
||||
func (w *WriterFilter) Close() {}
|
||||
|
||||
// NilFilter is safe to return as a Filter, but does nothing
|
||||
var NilFilter = FilterFunc(func(e Event) bool { return true })
|
|
@ -0,0 +1,100 @@
|
|||
package ln
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultTimeFormat represents the way in which time will be formatted by default
|
||||
DefaultTimeFormat = time.RFC3339
|
||||
)
|
||||
|
||||
// Formatter defines the formatting of events
|
||||
type Formatter interface {
|
||||
Format(Event) ([]byte, error)
|
||||
}
|
||||
|
||||
// DefaultFormatter is the default way in which to format events
|
||||
var DefaultFormatter Formatter
|
||||
|
||||
func init() {
|
||||
DefaultFormatter = NewTextFormatter()
|
||||
}
|
||||
|
||||
// TextFormatter formats events as key value pairs.
|
||||
// Any remaining text not wrapped in an instance of `F` will be
|
||||
// placed at the end.
|
||||
type TextFormatter struct {
|
||||
TimeFormat string
|
||||
}
|
||||
|
||||
// NewTextFormatter returns a Formatter that outputs as text.
|
||||
func NewTextFormatter() Formatter {
|
||||
return &TextFormatter{TimeFormat: DefaultTimeFormat}
|
||||
}
|
||||
|
||||
// Format implements the Formatter interface
|
||||
func (t *TextFormatter) Format(e Event) ([]byte, error) {
|
||||
var writer bytes.Buffer
|
||||
|
||||
writer.WriteString("time=\"")
|
||||
writer.WriteString(e.Time.Format(t.TimeFormat))
|
||||
writer.WriteString("\"")
|
||||
|
||||
for k, v := range e.Data {
|
||||
writer.WriteByte(' ')
|
||||
if shouldQuote(k) {
|
||||
writer.WriteString(fmt.Sprintf("%q", k))
|
||||
} else {
|
||||
writer.WriteString(k)
|
||||
}
|
||||
|
||||
writer.WriteByte('=')
|
||||
|
||||
switch v.(type) {
|
||||
case string:
|
||||
vs, _ := v.(string)
|
||||
if shouldQuote(vs) {
|
||||
fmt.Fprintf(&writer, "%q", vs)
|
||||
} else {
|
||||
writer.WriteString(vs)
|
||||
}
|
||||
case error:
|
||||
tmperr, _ := v.(error)
|
||||
es := tmperr.Error()
|
||||
|
||||
if shouldQuote(es) {
|
||||
fmt.Fprintf(&writer, "%q", es)
|
||||
} else {
|
||||
writer.WriteString(es)
|
||||
}
|
||||
case time.Time:
|
||||
tmptime, _ := v.(time.Time)
|
||||
writer.WriteString(tmptime.Format(time.RFC3339))
|
||||
default:
|
||||
fmt.Fprint(&writer, v)
|
||||
}
|
||||
}
|
||||
|
||||
if len(e.Message) > 0 {
|
||||
fmt.Fprintf(&writer, " _msg=%q", e.Message)
|
||||
}
|
||||
|
||||
writer.WriteByte('\n')
|
||||
return writer.Bytes(), nil
|
||||
}
|
||||
|
||||
func shouldQuote(s string) bool {
|
||||
for _, b := range s {
|
||||
if !((b >= 'A' && b <= 'Z') ||
|
||||
(b >= 'a' && b <= 'z') ||
|
||||
(b >= '0' && b <= '9') ||
|
||||
(b == '-' || b == '.' || b == '#' ||
|
||||
b == '/' || b == '_')) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
package ln
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Logger holds the current priority and list of filters
|
||||
type Logger struct {
|
||||
Filters []Filter
|
||||
}
|
||||
|
||||
// DefaultLogger is the default implementation of Logger
|
||||
var DefaultLogger *Logger
|
||||
|
||||
func init() {
|
||||
var defaultFilters []Filter
|
||||
|
||||
// Default to STDOUT for logging, but allow LN_OUT to change it.
|
||||
out := os.Stdout
|
||||
if os.Getenv("LN_OUT") == "<stderr>" {
|
||||
out = os.Stderr
|
||||
}
|
||||
|
||||
defaultFilters = append(defaultFilters, NewWriterFilter(out, nil))
|
||||
|
||||
DefaultLogger = &Logger{
|
||||
Filters: defaultFilters,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// F is a key-value mapping for structured data.
|
||||
type F map[string]interface{}
|
||||
|
||||
type Fer interface {
|
||||
F() map[string]interface{}
|
||||
}
|
||||
|
||||
// Event represents an event
|
||||
type Event struct {
|
||||
Time time.Time
|
||||
Data F
|
||||
Message string
|
||||
}
|
||||
|
||||
// Log is the generic logging method.
|
||||
func (l *Logger) Log(xs ...interface{}) {
|
||||
var bits []interface{}
|
||||
event := Event{Time: time.Now()}
|
||||
|
||||
addF := func(bf F) {
|
||||
if event.Data == nil {
|
||||
event.Data = bf
|
||||
} else {
|
||||
for k, v := range bf {
|
||||
event.Data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assemble the event
|
||||
for _, b := range xs {
|
||||
if bf, ok := b.(F); ok {
|
||||
addF(bf)
|
||||
} else if fer, ok := b.(Fer); ok {
|
||||
addF(F(fer.F()))
|
||||
} else {
|
||||
bits = append(bits, b)
|
||||
}
|
||||
}
|
||||
|
||||
event.Message = fmt.Sprint(bits...)
|
||||
|
||||
if os.Getenv("LN_DEBUG_ALL_EVENTS") == "1" {
|
||||
frame := callersFrame()
|
||||
if event.Data == nil {
|
||||
event.Data = make(F)
|
||||
}
|
||||
event.Data["_lineno"] = frame.lineno
|
||||
event.Data["_function"] = frame.function
|
||||
event.Data["_filename"] = frame.filename
|
||||
}
|
||||
|
||||
l.filter(event)
|
||||
}
|
||||
|
||||
func (l *Logger) filter(e Event) {
|
||||
for _, f := range l.Filters {
|
||||
if !f.Apply(e) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error logs an error and information about the context of said error.
|
||||
func (l *Logger) Error(err error, xs ...interface{}) {
|
||||
data := F{}
|
||||
frame := callersFrame()
|
||||
|
||||
data["_lineno"] = frame.lineno
|
||||
data["_function"] = frame.function
|
||||
data["_filename"] = frame.filename
|
||||
data["err"] = err
|
||||
|
||||
cause := errors.Cause(err)
|
||||
if cause != nil {
|
||||
data["cause"] = cause.Error()
|
||||
}
|
||||
|
||||
xs = append(xs, data)
|
||||
|
||||
l.Log(xs...)
|
||||
}
|
||||
|
||||
// Fatal logs this set of values, then exits with status code 1.
|
||||
func (l *Logger) Fatal(xs ...interface{}) {
|
||||
l.Log(xs...)
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Default Implementation
|
||||
|
||||
// Log is the generic logging method.
|
||||
func Log(xs ...interface{}) {
|
||||
DefaultLogger.Log(xs...)
|
||||
}
|
||||
|
||||
// Error logs an error and information about the context of said error.
|
||||
func Error(err error, xs ...interface{}) {
|
||||
DefaultLogger.Error(err, xs...)
|
||||
}
|
||||
|
||||
// Fatal logs this set of values, then exits with status code 1.
|
||||
func Fatal(xs ...interface{}) {
|
||||
DefaultLogger.Fatal(xs...)
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package ln
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type frame struct {
|
||||
filename string
|
||||
function string
|
||||
lineno int
|
||||
}
|
||||
|
||||
// skips 2 frames, since Caller returns the current frame, and we need
|
||||
// the caller's caller.
|
||||
func callersFrame() frame {
|
||||
var out frame
|
||||
pc, file, line, ok := runtime.Caller(3)
|
||||
if !ok {
|
||||
return out
|
||||
}
|
||||
srcLoc := strings.LastIndex(file, "/src/")
|
||||
if srcLoc >= 0 {
|
||||
file = file[srcLoc+5:]
|
||||
}
|
||||
out.filename = file
|
||||
out.function = functionName(pc)
|
||||
out.lineno = line
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func functionName(pc uintptr) string {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return "???"
|
||||
}
|
||||
name := fn.Name()
|
||||
beg := strings.LastIndex(name, string(os.PathSeparator))
|
||||
return name[beg+1:]
|
||||
// end := strings.LastIndex(name, string(os.PathSeparator))
|
||||
// return name[end+1 : len(name)]
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package feeds
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Generates Atom feed as XML
|
||||
|
||||
const ns = "http://www.w3.org/2005/Atom"
|
||||
|
||||
type AtomPerson struct {
|
||||
Name string `xml:"name,omitempty"`
|
||||
Uri string `xml:"uri,omitempty"`
|
||||
Email string `xml:"email,omitempty"`
|
||||
}
|
||||
|
||||
type AtomSummary struct {
|
||||
XMLName xml.Name `xml:"summary"`
|
||||
Content string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
type AtomContent struct {
|
||||
XMLName xml.Name `xml:"content"`
|
||||
Content string `xml:",chardata"`
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
type AtomAuthor struct {
|
||||
XMLName xml.Name `xml:"author"`
|
||||
AtomPerson
|
||||
}
|
||||
|
||||
type AtomContributor struct {
|
||||
XMLName xml.Name `xml:"contributor"`
|
||||
AtomPerson
|
||||
}
|
||||
|
||||
type AtomEntry struct {
|
||||
XMLName xml.Name `xml:"entry"`
|
||||
Xmlns string `xml:"xmlns,attr,omitempty"`
|
||||
Title string `xml:"title"` // required
|
||||
Updated string `xml:"updated"` // required
|
||||
Id string `xml:"id"` // required
|
||||
Category string `xml:"category,omitempty"`
|
||||
Content *AtomContent
|
||||
Rights string `xml:"rights,omitempty"`
|
||||
Source string `xml:"source,omitempty"`
|
||||
Published string `xml:"published,omitempty"`
|
||||
Contributor *AtomContributor
|
||||
Link *AtomLink // required if no child 'content' elements
|
||||
Summary *AtomSummary // required if content has src or content is base64
|
||||
Author *AtomAuthor // required if feed lacks an author
|
||||
}
|
||||
|
||||
type AtomLink struct {
|
||||
//Atom 1.0 <link rel="enclosure" type="audio/mpeg" title="MP3" href="http://www.example.org/myaudiofile.mp3" length="1234" />
|
||||
XMLName xml.Name `xml:"link"`
|
||||
Href string `xml:"href,attr"`
|
||||
Rel string `xml:"rel,attr,omitempty"`
|
||||
Type string `xml:"type,attr,omitempty"`
|
||||
Length string `xml:"length,attr,omitempty"`
|
||||
}
|
||||
|
||||
type AtomFeed struct {
|
||||
XMLName xml.Name `xml:"feed"`
|
||||
Xmlns string `xml:"xmlns,attr"`
|
||||
Title string `xml:"title"` // required
|
||||
Id string `xml:"id"` // required
|
||||
Updated string `xml:"updated"` // required
|
||||
Category string `xml:"category,omitempty"`
|
||||
Icon string `xml:"icon,omitempty"`
|
||||
Logo string `xml:"logo,omitempty"`
|
||||
Rights string `xml:"rights,omitempty"` // copyright used
|
||||
Subtitle string `xml:"subtitle,omitempty"`
|
||||
Link *AtomLink
|
||||
Author *AtomAuthor `xml:"author,omitempty"`
|
||||
Contributor *AtomContributor
|
||||
Entries []*AtomEntry
|
||||
}
|
||||
|
||||
type Atom struct {
|
||||
*Feed
|
||||
}
|
||||
|
||||
func newAtomEntry(i *Item) *AtomEntry {
|
||||
id := i.Id
|
||||
// assume the description is html
|
||||
c := &AtomContent{Content: i.Description, Type: "html"}
|
||||
|
||||
if len(id) == 0 {
|
||||
// if there's no id set, try to create one, either from data or just a uuid
|
||||
if len(i.Link.Href) > 0 && (!i.Created.IsZero() || !i.Updated.IsZero()) {
|
||||
dateStr := anyTimeFormat("2006-01-02", i.Updated, i.Created)
|
||||
host, path := i.Link.Href, "/invalid.html"
|
||||
if url, err := url.Parse(i.Link.Href); err == nil {
|
||||
host, path = url.Host, url.Path
|
||||
}
|
||||
id = fmt.Sprintf("tag:%s,%s:%s", host, dateStr, path)
|
||||
} else {
|
||||
id = "urn:uuid:" + NewUUID().String()
|
||||
}
|
||||
}
|
||||
var name, email string
|
||||
if i.Author != nil {
|
||||
name, email = i.Author.Name, i.Author.Email
|
||||
}
|
||||
|
||||
x := &AtomEntry{
|
||||
Title: i.Title,
|
||||
Link: &AtomLink{Href: i.Link.Href, Rel: i.Link.Rel, Type: i.Link.Type},
|
||||
Content: c,
|
||||
Id: id,
|
||||
Updated: anyTimeFormat(time.RFC3339, i.Updated, i.Created),
|
||||
}
|
||||
|
||||
intLength, err := strconv.ParseInt(i.Link.Length, 10, 64)
|
||||
|
||||
if err == nil && (intLength > 0 || i.Link.Type != "") {
|
||||
i.Link.Rel = "enclosure"
|
||||
x.Link = &AtomLink{Href: i.Link.Href, Rel: i.Link.Rel, Type: i.Link.Type, Length: i.Link.Length}
|
||||
}
|
||||
|
||||
if len(name) > 0 || len(email) > 0 {
|
||||
x.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: name, Email: email}}
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// create a new AtomFeed with a generic Feed struct's data
|
||||
func (a *Atom) AtomFeed() *AtomFeed {
|
||||
updated := anyTimeFormat(time.RFC3339, a.Updated, a.Created)
|
||||
feed := &AtomFeed{
|
||||
Xmlns: ns,
|
||||
Title: a.Title,
|
||||
Link: &AtomLink{Href: a.Link.Href, Rel: a.Link.Rel},
|
||||
Subtitle: a.Description,
|
||||
Id: a.Link.Href,
|
||||
Updated: updated,
|
||||
Rights: a.Copyright,
|
||||
}
|
||||
if a.Author != nil {
|
||||
feed.Author = &AtomAuthor{AtomPerson: AtomPerson{Name: a.Author.Name, Email: a.Author.Email}}
|
||||
}
|
||||
for _, e := range a.Items {
|
||||
feed.Entries = append(feed.Entries, newAtomEntry(e))
|
||||
}
|
||||
return feed
|
||||
}
|
||||
|
||||
// return an XML-Ready object for an Atom object
|
||||
func (a *Atom) FeedXml() interface{} {
|
||||
return a.AtomFeed()
|
||||
}
|
||||
|
||||
// return an XML-ready object for an AtomFeed object
|
||||
func (a *AtomFeed) FeedXml() interface{} {
|
||||
return a
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
Syndication (feed) generator library for golang.
|
||||
|
||||
Installing
|
||||
|
||||
go get github.com/gorilla/feeds
|
||||
|
||||
Feeds provides a simple, generic Feed interface with a generic Item object as well as RSS and Atom specific RssFeed and AtomFeed objects which allow access to all of each spec's defined elements.
|
||||
|
||||
Examples
|
||||
|
||||
Create a Feed and some Items in that feed using the generic interfaces:
|
||||
|
||||
import (
|
||||
"time"
|
||||
. "github.com/gorilla/feeds
|
||||
)
|
||||
|
||||
now = time.Now()
|
||||
|
||||
feed := &Feed{
|
||||
Title: "jmoiron.net blog",
|
||||
Link: &Link{Href: "http://jmoiron.net/blog"},
|
||||
Description: "discussion about tech, footie, photos",
|
||||
Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
|
||||
Created: now,
|
||||
Copyright: "This work is copyright © Benjamin Button",
|
||||
}
|
||||
|
||||
feed.Items = []*Item{
|
||||
&Item{
|
||||
Title: "Limiting Concurrency in Go",
|
||||
Link: &Link{Href: "http://jmoiron.net/blog/limiting-concurrency-in-go/"},
|
||||
Description: "A discussion on controlled parallelism in golang",
|
||||
Author: &Author{Name: "Jason Moiron", Email: "jmoiron@jmoiron.net"},
|
||||
Created: now,
|
||||
},
|
||||
&Item{
|
||||
Title: "Logic-less Template Redux",
|
||||
Link: &Link{Href: "http://jmoiron.net/blog/logicless-template-redux/"},
|
||||
Description: "More thoughts on logicless templates",
|
||||
Created: now,
|
||||
},
|
||||
&Item{
|
||||
Title: "Idiomatic Code Reuse in Go",
|
||||
Link: &Link{Href: "http://jmoiron.net/blog/idiomatic-code-reuse-in-go/"},
|
||||
Description: "How to use interfaces <em>effectively</em>",
|
||||
Created: now,
|
||||
},
|
||||
}
|
||||
|
||||
From here, you can output Atom or RSS versions of this feed easily
|
||||
|
||||
atom, err := feed.ToAtom()
|
||||
rss, err := feed.ToRss()
|
||||
|
||||
You can also get access to the underlying objects that feeds uses to export its XML
|
||||
|
||||
atomFeed := &Atom{feed}.AtomFeed()
|
||||
rssFeed := &Rss{feed}.RssFeed()
|
||||
|
||||
From here, you can modify or add each syndication's specific fields before outputting
|
||||
|
||||
atomFeed.Subtitle = "plays the blues"
|
||||
atom, err := ToXML(atomFeed)
|
||||
rssFeed.Generator = "gorilla/feeds v1.0 (github.com/gorilla/feeds)"
|
||||
rss, err := ToXML(rssFeed)
|
||||
|
||||
*/
|
||||
package feeds
|
|
@ -0,0 +1,106 @@
|
|||
package feeds
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Link struct {
|
||||
Href, Rel, Type, Length string
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Name, Email string
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Title string
|
||||
Link *Link
|
||||
Author *Author
|
||||
Description string // used as description in rss, summary in atom
|
||||
Id string // used as guid in rss, id in atom
|
||||
Updated time.Time
|
||||
Created time.Time
|
||||
}
|
||||
|
||||
type Feed struct {
|
||||
Title string
|
||||
Link *Link
|
||||
Description string
|
||||
Author *Author
|
||||
Updated time.Time
|
||||
Created time.Time
|
||||
Id string
|
||||
Subtitle string
|
||||
Items []*Item
|
||||
Copyright string
|
||||
}
|
||||
|
||||
// add a new Item to a Feed
|
||||
func (f *Feed) Add(item *Item) {
|
||||
f.Items = append(f.Items, item)
|
||||
}
|
||||
|
||||
// returns the first non-zero time formatted as a string or ""
|
||||
func anyTimeFormat(format string, times ...time.Time) string {
|
||||
for _, t := range times {
|
||||
if !t.IsZero() {
|
||||
return t.Format(format)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// interface used by ToXML to get a object suitable for exporting XML.
|
||||
type XmlFeed interface {
|
||||
FeedXml() interface{}
|
||||
}
|
||||
|
||||
// turn a feed object (either a Feed, AtomFeed, or RssFeed) into xml
|
||||
// returns an error if xml marshaling fails
|
||||
func ToXML(feed XmlFeed) (string, error) {
|
||||
x := feed.FeedXml()
|
||||
data, err := xml.MarshalIndent(x, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// strip empty line from default xml header
|
||||
s := xml.Header[:len(xml.Header)-1] + string(data)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Write a feed object (either a Feed, AtomFeed, or RssFeed) as XML into
|
||||
// the writer. Returns an error if XML marshaling fails.
|
||||
func WriteXML(feed XmlFeed, w io.Writer) error {
|
||||
x := feed.FeedXml()
|
||||
// write default xml header, without the newline
|
||||
if _, err := w.Write([]byte(xml.Header[:len(xml.Header)-1])); err != nil {
|
||||
return err
|
||||
}
|
||||
e := xml.NewEncoder(w)
|
||||
e.Indent("", " ")
|
||||
return e.Encode(x)
|
||||
}
|
||||
|
||||
// creates an Atom representation of this feed
|
||||
func (f *Feed) ToAtom() (string, error) {
|
||||
a := &Atom{f}
|
||||
return ToXML(a)
|
||||
}
|
||||
|
||||
// Writes an Atom representation of this feed to the writer.
|
||||
func (f *Feed) WriteAtom(w io.Writer) error {
|
||||
return WriteXML(&Atom{f}, w)
|
||||
}
|
||||
|
||||
// creates an Rss representation of this feed
|
||||
func (f *Feed) ToRss() (string, error) {
|
||||
r := &Rss{f}
|
||||
return ToXML(r)
|
||||
}
|
||||
|
||||
// Writes an RSS representation of this feed to the writer.
|
||||
func (f *Feed) WriteRss(w io.Writer) error {
|
||||
return WriteXML(&Rss{f}, w)
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package feeds
|
||||
|
||||
// rss support
|
||||
// validation done according to spec here:
|
||||
// http://cyber.law.harvard.edu/rss/rss.html
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// private wrapper around the RssFeed which gives us the <rss>..</rss> xml
|
||||
type rssFeedXml struct {
|
||||
XMLName xml.Name `xml:"rss"`
|
||||
Version string `xml:"version,attr"`
|
||||
Channel *RssFeed
|
||||
}
|
||||
|
||||
type RssImage struct {
|
||||
XMLName xml.Name `xml:"image"`
|
||||
Url string `xml:"url"`
|
||||
Title string `xml:"title"`
|
||||
Link string `xml:"link"`
|
||||
Width int `xml:"width,omitempty"`
|
||||
Height int `xml:"height,omitempty"`
|
||||
}
|
||||
|
||||
type RssTextInput struct {
|
||||
XMLName xml.Name `xml:"textInput"`
|
||||
Title string `xml:"title"`
|
||||
Description string `xml:"description"`
|
||||
Name string `xml:"name"`
|
||||
Link string `xml:"link"`
|
||||
}
|
||||
|
||||
type RssFeed struct {
|
||||
XMLName xml.Name `xml:"channel"`
|
||||
Title string `xml:"title"` // required
|
||||
Link string `xml:"link"` // required
|
||||
Description string `xml:"description"` // required
|
||||
Language string `xml:"language,omitempty"`
|
||||
Copyright string `xml:"copyright,omitempty"`
|
||||
ManagingEditor string `xml:"managingEditor,omitempty"` // Author used
|
||||
WebMaster string `xml:"webMaster,omitempty"`
|
||||
PubDate string `xml:"pubDate,omitempty"` // created or updated
|
||||
LastBuildDate string `xml:"lastBuildDate,omitempty"` // updated used
|
||||
Category string `xml:"category,omitempty"`
|
||||
Generator string `xml:"generator,omitempty"`
|
||||
Docs string `xml:"docs,omitempty"`
|
||||
Cloud string `xml:"cloud,omitempty"`
|
||||
Ttl int `xml:"ttl,omitempty"`
|
||||
Rating string `xml:"rating,omitempty"`
|
||||
SkipHours string `xml:"skipHours,omitempty"`
|
||||
SkipDays string `xml:"skipDays,omitempty"`
|
||||
Image *RssImage
|
||||
TextInput *RssTextInput
|
||||
Items []*RssItem
|
||||
}
|
||||
|
||||
type RssItem struct {
|
||||
XMLName xml.Name `xml:"item"`
|
||||
Title string `xml:"title"` // required
|
||||
Link string `xml:"link"` // required
|
||||
Description string `xml:"description"` // required
|
||||
Author string `xml:"author,omitempty"`
|
||||
Category string `xml:"category,omitempty"`
|
||||
Comments string `xml:"comments,omitempty"`
|
||||
Enclosure *RssEnclosure
|
||||
Guid string `xml:"guid,omitempty"` // Id used
|
||||
PubDate string `xml:"pubDate,omitempty"` // created or updated
|
||||
Source string `xml:"source,omitempty"`
|
||||
}
|
||||
|
||||
type RssEnclosure struct {
|
||||
//RSS 2.0 <enclosure url="http://example.com/file.mp3" length="123456789" type="audio/mpeg" />
|
||||
XMLName xml.Name `xml:"enclosure"`
|
||||
Url string `xml:"url,attr"`
|
||||
Length string `xml:"length,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
type Rss struct {
|
||||
*Feed
|
||||
}
|
||||
|
||||
// create a new RssItem with a generic Item struct's data
|
||||
func newRssItem(i *Item) *RssItem {
|
||||
item := &RssItem{
|
||||
Title: i.Title,
|
||||
Link: i.Link.Href,
|
||||
Description: i.Description,
|
||||
Guid: i.Id,
|
||||
PubDate: anyTimeFormat(time.RFC1123Z, i.Created, i.Updated),
|
||||
}
|
||||
|
||||
intLength, err := strconv.ParseInt(i.Link.Length, 10, 64)
|
||||
|
||||
if err == nil && (intLength > 0 || i.Link.Type != "") {
|
||||
item.Enclosure = &RssEnclosure{Url: i.Link.Href, Type: i.Link.Type, Length: i.Link.Length}
|
||||
}
|
||||
if i.Author != nil {
|
||||
item.Author = i.Author.Name
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
// create a new RssFeed with a generic Feed struct's data
|
||||
func (r *Rss) RssFeed() *RssFeed {
|
||||
pub := anyTimeFormat(time.RFC1123Z, r.Created, r.Updated)
|
||||
build := anyTimeFormat(time.RFC1123Z, r.Updated)
|
||||
author := ""
|
||||
if r.Author != nil {
|
||||
author = r.Author.Email
|
||||
if len(r.Author.Name) > 0 {
|
||||
author = fmt.Sprintf("%s (%s)", r.Author.Email, r.Author.Name)
|
||||
}
|
||||
}
|
||||
|
||||
channel := &RssFeed{
|
||||
Title: r.Title,
|
||||
Link: r.Link.Href,
|
||||
Description: r.Description,
|
||||
ManagingEditor: author,
|
||||
PubDate: pub,
|
||||
LastBuildDate: build,
|
||||
Copyright: r.Copyright,
|
||||
}
|
||||
for _, i := range r.Items {
|
||||
channel.Items = append(channel.Items, newRssItem(i))
|
||||
}
|
||||
return channel
|
||||
}
|
||||
|
||||
// return an XML-Ready object for an Rss object
|
||||
func (r *Rss) FeedXml() interface{} {
|
||||
// only generate version 2.0 feeds for now
|
||||
return r.RssFeed().FeedXml()
|
||||
|
||||
}
|
||||
|
||||
// return an XML-ready object for an RssFeed object
|
||||
func (r *RssFeed) FeedXml() interface{} {
|
||||
return &rssFeedXml{Version: "2.0", Channel: r}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package feeds
|
||||
|
||||
// relevant bits from https://github.com/abneptis/GoUUID/blob/master/uuid.go
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type UUID [16]byte
|
||||
|
||||
// create a new uuid v4
|
||||
func NewUUID() *UUID {
|
||||
u := &UUID{}
|
||||
_, err := rand.Read(u[:16])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
u[8] = (u[8] | 0x80) & 0xBf
|
||||
u[6] = (u[6] | 0x40) & 0x4f
|
||||
return u
|
||||
}
|
||||
|
||||
func (u *UUID) String() string {
|
||||
return fmt.Sprintf("%x-%x-%x-%x-%x", u[:4], u[4:6], u[6:8], u[8:10], u[10:])
|
||||
}
|
|
@ -0,0 +1,269 @@
|
|||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// and the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required the errors.WithStack and errors.WithMessage
|
||||
// functions destructure errors.Wrap into its component operations of annotating
|
||||
// an error with a stack trace and an a message, respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error which does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// causer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface.
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// Where errors.StackTrace is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// stackTracer interface is not exported by this package, but is considered a part
|
||||
// of stable public API.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is call, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s path of source file relative to the compile time GOPATH
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
}
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
||||
|
||||
func trimGOPATH(name, file string) string {
|
||||
// Here we want to get the source file path relative to the compile time
|
||||
// GOPATH. As of Go 1.6.x there is no direct way to know the compiled
|
||||
// GOPATH at runtime, but we can infer the number of path segments in the
|
||||
// GOPATH. We note that fn.Name() returns the function name qualified by
|
||||
// the import path, which does not include the GOPATH. Thus we can trim
|
||||
// segments from the beginning of the file path until the number of path
|
||||
// separators remaining is one more than the number of path separators in
|
||||
// the function name. For example, given:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// file /home/user/src/pkg/sub/file.go
|
||||
// fn.Name() pkg/sub.Type.Method
|
||||
//
|
||||
// We want to produce:
|
||||
//
|
||||
// pkg/sub/file.go
|
||||
//
|
||||
// From this we can easily see that fn.Name() has one less path separator
|
||||
// than our desired output. We count separators from the end of the file
|
||||
// path until it finds two more than in the function name and then move
|
||||
// one character forward to preserve the initial path segment without a
|
||||
// leading separator.
|
||||
const sep = "/"
|
||||
goal := strings.Count(name, sep) + 2
|
||||
i := len(file)
|
||||
for n := 0; n < goal; n++ {
|
||||
i = strings.LastIndex(file[:i], sep)
|
||||
if i == -1 {
|
||||
// not enough separators found, set i so that the slice expression
|
||||
// below leaves file unmodified
|
||||
i = -len(sep)
|
||||
break
|
||||
}
|
||||
}
|
||||
// get back to 0 or trim the leading separator
|
||||
file = file[i+len(sep):]
|
||||
return file
|
||||
}
|
Loading…
Reference in New Issue