1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2024-12-13 05:50:27 +00:00

reorganize the history package (and rewrite some parts with qtpl)

start work on grouping edits in feeds
This commit is contained in:
Elias Bomberger 2021-10-22 22:00:44 -04:00
parent e4cd5e4a5f
commit 924b011e06
8 changed files with 722 additions and 341 deletions

84
history/feed.go Normal file
View File

@ -0,0 +1,84 @@
package history
import (
"fmt"
"strings"
"time"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/gorilla/feeds"
)
var groupPeriod, _ = time.ParseDuration("30m")
func recentChangesFeed() *feeds.Feed {
feed := &feeds.Feed{
Title: "Recent changes",
Link: &feeds.Link{Href: cfg.URL},
Description: "List of 30 recent changes on the wiki",
Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"},
Updated: time.Now(),
}
revs := RecentChanges(30)
groups := groupRevisionsByPeriod(revs, groupPeriod)
for _, grp := range groups {
item := grp.feedItem()
feed.Add(&item)
}
return feed
}
// RecentChangesRSS creates recent changes feed in RSS format.
func RecentChangesRSS() (string, error) {
return recentChangesFeed().ToRss()
}
// RecentChangesAtom creates recent changes feed in Atom format.
func RecentChangesAtom() (string, error) {
return recentChangesFeed().ToAtom()
}
// RecentChangesJSON creates recent changes feed in JSON format.
func RecentChangesJSON() (string, error) {
return recentChangesFeed().ToJSON()
}
func (grp revisionGroup) feedItem() feeds.Item {
return feeds.Item{
Title: grp.title(),
Author: grp.author(),
Id: grp[0].Hash,
Description: grp.descriptionForFeed(),
Created: grp[len(grp)-1].Time, // earliest revision
Updated: grp[0].Time, // latest revision
Link: &feeds.Link{Href: cfg.URL + grp[0].bestLink()},
}
}
func (grp revisionGroup) title() string {
if len(grp) == 1 {
return grp[0].Message
} else {
return fmt.Sprintf("%d edits (%s, ...)", len(grp), grp[0].Message)
}
}
func (grp revisionGroup) author() *feeds.Author {
author := grp[0].Username
for _, rev := range grp[1:] {
// if they don't all have the same author, return nil
if rev.Username != author {
return nil
}
}
return &feeds.Author{Name: author}
}
func (grp revisionGroup) descriptionForFeed() string {
builder := strings.Builder{}
for _, rev := range grp {
builder.WriteString(rev.descriptionForFeed())
}
return builder.String()
}

View File

@ -4,14 +4,10 @@ package history
import (
"bytes"
"fmt"
"html"
"log"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/bouncepaw/mycorrhiza/files"
"github.com/bouncepaw/mycorrhiza/util"
@ -54,131 +50,6 @@ func InitGitRepo() {
}
}
// Revision represents a revision, duh. Hash is usually short. Username is extracted from email.
type Revision struct {
Hash string
Username string
Time time.Time
Message string
filesAffectedBuf []string
hyphaeAffectedBuf []string
}
// filesAffected tells what files have been affected by the revision.
func (rev *Revision) filesAffected() (filenames []string) {
if nil != rev.filesAffectedBuf {
return rev.filesAffectedBuf
}
// List of files affected by this revision, one per line.
out, err := silentGitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
// There's an error? Well, whatever, let's just assign an empty slice, who cares.
if err != nil {
rev.filesAffectedBuf = []string{}
} else {
rev.filesAffectedBuf = strings.Split(out.String(), "\n")
}
return rev.filesAffectedBuf
}
// 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 (
// 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
}
filesAffected = rev.filesAffected()
)
for _, filename := range filesAffected {
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.
func (rev Revision) TimeString() string {
return rev.Time.Format(time.RFC822)
}
// HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
func (rev Revision) HyphaeLinksHTML() (html string) {
hyphae := rev.hyphaeAffected()
for i, hyphaName := range hyphae {
if i > 0 {
html += `<span aria-hidden="true">, </span>`
}
html += fmt.Sprintf(`<a href="/hypha/%[1]s">%[1]s</a>`, hyphaName)
}
return html
}
// descriptionForFeed generates a good enough HTML contents for a web feed.
func (rev *Revision) descriptionForFeed() (htmlDesc string) {
return fmt.Sprintf(
`<p>%s</p>
<p><b>Hyphae affected:</b> %s</p>
<pre><code>%s</code></pre>`, rev.Message, rev.HyphaeLinksHTML(), html.EscapeString(rev.textDiff()))
}
// textDiff generates a good enough diff to display in a web feed. It is not html-escaped.
func (rev *Revision) textDiff() (diff string) {
filenames, ok := rev.mycoFiles()
if !ok {
return "No text changes"
}
for _, filename := range filenames {
text, err := PrimitiveDiffAtRevision(filename, rev.Hash)
if err != nil {
diff += "\nAn error has occurred with " + filename + "\n"
}
diff += text + "\n"
}
return diff
}
// mycoFiles returns filenames of .myco file. It is not ok if there are no myco files.
func (rev *Revision) mycoFiles() (filenames []string, ok bool) {
filenames = []string{}
for _, filename := range rev.filesAffected() {
if strings.HasSuffix(filename, ".myco") {
filenames = append(filenames, filename)
}
}
return filenames, len(filenames) > 0
}
// 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 "/hypha/" + renameRes[1]
case len(revs) == 0:
return ""
default:
return "/hypha/" + revs[0]
}
}
// I pronounce it as [gɪt͡ʃ].
// gitsh is async-safe, therefore all other git-related functions in this module are too.
func gitsh(args ...string) (out bytes.Buffer, err error) {
@ -204,16 +75,6 @@ func silentGitsh(args ...string) (out bytes.Buffer, err error) {
return *bytes.NewBuffer(b), err
}
// Convert a UNIX timestamp as string into a time. If nil is returned, it means that the timestamp could not be converted.
func unixTimestampAsTime(ts string) *time.Time {
i, err := strconv.ParseInt(ts, 10, 64)
if err != nil {
return nil
}
tm := time.Unix(i, 0)
return &tm
}
// Rename renames from `from` to `to` using `git mv`.
func Rename(from, to string) error {
log.Println(util.ShorterPath(from), util.ShorterPath(to))

View File

@ -1,199 +0,0 @@
package history
// information.go
// Things related to gathering existing information.
import (
"fmt"
"log"
"regexp"
"strconv"
"strings"
"time"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/files"
"github.com/gorilla/feeds"
)
func recentChangesFeed() *feeds.Feed {
feed := &feeds.Feed{
Title: "Recent changes",
Link: &feeds.Link{Href: cfg.URL},
Description: "List of 30 recent changes on the wiki",
Author: &feeds.Author{Name: "Wikimind", Email: "wikimind@mycorrhiza"},
Updated: time.Now(),
}
var (
out, err = silentGitsh(
"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))
}
}
log.Printf("Found %d recent changes", len(revs))
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: cfg.URL + rev.bestLink()},
})
}
return feed
}
// RecentChangesRSS creates recent changes feed in RSS format.
func RecentChangesRSS() (string, error) {
return recentChangesFeed().ToRss()
}
// RecentChangesAtom creates recent changes feed in Atom format.
func RecentChangesAtom() (string, error) {
return recentChangesFeed().ToAtom()
}
// RecentChangesJSON creates recent changes feed in JSON format.
func RecentChangesJSON() (string, error) {
return recentChangesFeed().ToJSON()
}
// RecentChanges gathers an arbitrary number of latest changes in form of revisions slice.
func RecentChanges(n int) []Revision {
var (
out, err = silentGitsh(
"log", "--oneline", "--no-merges",
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
"--max-count="+strconv.Itoa(n),
)
revs []Revision
)
if err == nil {
for _, line := range strings.Split(out.String(), "\n") {
revs = append(revs, parseRevisionLine(line))
}
}
log.Printf("Found %d recent changes", len(revs))
return revs
}
// FileChanged tells you if the file has been changed.
func FileChanged(path string) bool {
_, err := gitsh("diff", "--exit-code", path)
return err != nil
}
// Revisions returns slice of revisions for the given hypha name.
func Revisions(hyphaName string) ([]Revision, error) {
var (
out, err = silentGitsh(
"log", "--oneline", "--no-merges",
// Hash, author email, author time, commit msg separated by tab
"--pretty=format:\"%h\t%ae\t%at\t%s\"",
"--", hyphaName+".*",
)
revs []Revision
)
if err == nil {
for _, line := range strings.Split(out.String(), "\n") {
if line != "" {
revs = append(revs, parseRevisionLine(line))
}
}
}
log.Printf("Found %d revisions for %s\n", len(revs), hyphaName)
return revs, err
}
// WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
func WithRevisions(hyphaName string, revs []Revision) (html string) {
var (
currentYear int
currentMonth time.Month
)
for i, rev := range revs {
if rev.Time.Month() != currentMonth || rev.Time.Year() != currentYear {
currentYear = rev.Time.Year()
currentMonth = rev.Time.Month()
if i != 0 {
html += `
</ul>
</section>`
}
html += fmt.Sprintf(`
<section class="history__month">
<a href="#%[1]d-%[2]d" class="history__month-anchor">
<h2 id="%[1]d-%[2]d" class="history__month-title">%[3]s</h2>
</a>
<ul class="history__entries">`,
currentYear, currentMonth,
strconv.Itoa(currentYear)+" "+rev.Time.Month().String())
}
html += rev.asHistoryEntry(hyphaName)
}
return html
}
func (rev *Revision) asHistoryEntry(hyphaName string) (html string) {
author := ""
if rev.Username != "anon" {
author = fmt.Sprintf(`
<span class="history-entry__author">by <a href="/hypha/%[1]s/%[2]s" rel="author">%[2]s</span>`, cfg.UserHypha, rev.Username)
}
return fmt.Sprintf(`
<li class="history__entry">
<a class="history-entry" href="/rev/%[3]s/%[1]s">
<time class="history-entry__time">%[2]s</time>
<span class="history-entry__hash"><a href="/primitive-diff/%[3]s/%[1]s">%[3]s</a></span>
<span class="history-entry__msg">%[4]s</span>
</a>%[5]s
</li>
`, hyphaName, rev.timeToDisplay(), rev.Hash, rev.Message, author)
}
// Return time like mm-dd 13:42
func (rev *Revision) timeToDisplay() string {
D := rev.Time.Day()
h, m, _ := rev.Time.Clock()
return fmt.Sprintf("%02d — %02d:%02d", D, h, m)
}
// This regex is wrapped in "". For some reason, these quotes appear at some time and we have to get rid of them.
var revisionLinePattern = regexp.MustCompile("\"(.*)\t(.*)@.*\t(.*)\t(.*)\"")
func parseRevisionLine(line string) Revision {
results := revisionLinePattern.FindStringSubmatch(line)
return Revision{
Hash: results[1],
Username: results[2],
Time: *unixTimestampAsTime(results[3]),
Message: results[4],
}
}
// FileAtRevision shows how the file with the given file path looked at the commit with the hash. It may return an error if git fails.
func FileAtRevision(filepath, hash string) (string, error) {
out, err := gitsh("show", hash+":"+strings.TrimPrefix(filepath, files.HyphaeDir()+"/"))
if err != nil {
return "", err
}
return out.String(), err
}
// PrimitiveDiffAtRevision generates a plain-text diff for the given filepath at the commit with the given hash. It may return an error if git fails.
func PrimitiveDiffAtRevision(filepath, hash string) (string, error) {
out, err := silentGitsh("diff", "--unified=1", "--no-color", hash+"~", hash, "--", filepath)
if err != nil {
return "", err
}
return out.String(), err
}

View File

@ -95,9 +95,7 @@ func (hop *Op) WithFilesRenamed(pairs map[string]string) *Op {
hop.Errs = append(hop.Errs, err)
continue
}
if err := Rename(from, to); err != nil {
hop.Errs = append(hop.Errs, err)
}
hop.gitop("mv", "--force", from, to)
}
}
return hop

255
history/revision.go Normal file
View File

@ -0,0 +1,255 @@
package history
import (
"fmt"
"log"
"regexp"
"strconv"
"strings"
"time"
"github.com/bouncepaw/mycorrhiza/files"
)
// Revision represents a revision, duh. Hash is usually short. Username is extracted from email.
type Revision struct {
Hash string
Username string
Time time.Time
Message string
filesAffectedBuf []string
hyphaeAffectedBuf []string
}
// RecentChanges gathers an arbitrary number of latest changes in form of revisions slice, ordered most recent first.
func RecentChanges(n int) []Revision {
var (
out, err = silentGitsh(
"log", "--oneline", "--no-merges",
"--pretty=format:%h\t%ae\t%at\t%s",
"--max-count="+strconv.Itoa(n),
)
revs []Revision
)
if err == nil {
for _, line := range strings.Split(out.String(), "\n") {
revs = append(revs, parseRevisionLine(line))
}
}
log.Printf("Found %d recent changes", len(revs))
return revs
}
// FileChanged tells you if the file has been changed since the last commit.
func FileChanged(path string) bool {
_, err := gitsh("diff", "--exit-code", path)
return err != nil
}
// Revisions returns slice of revisions for the given hypha name, ordered most recent first.
func Revisions(hyphaName string) ([]Revision, error) {
var (
out, err = silentGitsh(
"log", "--oneline", "--no-merges",
// Hash, author email, author time, commit msg separated by tab
"--pretty=format:%h\t%ae\t%at\t%s",
"--", hyphaName+".*",
)
revs []Revision
)
if err == nil {
for _, line := range strings.Split(out.String(), "\n") {
if line != "" {
revs = append(revs, parseRevisionLine(line))
}
}
}
log.Printf("Found %d revisions for %s\n", len(revs), hyphaName)
return revs, err
}
// Return time like mm-dd 13:42
func (rev *Revision) timeToDisplay() string {
D := rev.Time.Day()
h, m, _ := rev.Time.Clock()
return fmt.Sprintf("%02d — %02d:%02d", D, h, m)
}
var revisionLinePattern = regexp.MustCompile("(.*)\t(.*)@.*\t(.*)\t(.*)")
// Convert a UNIX timestamp as string into a time. If nil is returned, it means that the timestamp could not be converted.
func unixTimestampAsTime(ts string) *time.Time {
i, err := strconv.ParseInt(ts, 10, 64)
if err != nil {
return nil
}
tm := time.Unix(i, 0)
return &tm
}
func parseRevisionLine(line string) Revision {
results := revisionLinePattern.FindStringSubmatch(line)
return Revision{
Hash: results[1],
Username: results[2],
Time: *unixTimestampAsTime(results[3]),
Message: results[4],
}
}
// filesAffected tells what files have been affected by the revision.
func (rev *Revision) filesAffected() (filenames []string) {
if nil != rev.filesAffectedBuf {
return rev.filesAffectedBuf
}
// List of files affected by this revision, one per line.
out, err := silentGitsh("diff-tree", "--no-commit-id", "--name-only", "-r", rev.Hash)
// There's an error? Well, whatever, let's just assign an empty slice, who cares.
if err != nil {
rev.filesAffectedBuf = []string{}
} else {
rev.filesAffectedBuf = strings.Split(out.String(), "\n")
}
return rev.filesAffectedBuf
}
// 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 (
// 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
}
filesAffected = rev.filesAffected()
)
for _, filename := range filesAffected {
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.
func (rev Revision) TimeString() string {
return rev.Time.Format(time.RFC822)
}
// textDiff generates a good enough diff to display in a web feed. It is not html-escaped.
func (rev *Revision) textDiff() (diff string) {
filenames, ok := rev.mycoFiles()
if !ok {
return "No text changes"
}
for _, filename := range filenames {
text, err := PrimitiveDiffAtRevision(filename, rev.Hash)
if err != nil {
diff += "\nAn error has occurred with " + filename + "\n"
}
diff += text + "\n"
}
return diff
}
// mycoFiles returns filenames of .myco file. It is not ok if there are no myco files.
func (rev *Revision) mycoFiles() (filenames []string, ok bool) {
filenames = []string{}
for _, filename := range rev.filesAffected() {
if strings.HasSuffix(filename, ".myco") {
filenames = append(filenames, filename)
}
}
return filenames, len(filenames) > 0
}
// 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 "/hypha/" + renameRes[1]
case len(revs) == 0:
return ""
default:
return "/hypha/" + revs[0]
}
}
// FileAtRevision shows how the file with the given file path looked at the commit with the hash. It may return an error if git fails.
func FileAtRevision(filepath, hash string) (string, error) {
out, err := gitsh("show", hash+":"+strings.TrimPrefix(filepath, files.HyphaeDir()+"/"))
if err != nil {
return "", err
}
return out.String(), err
}
// PrimitiveDiffAtRevision generates a plain-text diff for the given filepath at the commit with the given hash. It may return an error if git fails.
func PrimitiveDiffAtRevision(filepath, hash string) (string, error) {
out, err := silentGitsh("diff", "--unified=1", "--no-color", hash+"~", hash, "--", filepath)
if err != nil {
return "", err
}
return out.String(), err
}
type revisionGroup []Revision
func newRevisionGroup(rev Revision) revisionGroup {
return revisionGroup([]Revision{rev})
}
func (grp *revisionGroup) addRevision(rev Revision) {
*grp = append(*grp, rev)
}
func groupRevisionsByMonth(revs []Revision) (res []revisionGroup) {
var (
currentYear int
currentMonth time.Month
)
for _, rev := range revs {
if rev.Time.Month() != currentMonth || rev.Time.Year() != currentYear {
currentYear = rev.Time.Year()
currentMonth = rev.Time.Month()
res = append(res, newRevisionGroup(rev))
} else {
res[len(res)-1].addRevision(rev)
}
}
return res
}
// groupRevisionsByPeriod groups revisions by how long ago they were.
// Revisions less than one period ago are placed in one group, revisions between one and two periods are in another, and so on.
func groupRevisionsByPeriod(revs []Revision, period time.Duration) (res []revisionGroup) {
now := time.Now()
currentPeriod := -1
for _, rev := range revs {
newPeriod := int(now.Sub(rev.Time).Seconds() / period.Seconds())
if newPeriod != currentPeriod {
currentPeriod = newPeriod
res = append(res, newRevisionGroup(rev))
} else {
res[len(res)-1].addRevision(rev)
}
}
return res
}

55
history/view.qtpl Normal file
View File

@ -0,0 +1,55 @@
{% import "fmt" %}
{% import "github.com/bouncepaw/mycorrhiza/cfg" %}
HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
{% func (rev Revision) HyphaeLinksHTML() %}
{% stripspace %}
{% for i, hyphaName := range rev.hyphaeAffected() %}
{% if i > 0 %}
<span aria-hidden="true">, </span>
{% endif %}
<a href="/hypha/{%s hyphaName %}">{%s hyphaName %}</a>
{% endfor %}
{% endstripspace %}
{% endfunc %}
descriptionForFeed generates a good enough HTML contents for a web feed.
{% func (rev *Revision) descriptionForFeed() %}
<p><b>{%s rev.Message %}</b> (by {%s rev.Username %} at {%s rev.TimeString() %})</p>
<p>Hyphae affected: {%= rev.HyphaeLinksHTML() %}</p>
<pre><code>{%s rev.textDiff() %}</code></pre>
{% endfunc %}
WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
{% func WithRevisions(hyphaName string, revs []Revision) %}
{% for _, grp := range groupRevisionsByMonth(revs) %}
{% code
currentYear := grp[0].Time.Year()
currentMonth := grp[0].Time.Month()
sectionId := fmt.Sprintf("%d-%d", currentYear, currentMonth)
%}
<section class="history__month">
<a href="#{%s sectionId %}" class="history__month-anchor">
<h2 id="{%s sectionId %}" class="history__month-title">{%d currentYear %} {%s currentMonth.String() %}</h2>
</a>
<ul class="history__entries">
{% for _, rev := range grp %}
{%= rev.asHistoryEntry(hyphaName) %}
{% endfor %}
</ul>
</section>
{% endfor %}
{% endfunc %}
{% func (rev *Revision) asHistoryEntry(hyphaName string) %}
<li class="history__entry">
<a class="history-entry" href="/rev/{%s rev.Hash %}/{%s hyphaName %}">
<time class="history-entry__time">{%s rev.timeToDisplay() %}</time>
</a>
<span class="history-entry__hash"><a href="/primitive-diff/{%s rev.Hash %}/{%s hyphaName %}">{%s rev.Hash %}</a></span>
<span class="history-entry__msg">{%s rev.Message %}</span>
{% if rev.Username != "anon" %}
<span class="history-entry__author">by <a href="/hypha/{%s cfg.UserHypha %}/{%s rev.Username %}" rel="author">{%s rev.Username %}</a></span>
{% endif %}
</li>
{% endfunc %}

326
history/view.qtpl.go Normal file
View File

@ -0,0 +1,326 @@
// Code generated by qtc from "view.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line history/view.qtpl:1
package history
//line history/view.qtpl:1
import "fmt"
//line history/view.qtpl:2
import "github.com/bouncepaw/mycorrhiza/cfg"
// HyphaeLinksHTML returns a comma-separated list of hyphae that were affected by this revision as HTML string.
//line history/view.qtpl:5
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line history/view.qtpl:5
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line history/view.qtpl:5
func (rev Revision) StreamHyphaeLinksHTML(qw422016 *qt422016.Writer) {
//line history/view.qtpl:5
qw422016.N().S(`
`)
//line history/view.qtpl:7
for i, hyphaName := range rev.hyphaeAffected() {
//line history/view.qtpl:8
if i > 0 {
//line history/view.qtpl:8
qw422016.N().S(`<span aria-hidden="true">, </span>`)
//line history/view.qtpl:10
}
//line history/view.qtpl:10
qw422016.N().S(`<a href="/hypha/`)
//line history/view.qtpl:11
qw422016.E().S(hyphaName)
//line history/view.qtpl:11
qw422016.N().S(`">`)
//line history/view.qtpl:11
qw422016.E().S(hyphaName)
//line history/view.qtpl:11
qw422016.N().S(`</a>`)
//line history/view.qtpl:12
}
//line history/view.qtpl:13
qw422016.N().S(`
`)
//line history/view.qtpl:14
}
//line history/view.qtpl:14
func (rev Revision) WriteHyphaeLinksHTML(qq422016 qtio422016.Writer) {
//line history/view.qtpl:14
qw422016 := qt422016.AcquireWriter(qq422016)
//line history/view.qtpl:14
rev.StreamHyphaeLinksHTML(qw422016)
//line history/view.qtpl:14
qt422016.ReleaseWriter(qw422016)
//line history/view.qtpl:14
}
//line history/view.qtpl:14
func (rev Revision) HyphaeLinksHTML() string {
//line history/view.qtpl:14
qb422016 := qt422016.AcquireByteBuffer()
//line history/view.qtpl:14
rev.WriteHyphaeLinksHTML(qb422016)
//line history/view.qtpl:14
qs422016 := string(qb422016.B)
//line history/view.qtpl:14
qt422016.ReleaseByteBuffer(qb422016)
//line history/view.qtpl:14
return qs422016
//line history/view.qtpl:14
}
// descriptionForFeed generates a good enough HTML contents for a web feed.
//line history/view.qtpl:17
func (rev *Revision) streamdescriptionForFeed(qw422016 *qt422016.Writer) {
//line history/view.qtpl:17
qw422016.N().S(`
<p><b>`)
//line history/view.qtpl:18
qw422016.E().S(rev.Message)
//line history/view.qtpl:18
qw422016.N().S(`</b> (by `)
//line history/view.qtpl:18
qw422016.E().S(rev.Username)
//line history/view.qtpl:18
qw422016.N().S(` at `)
//line history/view.qtpl:18
qw422016.E().S(rev.TimeString())
//line history/view.qtpl:18
qw422016.N().S(`)</p>
<p>Hyphae affected: `)
//line history/view.qtpl:19
rev.StreamHyphaeLinksHTML(qw422016)
//line history/view.qtpl:19
qw422016.N().S(`</p>
<pre><code>`)
//line history/view.qtpl:20
qw422016.E().S(rev.textDiff())
//line history/view.qtpl:20
qw422016.N().S(`</code></pre>
`)
//line history/view.qtpl:21
}
//line history/view.qtpl:21
func (rev *Revision) writedescriptionForFeed(qq422016 qtio422016.Writer) {
//line history/view.qtpl:21
qw422016 := qt422016.AcquireWriter(qq422016)
//line history/view.qtpl:21
rev.streamdescriptionForFeed(qw422016)
//line history/view.qtpl:21
qt422016.ReleaseWriter(qw422016)
//line history/view.qtpl:21
}
//line history/view.qtpl:21
func (rev *Revision) descriptionForFeed() string {
//line history/view.qtpl:21
qb422016 := qt422016.AcquireByteBuffer()
//line history/view.qtpl:21
rev.writedescriptionForFeed(qb422016)
//line history/view.qtpl:21
qs422016 := string(qb422016.B)
//line history/view.qtpl:21
qt422016.ReleaseByteBuffer(qb422016)
//line history/view.qtpl:21
return qs422016
//line history/view.qtpl:21
}
// WithRevisions returns an html representation of `revs` that is meant to be inserted in a history page.
//line history/view.qtpl:24
func StreamWithRevisions(qw422016 *qt422016.Writer, hyphaName string, revs []Revision) {
//line history/view.qtpl:24
qw422016.N().S(`
`)
//line history/view.qtpl:25
for _, grp := range groupRevisionsByMonth(revs) {
//line history/view.qtpl:25
qw422016.N().S(`
`)
//line history/view.qtpl:27
currentYear := grp[0].Time.Year()
currentMonth := grp[0].Time.Month()
sectionId := fmt.Sprintf("%d-%d", currentYear, currentMonth)
//line history/view.qtpl:30
qw422016.N().S(`
<section class="history__month">
<a href="#`)
//line history/view.qtpl:32
qw422016.E().S(sectionId)
//line history/view.qtpl:32
qw422016.N().S(`" class="history__month-anchor">
<h2 id="`)
//line history/view.qtpl:33
qw422016.E().S(sectionId)
//line history/view.qtpl:33
qw422016.N().S(`" class="history__month-title">`)
//line history/view.qtpl:33
qw422016.N().D(currentYear)
//line history/view.qtpl:33
qw422016.N().S(` `)
//line history/view.qtpl:33
qw422016.E().S(currentMonth.String())
//line history/view.qtpl:33
qw422016.N().S(`</h2>
</a>
<ul class="history__entries">
`)
//line history/view.qtpl:36
for _, rev := range grp {
//line history/view.qtpl:36
qw422016.N().S(`
`)
//line history/view.qtpl:37
rev.streamasHistoryEntry(qw422016, hyphaName)
//line history/view.qtpl:37
qw422016.N().S(`
`)
//line history/view.qtpl:38
}
//line history/view.qtpl:38
qw422016.N().S(`
</ul>
</section>
`)
//line history/view.qtpl:41
}
//line history/view.qtpl:41
qw422016.N().S(`
`)
//line history/view.qtpl:42
}
//line history/view.qtpl:42
func WriteWithRevisions(qq422016 qtio422016.Writer, hyphaName string, revs []Revision) {
//line history/view.qtpl:42
qw422016 := qt422016.AcquireWriter(qq422016)
//line history/view.qtpl:42
StreamWithRevisions(qw422016, hyphaName, revs)
//line history/view.qtpl:42
qt422016.ReleaseWriter(qw422016)
//line history/view.qtpl:42
}
//line history/view.qtpl:42
func WithRevisions(hyphaName string, revs []Revision) string {
//line history/view.qtpl:42
qb422016 := qt422016.AcquireByteBuffer()
//line history/view.qtpl:42
WriteWithRevisions(qb422016, hyphaName, revs)
//line history/view.qtpl:42
qs422016 := string(qb422016.B)
//line history/view.qtpl:42
qt422016.ReleaseByteBuffer(qb422016)
//line history/view.qtpl:42
return qs422016
//line history/view.qtpl:42
}
//line history/view.qtpl:44
func (rev *Revision) streamasHistoryEntry(qw422016 *qt422016.Writer, hyphaName string) {
//line history/view.qtpl:44
qw422016.N().S(`
<li class="history__entry">
<a class="history-entry" href="/rev/`)
//line history/view.qtpl:46
qw422016.E().S(rev.Hash)
//line history/view.qtpl:46
qw422016.N().S(`/`)
//line history/view.qtpl:46
qw422016.E().S(hyphaName)
//line history/view.qtpl:46
qw422016.N().S(`">
<time class="history-entry__time">`)
//line history/view.qtpl:47
qw422016.E().S(rev.timeToDisplay())
//line history/view.qtpl:47
qw422016.N().S(`</time>
</a>
<span class="history-entry__hash"><a href="/primitive-diff/`)
//line history/view.qtpl:49
qw422016.E().S(rev.Hash)
//line history/view.qtpl:49
qw422016.N().S(`/`)
//line history/view.qtpl:49
qw422016.E().S(hyphaName)
//line history/view.qtpl:49
qw422016.N().S(`">`)
//line history/view.qtpl:49
qw422016.E().S(rev.Hash)
//line history/view.qtpl:49
qw422016.N().S(`</a></span>
<span class="history-entry__msg">`)
//line history/view.qtpl:50
qw422016.E().S(rev.Message)
//line history/view.qtpl:50
qw422016.N().S(`</span>
`)
//line history/view.qtpl:51
if rev.Username != "anon" {
//line history/view.qtpl:51
qw422016.N().S(`
<span class="history-entry__author">by <a href="/hypha/`)
//line history/view.qtpl:52
qw422016.E().S(cfg.UserHypha)
//line history/view.qtpl:52
qw422016.N().S(`/`)
//line history/view.qtpl:52
qw422016.E().S(rev.Username)
//line history/view.qtpl:52
qw422016.N().S(`" rel="author">`)
//line history/view.qtpl:52
qw422016.E().S(rev.Username)
//line history/view.qtpl:52
qw422016.N().S(`</a></span>
`)
//line history/view.qtpl:53
}
//line history/view.qtpl:53
qw422016.N().S(`
</li>
`)
//line history/view.qtpl:55
}
//line history/view.qtpl:55
func (rev *Revision) writeasHistoryEntry(qq422016 qtio422016.Writer, hyphaName string) {
//line history/view.qtpl:55
qw422016 := qt422016.AcquireWriter(qq422016)
//line history/view.qtpl:55
rev.streamasHistoryEntry(qw422016, hyphaName)
//line history/view.qtpl:55
qt422016.ReleaseWriter(qw422016)
//line history/view.qtpl:55
}
//line history/view.qtpl:55
func (rev *Revision) asHistoryEntry(hyphaName string) string {
//line history/view.qtpl:55
qb422016 := qt422016.AcquireByteBuffer()
//line history/view.qtpl:55
rev.writeasHistoryEntry(qb422016, hyphaName)
//line history/view.qtpl:55
qs422016 := string(qb422016.B)
//line history/view.qtpl:55
qt422016.ReleaseByteBuffer(qb422016)
//line history/view.qtpl:55
return qs422016
//line history/view.qtpl:55
}

View File

@ -1,5 +1,6 @@
//go:generate qtc -dir=views
//go:generate qtc -dir=tree
//go:generate qtc -dir=history
//go:generate go-localize -input l18n_src -output l18n
// Command mycorrhiza is a program that runs a mycorrhiza wiki.
package main