initial commit
This commit is contained in:
commit
3f79fc8e6c
527 changed files with 373170 additions and 0 deletions
252
engine.go
Normal file
252
engine.go
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"html/template"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blevesearch/bleve"
|
||||
"github.com/blevesearch/bleve/document"
|
||||
)
|
||||
|
||||
const timeFmt = "2006-01-02 15:04:05"
|
||||
|
||||
var titleRe = regexp.MustCompile("[^a-zA-Z0-9+]+")
|
||||
|
||||
func now() string {
|
||||
return time.Now().Format(timeFmt)
|
||||
}
|
||||
|
||||
type Article struct {
|
||||
Title string `json:"title"`
|
||||
LinkTitle string
|
||||
Search string `json:"search"`
|
||||
Index template.HTML `json:"index"`
|
||||
Text template.HTML `json:"text"`
|
||||
MD string `json:"md"`
|
||||
Created string `json:"created"`
|
||||
Updated string `json:"updated"`
|
||||
Public bool `json:"public"`
|
||||
}
|
||||
|
||||
type Engine struct {
|
||||
index bleve.Index
|
||||
}
|
||||
|
||||
func NewEngine(index bleve.Index) *Engine {
|
||||
return &Engine{index}
|
||||
}
|
||||
|
||||
func (e *Engine) Close() error {
|
||||
return e.index.Close()
|
||||
}
|
||||
|
||||
func (e *Engine) Index(title, text, user string, public bool) (string, error) {
|
||||
if user == "" {
|
||||
return "", errors.New("engine: user cannot be empty")
|
||||
}
|
||||
title = titleRe.ReplaceAllString(title, "")
|
||||
newTitle := strings.Replace(title, " ", "+", -1)
|
||||
if err := e.Exists(false, newTitle); err == nil {
|
||||
return "", errors.New("engine: article already exists")
|
||||
}
|
||||
art := render(text)
|
||||
art.Title = title
|
||||
art.Created = user + " " + now()
|
||||
art.Public = public
|
||||
e.index.Index(newTitle, art)
|
||||
return newTitle, nil
|
||||
}
|
||||
|
||||
func (e *Engine) Exists(public bool, title string) error {
|
||||
_, err := e.Get(public, title)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Engine) Delete(title string) error {
|
||||
if title == "Index" {
|
||||
return errors.New("engine: the index page cannot be deleted")
|
||||
}
|
||||
if err := e.Exists(false, title); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.index.Delete(title)
|
||||
}
|
||||
|
||||
func (e *Engine) Update(title, text, user string, public bool) error {
|
||||
if user == "" {
|
||||
return errors.New("engine: user cannot be empty")
|
||||
}
|
||||
if title == "Index" && !public {
|
||||
return errors.New("engine: page 'Index' must stay public")
|
||||
}
|
||||
if err := e.Exists(false, title); err != nil {
|
||||
return err
|
||||
}
|
||||
art, err := e.Get(false, title)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rend := render(text)
|
||||
art.Updated = user + " " + now()
|
||||
art.Search = rend.Search
|
||||
art.Index = rend.Index
|
||||
art.Text = rend.Text
|
||||
art.MD = rend.MD
|
||||
art.Public = public
|
||||
err = e.index.Index(title, art)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Engine) Get(public bool, title string) (Article, error) {
|
||||
var art Article
|
||||
doc, err := e.index.Document(title)
|
||||
if err != nil {
|
||||
return art, err
|
||||
}
|
||||
if doc == nil || doc.Fields == nil {
|
||||
return art, errors.New("engine: article does not esist")
|
||||
}
|
||||
for _, field := range doc.Fields {
|
||||
switch f := field.(type) {
|
||||
case *document.TextField:
|
||||
switch field.Name() {
|
||||
case "title":
|
||||
art.Title = string(f.Value())
|
||||
case "index":
|
||||
art.Index = template.HTML(string(f.Value()))
|
||||
case "text":
|
||||
art.Text = template.HTML(string(f.Value()))
|
||||
case "md":
|
||||
art.MD = string(f.Value())
|
||||
case "created":
|
||||
art.Created = string(f.Value())
|
||||
case "updated":
|
||||
art.Updated = string(f.Value())
|
||||
}
|
||||
case *document.BooleanField:
|
||||
if field.Name() == "public" {
|
||||
p, err := f.Boolean()
|
||||
if err != nil {
|
||||
return art, err
|
||||
}
|
||||
art.Public = p
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
art.LinkTitle = title
|
||||
if public && !art.Public {
|
||||
err = errors.New("engine: article does not exist")
|
||||
}
|
||||
return art, err
|
||||
}
|
||||
|
||||
func (e *Engine) Search(public bool, search string) (string, error) {
|
||||
var (
|
||||
res *bleve.SearchResult
|
||||
err error
|
||||
)
|
||||
if public {
|
||||
req := bleve.NewSearchRequest(bleve.NewConjunctionQuery([]bleve.Query{
|
||||
bleve.NewBoolFieldQuery(true).SetField("public"),
|
||||
bleve.NewQueryStringQuery("title:" + search + " search:" + search),
|
||||
}))
|
||||
req.Highlight = bleve.NewHighlightWithStyle("html")
|
||||
res, err = e.index.Search(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
req := bleve.NewSearchRequest(bleve.NewQueryStringQuery("title:" + search + " search:" + search))
|
||||
req.Highlight = bleve.NewHighlightWithStyle("html")
|
||||
res, err = e.index.Search(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
}
|
||||
if res.Total == 0 {
|
||||
return "<h4>No search results</h4>", nil
|
||||
} else if res.Total > 100 {
|
||||
return "", errors.New("engine: too many search results")
|
||||
}
|
||||
html := ""
|
||||
for _, hit := range res.Hits {
|
||||
art, err := e.Get(false, hit.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
html += `<h5><a href="/wiki/` + hit.ID + `">` + art.Title + "</a></h5>"
|
||||
for fragField, frags := range hit.Fragments {
|
||||
if fragField == "title" || fragField == "search" {
|
||||
html += `<pre style="white-space: pre-wrap">`
|
||||
for _, f := range frags {
|
||||
html += f
|
||||
}
|
||||
html += "</pre>"
|
||||
break
|
||||
}
|
||||
}
|
||||
html += "<br>"
|
||||
}
|
||||
return html, nil
|
||||
}
|
||||
|
||||
type Title struct {
|
||||
Title string
|
||||
LinkTitle string
|
||||
}
|
||||
|
||||
type Titles []Title
|
||||
|
||||
func (t Titles) Len() int { return len(t) }
|
||||
func (t Titles) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t Titles) Less(i, j int) bool { return t[i].Title < t[j].Title }
|
||||
|
||||
func (e *Engine) GetAll(public bool) (t Titles, err error) {
|
||||
var res *bleve.SearchResult
|
||||
if public {
|
||||
req := bleve.NewSearchRequest(bleve.NewBoolFieldQuery(true).SetField("public"))
|
||||
res, err = e.index.Search(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
req := bleve.NewSearchRequest(bleve.NewMatchAllQuery())
|
||||
res, err = e.index.Search(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
t = make(Titles, len(res.Hits)-1)
|
||||
i := 0
|
||||
for _, v := range res.Hits {
|
||||
if v.ID == "Index" {
|
||||
continue
|
||||
}
|
||||
doc, err := e.index.Document(v.ID)
|
||||
if err != nil || doc == nil || doc.Fields == nil {
|
||||
continue
|
||||
}
|
||||
for _, field := range doc.Fields {
|
||||
if field.Name() != "title" {
|
||||
continue
|
||||
}
|
||||
f, ok := field.(*document.TextField)
|
||||
if ok {
|
||||
t[i] = Title{string(f.Value()), v.ID}
|
||||
i++
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(t)
|
||||
return
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue