rip out go code
This commit is contained in:
parent
d8aea8424b
commit
0aad5be271
|
@ -1,41 +0,0 @@
|
|||
name: "Code scanning - action"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '0 18 * * 6'
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
fetch-depth: 2
|
||||
|
||||
# If this run was triggered by a pull request event, then checkout
|
||||
# the head of the pull request instead of the merge commit.
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
# Override language selection by uncommenting this and choosing your languages
|
||||
with:
|
||||
languages: go
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
|
@ -1,21 +0,0 @@
|
|||
name: Go
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.14
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.14
|
||||
id: go
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v1
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
env:
|
||||
GO111MODULE: on
|
||||
GOPROXY: https://cache.greedo.xeserv.us
|
|
@ -1,25 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ClackSet []string
|
||||
|
||||
func (cs ClackSet) Name() string {
|
||||
return "GNU " + cs[rand.Intn(len(cs))]
|
||||
}
|
||||
|
||||
func (cs ClackSet) Middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("X-Clacks-Overhead", cs.Name())
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().Unix())
|
||||
}
|
245
cmd/site/html.go
245
cmd/site/html.go
|
@ -1,245 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"christine.website/cmd/site/internal"
|
||||
"christine.website/cmd/site/internal/blog"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"within.website/ln"
|
||||
"within.website/ln/opname"
|
||||
)
|
||||
|
||||
var (
|
||||
templateRenderTime = promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "template_render_time",
|
||||
Help: "Template render time in nanoseconds",
|
||||
}, []string{"name"})
|
||||
)
|
||||
|
||||
func logTemplateTime(ctx context.Context, name string, f ln.F, from time.Time) {
|
||||
dur := time.Since(from)
|
||||
templateRenderTime.With(prometheus.Labels{"name": name}).Observe(float64(dur))
|
||||
ln.Log(ctx, f, ln.F{"dur": dur, "name": name})
|
||||
}
|
||||
|
||||
func (s *Site) renderTemplatePage(templateFname string, data interface{}) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := opname.With(r.Context(), "renderTemplatePage")
|
||||
fetag := "W/" + internal.Hash(templateFname, etag) + "-1"
|
||||
|
||||
f := ln.F{"etag": fetag, "if_none_match": r.Header.Get("If-None-Match")}
|
||||
|
||||
if r.Header.Get("If-None-Match") == fetag {
|
||||
http.Error(w, "Cached data OK", http.StatusNotModified)
|
||||
ln.Log(ctx, f, ln.Info("Cache hit"))
|
||||
return
|
||||
}
|
||||
|
||||
defer logTemplateTime(ctx, templateFname, f, time.Now())
|
||||
|
||||
var t *template.Template
|
||||
var err error
|
||||
|
||||
t, err = template.ParseFiles("templates/base.html", "templates/"+templateFname)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
ln.Error(ctx, err, ln.F{"action": "renderTemplatePage", "page": templateFname})
|
||||
fmt.Fprintf(w, "error: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("ETag", fetag)
|
||||
w.Header().Set("Cache-Control", "max-age=432000")
|
||||
|
||||
err = t.Execute(w, data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var postView = promauto.NewCounterVec(prometheus.CounterOpts{
|
||||
Name: "posts_viewed",
|
||||
Help: "The number of views per post or talk",
|
||||
}, []string{"base"})
|
||||
|
||||
func (s *Site) listSeries(w http.ResponseWriter, r *http.Request) {
|
||||
s.renderTemplatePage("series.html", s.Series).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (s *Site) showSeries(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RequestURI == "/blog/series/" {
|
||||
http.Redirect(w, r, "/blog/series", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
series := filepath.Base(r.URL.Path)
|
||||
var posts []blog.Post
|
||||
|
||||
for _, p := range s.Posts {
|
||||
if p.Series == series {
|
||||
posts = append(posts, p)
|
||||
}
|
||||
}
|
||||
|
||||
s.renderTemplatePage("serieslist.html", struct {
|
||||
Name string
|
||||
Posts []blog.Post
|
||||
}{
|
||||
Name: series,
|
||||
Posts: posts,
|
||||
}).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (s *Site) showGallery(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RequestURI == "/gallery/" {
|
||||
http.Redirect(w, r, "/gallery", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
cmp := r.URL.Path[1:]
|
||||
var p blog.Post
|
||||
var found bool
|
||||
for _, pst := range s.Gallery {
|
||||
if pst.Link == cmp {
|
||||
p = pst
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
s.renderTemplatePage("error.html", "no such post found: "+r.RequestURI).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var tags string
|
||||
if len(p.Tags) != 0 {
|
||||
for _, t := range p.Tags {
|
||||
tags = tags + " #" + strings.ReplaceAll(t, "-", "")
|
||||
}
|
||||
}
|
||||
|
||||
h := s.renderTemplatePage("gallerypost.html", struct {
|
||||
Title string
|
||||
Link string
|
||||
BodyHTML template.HTML
|
||||
Date string
|
||||
Tags string
|
||||
Image string
|
||||
}{
|
||||
Title: p.Title,
|
||||
Link: p.Link,
|
||||
BodyHTML: p.BodyHTML,
|
||||
Date: internal.IOS13Detri(p.Date),
|
||||
Tags: tags,
|
||||
Image: p.ImageURL,
|
||||
})
|
||||
|
||||
if h == nil {
|
||||
panic("how did we get here?")
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
postView.With(prometheus.Labels{"base": filepath.Base(p.Link)}).Inc()
|
||||
}
|
||||
|
||||
func (s *Site) showTalk(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RequestURI == "/talks/" {
|
||||
http.Redirect(w, r, "/talks", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
cmp := r.URL.Path[1:]
|
||||
var p blog.Post
|
||||
var found bool
|
||||
for _, pst := range s.Talks {
|
||||
if pst.Link == cmp {
|
||||
p = pst
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
s.renderTemplatePage("error.html", "no such post found: "+r.RequestURI).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
h := s.renderTemplatePage("talkpost.html", struct {
|
||||
Title string
|
||||
Link string
|
||||
BodyHTML template.HTML
|
||||
Date string
|
||||
SlidesLink string
|
||||
}{
|
||||
Title: p.Title,
|
||||
Link: p.Link,
|
||||
BodyHTML: p.BodyHTML,
|
||||
Date: internal.IOS13Detri(p.Date),
|
||||
SlidesLink: p.SlidesLink,
|
||||
})
|
||||
|
||||
if h == nil {
|
||||
panic("how did we get here?")
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
postView.With(prometheus.Labels{"base": filepath.Base(p.Link)}).Inc()
|
||||
}
|
||||
|
||||
func (s *Site) showPost(w http.ResponseWriter, r *http.Request) {
|
||||
if r.RequestURI == "/blog/" {
|
||||
http.Redirect(w, r, "/blog", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
cmp := r.URL.Path[1:]
|
||||
var p blog.Post
|
||||
var found bool
|
||||
for _, pst := range s.Posts {
|
||||
if pst.Link == cmp {
|
||||
p = pst
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
s.renderTemplatePage("error.html", "no such post found: "+r.RequestURI).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var tags string
|
||||
|
||||
if len(p.Tags) != 0 {
|
||||
for _, t := range p.Tags {
|
||||
tags = tags + " #" + strings.ReplaceAll(t, "-", "")
|
||||
}
|
||||
}
|
||||
|
||||
s.renderTemplatePage("blogpost.html", struct {
|
||||
Title string
|
||||
Link string
|
||||
BodyHTML template.HTML
|
||||
Date string
|
||||
Series, SeriesTag string
|
||||
Tags string
|
||||
}{
|
||||
Title: p.Title,
|
||||
Link: p.Link,
|
||||
BodyHTML: p.BodyHTML,
|
||||
Date: internal.IOS13Detri(p.Date),
|
||||
Series: p.Series,
|
||||
SeriesTag: strings.ReplaceAll(p.Series, "-", ""),
|
||||
Tags: tags,
|
||||
}).ServeHTTP(w, r)
|
||||
postView.With(prometheus.Labels{"base": filepath.Base(p.Link)}).Inc()
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
package blog
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"christine.website/cmd/site/internal/front"
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
// Post is a single blogpost.
|
||||
type Post struct {
|
||||
Title string `json:"title"`
|
||||
Link string `json:"link"`
|
||||
Summary string `json:"summary,omitifempty"`
|
||||
Body string `json:"-"`
|
||||
BodyHTML template.HTML `json:"body"`
|
||||
Series string `json:"series"`
|
||||
Tags []string `json:"tags"`
|
||||
SlidesLink string `json:"slides_link"`
|
||||
ImageURL string `json:"image_url"`
|
||||
ThumbURL string `json:"thumb_url"`
|
||||
Date time.Time
|
||||
DateString string `json:"date"`
|
||||
}
|
||||
|
||||
// Posts implements sort.Interface for a slice of Post objects.
|
||||
type Posts []Post
|
||||
|
||||
func (p Posts) Series() []string {
|
||||
names := map[string]struct{}{}
|
||||
|
||||
for _, ps := range p {
|
||||
if ps.Series != "" {
|
||||
names[ps.Series] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
var result []string
|
||||
|
||||
for name := range names {
|
||||
result = append(result, name)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (p Posts) Len() int { return len(p) }
|
||||
func (p Posts) Less(i, j int) bool {
|
||||
iDate := p[i].Date
|
||||
jDate := p[j].Date
|
||||
|
||||
return iDate.Unix() < jDate.Unix()
|
||||
}
|
||||
func (p Posts) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
// LoadPosts loads posts for a given directory.
|
||||
func LoadPosts(path string, prepend string) (Posts, error) {
|
||||
type postFM struct {
|
||||
Title string
|
||||
Date string
|
||||
Series string
|
||||
Tags []string
|
||||
SlidesLink string `yaml:"slides_link"`
|
||||
Image string
|
||||
Thumb string
|
||||
Show string
|
||||
}
|
||||
var result Posts
|
||||
|
||||
err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
fin, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fin.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(fin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var fm postFM
|
||||
remaining, err := front.Unmarshal(content, &fm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output := blackfriday.Run(remaining)
|
||||
|
||||
const timeFormat = `2006-01-02`
|
||||
date, err := time.Parse(timeFormat, fm.Date)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fname := filepath.Base(path)
|
||||
fname = strings.TrimSuffix(fname, filepath.Ext(fname))
|
||||
|
||||
p := Post{
|
||||
Title: fm.Title,
|
||||
Date: date,
|
||||
DateString: fm.Date,
|
||||
Link: filepath.Join(prepend, fname),
|
||||
Body: string(remaining),
|
||||
BodyHTML: template.HTML(output),
|
||||
SlidesLink: fm.SlidesLink,
|
||||
Series: fm.Series,
|
||||
Tags: fm.Tags,
|
||||
ImageURL: fm.Image,
|
||||
ThumbURL: fm.Thumb,
|
||||
}
|
||||
result = append(result, p)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Sort(sort.Reverse(result))
|
||||
|
||||
return result, nil
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package blog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadPosts(t *testing.T) {
|
||||
posts, err := LoadPosts("../../../../blog", "blog")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, post := range posts {
|
||||
t.Run(post.Link, post.test)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadTalks(t *testing.T) {
|
||||
talks, err := LoadPosts("../../../../talks", "talks")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, talk := range talks {
|
||||
t.Run(talk.Link, talk.test)
|
||||
if talk.SlidesLink == "" {
|
||||
t.Errorf("talk %s (%s) doesn't have a slides link", talk.Title, talk.DateString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadGallery(t *testing.T) {
|
||||
gallery, err := LoadPosts("../../../../gallery", "gallery")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, art := range gallery {
|
||||
t.Run(art.Link, art.test)
|
||||
if art.ImageURL == "" {
|
||||
t.Errorf("art %s (%s) doesn't have an image link", art.Title, art.DateString)
|
||||
}
|
||||
if art.ThumbURL == "" {
|
||||
t.Errorf("art %s (%s) doesn't have a thumbnail link", art.Title, art.DateString)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (p Post) test(t *testing.T) {
|
||||
if p.Title == "" {
|
||||
t.Error("no post title")
|
||||
}
|
||||
|
||||
if p.DateString == "" {
|
||||
t.Error("no date")
|
||||
}
|
||||
|
||||
if p.Link == "" {
|
||||
t.Error("no link")
|
||||
}
|
||||
|
||||
if p.Body == "" {
|
||||
t.Error("no body")
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package internal
|
||||
|
||||
import "time"
|
||||
|
||||
const iOS13DetriFormat = `2006 M1 2`
|
||||
|
||||
// IOS13Detri formats a datestamp like iOS 13 does with the Lojban locale.
|
||||
func IOS13Detri(t time.Time) string {
|
||||
return t.Format(iOS13DetriFormat)
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIOS13Detri(t *testing.T) {
|
||||
cases := []struct {
|
||||
in time.Time
|
||||
out string
|
||||
}{
|
||||
{
|
||||
in: time.Date(2019, time.March, 30, 0, 0, 0, 0, time.FixedZone("UTC", 0)),
|
||||
out: "2019 M3 30",
|
||||
},
|
||||
}
|
||||
|
||||
for _, cs := range cases {
|
||||
t.Run(fmt.Sprintf("%s -> %s", cs.in.Format(time.RFC3339), cs.out), func(t *testing.T) {
|
||||
result := IOS13Detri(cs.in)
|
||||
if result != cs.out {
|
||||
t.Fatalf("wanted: %s, got: %s", cs.out, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
Copyright (c) 2017 TJ Holowaychuk <tj@vision-media.ca>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
|
@ -1,24 +0,0 @@
|
|||
// Package front provides YAML frontmatter unmarshalling.
|
||||
package front
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Delimiter.
|
||||
var delim = []byte("---")
|
||||
|
||||
// Unmarshal parses YAML frontmatter and returns the content. When no
|
||||
// frontmatter delimiters are present the original content is returned.
|
||||
func Unmarshal(b []byte, v interface{}) (content []byte, err error) {
|
||||
if !bytes.HasPrefix(b, delim) {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
parts := bytes.SplitN(b, delim, 3)
|
||||
content = parts[2]
|
||||
err = yaml.Unmarshal(parts[1], v)
|
||||
return
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package front_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"christine.website/cmd/site/internal/front"
|
||||
)
|
||||
|
||||
var markdown = []byte(`---
|
||||
title: Ferrets
|
||||
authors:
|
||||
- Tobi
|
||||
- Loki
|
||||
- Jane
|
||||
---
|
||||
Some content here, so
|
||||
interesting, you just
|
||||
want to keep reading.`)
|
||||
|
||||
type article struct {
|
||||
Title string
|
||||
Authors []string
|
||||
}
|
||||
|
||||
func Example() {
|
||||
var a article
|
||||
|
||||
content, err := front.Unmarshal(markdown, &a)
|
||||
if err != nil {
|
||||
log.Fatalf("error unmarshalling: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%#v\n", a)
|
||||
fmt.Printf("%s\n", string(content))
|
||||
// Output:
|
||||
// front_test.article{Title:"Ferrets", Authors:[]string{"Tobi", "Loki", "Jane"}}
|
||||
//
|
||||
// Some content here, so
|
||||
// interesting, you just
|
||||
// want to keep reading.
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package internal
|
||||
|
||||
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)
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
var (
|
||||
requestCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "handler_requests_total",
|
||||
Help: "Total number of request/responses by HTTP status code.",
|
||||
}, []string{"handler", "code"})
|
||||
|
||||
requestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "handler_request_duration",
|
||||
Help: "Handler request duration.",
|
||||
}, []string{"handler", "method"})
|
||||
|
||||
requestInFlight = prometheus.NewGaugeVec(prometheus.GaugeOpts{
|
||||
Name: "handler_requests_in_flight",
|
||||
Help: "Current number of requests being served.",
|
||||
}, []string{"handler"})
|
||||
)
|
||||
|
||||
func init() {
|
||||
_ = prometheus.Register(requestCounter)
|
||||
_ = prometheus.Register(requestDuration)
|
||||
_ = prometheus.Register(requestInFlight)
|
||||
}
|
||||
|
||||
// Metrics captures request duration, request count and in-flight request count
|
||||
// metrics for HTTP handlers. The family field is used to discriminate handlers.
|
||||
func Metrics(family string, next http.Handler) http.Handler {
|
||||
return promhttp.InstrumentHandlerDuration(
|
||||
requestDuration.MustCurryWith(prometheus.Labels{"handler": family}),
|
||||
promhttp.InstrumentHandlerCounter(requestCounter.MustCurryWith(prometheus.Labels{"handler": family}),
|
||||
promhttp.InstrumentHandlerInFlight(requestInFlight.With(prometheus.Labels{"handler": family}), next),
|
||||
),
|
||||
)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/celrenheit/sandflake"
|
||||
"within.website/ln"
|
||||
)
|
||||
|
||||
// RequestID appends a unique (sandflake) request ID to each request's
|
||||
// X-Request-Id header field, much like Heroku's router does.
|
||||
func RequestID(next http.Handler) http.Handler {
|
||||
var g sandflake.Generator
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
id := g.Next().String()
|
||||
|
||||
if rid := r.Header.Get("X-Request-Id"); rid != "" {
|
||||
id = rid + "," + id
|
||||
}
|
||||
|
||||
ctx := ln.WithF(r.Context(), ln.F{
|
||||
"request_id": id,
|
||||
})
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
w.Header().Set("X-Request-Id", id)
|
||||
r.Header.Set("X-Request-Id", id)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
296
cmd/site/main.go
296
cmd/site/main.go
|
@ -1,296 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"christine.website/cmd/site/internal/blog"
|
||||
"christine.website/cmd/site/internal/middleware"
|
||||
"christine.website/jsonfeed"
|
||||
"github.com/gorilla/feeds"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
"github.com/povilasv/prommod"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
blackfriday "github.com/russross/blackfriday"
|
||||
"github.com/sebest/xff"
|
||||
"github.com/snabb/sitemap"
|
||||
"within.website/ln"
|
||||
"within.website/ln/ex"
|
||||
"within.website/ln/opname"
|
||||
)
|
||||
|
||||
var port = os.Getenv("PORT")
|
||||
|
||||
func main() {
|
||||
if port == "" {
|
||||
port = "29384"
|
||||
}
|
||||
|
||||
ctx := ln.WithF(opname.With(context.Background(), "main"), ln.F{
|
||||
"port": port,
|
||||
"git_rev": gitRev,
|
||||
})
|
||||
|
||||
_ = prometheus.Register(prommod.NewCollector("christine"))
|
||||
|
||||
s, err := Build()
|
||||
if err != nil {
|
||||
ln.FatalErr(ctx, err, ln.Action("Build"))
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/.within/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "OK", http.StatusOK)
|
||||
})
|
||||
mux.Handle("/", s)
|
||||
|
||||
ln.Log(ctx, ln.Action("http_listening"))
|
||||
ln.FatalErr(ctx, http.ListenAndServe(":"+port, mux))
|
||||
}
|
||||
|
||||
// Site is the parent object for https://christine.website's backend.
|
||||
type Site struct {
|
||||
Posts blog.Posts
|
||||
Talks blog.Posts
|
||||
Gallery blog.Posts
|
||||
Resume template.HTML
|
||||
Series []string
|
||||
SignalBoost []Person
|
||||
|
||||
clacks ClackSet
|
||||
patrons []string
|
||||
rssFeed *feeds.Feed
|
||||
jsonFeed *jsonfeed.Feed
|
||||
|
||||
mux *http.ServeMux
|
||||
xffmw *xff.XFF
|
||||
}
|
||||
|
||||
var gitRev = os.Getenv("GIT_REV")
|
||||
|
||||
func envOr(key, or string) string {
|
||||
if result, ok := os.LookupEnv(key); ok {
|
||||
return result
|
||||
}
|
||||
|
||||
return or
|
||||
}
|
||||
|
||||
func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := opname.With(r.Context(), "site.ServeHTTP")
|
||||
ctx = ln.WithF(ctx, ln.F{
|
||||
"user_agent": r.Header.Get("User-Agent"),
|
||||
})
|
||||
r = r.WithContext(ctx)
|
||||
if gitRev != "" {
|
||||
w.Header().Add("X-Git-Rev", gitRev)
|
||||
}
|
||||
|
||||
w.Header().Add("X-Hacker", "If you are reading this, check out /signalboost to find people for your team")
|
||||
|
||||
s.clacks.Middleware(
|
||||
middleware.RequestID(
|
||||
s.xffmw.Handler(
|
||||
ex.HTTPLog(s.mux),
|
||||
),
|
||||
),
|
||||
).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
var arbDate = time.Date(2020, time.May, 21, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// Build creates a new Site instance or fails.
|
||||
func Build() (*Site, error) {
|
||||
pc, err := NewPatreonClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pledges, err := GetPledges(pc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
people, err := loadPeople("./signalboost.dhall")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
smi := sitemap.New()
|
||||
smi.Add(&sitemap.URL{
|
||||
Loc: "https://christine.website/resume",
|
||||
LastMod: &arbDate,
|
||||
ChangeFreq: sitemap.Monthly,
|
||||
})
|
||||
|
||||
smi.Add(&sitemap.URL{
|
||||
Loc: "https://christine.website/contact",
|
||||
LastMod: &arbDate,
|
||||
ChangeFreq: sitemap.Monthly,
|
||||
})
|
||||
|
||||
smi.Add(&sitemap.URL{
|
||||
Loc: "https://christine.website/",
|
||||
LastMod: &arbDate,
|
||||
ChangeFreq: sitemap.Monthly,
|
||||
})
|
||||
|
||||
smi.Add(&sitemap.URL{
|
||||
Loc: "https://christine.website/patrons",
|
||||
LastMod: &arbDate,
|
||||
ChangeFreq: sitemap.Weekly,
|
||||
})
|
||||
|
||||
smi.Add(&sitemap.URL{
|
||||
Loc: "https://christine.website/blog",
|
||||
LastMod: &arbDate,
|
||||
ChangeFreq: sitemap.Weekly,
|
||||
})
|
||||
|
||||
xffmw, err := xff.Default()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &Site{
|
||||
rssFeed: &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.",
|
||||
},
|
||||
jsonFeed: &jsonfeed.Feed{
|
||||
Version: jsonfeed.CurrentVersion,
|
||||
Title: "Christine Dodrill's Blog",
|
||||
HomePageURL: "https://christine.website",
|
||||
FeedURL: "https://christine.website/blog.json",
|
||||
Description: "My blog posts and rants about various technology things.",
|
||||
UserComment: "This is a JSON feed of my blogposts. For more information read: https://jsonfeed.org/version/1",
|
||||
Icon: icon,
|
||||
Favicon: icon,
|
||||
Author: jsonfeed.Author{
|
||||
Name: "Christine Dodrill",
|
||||
Avatar: icon,
|
||||
},
|
||||
},
|
||||
mux: http.NewServeMux(),
|
||||
xffmw: xffmw,
|
||||
|
||||
clacks: ClackSet(strings.Split(envOr("CLACK_SET", "Ashlynn"), ",")),
|
||||
patrons: pledges,
|
||||
SignalBoost: people,
|
||||
}
|
||||
|
||||
posts, err := blog.LoadPosts("./blog/", "blog")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Posts = posts
|
||||
s.Series = posts.Series()
|
||||
sort.Strings(s.Series)
|
||||
|
||||
talks, err := blog.LoadPosts("./talks", "talks")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Talks = talks
|
||||
|
||||
gallery, err := blog.LoadPosts("./gallery", "gallery")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Gallery = gallery
|
||||
|
||||
var everything blog.Posts
|
||||
everything = append(everything, posts...)
|
||||
everything = append(everything, talks...)
|
||||
everything = append(everything, gallery...)
|
||||
|
||||
sort.Sort(sort.Reverse(everything))
|
||||
|
||||
resumeData, err := ioutil.ReadFile("./static/resume/resume.md")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.Resume = template.HTML(blackfriday.Run(resumeData))
|
||||
|
||||
for _, item := range everything {
|
||||
s.rssFeed.Items = append(s.rssFeed.Items, &feeds.Item{
|
||||
Title: item.Title,
|
||||
Link: &feeds.Link{Href: "https://christine.website/" + item.Link},
|
||||
Description: item.Summary,
|
||||
Created: item.Date,
|
||||
Content: string(item.BodyHTML),
|
||||
})
|
||||
|
||||
s.jsonFeed.Items = append(s.jsonFeed.Items, jsonfeed.Item{
|
||||
ID: "https://christine.website/" + item.Link,
|
||||
URL: "https://christine.website/" + item.Link,
|
||||
Title: item.Title,
|
||||
DatePublished: item.Date,
|
||||
ContentHTML: string(item.BodyHTML),
|
||||
Tags: item.Tags,
|
||||
})
|
||||
|
||||
smi.Add(&sitemap.URL{
|
||||
Loc: "https://christine.website/" + item.Link,
|
||||
LastMod: &item.Date,
|
||||
ChangeFreq: sitemap.Monthly,
|
||||
})
|
||||
}
|
||||
|
||||
// Add HTTP routes here
|
||||
s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
s.renderTemplatePage("error.html", "can't find "+r.URL.Path).ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
s.renderTemplatePage("index.html", nil).ServeHTTP(w, r)
|
||||
})
|
||||
s.mux.Handle("/metrics", promhttp.Handler())
|
||||
s.mux.Handle("/feeds", middleware.Metrics("feeds", s.renderTemplatePage("feeds.html", nil)))
|
||||
s.mux.Handle("/patrons", middleware.Metrics("patrons", s.renderTemplatePage("patrons.html", s.patrons)))
|
||||
s.mux.Handle("/signalboost", middleware.Metrics("signalboost", s.renderTemplatePage("signalboost.html", s.SignalBoost)))
|
||||
s.mux.Handle("/resume", middleware.Metrics("resume", s.renderTemplatePage("resume.html", s.Resume)))
|
||||
s.mux.Handle("/blog", middleware.Metrics("blog", s.renderTemplatePage("blogindex.html", s.Posts)))
|
||||
s.mux.Handle("/talks", middleware.Metrics("talks", s.renderTemplatePage("talkindex.html", s.Talks)))
|
||||
s.mux.Handle("/gallery", middleware.Metrics("gallery", s.renderTemplatePage("galleryindex.html", s.Gallery)))
|
||||
s.mux.Handle("/contact", middleware.Metrics("contact", s.renderTemplatePage("contact.html", nil)))
|
||||
s.mux.Handle("/blog.rss", middleware.Metrics("blog.rss", http.HandlerFunc(s.createFeed)))
|
||||
s.mux.Handle("/blog.atom", middleware.Metrics("blog.atom", http.HandlerFunc(s.createAtom)))
|
||||
s.mux.Handle("/blog.json", middleware.Metrics("blog.json", http.HandlerFunc(s.createJSONFeed)))
|
||||
s.mux.Handle("/blog/", middleware.Metrics("blogpost", http.HandlerFunc(s.showPost)))
|
||||
s.mux.Handle("/blog/series", http.HandlerFunc(s.listSeries))
|
||||
s.mux.Handle("/blog/series/", http.HandlerFunc(s.showSeries))
|
||||
s.mux.Handle("/talks/", middleware.Metrics("talks", http.HandlerFunc(s.showTalk)))
|
||||
s.mux.Handle("/gallery/", middleware.Metrics("gallery", http.HandlerFunc(s.showGallery)))
|
||||
s.mux.Handle("/css/", http.FileServer(http.Dir(".")))
|
||||
s.mux.Handle("/static/", http.FileServer(http.Dir(".")))
|
||||
s.mux.HandleFunc("/sw.js", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "./static/js/sw.js")
|
||||
})
|
||||
s.mux.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "./static/robots.txt")
|
||||
})
|
||||
s.mux.Handle("/sitemap.xml", middleware.Metrics("sitemap", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/xml")
|
||||
_, _ = smi.WriteTo(w)
|
||||
})))
|
||||
s.mux.HandleFunc("/api/pageview-timer", handlePageViewTimer)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
const icon = "https://christine.website/static/img/avatar.png"
|
|
@ -1,53 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"within.website/ln"
|
||||
)
|
||||
|
||||
var (
|
||||
readTimes = prometheus.NewHistogramVec(prometheus.HistogramOpts{
|
||||
Name: "blogpage_read_times",
|
||||
Help: "This tracks how much time people spend reading articles on my blog",
|
||||
}, []string{"path"})
|
||||
)
|
||||
|
||||
func init() {
|
||||
_ = prometheus.Register(readTimes)
|
||||
}
|
||||
|
||||
func handlePageViewTimer(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("DNT") == "1" {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
ln.Error(r.Context(), err, ln.Info("while reading data"))
|
||||
http.Error(w, "oopsie whoopsie uwu", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
r.Body.Close()
|
||||
|
||||
type metricsData struct {
|
||||
Path string `json:"path"`
|
||||
StartTime time.Time `json:"start_time"`
|
||||
EndTime time.Time `json:"end_time"`
|
||||
}
|
||||
var md metricsData
|
||||
err = json.Unmarshal(data, &md)
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
diff := md.EndTime.Sub(md.StartTime).Seconds()
|
||||
|
||||
readTimes.WithLabelValues(md.Path).Observe(float64(diff))
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/mxpv/patreon-go"
|
||||
"golang.org/x/oauth2"
|
||||
"within.website/ln"
|
||||
)
|
||||
|
||||
func NewPatreonClient() (*patreon.Client, error) {
|
||||
for _, name := range []string{"CLIENT_ID", "CLIENT_SECRET", "ACCESS_TOKEN", "REFRESH_TOKEN"} {
|
||||
if os.Getenv("PATREON_"+name) == "" {
|
||||
return nil, fmt.Errorf("wanted envvar PATREON_%s", name)
|
||||
}
|
||||
}
|
||||
|
||||
config := oauth2.Config{
|
||||
ClientID: os.Getenv("PATREON_CLIENT_ID"),
|
||||
ClientSecret: os.Getenv("PATREON_CLIENT_SECRET"),
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: patreon.AuthorizationURL,
|
||||
TokenURL: patreon.AccessTokenURL,
|
||||
},
|
||||
Scopes: []string{"users", "campaigns", "pledges", "pledges-to-me", "my-campaign"},
|
||||
}
|
||||
|
||||
token := oauth2.Token{
|
||||
AccessToken: os.Getenv("PATREON_ACCESS_TOKEN"),
|
||||
RefreshToken: os.Getenv("PATREON_REFRESH_TOKEN"),
|
||||
// Must be non-nil, otherwise token will not be expired
|
||||
Expiry: time.Now().Add(90 * 24 * time.Hour),
|
||||
}
|
||||
|
||||
tc := config.Client(context.Background(), &token)
|
||||
|
||||
trans := tc.Transport
|
||||
tc.Transport = lnLoggingTransport{next: trans}
|
||||
client := patreon.NewClient(tc)
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func GetPledges(pc *patreon.Client) ([]string, error) {
|
||||
campaign, err := pc.FetchCampaign()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("campaign fetch error: %w", err)
|
||||
}
|
||||
|
||||
campaignID := campaign.Data[0].ID
|
||||
|
||||
cursor := ""
|
||||
var result []string
|
||||
|
||||
for {
|
||||
pledgesResponse, err := pc.FetchPledges(campaignID, patreon.WithPageSize(25), patreon.WithCursor(cursor))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users := make(map[string]*patreon.User)
|
||||
for _, item := range pledgesResponse.Included.Items {
|
||||
u, ok := item.(*patreon.User)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
users[u.ID] = u
|
||||
}
|
||||
|
||||
for _, pledge := range pledgesResponse.Data {
|
||||
pid := pledge.Relationships.Patron.Data.ID
|
||||
patronFullName := users[pid].Attributes.FullName
|
||||
|
||||
result = append(result, patronFullName)
|
||||
}
|
||||
|
||||
cursor = pledgesResponse.Links.Next
|
||||
if cursor == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type lnLoggingTransport struct{ next http.RoundTripper }
|
||||
|
||||
func (l lnLoggingTransport) RoundTrip(r *http.Request) (*http.Response, error) {
|
||||
ctx := r.Context()
|
||||
f := ln.F{
|
||||
"url": r.URL.String(),
|
||||
"has_token": r.Header.Get("Authorization") != "",
|
||||
}
|
||||
|
||||
resp, err := l.next.RoundTrip(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f["status"] = resp.Status
|
||||
|
||||
ln.Log(ctx, f)
|
||||
|
||||
return resp, nil
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"christine.website/cmd/site/internal"
|
||||
"within.website/ln"
|
||||
"within.website/ln/opname"
|
||||
)
|
||||
|
||||
var bootTime = time.Now()
|
||||
var etag = internal.Hash(bootTime.String(), IncrediblySecureSalt)
|
||||
|
||||
// IncrediblySecureSalt *******
|
||||
const IncrediblySecureSalt = "hunter2"
|
||||
|
||||
func (s *Site) createFeed(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := opname.With(r.Context(), "rss-feed")
|
||||
fetag := "W/" + internal.Hash(bootTime.String(), IncrediblySecureSalt)
|
||||
w.Header().Set("ETag", fetag)
|
||||
|
||||
if r.Header.Get("If-None-Match") == fetag {
|
||||
http.Error(w, "Cached data OK", http.StatusNotModified)
|
||||
ln.Log(ctx, ln.Info("cache hit"))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/rss+xml")
|
||||
err := s.rssFeed.WriteRss(w)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
ln.Error(r.Context(), err, ln.F{
|
||||
"remote_addr": r.RemoteAddr,
|
||||
"action": "generating_rss",
|
||||
"uri": r.RequestURI,
|
||||
"host": r.Host,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Site) createAtom(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := opname.With(r.Context(), "atom-feed")
|
||||
fetag := "W/" + internal.Hash(bootTime.String(), IncrediblySecureSalt)
|
||||
w.Header().Set("ETag", fetag)
|
||||
|
||||
if r.Header.Get("If-None-Match") == fetag {
|
||||
http.Error(w, "Cached data OK", http.StatusNotModified)
|
||||
ln.Log(ctx, ln.Info("cache hit"))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/atom+xml")
|
||||
err := s.rssFeed.WriteAtom(w)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
ln.Error(ctx, err, ln.F{
|
||||
"remote_addr": r.RemoteAddr,
|
||||
"action": "generating_atom",
|
||||
"uri": r.RequestURI,
|
||||
"host": r.Host,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Site) createJSONFeed(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := opname.With(r.Context(), "atom-feed")
|
||||
fetag := "W/" + internal.Hash(bootTime.String(), IncrediblySecureSalt)
|
||||
w.Header().Set("ETag", fetag)
|
||||
|
||||
if r.Header.Get("If-None-Match") == fetag {
|
||||
http.Error(w, "Cached data OK", http.StatusNotModified)
|
||||
ln.Log(ctx, ln.Info("cache hit"))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
e := json.NewEncoder(w)
|
||||
e.SetIndent("", "\t")
|
||||
err := e.Encode(s.jsonFeed)
|
||||
if err != nil {
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
ln.Error(ctx, err, ln.F{
|
||||
"remote_addr": r.RemoteAddr,
|
||||
"action": "generating_jsonfeed",
|
||||
"uri": r.RequestURI,
|
||||
"host": r.Host,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/philandstuff/dhall-golang"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
Name string `dhall:"name"`
|
||||
GitLink string `dhall:"gitLink"`
|
||||
Twitter string `dhall:"twitter"`
|
||||
Tags []string `dhall:"tags"`
|
||||
}
|
||||
|
||||
func loadPeople(path string) ([]Person, error) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var people []Person
|
||||
err = dhall.Unmarshal(data, &people)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return people, nil
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLoadPeople(t *testing.T) {
|
||||
people, err := loadPeople("../../signalboost.dhall")
|
||||
if err != nil {t.Fatal(err)}
|
||||
|
||||
for _, person := range people {
|
||||
t.Run(person.Name, func(t *testing.T) {
|
||||
if person.Name == "" {
|
||||
t.Error("missing name")
|
||||
}
|
||||
|
||||
if len(person.Tags) == 0 {
|
||||
t.Error("missing tags")
|
||||
}
|
||||
|
||||
if person.Twitter == "" {
|
||||
t.Error("missing twitter")
|
||||
}
|
||||
|
||||
if person.GitLink == "" {
|
||||
t.Error("missing git link")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
21
go.mod
21
go.mod
|
@ -1,21 +0,0 @@
|
|||
module christine.website
|
||||
|
||||
require (
|
||||
github.com/celrenheit/sandflake v0.0.0-20190410195419-50a943690bc2
|
||||
github.com/gorilla/feeds v1.1.1
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/mxpv/patreon-go v0.0.0-20190917022727-646111f1d983
|
||||
github.com/philandstuff/dhall-golang v1.0.0
|
||||
github.com/povilasv/prommod v0.0.12
|
||||
github.com/prometheus/client_golang v1.7.1
|
||||
github.com/russross/blackfriday v2.0.0+incompatible
|
||||
github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
|
||||
github.com/snabb/sitemap v1.0.0
|
||||
github.com/stretchr/testify v1.6.1
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
within.website/ln v0.9.1
|
||||
)
|
||||
|
||||
go 1.13
|
188
go.sum
188
go.sum
|
@ -1,188 +0,0 @@
|
|||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/celrenheit/sandflake v0.0.0-20190410195419-50a943690bc2 h1:/BpnZPo/sk1vPlt62dLya5KCn7PN9ZBDrpTGlQzgUZI=
|
||||
github.com/celrenheit/sandflake v0.0.0-20190410195419-50a943690bc2/go.mod h1:7L8gY0+4GYeBc9TvqVuDUq7tXuM6Sj7llnt7HkVwWlQ=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
|
||||
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406 h1:+OUpk+IVvmKU0jivOVFGtOzA6U5AWFs8HE4DRzWLOUE=
|
||||
github.com/leanovate/gopter v0.2.5-0.20190402064358-634a59d12406/go.mod h1:gNcbPWNEWRe4lm+bycKqxUYoH5uoVje5SkOJ3uoLer8=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxpv/patreon-go v0.0.0-20190917022727-646111f1d983 h1:r32TFg+FHLnoF8PCqCQNp+R9EjMBuP62FXkD/Eqp9Us=
|
||||
github.com/mxpv/patreon-go v0.0.0-20190917022727-646111f1d983/go.mod h1:ksYjm2GAbGlgIP7jO9Q5/AdyE4MwwEbgQ+lFMx3hyiM=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/philandstuff/dhall-golang v1.0.0 h1:4iYE+OfVjpXtwB6todsw5w+rnBvAhufgpNzAo9K0ljw=
|
||||
github.com/philandstuff/dhall-golang v1.0.0/go.mod h1:nYfzcKjqq6UDCStpXV6UxRwD0HX9IK9z/MuHmHghbEY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/povilasv/prommod v0.0.12 h1:0bk9QJ7kD6SmSsk9MeHhz5Qe6OpQl11Fvo7cvvmNUQM=
|
||||
github.com/povilasv/prommod v0.0.12/go.mod h1:GnuK7wLoVBwZXj8bhbJNx/xFSldy7Q49A44RJKNM8XQ=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk=
|
||||
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35 h1:eajwn6K3weW5cd1ZXLu2sJ4pvwlBiCWY4uDejOr73gM=
|
||||
github.com/sebest/xff v0.0.0-20160910043805-6c115e0ffa35/go.mod h1:wozgYq9WEBQBaIJe4YZ0qTSFAMxmcwBhQH0fO0R34Z0=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/snabb/diagio v1.0.0 h1:kovhQ1rDXoEbmpf/T5N2sUp2iOdxEg+TcqzbYVHV2V0=
|
||||
github.com/snabb/diagio v1.0.0/go.mod h1:ZyGaWFhfBVqstGUw6laYetzeTwZ2xxVPqTALx1QQa1w=
|
||||
github.com/snabb/sitemap v1.0.0 h1:7vJeNPAaaj7fQSRS3WYuJHzUjdnhLdSLLpvVtnhbzC0=
|
||||
github.com/snabb/sitemap v1.0.0/go.mod h1:Id8uz1+WYdiNmSjEi4BIvL5UwNPYLsTHzRbjmDwNDzA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/ugorji/go v1.1.5-0.20190603013658-a2c9fa250719 h1:UW5IeyWBDAPQ+Qu1hT/lwtxL7pP3L+ETA8WuBvvvBWU=
|
||||
github.com/ugorji/go v1.1.5-0.20190603013658-a2c9fa250719/go.mod h1:RaaajvHwnCbhlqWLTIB78hyPWp24YUXhQ3YXM7Hg7os=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
within.website/ln v0.9.1 h1:Qi8IjeCnU43jXijKtr5qtcbjuiCVAudOIxqTim7svnc=
|
||||
within.website/ln v0.9.1/go.mod h1:I+Apk8qxMStNXTZdyDMqDqe6CB8Hn6+W/Gyf5QbY+2E=
|
|
@ -1,17 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.8.1
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- go test -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
363
jsonfeed/LICENSE
363
jsonfeed/LICENSE
|
@ -1,363 +0,0 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. "Contributor"
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the terms of
|
||||
a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
|
||||
means a work that combines Covered Software with other material, in a
|
||||
separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether
|
||||
at the time of the initial grant or subsequently, any and all of the
|
||||
rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the License,
|
||||
by the making, using, selling, offering for sale, having made, import,
|
||||
or transfer of either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights to
|
||||
grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter the
|
||||
recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
||||
limitations of liability) contained within the Source Code Form of the
|
||||
Covered Software, except that You may alter any license notices to the
|
||||
extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute,
|
||||
judicial order, or regulation then You must: (a) comply with the terms of
|
||||
this License to the maximum extent possible; and (b) describe the
|
||||
limitations and the code they affect. Such description must be placed in a
|
||||
text file included with all distributions of the Covered Software under
|
||||
this License. Except to the extent prohibited by statute or regulation,
|
||||
such description must be sufficiently detailed for a recipient of ordinary
|
||||
skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
||||
basis, if such Contributor fails to notify You of the non-compliance by
|
||||
some reasonable means prior to 60 days after You have come back into
|
||||
compliance. Moreover, Your grants from a particular Contributor are
|
||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
||||
non-compliance by some reasonable means, this is the first time You have
|
||||
received notice of non-compliance with this License from such
|
||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||
of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an "as is" basis,
|
||||
without warranty of any kind, either expressed, implied, or statutory,
|
||||
including, without limitation, warranties that the Covered Software is free
|
||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
The entire risk as to the quality and performance of the Covered Software
|
||||
is with You. Should any Covered Software prove defective in any respect,
|
||||
You (not any Contributor) assume the cost of any necessary servicing,
|
||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
||||
part of this License. No use of any Covered Software is authorized under
|
||||
this License except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from
|
||||
such party's negligence to the extent applicable law prohibits such
|
||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
||||
incidental or consequential damages, so this exclusion and limitation may
|
||||
not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts
|
||||
of a jurisdiction where the defendant maintains its principal place of
|
||||
business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
||||
in this Section shall prevent a party's ability to bring cross-claims or
|
||||
counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides that
|
||||
the language of a contract shall be construed against the drafter shall not
|
||||
be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses If You choose to distribute Source Code Form that is
|
||||
Incompatible With Secondary Licenses under the terms of this version of
|
||||
the License, the notice described in Exhibit B of this License must be
|
||||
attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file,
|
||||
then You may include the notice in a location (such as a LICENSE file in a
|
||||
relevant directory) where a recipient would be likely to look for such a
|
||||
notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
This Source Code Form is "Incompatible
|
||||
With Secondary Licenses", as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# JSONFeed - Go Package to parse JSON Feed streams
|
||||
|
||||
[![Build Status](https://travis-ci.org/st3fan/jsonfeed.svg?branch=master)](https://travis-ci.org/st3fan/jsonfeed) [![Go Report Card](https://goreportcard.com/badge/github.com/st3fan/jsonfeed)](https://goreportcard.com/report/github.com/st3fan/jsonfeed) [![codecov](https://codecov.io/gh/st3fan/jsonfeed/branch/master/graph/badge.svg)](https://codecov.io/gh/st3fan/jsonfeed)
|
||||
|
||||
|
||||
*Stefan Arentz, May 2017*
|
||||
|
||||
Work in progress. Minimal package to parse JSON Feed streams. Please file feature requests.
|
|
@ -1,73 +0,0 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/
|
||||
|
||||
package jsonfeed
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const CurrentVersion = "https://jsonfeed.org/version/1"
|
||||
|
||||
type Item struct {
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
ExternalURL string `json:"external_url"`
|
||||
Title string `json:"title"`
|
||||
ContentHTML string `json:"content_html"`
|
||||
ContentText string `json:"content_text"`
|
||||
Summary string `json:"summary"`
|
||||
Image string `json:"image"`
|
||||
BannerImage string `json:"banner_image"`
|
||||
DatePublished time.Time `json:"date_published"`
|
||||
DateModified time.Time `json:"date_modified"`
|
||||
Author Author `json:"author"`
|
||||
Tags []string `json:"tags"`
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Avatar string `json:"avatar"`
|
||||
}
|
||||
|
||||
type Hub struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
URL string `json:"url"`
|
||||
MIMEType string `json:"mime_type"`
|
||||
Title string `json:"title"`
|
||||
SizeInBytes int64 `json:"size_in_bytes"`
|
||||
DurationInSeconds int64 `json:"duration_in_seconds"`
|
||||
}
|
||||
|
||||
type Feed struct {
|
||||
Version string `json:"version"`
|
||||
Title string `json:"title"`
|
||||
HomePageURL string `json:"home_page_url"`
|
||||
FeedURL string `json:"feed_url"`
|
||||
Description string `json:"description"`
|
||||
UserComment string `json:"user_comment"`
|
||||
NextURL string `json:"next_url"`
|
||||
Icon string `json:"icon"`
|
||||
Favicon string `json:"favicon"`
|
||||
Author Author `json:"author"`
|
||||
Expired bool `json:"expired"`
|
||||
Hubs []Hub `json:"hubs"`
|
||||
Items []Item `json:"items"`
|
||||
}
|
||||
|
||||
func Parse(r io.Reader) (Feed, error) {
|
||||
var feed Feed
|
||||
decoder := json.NewDecoder(r)
|
||||
if err := decoder.Decode(&feed); err != nil {
|
||||
return Feed{}, err
|
||||
}
|
||||
return feed, nil
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/
|
||||
|
||||
package jsonfeed
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseSimple(t *testing.T) {
|
||||
r, err := os.Open("testdata/feed.json")
|
||||
assert.NoError(t, err, "Could not open testdata/feed.json")
|
||||
|
||||
feed, err := Parse(r)
|
||||
assert.NoError(t, err, "Could not parse testdata/feed.json")
|
||||
|
||||
assert.Equal(t, "https://jsonfeed.org/version/1", feed.Version)
|
||||
assert.Equal(t, "JSON Feed", feed.Title)
|
||||
assert.Equal(t, "JSON Feed is a ...", feed.Description)
|
||||
assert.Equal(t, "https://jsonfeed.org/", feed.HomePageURL)
|
||||
assert.Equal(t, "https://jsonfeed.org/feed.json", feed.FeedURL)
|
||||
assert.Equal(t, "This feed allows ...", feed.UserComment)
|
||||
assert.Equal(t, "https://jsonfeed.org/graphics/icon.png", feed.Favicon)
|
||||
assert.Equal(t, "Brent Simmons and Manton Reece", feed.Author.Name)
|
||||
|
||||
assert.Equal(t, 1, len(feed.Items))
|
||||
|
||||
assert.Equal(t, "https://jsonfeed.org/2017/05/17/announcing_json_feed", feed.Items[0].ID)
|
||||
assert.Equal(t, "https://jsonfeed.org/2017/05/17/announcing_json_feed", feed.Items[0].URL)
|
||||
assert.Equal(t, "Announcing JSON Feed", feed.Items[0].Title)
|
||||
assert.Equal(t, "<p>We ...", feed.Items[0].ContentHTML)
|
||||
|
||||
datePublished, err := time.Parse("2006-01-02T15:04:05-07:00", "2017-05-17T08:02:12-07:00")
|
||||
assert.NoError(t, err, "Could not parse timestamp")
|
||||
|
||||
assert.Equal(t, datePublished, feed.Items[0].DatePublished)
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"version": "https://jsonfeed.org/version/1",
|
||||
"title": "JSON Feed",
|
||||
"description": "JSON Feed is a ...",
|
||||
"home_page_url": "https://jsonfeed.org/",
|
||||
"feed_url": "https://jsonfeed.org/feed.json",
|
||||
"user_comment": "This feed allows ...",
|
||||
"favicon": "https://jsonfeed.org/graphics/icon.png",
|
||||
"author": {
|
||||
"name": "Brent Simmons and Manton Reece"
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": "https://jsonfeed.org/2017/05/17/announcing_json_feed",
|
||||
"url": "https://jsonfeed.org/2017/05/17/announcing_json_feed",
|
||||
"title": "Announcing JSON Feed",
|
||||
"content_html": "<p>We ...",
|
||||
"date_published": "2017-05-17T08:02:12-07:00"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue