diff --git a/cfg/config.go b/cfg/config.go index e85dd13..f5e6251 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -8,10 +8,18 @@ import ( "path/filepath" ) +const ( + HyphaPattern = `[^\s\d:/?&\\][^:?&\\]*` + HyphaUrl = `/{hypha:` + HyphaPattern + `}` + RevisionPattern = `[\d]+` + RevQuery = `{rev:` + RevisionPattern + `}` +) + var ( - WikiDir string - TemplatesDir string - configJsonPath string + DescribeHyphaHerePattern = "Describe %s here" + WikiDir string + TemplatesDir string + configJsonPath string // Default values that can be overriden in config.json Address = "127.0.0.1:80" diff --git a/fs/data.go b/fs/data.go new file mode 100644 index 0000000..0eb142d --- /dev/null +++ b/fs/data.go @@ -0,0 +1,81 @@ +// This file contains methods for Hypha that calculate data about the hypha based on known information. +package fs + +import ( + "fmt" + "io/ioutil" + "log" + "path/filepath" + "strconv" + "strings" + + "github.com/bouncepaw/mycorrhiza/cfg" +) + +func (h *Hypha) MetaJsonPath() string { + return filepath.Join(h.Path(), "meta.json") +} + +func (h *Hypha) Path() string { + return filepath.Join(cfg.WikiDir, h.FullName) +} + +func (h *Hypha) TextPath() string { + return h.actual.TextPath +} + +func (h *Hypha) parentName() string { + return filepath.Dir(h.FullName) +} + +// hasBinaryData returns true if the revision has any binary data associated. +// During initialisation, it is guaranteed that r.BinaryMime is set to "" if the revision has no binary data. (is it?) +func (h *Hypha) hasBinaryData() bool { + return h.actual.BinaryMime != "" +} + +func (h *Hypha) TagsJoined() string { + if h.Exists { + return strings.Join(h.actual.Tags, ", ") + } + return "" +} + +func (h *Hypha) TextMime() string { + if h.Exists { + return h.actual.TextMime + } + return "text/markdown" +} + +func (h *Hypha) mimeTypeForActionRaw() string { + // If text mime type is text/html, it is not good as it will be rendered. + if h.actual.TextMime == "text/html" { + return "text/plain" + } + return h.actual.TextMime +} + +// NewestId finds the largest id among all revisions. +func (h *Hypha) NewestId() string { + var largest int + for k, _ := range h.Revisions { + id, _ := strconv.Atoi(k) + if id > largest { + largest = id + } + } + return strconv.Itoa(largest) +} + +func (h *Hypha) TextContent() string { + if h.Exists { + contents, err := ioutil.ReadFile(h.TextPath()) + if err != nil { + log.Println("Could not read", h.FullName) + return "Error: could not hypha text content file. It is recommended to cancel editing. Please contact the wiki admin. If you are the admin, see the logs." + } + return string(contents) + } + return fmt.Sprintf(cfg.DescribeHyphaHerePattern, h.FullName) +} diff --git a/fs/fs.go b/fs/fs.go new file mode 100644 index 0000000..b10dc74 --- /dev/null +++ b/fs/fs.go @@ -0,0 +1,59 @@ +package fs + +import ( + "github.com/bouncepaw/mycorrhiza/cfg" + "io/ioutil" + "log" + "path/filepath" + "regexp" +) + +type Storage struct { + // hypha name => path + paths map[string]string + root string +} + +var Hs *Storage + +// InitStorage initiates filesystem-based hypha storage. It has to be called after configuration was inited. +func InitStorage() { + Hs = &Storage{ + paths: make(map[string]string), + root: cfg.WikiDir, + } + Hs.indexHyphae(Hs.root) + log.Printf("Indexed %v hyphae\n", len(Hs.paths)) +} + +// hyphaName gets name of a hypha by stripping path to the hypha in `fullPath` +func hyphaName(fullPath string) string { + // {cfg.WikiDir}/{the name} + return fullPath[len(cfg.WikiDir)+1:] +} + +// indexHyphae searches for all hyphae that seem valid in `path` and saves their absolute paths to `s.paths`. This function is recursive. +func (s *Storage) indexHyphae(path string) { + nodes, err := ioutil.ReadDir(path) + if err != nil { + log.Fatal("Error when checking", path, ":", err, "; skipping") + return + } + + for _, node := range nodes { + matchesHypha, err := regexp.MatchString(cfg.HyphaPattern, node.Name()) + if err != nil { + log.Fatal("Error when matching", node.Name(), err, "\n") + return + } + switch name := filepath.Join(path, node.Name()); { + case matchesHypha && node.IsDir(): + s.indexHyphae(name) + case node.Name() == "meta.json" && !node.IsDir(): + s.paths[hyphaName(path)] = path + } + } +} + +func (h *Hypha) Close() { +} diff --git a/genealogy.go b/fs/genealogy.go similarity index 61% rename from genealogy.go rename to fs/genealogy.go index 8b2a0af..3addd3d 100644 --- a/genealogy.go +++ b/fs/genealogy.go @@ -1,5 +1,4 @@ -/* Genealogy is all about relationships between hyphae.*/ -package main +package fs import ( "fmt" @@ -9,18 +8,7 @@ import ( "strings" ) -// setRelations fills in all children names based on what hyphae call their parents. -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) - } - } -} - -// AddChild adds a name to the list of children names of the hypha. -func (h *Hypha) AddChild(childName string) { - h.ChildrenNames = append(h.ChildrenNames, childName) +func (s *Storage) RenderHypha(h *Hypha) { } // If Name == "", the tree is empty. @@ -35,11 +23,10 @@ type Tree struct { // GetTree generates a Tree for the given hypha name. // It can also generate trees for non-existent hyphae, that's why we use `name string` instead of making it a method on `Hypha`. // In `root` is `false`, siblings will not be fetched. -// Parameter `limit` is unused now but it is meant to limit how many subhyphae can be shown. -func GetTree(name string, root bool, limit ...int) *Tree { +func (s *Storage) GetTree(name string, root bool) *Tree { t := &Tree{Name: name, Root: root} - for hyphaName, _ := range hyphae { - t.compareNamesAndAppend(hyphaName) + for hyphaName, _ := range s.paths { + s.compareNamesAndAppend(t, hyphaName) } sort.Slice(t.Ancestors, func(i, j int) bool { return strings.Count(t.Ancestors[i], "/") < strings.Count(t.Ancestors[j], "/") @@ -55,7 +42,7 @@ func GetTree(name string, root bool, limit ...int) *Tree { } // Compares names appends name2 to an array of `t`: -func (t *Tree) compareNamesAndAppend(name2 string) { +func (s *Storage) compareNamesAndAppend(t *Tree, name2 string) { switch { case t.Name == name2: case strings.HasPrefix(t.Name, name2): @@ -64,11 +51,11 @@ func (t *Tree) compareNamesAndAppend(name2 string) { (filepath.Dir(t.Name) == filepath.Dir(name2))): t.Siblings = append(t.Siblings, name2) case strings.HasPrefix(name2, t.Name): - t.Descendants = append(t.Descendants, GetTree(name2, false)) + t.Descendants = append(t.Descendants, s.GetTree(name2, false)) } } -// AsHtml returns HTML representation of a tree. +// asHtml returns HTML representation of a tree. // It recursively itself on the tree's children. // TODO: redo with templates. I'm not in mood for it now. func (t *Tree) AsHtml() (html string) { @@ -78,16 +65,13 @@ func (t *Tree) AsHtml() (html string) { html += `