1
0
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:
bouncepaw 2020-12-08 20:15:32 +05:00
parent 5269411ea2
commit 8810e9891d
11 changed files with 194 additions and 67 deletions

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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(`

View File

@ -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(

View File

@ -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")
}

View File

@ -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;}

View File

@ -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;}

View File

@ -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

View File

@ -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
}

View File

@ -8,6 +8,7 @@ import (
)
var (
URL string
ServerPort string
HomePage string
SiteTitle string