mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2025-01-19 07:02:51 +00:00
Implement RSS, Atom and JSON feeds and add a new flag
This commit is contained in:
parent
5269411ea2
commit
8810e9891d
9
flag.go
9
flag.go
@ -10,8 +10,9 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at")
|
||||
flag.StringVar(&util.HomePage, "home", "home", "The home page")
|
||||
flag.StringVar(&util.URL, "url", "http://0.0.0.0:$port", "URL at which your wiki can be found. Used to generate feeds")
|
||||
flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at using HTTP")
|
||||
flag.StringVar(&util.HomePage, "home", "home", "The home page name")
|
||||
flag.StringVar(&util.SiteTitle, "title", "🍄", "How to call your wiki in the navititle")
|
||||
flag.StringVar(&util.UserTree, "user-tree", "u", "Hypha which is a superhypha of all user pages")
|
||||
flag.StringVar(&util.AuthMethod, "auth-method", "none", "What auth method to use. Variants: \"none\", \"fixed\"")
|
||||
@ -34,6 +35,10 @@ func parseCliArgs() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if util.URL == "http://0.0.0.0:$port" {
|
||||
util.URL = "http://0.0.0.0:" + util.ServerPort
|
||||
}
|
||||
|
||||
if !isCanonicalName(util.HomePage) {
|
||||
log.Fatal("Error: you must use a proper name for the homepage")
|
||||
}
|
||||
|
1
go.mod
1
go.mod
@ -4,5 +4,6 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.2.2
|
||||
github.com/gorilla/feeds v1.1.1
|
||||
github.com/valyala/quicktemplate v1.6.3
|
||||
)
|
||||
|
2
go.sum
2
go.sum
@ -2,6 +2,8 @@ github.com/adrg/xdg v0.2.2 h1:A7ZHKRz5KGOLJX/bg7IPzStryhvCzAE1wX+KWawPiAo=
|
||||
github.com/adrg/xdg v0.2.2/go.mod h1:7I2hH/IT30IsupOpKZ5ue7/qNi3CoKzD6tL3HwpaRMQ=
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.11.0/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@ -13,6 +14,8 @@ import (
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
)
|
||||
|
||||
var renameMsgPattern = regexp.MustCompile(`^Rename ‘(.*)’ to ‘.*’`)
|
||||
|
||||
// Start initializes git credentials.
|
||||
func Start(wikiDir string) {
|
||||
_, err := gitsh("config", "user.name", "wikimind")
|
||||
@ -31,6 +34,42 @@ type Revision struct {
|
||||
Username string
|
||||
Time time.Time
|
||||
Message string
|
||||
hyphaeAffectedBuf []string
|
||||
}
|
||||
|
||||
// determine what hyphae were affected by this revision
|
||||
func (rev *Revision) hyphaeAffected() (hyphae []string) {
|
||||
if nil != rev.hyphaeAffectedBuf {
|
||||
return rev.hyphaeAffectedBuf
|
||||
}
|
||||
hyphae = make([]string, 0)
|
||||
var (
|
||||
// List of files affected by this revision, one per line.
|
||||
out, err = gitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
|
||||
// set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most currently).
|
||||
set = make(map[string]bool)
|
||||
isNewName = func(hyphaName string) bool {
|
||||
if _, present := set[hyphaName]; present {
|
||||
return false
|
||||
}
|
||||
set[hyphaName] = true
|
||||
return true
|
||||
}
|
||||
)
|
||||
if err != nil {
|
||||
return hyphae
|
||||
}
|
||||
for _, filename := range strings.Split(out.String(), "\n") {
|
||||
if strings.IndexRune(filename, '.') >= 0 {
|
||||
dotPos := strings.LastIndexByte(filename, '.')
|
||||
hyphaName := string([]byte(filename)[0:dotPos]) // is it safe?
|
||||
if isNewName(hyphaName) {
|
||||
hyphae = append(hyphae, hyphaName)
|
||||
}
|
||||
}
|
||||
}
|
||||
rev.hyphaeAffectedBuf = hyphae
|
||||
return hyphae
|
||||
}
|
||||
|
||||
// TimeString returns a human readable time representation.
|
||||
@ -40,42 +79,38 @@ func (rev Revision) TimeString() string {
|
||||
|
||||
// HyphaeLinks returns a comma-separated list of hyphae that were affected by this revision as HTML string.
|
||||
func (rev Revision) HyphaeLinks() (html string) {
|
||||
// diff-tree --no-commit-id --name-only -r
|
||||
var (
|
||||
// List of files affected by this revision, one per line.
|
||||
out, err = gitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
|
||||
// set is used to determine if a certain hypha has been already noted (hyphae are stored in 2 files at most).
|
||||
set = make(map[string]bool)
|
||||
isNewName = func(hyphaName string) bool {
|
||||
if _, present := set[hyphaName]; present {
|
||||
return false
|
||||
} else {
|
||||
set[hyphaName] = true
|
||||
return true
|
||||
}
|
||||
}
|
||||
)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, filename := range strings.Split(out.String(), "\n") {
|
||||
// If filename has an ampersand:
|
||||
if strings.IndexRune(filename, '.') >= 0 {
|
||||
// Remove ampersanded suffix from filename:
|
||||
ampersandPos := strings.LastIndexByte(filename, '.')
|
||||
hyphaName := string([]byte(filename)[0:ampersandPos]) // is it safe?
|
||||
if isNewName(hyphaName) {
|
||||
// Entries are separated by commas
|
||||
if len(set) > 1 {
|
||||
hyphae := rev.hyphaeAffected()
|
||||
for i, hyphaName := range hyphae {
|
||||
if i > 0 {
|
||||
html += `<span aria-hidden="true">, </span>`
|
||||
}
|
||||
html += fmt.Sprintf(`<a href="/page/%[1]s">%[1]s</a>`, hyphaName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return html
|
||||
}
|
||||
|
||||
func (rev *Revision) descriptionForFeed() (html string) {
|
||||
return fmt.Sprintf(
|
||||
`<p>%s</p>
|
||||
<p><b>Hyphae affected:</b> %s</p>`, rev.Message, rev.HyphaeLinks())
|
||||
}
|
||||
|
||||
// Try and guess what link is the most important by looking at the message.
|
||||
func (rev *Revision) bestLink() string {
|
||||
var (
|
||||
revs = rev.hyphaeAffected()
|
||||
renameRes = renameMsgPattern.FindStringSubmatch(rev.Message)
|
||||
)
|
||||
switch {
|
||||
case renameRes != nil:
|
||||
return "/page/" + renameRes[1]
|
||||
case len(revs) == 0:
|
||||
return ""
|
||||
default:
|
||||
return "/page/" + revs[0]
|
||||
}
|
||||
}
|
||||
|
||||
func (rev Revision) RecentChangesEntry() (html string) {
|
||||
if user.AuthUsed && rev.Username != "anon" {
|
||||
return fmt.Sprintf(`
|
||||
|
@ -11,8 +11,56 @@ import (
|
||||
|
||||
"github.com/bouncepaw/mycorrhiza/templates"
|
||||
"github.com/bouncepaw/mycorrhiza/util"
|
||||
"github.com/gorilla/feeds"
|
||||
)
|
||||
|
||||
func recentChangesFeed() *feeds.Feed {
|
||||
feed := &feeds.Feed{
|
||||
Title: "Recent changes",
|
||||
Link: &feeds.Link{Href: util.URL},
|
||||
Description: "List of 30 recent changes on the wiki",
|
||||
Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"},
|
||||
Updated: time.Now(),
|
||||
}
|
||||
var (
|
||||
out, err = gitsh(
|
||||
"log", "--oneline", "--no-merges",
|
||||
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
|
||||
"--max-count=30",
|
||||
)
|
||||
revs []Revision
|
||||
)
|
||||
if err == nil {
|
||||
for _, line := range strings.Split(out.String(), "\n") {
|
||||
revs = append(revs, parseRevisionLine(line))
|
||||
}
|
||||
}
|
||||
for _, rev := range revs {
|
||||
feed.Add(&feeds.Item{
|
||||
Title: rev.Message,
|
||||
Author: &feeds.Author{Name: rev.Username},
|
||||
Id: rev.Hash,
|
||||
Description: rev.descriptionForFeed(),
|
||||
Created: rev.Time,
|
||||
Updated: rev.Time,
|
||||
Link: &feeds.Link{Href: util.URL + rev.bestLink()},
|
||||
})
|
||||
}
|
||||
return feed
|
||||
}
|
||||
|
||||
func RecentChangesRSS() (string, error) {
|
||||
return recentChangesFeed().ToRss()
|
||||
}
|
||||
|
||||
func RecentChangesAtom() (string, error) {
|
||||
return recentChangesFeed().ToAtom()
|
||||
}
|
||||
|
||||
func RecentChangesJSON() (string, error) {
|
||||
return recentChangesFeed().ToJSON()
|
||||
}
|
||||
|
||||
func RecentChanges(n int) string {
|
||||
var (
|
||||
out, err = gitsh(
|
||||
|
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@ -14,6 +15,9 @@ import (
|
||||
func init() {
|
||||
http.HandleFunc("/history/", handlerHistory)
|
||||
http.HandleFunc("/recent-changes/", handlerRecentChanges)
|
||||
http.HandleFunc("/recent-changes-rss", handlerRecentChangesRSS)
|
||||
http.HandleFunc("/recent-changes-atom", handlerRecentChangesAtom)
|
||||
http.HandleFunc("/recent-changes-json", handlerRecentChangesJSON)
|
||||
}
|
||||
|
||||
// handlerHistory lists all revisions of a hypha
|
||||
@ -46,3 +50,28 @@ func handlerRecentChanges(w http.ResponseWriter, rq *http.Request) {
|
||||
http.Redirect(w, rq, "/recent-changes/20", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func genericHandlerOfFeeds(w http.ResponseWriter, rq *http.Request, f func() (string, error), name string) {
|
||||
log.Println(rq.URL)
|
||||
if content, err := f(); err != nil {
|
||||
w.Header().Set("Content-Type", "text/plain;charset=utf-8")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, "An error while generating "+name+": "+err.Error())
|
||||
} else {
|
||||
w.Header().Set("Content-Type", "application/rss+xml")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, content)
|
||||
}
|
||||
}
|
||||
|
||||
func handlerRecentChangesRSS(w http.ResponseWriter, rq *http.Request) {
|
||||
genericHandlerOfFeeds(w, rq, history.RecentChangesRSS, "RSS")
|
||||
}
|
||||
|
||||
func handlerRecentChangesAtom(w http.ResponseWriter, rq *http.Request) {
|
||||
genericHandlerOfFeeds(w, rq, history.RecentChangesAtom, "Atom")
|
||||
}
|
||||
|
||||
func handlerRecentChangesJSON(w http.ResponseWriter, rq *http.Request) {
|
||||
genericHandlerOfFeeds(w, rq, history.RecentChangesJSON, "JSON feed")
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ textarea {font-size:15px;}
|
||||
.edit {height:100%;}
|
||||
.edit-form {height:90%;}
|
||||
.edit-form textarea {width:100%;height:90%;}
|
||||
.icon {margin-right: .25rem; vertical-align: bottom; }
|
||||
|
||||
main h1:not(.navi-title) {font-size:1.7rem;}
|
||||
blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;}
|
||||
|
@ -14,6 +14,7 @@ textarea {font-size:15px;}
|
||||
.edit {height:100%;}
|
||||
.edit-form {height:90%;}
|
||||
.edit-form textarea {width:100%;height:90%;}
|
||||
.icon {margin-right: .25rem; vertical-align: bottom; }
|
||||
|
||||
main h1:not(.navi-title) {font-size:1.7rem;}
|
||||
blockquote {border-left: 4px black solid; margin-left: 0; padding-left: 1rem;}
|
||||
|
@ -18,6 +18,8 @@
|
||||
recent changes
|
||||
</nav>
|
||||
|
||||
<p><img class="icon" width="20" height="20" src="https://upload.wikimedia.org/wikipedia/commons/4/46/Generic_Feed-icon.svg">Subscribe via <a href="/recent-changes-rss">RSS</a>, <a href="/recent-changes-atom">Atom</a> or <a href="/recent-changes-json">JSON feed</a>.</p>
|
||||
|
||||
{% comment %}
|
||||
Here I am, willing to add some accesibility using ARIA. Turns out,
|
||||
role="feed" is not supported in any screen reader as of September
|
||||
|
@ -77,81 +77,83 @@ func StreamRecentChangesHTML(qw422016 *qt422016.Writer, changes []string, n int)
|
||||
recent changes
|
||||
</nav>
|
||||
|
||||
<p><img class="icon" width="20" height="20" src="https://upload.wikimedia.org/wikipedia/commons/4/46/Generic_Feed-icon.svg">Subscribe via <a href="/recent-changes-rss">RSS</a>, <a href="/recent-changes-atom">Atom</a> or <a href="/recent-changes-json">JSON feed</a>.</p>
|
||||
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:26
|
||||
//line templates/recent_changes.qtpl:28
|
||||
qw422016.N().S(`
|
||||
|
||||
<section class="recent-changes__list" role="feed">
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:29
|
||||
//line templates/recent_changes.qtpl:31
|
||||
if len(changes) == 0 {
|
||||
//line templates/recent_changes.qtpl:29
|
||||
//line templates/recent_changes.qtpl:31
|
||||
qw422016.N().S(`
|
||||
<p>Could not find any recent changes.</p>
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:31
|
||||
//line templates/recent_changes.qtpl:33
|
||||
} else {
|
||||
//line templates/recent_changes.qtpl:31
|
||||
//line templates/recent_changes.qtpl:33
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:32
|
||||
//line templates/recent_changes.qtpl:34
|
||||
for i, entry := range changes {
|
||||
//line templates/recent_changes.qtpl:32
|
||||
//line templates/recent_changes.qtpl:34
|
||||
qw422016.N().S(`
|
||||
<ul class="recent-changes__entry rc-entry" role="article"
|
||||
aria-setsize="`)
|
||||
//line templates/recent_changes.qtpl:34
|
||||
//line templates/recent_changes.qtpl:36
|
||||
qw422016.N().D(n)
|
||||
//line templates/recent_changes.qtpl:34
|
||||
//line templates/recent_changes.qtpl:36
|
||||
qw422016.N().S(`" aria-posinset="`)
|
||||
//line templates/recent_changes.qtpl:34
|
||||
//line templates/recent_changes.qtpl:36
|
||||
qw422016.N().D(i)
|
||||
//line templates/recent_changes.qtpl:34
|
||||
//line templates/recent_changes.qtpl:36
|
||||
qw422016.N().S(`">
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:35
|
||||
//line templates/recent_changes.qtpl:37
|
||||
qw422016.N().S(entry)
|
||||
//line templates/recent_changes.qtpl:35
|
||||
//line templates/recent_changes.qtpl:37
|
||||
qw422016.N().S(`
|
||||
</ul>
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:37
|
||||
//line templates/recent_changes.qtpl:39
|
||||
}
|
||||
//line templates/recent_changes.qtpl:37
|
||||
//line templates/recent_changes.qtpl:39
|
||||
qw422016.N().S(`
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:38
|
||||
//line templates/recent_changes.qtpl:40
|
||||
}
|
||||
//line templates/recent_changes.qtpl:38
|
||||
//line templates/recent_changes.qtpl:40
|
||||
qw422016.N().S(`
|
||||
</section>
|
||||
</main>
|
||||
`)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
}
|
||||
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
func WriteRecentChangesHTML(qq422016 qtio422016.Writer, changes []string, n int) {
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
qw422016 := qt422016.AcquireWriter(qq422016)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
StreamRecentChangesHTML(qw422016, changes, n)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
qt422016.ReleaseWriter(qw422016)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
}
|
||||
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
func RecentChangesHTML(changes []string, n int) string {
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
qb422016 := qt422016.AcquireByteBuffer()
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
WriteRecentChangesHTML(qb422016, changes, n)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
qs422016 := string(qb422016.B)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
qt422016.ReleaseByteBuffer(qb422016)
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
return qs422016
|
||||
//line templates/recent_changes.qtpl:41
|
||||
//line templates/recent_changes.qtpl:43
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
URL string
|
||||
ServerPort string
|
||||
HomePage string
|
||||
SiteTitle string
|
||||
|
Loading…
Reference in New Issue
Block a user