commit 79991a3e64442a9e8afa92f1249801a69d688df7 Author: Timur Ismagilov Date: Fri Jun 12 21:22:02 2020 +0500 Hypha search works ok diff --git a/genealogy.go b/genealogy.go new file mode 100644 index 0000000..bc8354e --- /dev/null +++ b/genealogy.go @@ -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) + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..48a716c --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5c85cbd --- /dev/null +++ b/go.sum @@ -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= diff --git a/hypha b/hypha new file mode 100755 index 0000000..e3d178d Binary files /dev/null and b/hypha differ diff --git a/hypha.go b/hypha.go new file mode 100644 index 0000000..4151491 --- /dev/null +++ b/hypha.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" +) + +type Hypha struct { + // Hypha is physically located here. Most fields below are stored in /mm.ini (mm for metametadata). Its revisions are physically located in // subfolders. ∈ [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 /.. + ParentName string + // Hypha can have any number of children which are stored as subfolders in . + 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) +} diff --git a/main.gg b/main.gg new file mode 100644 index 0000000..21ac5fc --- /dev/null +++ b/main.gg @@ -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()) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..abe480b --- /dev/null +++ b/main.go @@ -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) +} diff --git a/w/README.md b/w/README.md new file mode 100644 index 0000000..46c1a4f --- /dev/null +++ b/w/README.md @@ -0,0 +1 @@ +This is root wiki directory. diff --git a/w/m/Fruit/0/m.json b/w/m/Fruit/0/m.json new file mode 100644 index 0000000..85557d8 --- /dev/null +++ b/w/m/Fruit/0/m.json @@ -0,0 +1,9 @@ +{ + "createdAt": 1591636222, + "comment": "update Fruit", + "tags": ["fetus", "tasty"], + "mimeType": "application/x-hypha", + "markup": "md", + "name": "Fruit", + "author": "fungimaster" +} diff --git a/w/m/Fruit/0/t.txt b/w/m/Fruit/0/t.txt new file mode 100644 index 0000000..e438aab --- /dev/null +++ b/w/m/Fruit/0/t.txt @@ -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 diff --git a/w/m/Fruit/1/m.json b/w/m/Fruit/1/m.json new file mode 100644 index 0000000..26649e0 --- /dev/null +++ b/w/m/Fruit/1/m.json @@ -0,0 +1,8 @@ +{ +"createdAt":1591635559, +"comment":"create Fruit", +"tags":["fetus", "tasty"], +"mimeType":"application/x-hypha", +"markup":"md", +"name":"Fruit", +"author":"bouncepaw"} diff --git a/w/m/Fruit/1/t.txt b/w/m/Fruit/1/t.txt new file mode 100644 index 0000000..a70eec8 --- /dev/null +++ b/w/m/Fruit/1/t.txt @@ -0,0 +1 @@ +Fruit is a type of fetus. Is tasty so cool coo l ha i love fwriotwsn diff --git a/w/m/Fruit/Apple/0/b b/w/m/Fruit/Apple/0/b new file mode 100644 index 0000000..eb03b60 Binary files /dev/null and b/w/m/Fruit/Apple/0/b differ diff --git a/w/m/Fruit/Apple/0/m.json b/w/m/Fruit/Apple/0/m.json new file mode 100644 index 0000000..c6d27a6 --- /dev/null +++ b/w/m/Fruit/Apple/0/m.json @@ -0,0 +1,7 @@ +{"createdAt":1591639464, +"comment":"add apple pic hehehe", +"tags":["img"], +"mimeType":"image/jpeg", +"markup":"plain", +"name":"Apple", +"author":"bouncepaw"} diff --git a/w/m/Fruit/Apple/0/t.txt b/w/m/Fruit/Apple/0/t.txt new file mode 100644 index 0000000..cc4fce0 --- /dev/null +++ b/w/m/Fruit/Apple/0/t.txt @@ -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 diff --git a/w/m/Fruit/Apple/mm.json b/w/m/Fruit/Apple/mm.json new file mode 100644 index 0000000..d541574 --- /dev/null +++ b/w/m/Fruit/Apple/mm.json @@ -0,0 +1 @@ +{"creationTime": 1591639284} diff --git a/w/m/Fruit/mm.json b/w/m/Fruit/mm.json new file mode 100644 index 0000000..baaae88 --- /dev/null +++ b/w/m/Fruit/mm.json @@ -0,0 +1 @@ +{"creationTime":1591635559} diff --git a/w/m/README.md b/w/m/README.md new file mode 100644 index 0000000..2b3b540 --- /dev/null +++ b/w/m/README.md @@ -0,0 +1 @@ +This is directory with hyphae diff --git a/walk.go b/walk.go new file mode 100644 index 0000000..ddd5afb --- /dev/null +++ b/walk.go @@ -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 +}