mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-12-12 13:30:26 +00:00
Merge code from two branches, make it show something
This commit is contained in:
parent
c69002de7b
commit
2a3f346034
@ -9,8 +9,8 @@ type Genealogy struct {
|
|||||||
|
|
||||||
func setRelations(hyphae map[string]*Hypha) {
|
func setRelations(hyphae map[string]*Hypha) {
|
||||||
for name, h := range hyphae {
|
for name, h := range hyphae {
|
||||||
if _, ok := hyphae[h.ParentName]; ok && h.ParentName != "." {
|
if _, ok := hyphae[h.ParentName()]; ok && h.ParentName() != "." {
|
||||||
hyphae[h.ParentName].ChildrenNames = append(hyphae[h.ParentName].ChildrenNames, name)
|
hyphae[h.ParentName()].ChildrenNames = append(hyphae[h.ParentName()].ChildrenNames, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
84
hypha.go
84
hypha.go
@ -1,70 +1,52 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Hypha struct {
|
type Hypha struct {
|
||||||
// Hypha is physically located here. Most fields below are stored in <path>/mm.ini (mm for metametadata). Its revisions are physically located in <path>/<n>/ subfolders. <n> ∈ [0;∞] with 0 being latest revision, 1 the first.
|
FullName string
|
||||||
Path string
|
Path string
|
||||||
// Every hypha was created at some point
|
ViewCount int `json:"views"`
|
||||||
CreationTime int `json:"creationTime"`
|
Deleted bool `json:"deleted"`
|
||||||
// Hypha has name but it can be changed
|
Revisions map[string]*Revision `json:"revisions"`
|
||||||
Name string `json:"name"`
|
|
||||||
// Hypha can be deleted. If it is deleted, it is not indexed by most of the software but still can be recovered at some point.
|
|
||||||
Deleted bool `json:"deleted"`
|
|
||||||
// Fields below are not part of m.ini and are created when traversing the file tree.
|
|
||||||
// Hypha can be a child of any other hypha except its children. The parent hypha is stored in <path>/..
|
|
||||||
ParentName string
|
|
||||||
// Hypha can have any number of children which are stored as subfolders in <path>.
|
|
||||||
ChildrenNames []string
|
ChildrenNames []string
|
||||||
Revisions []Revision
|
parentName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Hypha) String() string {
|
func (h *Hypha) AddChild(childName string) {
|
||||||
var revbuf string
|
h.ChildrenNames = append(h.ChildrenNames, childName)
|
||||||
for _, r := range h.Revisions {
|
}
|
||||||
revbuf += r.String() + "\n"
|
|
||||||
|
// Used with action=zen|view
|
||||||
|
func (h *Hypha) AsHtml(hyphae map[string]*Hypha, rev string) (string, error) {
|
||||||
|
if "0" == rev {
|
||||||
|
rev = h.NewestRevision()
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("Hypha %v {\n\t"+
|
r, ok := h.Revisions[rev]
|
||||||
"path %v\n\t"+
|
if !ok {
|
||||||
"created at %v\n\t"+
|
return "", fmt.Errorf("Hypha %v has no such revision: %v", h.FullName, rev)
|
||||||
"child of %v\n\t"+
|
}
|
||||||
"parent of %v\n\t"+
|
html, err := r.AsHtml(hyphae)
|
||||||
"Having these revisions:\n%v"+
|
return html, err
|
||||||
"}\n", h.Name, h.Path, h.CreationTime, h.ParentName, h.ChildrenNames,
|
|
||||||
revbuf)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRevision(hyphae map[string]*Hypha, hyphaName string, rev string, w http.ResponseWriter) (Revision, bool) {
|
func (h *Hypha) Name() string {
|
||||||
for name, _ := range hyphae {
|
return h.FullName
|
||||||
if name == hyphaName {
|
}
|
||||||
for _, r := range hyphae[name].Revisions {
|
|
||||||
id, err := strconv.Atoi(rev)
|
func (h *Hypha) NewestRevision() string {
|
||||||
if err != nil {
|
var largest int
|
||||||
log.Println("No such revision", rev, "at hypha", hyphaName)
|
for k, _ := range h.Revisions {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
rev, _ := strconv.Atoi(k)
|
||||||
return Revision{}, false
|
if rev > largest {
|
||||||
}
|
largest = rev
|
||||||
if r.Id == id {
|
|
||||||
return r, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Revision{}, false
|
return strconv.Itoa(largest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// `rev` is the id of revision to render. If it = 0, the last one is rendered. If the revision is not found, an error is returned.
|
func (h *Hypha) ParentName() string {
|
||||||
func (h Hypha) Render(hyphae map[string]*Hypha, rev int) (ret string, err error) {
|
return h.parentName
|
||||||
for _, r := range h.Revisions {
|
|
||||||
if r.Id == rev {
|
|
||||||
return r.Render(hyphae)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", errors.New("Revision was not found")
|
|
||||||
}
|
}
|
||||||
|
29
main.go
29
main.go
@ -12,6 +12,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func GetRevision(hyphae map[string]*Hypha, hyphaName string, rev string, w http.ResponseWriter) (Revision, bool) {
|
||||||
|
for name, _ := range hyphae {
|
||||||
|
if name == hyphaName {
|
||||||
|
for id, r := range hyphae[name].Revisions {
|
||||||
|
if rev == id {
|
||||||
|
return *r, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Revision{}, false
|
||||||
|
}
|
||||||
|
|
||||||
func RevInMap(m map[string]string) string {
|
func RevInMap(m map[string]string) string {
|
||||||
if val, ok := m["rev"]; ok {
|
if val, ok := m["rev"]; ok {
|
||||||
return val
|
return val
|
||||||
@ -33,7 +46,7 @@ func HandlerGetBinary(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", rev.MimeType)
|
w.Header().Set("Content-Type", rev.BinaryMime)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(fileContents)
|
w.Write(fileContents)
|
||||||
log.Println("Showing image of", rev.FullName, rev.Id)
|
log.Println("Showing image of", rev.FullName, rev.Id)
|
||||||
@ -65,7 +78,7 @@ func HandlerZen(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
html, err := rev.Render(hyphae)
|
html, err := rev.AsHtml(hyphae)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to render", rev.FullName)
|
log.Println("Failed to render", rev.FullName)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
@ -83,7 +96,7 @@ func HandlerView(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
html, err := rev.Render(hyphae)
|
html, err := rev.AsHtml(hyphae)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Failed to render", rev.FullName)
|
log.Println("Failed to render", rev.FullName)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
@ -131,7 +144,7 @@ var hyphae map[string]*Hypha
|
|||||||
func hyphaeAsMap(hyphae []*Hypha) map[string]*Hypha {
|
func hyphaeAsMap(hyphae []*Hypha) map[string]*Hypha {
|
||||||
mh := make(map[string]*Hypha)
|
mh := make(map[string]*Hypha)
|
||||||
for _, h := range hyphae {
|
for _, h := range hyphae {
|
||||||
mh[h.Name] = h
|
mh[h.Name()] = h
|
||||||
}
|
}
|
||||||
return mh
|
return mh
|
||||||
}
|
}
|
||||||
@ -147,8 +160,8 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hyphae = hyphaeAsMap(recurFindHyphae(rootWikiDir))
|
hyphae = recurFindHyphae(rootWikiDir)
|
||||||
setRelations(hyphae)
|
// setRelations(hyphae)
|
||||||
|
|
||||||
// Start server code
|
// Start server code
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
@ -197,8 +210,8 @@ func main() {
|
|||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
for _, v := range hyphae {
|
for _, v := range hyphae {
|
||||||
log.Println("Rendering latest revision of hypha", v.Name)
|
log.Println("Rendering latest revision of hypha", v.Name())
|
||||||
html, err := v.Render(hyphae, 0)
|
html, err := v.AsHtml(hyphae, "0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(w, err)
|
fmt.Fprintln(w, err)
|
||||||
}
|
}
|
||||||
|
126
revision.go
126
revision.go
@ -5,55 +5,39 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gomarkdown/markdown"
|
"github.com/gomarkdown/markdown"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Revision struct {
|
type Revision struct {
|
||||||
// Revision is hypha's state at some point in time. Future revisions are not really supported. Most data here is stored in m.ini.
|
Id int
|
||||||
Id int
|
Tags []string `json:"tags"`
|
||||||
// Name used at this revision
|
FullName string `json:"name"`
|
||||||
Name string `json:"name"`
|
Comment string `json:"comment"`
|
||||||
// Name of hypha
|
Author string `json:"author"`
|
||||||
FullName string
|
Time int `json:"time"`
|
||||||
// Present in every hypha. Stored in t.txt.
|
TextMime string `json:"text_mime"`
|
||||||
TextPath string
|
BinaryMime string `json:"binary_mime"`
|
||||||
// In at least one markup. Supported ones are "myco", "html", "md", "plain"
|
TextPath string
|
||||||
Markup string `json:"markup"`
|
BinaryPath string
|
||||||
// Some hyphæ have binary contents such as images. Their presence change hypha's behavior in a lot of ways (see methods' implementations). If stored, it is stored in b (filename "b")
|
|
||||||
BinaryPath string
|
|
||||||
BinaryRequest string
|
|
||||||
// To tell what is meaning of binary content, mimeType for them is stored. If the hypha has no binary content, this field must be "application/x-hypha"
|
|
||||||
MimeType string `json:"mimeType"`
|
|
||||||
// Every revision was created at some point. This field stores the creation time of the latest revision
|
|
||||||
RevisionTime int `json:"createdAt"`
|
|
||||||
// Every hypha has any number of tags
|
|
||||||
Tags []string `json:"tags"`
|
|
||||||
// Current revision is authored by someone
|
|
||||||
RevisionAuthor string `json:"author"`
|
|
||||||
// and has a comment in plain text
|
|
||||||
RevisionComment string `json:"comment"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Revision) String() string {
|
// During initialisation, it is guaranteed that r.BinaryMime is set to "" if the revision has no binary data.
|
||||||
return fmt.Sprintf(`Revision %v created at %v {
|
func (r *Revision) hasBinaryData() bool {
|
||||||
name: %v
|
return r.BinaryMime != ""
|
||||||
textPath: %v
|
|
||||||
markup: %v
|
|
||||||
binaryPath: %v
|
|
||||||
mimeType: %v
|
|
||||||
tags: %v
|
|
||||||
revisionAuthor: %v
|
|
||||||
revisionComment: %v
|
|
||||||
}`, h.Id, h.RevisionTime, h.Name, h.TextPath, h.Markup, h.BinaryPath, h.MimeType, h.Tags, h.RevisionAuthor, h.RevisionComment)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is meant to be called only by Hypha#Render.
|
func (r *Revision) urlOfBinary() string {
|
||||||
func (r Revision) Render(hyphae map[string]*Hypha) (ret string, err error) {
|
return fmt.Sprintf("/%s?action=getBinary&rev=%d", r.FullName, r.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: use templates https://github.com/bouncepaw/mycorrhiza/issues/2
|
||||||
|
func (r *Revision) AsHtml(hyphae map[string]*Hypha) (ret string, err error) {
|
||||||
ret += `<article class="page">
|
ret += `<article class="page">
|
||||||
`
|
`
|
||||||
// If it is a binary hypha (we support only images for now):
|
// TODO: support things other than images
|
||||||
// TODO: support things other than images.
|
if r.hasBinaryData() {
|
||||||
if r.MimeType != "application/x-hypha" {
|
ret += fmt.Sprintf(`<img src="/%s" class="page__image"/>`, r.urlOfBinary())
|
||||||
ret += fmt.Sprintf(`<img src="/%s" class="page__image"/>`, r.BinaryRequest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err := ioutil.ReadFile(r.TextPath)
|
contents, err := ioutil.ReadFile(r.TextPath)
|
||||||
@ -63,17 +47,71 @@ func (r Revision) Render(hyphae map[string]*Hypha) (ret string, err error) {
|
|||||||
|
|
||||||
// TODO: support more markups.
|
// TODO: support more markups.
|
||||||
// TODO: support mycorrhiza extensions like transclusion.
|
// TODO: support mycorrhiza extensions like transclusion.
|
||||||
switch r.Markup {
|
switch r.TextMime {
|
||||||
case "plain":
|
case "text/plain":
|
||||||
ret += fmt.Sprintf(`<pre>%s</pre>`, contents)
|
ret += fmt.Sprintf(`<pre>%s</pre>`, contents)
|
||||||
case "md":
|
case "text/markdown":
|
||||||
html := markdown.ToHTML(contents, nil, nil)
|
html := markdown.ToHTML(contents, nil, nil)
|
||||||
ret += string(html)
|
ret += string(html)
|
||||||
default:
|
default:
|
||||||
return "", errors.New("Unsupported markup: " + r.Markup)
|
return "", errors.New("Unsupported mime-type: " + r.TextMime)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret += `
|
ret += `
|
||||||
</article>`
|
</article>`
|
||||||
return ret, nil
|
return ret, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Revision) ActionGetBinary(w http.ResponseWriter) {
|
||||||
|
fileContents, err := ioutil.ReadFile(r.urlOfBinary())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to load binary data of", r.FullName, r.Id)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", r.BinaryMime)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(fileContents)
|
||||||
|
log.Println("Serving binary data of", r.FullName, r.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Revision) ActionRaw(w http.ResponseWriter) {
|
||||||
|
fileContents, err := ioutil.ReadFile(r.TextPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to load text data of", r.FullName, r.Id)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", r.TextMime)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write(fileContents)
|
||||||
|
log.Println("Serving text data of", r.FullName, r.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Revision) ActionZen(w http.ResponseWriter, hyphae map[string]*Hypha) {
|
||||||
|
html, err := r.AsHtml(hyphae)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to render", r.FullName)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, html)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Revision) ActionView(w http.ResponseWriter, hyphae map[string]*Hypha, layoutFun func(map[string]*Hypha, Revision, string) string) {
|
||||||
|
html, err := r.AsHtml(hyphae)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("Failed to render", r.FullName)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, layoutFun(hyphae, *r, html))
|
||||||
|
log.Println("Rendering", r.FullName)
|
||||||
|
}
|
||||||
|
func (r *Revision) Name() string {
|
||||||
|
return r.FullName
|
||||||
|
}
|
||||||
|
244
walk.go
244
walk.go
@ -2,190 +2,156 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func scanHyphaDir(fullPath string) (structureMet bool, possibleRevisionPaths []string, possibleHyphaPaths []string, reterr error) {
|
const (
|
||||||
|
hyphaPattern = `[^\s\d:/?&\\][^:?&\\]*`
|
||||||
|
hyphaUrl = `/{hypha:` + hyphaPattern + `}`
|
||||||
|
revisionPattern = `[\d]+`
|
||||||
|
revQuery = `{rev:` + revisionPattern + `}`
|
||||||
|
revTxtPattern = revisionPattern + `\.txt`
|
||||||
|
revBinPattern = revisionPattern + `\.bin`
|
||||||
|
metaJsonPattern = `meta\.json`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
leadingInt = regexp.MustCompile(`^[-+]?\d+`)
|
||||||
|
)
|
||||||
|
|
||||||
|
func matchNameToEverything(name string) (hyphaM bool, revTxtM bool, revBinM bool, metaJsonM bool) {
|
||||||
|
simpleMatch := func(s string, p string) bool {
|
||||||
|
m, _ := regexp.MatchString(p, s)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case simpleMatch(name, revTxtPattern):
|
||||||
|
revTxtM = true
|
||||||
|
case simpleMatch(name, revBinPattern):
|
||||||
|
revBinM = true
|
||||||
|
case simpleMatch(name, metaJsonPattern):
|
||||||
|
metaJsonM = true
|
||||||
|
case simpleMatch(name, hyphaPattern):
|
||||||
|
hyphaM = true
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripLeadingInt(s string) string {
|
||||||
|
return leadingInt.FindString(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func hyphaDirRevsValidate(dto map[string]map[string]string) (res bool) {
|
||||||
|
for k, _ := range dto {
|
||||||
|
switch k {
|
||||||
|
case "0":
|
||||||
|
delete(dto, "0")
|
||||||
|
default:
|
||||||
|
res = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanHyphaDir(fullPath string) (valid bool, revs map[string]map[string]string, possibleSubhyphae []string, metaJsonPath string, err error) {
|
||||||
|
revs = make(map[string]map[string]string)
|
||||||
nodes, err := ioutil.ReadDir(fullPath)
|
nodes, err := ioutil.ReadDir(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
reterr = err
|
|
||||||
return // implicit return values
|
return // implicit return values
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
mmJsonPresent bool
|
|
||||||
zeroDirPresent bool
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
matchedHypha, _ := regexp.MatchString(hyphaPattern, node.Name())
|
hyphaM, revTxtM, revBinM, metaJsonM := matchNameToEverything(node.Name())
|
||||||
matchedRev, _ := regexp.MatchString(revisionPattern, node.Name())
|
|
||||||
switch {
|
switch {
|
||||||
case matchedRev && node.IsDir():
|
case hyphaM && node.IsDir():
|
||||||
if node.Name() == "0" {
|
possibleSubhyphae = append(possibleSubhyphae, filepath.Join(fullPath, node.Name()))
|
||||||
zeroDirPresent = true
|
case revTxtM && !node.IsDir():
|
||||||
|
revId := stripLeadingInt(node.Name())
|
||||||
|
if _, ok := revs[revId]; !ok {
|
||||||
|
revs[revId] = make(map[string]string)
|
||||||
}
|
}
|
||||||
possibleRevisionPaths = append(
|
revs[revId]["txt"] = filepath.Join(fullPath, node.Name())
|
||||||
possibleRevisionPaths,
|
case revBinM && !node.IsDir():
|
||||||
filepath.Join(fullPath, node.Name()),
|
revId := stripLeadingInt(node.Name())
|
||||||
)
|
if _, ok := revs[revId]; !ok {
|
||||||
case (node.Name() == "mm.json") && !node.IsDir():
|
revs[revId] = make(map[string]string)
|
||||||
mmJsonPresent = true
|
}
|
||||||
case matchedHypha && node.IsDir():
|
revs[revId]["bin"] = filepath.Join(fullPath, node.Name())
|
||||||
possibleHyphaPaths = append(
|
case metaJsonM && !node.IsDir():
|
||||||
possibleHyphaPaths,
|
metaJsonPath = filepath.Join(fullPath, "meta.json")
|
||||||
filepath.Join(fullPath, node.Name()),
|
|
||||||
)
|
|
||||||
// Other nodes are ignored. It is not promised they will be ignored in future versions
|
// Other nodes are ignored. It is not promised they will be ignored in future versions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if mmJsonPresent && zeroDirPresent {
|
valid = hyphaDirRevsValidate(revs)
|
||||||
structureMet = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return // implicit return values
|
return // implicit return values
|
||||||
}
|
}
|
||||||
|
|
||||||
func check(e error) {
|
|
||||||
if e != nil {
|
|
||||||
panic(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hypha name is rootWikiDir/{here}
|
// Hypha name is rootWikiDir/{here}
|
||||||
func hyphaName(fullPath string) string {
|
func hyphaName(fullPath string) string {
|
||||||
return fullPath[len(rootWikiDir)+1:]
|
return fullPath[len(rootWikiDir)+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
func recurFindHyphae(fullPath string) map[string]*Hypha {
|
||||||
hyphaPattern = `[^\s\d:/?&\\][^:?&\\]*`
|
hyphae := make(map[string]*Hypha)
|
||||||
revisionPattern = `[\d]+`
|
valid, revs, possibleSubhyphae, metaJsonPath, err := scanHyphaDir(fullPath)
|
||||||
hyphaUrl = "/{hypha:" + hyphaPattern + "}"
|
|
||||||
revQuery = `{rev:[\d]+}`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sends found hyphae to the `ch`. `fullPath` is tested for hyphaness, then its subdirs with hyphaesque names are tested too using goroutines for each subdir. The function is recursive.
|
|
||||||
func recurFindHyphae(fullPath string) (hyphae []*Hypha) {
|
|
||||||
|
|
||||||
structureMet, possibleRevisionPaths, possibleHyphaPaths, err := scanHyphaDir(fullPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return hyphae
|
return hyphae
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, let's process inner hyphae
|
// First, let's process subhyphae
|
||||||
for _, possibleHyphaPath := range possibleHyphaPaths {
|
for _, possibleSubhypha := range possibleSubhyphae {
|
||||||
hyphae = append(hyphae, recurFindHyphae(possibleHyphaPath)...)
|
for k, v := range recurFindHyphae(possibleSubhypha) {
|
||||||
|
hyphae[k] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This folder is not a hypha itself, nothing to do here
|
// This folder is not a hypha itself, nothing to do here
|
||||||
if !structureMet {
|
if !valid {
|
||||||
return hyphae
|
return hyphae
|
||||||
}
|
}
|
||||||
|
|
||||||
// Template hypha struct. Other fields are default jsont values.
|
// Template hypha struct. Other fields are default json values.
|
||||||
h := Hypha{
|
h := Hypha{
|
||||||
|
FullName: hyphaName(fullPath),
|
||||||
Path: fullPath,
|
Path: fullPath,
|
||||||
Name: hyphaName(fullPath),
|
parentName: filepath.Dir(hyphaName(fullPath)),
|
||||||
ParentName: filepath.Dir(hyphaName(fullPath)),
|
|
||||||
// Children names are unknown now
|
// Children names are unknown now
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in every revision
|
metaJsonContents, err := ioutil.ReadFile(metaJsonPath)
|
||||||
for _, possibleRevisionPath := range possibleRevisionPaths {
|
if err != nil {
|
||||||
rev, err := makeRevision(possibleRevisionPath, h.Name)
|
log.Printf("Error when reading `%s`; skipping", metaJsonPath)
|
||||||
if err == nil {
|
return hyphae
|
||||||
h.Revisions = append(h.Revisions, rev)
|
}
|
||||||
}
|
err = json.Unmarshal(metaJsonContents, &h)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error when unmarshaling `%s`; skipping", metaJsonPath)
|
||||||
|
return hyphae
|
||||||
}
|
}
|
||||||
|
|
||||||
mmJsonPath := filepath.Join(fullPath, "mm.json")
|
// Fill in every revision paths
|
||||||
mmJsonContents, err := ioutil.ReadFile(mmJsonPath)
|
for id, paths := range revs {
|
||||||
if err != nil {
|
if r, ok := h.Revisions[id]; ok {
|
||||||
fmt.Println(fullPath, ">\tError:", err)
|
for fType, fPath := range paths {
|
||||||
return hyphae
|
switch fType {
|
||||||
}
|
case "bin":
|
||||||
err = json.Unmarshal(mmJsonContents, &h)
|
r.BinaryPath = fPath
|
||||||
if err != nil {
|
case "txt":
|
||||||
fmt.Println(fullPath, ">\tError:", err)
|
r.TextPath = fPath
|
||||||
return hyphae
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Error when reading hyphae from disk: hypha `%s`'s meta.json provided no information about revision `%s`, but files %s are provided; skipping\n", h.FullName, id, paths)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now the hypha should be ok, gotta send structs
|
// Now the hypha should be ok, gotta send structs
|
||||||
hyphae = append(hyphae, &h)
|
hyphae[h.FullName] = &h
|
||||||
return hyphae
|
return hyphae
|
||||||
}
|
|
||||||
|
|
||||||
func makeRevision(fullPath string, fullName string) (r Revision, err error) {
|
|
||||||
// fullPath is expected to be a path to a dir.
|
|
||||||
// Revision directory must have at least `m.json` and `t.txt` files.
|
|
||||||
var (
|
|
||||||
mJsonPresent bool
|
|
||||||
tTxtPresent bool
|
|
||||||
bPresent bool
|
|
||||||
)
|
|
||||||
|
|
||||||
nodes, err := ioutil.ReadDir(fullPath)
|
|
||||||
if err != nil {
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, node := range nodes {
|
|
||||||
if node.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch node.Name() {
|
|
||||||
case "m.json":
|
|
||||||
mJsonPresent = true
|
|
||||||
case "t.txt":
|
|
||||||
tTxtPresent = true
|
|
||||||
case "b":
|
|
||||||
bPresent = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !(mJsonPresent && tTxtPresent) {
|
|
||||||
return r, errors.New("makeRevision: m.json and t.txt files are not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all the flags are true, this directory is assumed to be a revision. Gotta check further. This is template Revision struct. Other fields fall back to default init values.
|
|
||||||
mJsonPath := filepath.Join(fullPath, "m.json")
|
|
||||||
mJsonContents, err := ioutil.ReadFile(mJsonPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(fullPath, ">\tError:", err)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r = Revision{FullName: fullName}
|
|
||||||
err = json.Unmarshal(mJsonContents, &r)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(fullPath, ">\tError:", err)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now, let's fill in t.txt path
|
|
||||||
r.TextPath = filepath.Join(fullPath, "t.txt")
|
|
||||||
|
|
||||||
// There's sense in reading binary file only if the hypha is marked as such
|
|
||||||
if r.MimeType != "application/x-hypha" {
|
|
||||||
// Do not check for binary file presence, attempt to read it will fail anyway
|
|
||||||
if bPresent {
|
|
||||||
r.BinaryPath = filepath.Join(fullPath, "b")
|
|
||||||
r.BinaryRequest = fmt.Sprintf("%s?rev=%d&action=getBinary", r.FullName, r.Id)
|
|
||||||
} else {
|
|
||||||
return r, errors.New("makeRevision: b file not present")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// So far, so good. Let's fill in id. It is guaranteed to be correct, so no error checking
|
|
||||||
id, _ := strconv.Atoi(filepath.Base(fullPath))
|
|
||||||
r.Id = id
|
|
||||||
|
|
||||||
// It is safe now to return, I guess
|
|
||||||
return r, nil
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user