mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2024-12-12 05:20:26 +00:00
Hypha search works ok
This commit is contained in:
commit
79991a3e64
16
genealogy.go
Normal file
16
genealogy.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/* Genealogy is all about relationships between hyphae. For now, the only goal of this file is to help find children of hyphae as they are not marked during the hypha search phase.
|
||||||
|
*/
|
||||||
|
package main
|
||||||
|
|
||||||
|
type Genealogy struct {
|
||||||
|
parent string
|
||||||
|
child string
|
||||||
|
}
|
||||||
|
|
||||||
|
func setRelations(hyphae map[string]*Hypha) {
|
||||||
|
for name, h := range hyphae {
|
||||||
|
if _, ok := hyphae[h.ParentName]; ok && h.ParentName != "." {
|
||||||
|
hyphae[h.ParentName].ChildrenNames = append(hyphae[h.ParentName].ChildrenNames, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
module github.com/bouncepaw/mycorrhiza
|
||||||
|
|
||||||
|
go 1.14
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/mux v1.7.4
|
||||||
|
github.com/sirupsen/logrus v1.6.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.57.0
|
||||||
|
)
|
12
go.sum
Normal file
12
go.sum
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||||
|
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
||||||
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||||
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
||||||
|
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
74
hypha.go
Normal file
74
hypha.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.
|
||||||
|
Path string
|
||||||
|
// Every hypha was created at some point
|
||||||
|
CreationTime int `json:"creationTime"`
|
||||||
|
// Hypha has name but it can be changed
|
||||||
|
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
|
||||||
|
Revisions []Revision
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Hypha) String() string {
|
||||||
|
var revbuf string
|
||||||
|
for _, r := range h.Revisions {
|
||||||
|
revbuf += r.String() + "\n"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Hypha %v {\n\t"+
|
||||||
|
"path %v\n\t"+
|
||||||
|
"created at %v\n\t"+
|
||||||
|
"child of %v\n\t"+
|
||||||
|
"parent of %v\n\t"+
|
||||||
|
"Having these revisions:\n%v"+
|
||||||
|
"}\n", h.Name, h.Path, h.CreationTime, h.ParentName, h.ChildrenNames,
|
||||||
|
revbuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
// Name used at this revision
|
||||||
|
Name string `json:"name"`
|
||||||
|
// Present in every hypha. Stored in t.txt.
|
||||||
|
TextPath string
|
||||||
|
// In at least one markup. Supported ones are "myco", "html", "md", "plain"
|
||||||
|
Markup string `json:"markup"`
|
||||||
|
// 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
|
||||||
|
// 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"`
|
||||||
|
// Rest of fields are ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Revision) String() string {
|
||||||
|
return fmt.Sprintf(`Revision %v created at %v {
|
||||||
|
name: %v
|
||||||
|
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)
|
||||||
|
}
|
46
main.gg
Normal file
46
main.gg
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegexForHypha(s string) string {
|
||||||
|
return s + ":[^\\s\\d:/?&\\\\][^:?&\\\\]*}"
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditFillHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func HyphaHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
fmt.Fprintf(w, "Accessing hypha %v\n", vars["hypha"])
|
||||||
|
fmt.Fprintf(w, "Request at %v", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.Queries(
|
||||||
|
"action", "edit",
|
||||||
|
"fill", "{templateName}").
|
||||||
|
Path(RegexForHypha("/{hypha")).
|
||||||
|
HandlerFunc(EditFillHandler)
|
||||||
|
|
||||||
|
r.HandleFunc(RegexForHypha("/{hypha"), HyphaHandler)
|
||||||
|
r.HandleFunc("/kek", HyphaHandler)
|
||||||
|
http.Handle("/", r)
|
||||||
|
|
||||||
|
srv := &http.Server{
|
||||||
|
Handler: r,
|
||||||
|
Addr: "127.0.0.1:8000",
|
||||||
|
// Good practice: enforce timeouts for servers you create!
|
||||||
|
WriteTimeout: 15 * time.Second,
|
||||||
|
ReadTimeout: 15 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Fatal(srv.ListenAndServe())
|
||||||
|
}
|
34
main.go
Normal file
34
main.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootWikiDir string
|
||||||
|
|
||||||
|
func hyphaeAsMap(hyphae []*Hypha) map[string]*Hypha {
|
||||||
|
mh := make(map[string]*Hypha)
|
||||||
|
for _, h := range hyphae {
|
||||||
|
mh[h.Name] = h
|
||||||
|
}
|
||||||
|
return mh
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) == 1 {
|
||||||
|
panic("Expected a root wiki pages directory")
|
||||||
|
}
|
||||||
|
// Required so the rootWikiDir hereinbefore does not get redefined.
|
||||||
|
var err error
|
||||||
|
rootWikiDir, err = filepath.Abs(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hyphae := hyphaeAsMap(recurFindHyphae(rootWikiDir))
|
||||||
|
setRelations(hyphae)
|
||||||
|
|
||||||
|
fmt.Println(hyphae)
|
||||||
|
}
|
1
w/README.md
Normal file
1
w/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
This is root wiki directory.
|
9
w/m/Fruit/0/m.json
Normal file
9
w/m/Fruit/0/m.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"createdAt": 1591636222,
|
||||||
|
"comment": "update Fruit",
|
||||||
|
"tags": ["fetus", "tasty"],
|
||||||
|
"mimeType": "application/x-hypha",
|
||||||
|
"markup": "md",
|
||||||
|
"name": "Fruit",
|
||||||
|
"author": "fungimaster"
|
||||||
|
}
|
3
w/m/Fruit/0/t.txt
Normal file
3
w/m/Fruit/0/t.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Fruit
|
||||||
|
Many people ask the question: what is fruit exactly?
|
||||||
|
Fruit is a type of fetus. Is tasty so cool coo l ha i love fwriotwsn
|
8
w/m/Fruit/1/m.json
Normal file
8
w/m/Fruit/1/m.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"createdAt":1591635559,
|
||||||
|
"comment":"create Fruit",
|
||||||
|
"tags":["fetus", "tasty"],
|
||||||
|
"mimeType":"application/x-hypha",
|
||||||
|
"markup":"md",
|
||||||
|
"name":"Fruit",
|
||||||
|
"author":"bouncepaw"}
|
1
w/m/Fruit/1/t.txt
Normal file
1
w/m/Fruit/1/t.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fruit is a type of fetus. Is tasty so cool coo l ha i love fwriotwsn
|
BIN
w/m/Fruit/Apple/0/b
Normal file
BIN
w/m/Fruit/Apple/0/b
Normal file
Binary file not shown.
After Width: | Height: | Size: 43 KiB |
7
w/m/Fruit/Apple/0/m.json
Normal file
7
w/m/Fruit/Apple/0/m.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{"createdAt":1591639464,
|
||||||
|
"comment":"add apple pic hehehe",
|
||||||
|
"tags":["img"],
|
||||||
|
"mimeType":"image/jpeg",
|
||||||
|
"markup":"plain",
|
||||||
|
"name":"Apple",
|
||||||
|
"author":"bouncepaw"}
|
3
w/m/Fruit/Apple/0/t.txt
Normal file
3
w/m/Fruit/Apple/0/t.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
A honeycrisp apple from an organic food farm co-op.
|
||||||
|
|
||||||
|
Source: https://ru.wikipedia.org/wiki/%D0%A4%D0%B0%D0%B9%D0%BB:Honeycrisp-Apple.jpg
|
1
w/m/Fruit/Apple/mm.json
Normal file
1
w/m/Fruit/Apple/mm.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"creationTime": 1591639284}
|
1
w/m/Fruit/mm.json
Normal file
1
w/m/Fruit/mm.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"creationTime":1591635559}
|
1
w/m/README.md
Normal file
1
w/m/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
This is directory with hyphae
|
188
walk.go
Normal file
188
walk.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func scanHyphaDir(fullPath string) (structureMet bool, possibleRevisionPaths []string, possibleHyphaPaths []string, reterr error) {
|
||||||
|
nodes, err := ioutil.ReadDir(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
reterr = err
|
||||||
|
return // implicit return values
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mmJsonPresent bool
|
||||||
|
zeroDirPresent bool
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
matchedHypha, _ := regexp.MatchString(hyphaPattern, node.Name())
|
||||||
|
matchedRev, _ := regexp.MatchString(revisionPattern, node.Name())
|
||||||
|
switch {
|
||||||
|
case matchedRev && node.IsDir():
|
||||||
|
if node.Name() == "0" {
|
||||||
|
zeroDirPresent = true
|
||||||
|
}
|
||||||
|
possibleRevisionPaths = append(
|
||||||
|
possibleRevisionPaths,
|
||||||
|
filepath.Join(fullPath, node.Name()),
|
||||||
|
)
|
||||||
|
case (node.Name() == "mm.json") && !node.IsDir():
|
||||||
|
mmJsonPresent = true
|
||||||
|
case matchedHypha && node.IsDir():
|
||||||
|
possibleHyphaPaths = append(
|
||||||
|
possibleHyphaPaths,
|
||||||
|
filepath.Join(fullPath, node.Name()),
|
||||||
|
)
|
||||||
|
// Other nodes are ignored. It is not promised they will be ignored in future versions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mmJsonPresent && zeroDirPresent {
|
||||||
|
structureMet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return // implicit return values
|
||||||
|
}
|
||||||
|
|
||||||
|
func check(e error) {
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hypha name is rootWikiDir/{here}
|
||||||
|
func hyphaName(fullPath string) string {
|
||||||
|
return fullPath[len(rootWikiDir)+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
hyphaPattern = `[^\s\d:/?&\\][^:?&\\]*`
|
||||||
|
revisionPattern = `[\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 {
|
||||||
|
return hyphae
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, let's process inner hyphae
|
||||||
|
for _, possibleHyphaPath := range possibleHyphaPaths {
|
||||||
|
hyphae = append(hyphae, recurFindHyphae(possibleHyphaPath)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This folder is not a hypha itself, nothing to do here
|
||||||
|
if !structureMet {
|
||||||
|
return hyphae
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template hypha struct. Other fields are default jsont values.
|
||||||
|
h := Hypha{
|
||||||
|
Path: fullPath,
|
||||||
|
Name: hyphaName(fullPath),
|
||||||
|
ParentName: filepath.Dir(hyphaName(fullPath)),
|
||||||
|
// Children names are unknown now
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in every revision
|
||||||
|
for _, possibleRevisionPath := range possibleRevisionPaths {
|
||||||
|
rev, err := makeRevision(possibleRevisionPath)
|
||||||
|
if err == nil {
|
||||||
|
h.Revisions = append(h.Revisions, rev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mmJsonPath := filepath.Join(fullPath, "mm.json")
|
||||||
|
mmJsonContents, err := ioutil.ReadFile(mmJsonPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(fullPath, ">\tError:", err)
|
||||||
|
return hyphae
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(mmJsonContents, &h)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(fullPath, ">\tError:", err)
|
||||||
|
return hyphae
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now the hypha should be ok, gotta send structs
|
||||||
|
hyphae = append(hyphae, &h)
|
||||||
|
return hyphae
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRevision(fullPath 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{}
|
||||||
|
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")
|
||||||
|
} 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