1
0
mirror of https://github.com/osmarks/mycorrhiza.git synced 2025-01-22 08:06:52 +00:00

Verify presence of mycelial dirs, start moving to new file structure

This commit is contained in:
Timur Ismagilov 2020-07-03 00:03:30 +05:00
parent 426666e9f0
commit 82c8b7ee27
69 changed files with 264 additions and 50 deletions

View File

@ -1,2 +1,6 @@
# mycorrhiza wiki # mycorrhiza wiki
Simple wiki engine inspired by fungi. A wiki engine inspired by fungi.
Current version: 0.5.
Not production-ready.

View File

@ -8,17 +8,23 @@ import (
"path/filepath" "path/filepath"
) )
type MyceliumConfig struct {
Names []string `json:"names"`
Type string `json:"type"`
}
const ( const (
HyphaPattern = `[^\s\d:/?&\\][^:?&\\]*` HyphaPattern = `[^\s\d:/?&\\][^:?&\\]*`
HyphaUrl = `/{hypha:` + HyphaPattern + `}` HyphaUrl = `/{hypha:` + HyphaPattern + `}`
RevisionPattern = `[\d]+` RevisionPattern = `[\d]+`
RevQuery = `{rev:` + RevisionPattern + `}` RevQuery = `{rev:` + RevisionPattern + `}`
MyceliumPattern = `:` + HyphaPattern
MyceliumUrl = `/{mycelium:` + MyceliumPattern + `}`
) )
var ( var (
DescribeHyphaHerePattern = "Describe %s here" DescribeHyphaHerePattern = "Describe %s here"
WikiDir string WikiDir string
TemplatesDir string
configJsonPath string configJsonPath string
// Default values that can be overriden in config.json // Default values that can be overriden in config.json
@ -28,13 +34,16 @@ var (
GenericErrorMsg = `<b>Sorry, something went wrong</b>` GenericErrorMsg = `<b>Sorry, something went wrong</b>`
SiteTitle = `MycorrhizaWiki` SiteTitle = `MycorrhizaWiki`
Theme = `default-light` Theme = `default-light`
Mycelia = []MyceliumConfig{
{[]string{"main"}, "main"},
{[]string{"sys", "system"}, "system"},
}
) )
func InitConfig(wd string) bool { func InitConfig(wd string) bool {
log.Println("WikiDir is", wd) log.Println("WikiDir is", wd)
WikiDir = wd WikiDir = wd
TemplatesDir = "Templates" configJsonPath = filepath.Join(WikiDir, "config.json")
configJsonPath = filepath.Join(filepath.Dir(WikiDir), "config.json")
if _, err := os.Stat(configJsonPath); os.IsNotExist(err) { if _, err := os.Stat(configJsonPath); os.IsNotExist(err) {
log.Println("config.json not found, using default values") log.Println("config.json not found, using default values")
@ -55,6 +64,7 @@ func readConfig() bool {
Address string `json:"address"` Address string `json:"address"`
Theme string `json:"theme"` Theme string `json:"theme"`
SiteTitle string `json:"site-title"` SiteTitle string `json:"site-title"`
Mycelia []MyceliumConfig `json:"mycelia"`
TitleTemplates struct { TitleTemplates struct {
EditHypha string `json:"edit-hypha"` EditHypha string `json:"edit-hypha"`
ViewHypha string `json:"view-hypha"` ViewHypha string `json:"view-hypha"`
@ -72,6 +82,7 @@ func readConfig() bool {
SiteTitle = cfg.SiteTitle SiteTitle = cfg.SiteTitle
TitleEditTemplate = cfg.TitleTemplates.EditHypha TitleEditTemplate = cfg.TitleTemplates.EditHypha
TitleTemplate = cfg.TitleTemplates.ViewHypha TitleTemplate = cfg.TitleTemplates.ViewHypha
Mycelia = cfg.Mycelia
return true return true
} }

View File

@ -17,8 +17,12 @@ func (h *Hypha) MetaJsonPath() string {
return filepath.Join(h.Path(), "meta.json") return filepath.Join(h.Path(), "meta.json")
} }
func (h *Hypha) CanonicalName() string {
return util.UrlToCanonical(h.FullName)
}
func (h *Hypha) Path() string { func (h *Hypha) Path() string {
return filepath.Join(cfg.WikiDir, util.UrlToCanonical(h.FullName)) return filepath.Join(cfg.WikiDir, h.CanonicalName())
} }
func (h *Hypha) TextPath() string { func (h *Hypha) TextPath() string {

View File

@ -12,7 +12,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/util" "github.com/bouncepaw/mycorrhiza/util"
) )
@ -23,8 +22,8 @@ type Hypha struct {
Deleted bool `json:"deleted"` Deleted bool `json:"deleted"`
Revisions map[string]*Revision `json:"revisions"` Revisions map[string]*Revision `json:"revisions"`
actual *Revision `json:"-"` actual *Revision `json:"-"`
Invalid bool Invalid bool `json:"-"`
Err error Err error `json:"-"`
} }
func (h *Hypha) Invalidate(err error) *Hypha { func (h *Hypha) Invalidate(err error) *Hypha {
@ -183,7 +182,7 @@ func (h *Hypha) CreateDirIfNeeded() *Hypha {
return h return h
} }
// os.MkdirAll created dir if it is not there. Basically, checks it for us. // os.MkdirAll created dir if it is not there. Basically, checks it for us.
err := os.MkdirAll(filepath.Join(cfg.WikiDir, h.FullName), os.ModePerm) err := os.MkdirAll(h.Path(), os.ModePerm)
if err != nil { if err != nil {
h.Invalidate(err) h.Invalidate(err)
} }
@ -315,7 +314,7 @@ func (h *Hypha) SaveJson() *Hypha {
// Store adds `h` to the `Hs` if it is not already there // Store adds `h` to the `Hs` if it is not already there
func (h *Hypha) Store() *Hypha { func (h *Hypha) Store() *Hypha {
if !h.Invalid { if !h.Invalid {
Hs.paths[h.FullName] = h.Path() Hs.paths[h.CanonicalName()] = h.Path()
} }
return h return h
} }

56
fs/mycelium.go Normal file
View File

@ -0,0 +1,56 @@
package fs
import (
"io/ioutil"
"log"
"github.com/bouncepaw/mycorrhiza/cfg"
)
var (
MainMycelium string
SystemMycelium string
)
func gatherDirNames(path string) map[string]struct{} {
res := make(map[string]struct{})
nodes, err := ioutil.ReadDir(path)
if err != nil {
log.Fatal(err)
}
for _, node := range nodes {
if node.IsDir() {
res[node.Name()] = struct{}{}
}
}
return res
}
func VerifyMycelia() {
var (
dirs = gatherDirNames(cfg.WikiDir)
mainPresent bool
systemPresent bool
)
for _, mycelium := range cfg.Mycelia {
switch mycelium.Type {
case "main":
mainPresent = true
MainMycelium = mycelium.Names[0]
case "system":
systemPresent = true
SystemMycelium = mycelium.Names[0]
}
// Check if there is a dir corresponding to the mycelium
if _, ok := dirs[mycelium.Names[0]]; !ok {
log.Fatal("No directory found for mycelium " + mycelium.Names[0])
}
}
if !mainPresent {
log.Fatal("No `main` mycelium given in config.json")
}
if !systemPresent {
log.Fatal("No `system` mycelium given in config.json")
}
log.Println("Mycelial dirs are present")
}

51
main.go
View File

@ -22,6 +22,25 @@ func RevInMap(m map[string]string) string {
return "0" return "0"
} }
func IdempotentRouterBoiler(router *mux.Router, action string, handler func(w http.ResponseWriter, rq *http.Request)) {
router.
Queries("action", action, "rev", cfg.RevQuery).
Path(cfg.MyceliumUrl + cfg.HyphaUrl).
HandlerFunc(handler)
router.
Queries("action", action).
Path(cfg.MyceliumUrl + cfg.HyphaUrl).
HandlerFunc(handler)
router.
Queries("action", action, "rev", cfg.RevQuery).
Path(cfg.HyphaUrl).
HandlerFunc(handler)
router.
Queries("action", action).
Path(cfg.HyphaUrl).
HandlerFunc(handler)
}
func main() { func main() {
if len(os.Args) == 1 { if len(os.Args) == 1 {
panic("Expected a root wiki pages directory") panic("Expected a root wiki pages directory")
@ -34,6 +53,7 @@ func main() {
log.Println("Welcome to MycorrhizaWiki α") log.Println("Welcome to MycorrhizaWiki α")
cfg.InitConfig(wikiDir) cfg.InitConfig(wikiDir)
log.Println("Indexing hyphae...") log.Println("Indexing hyphae...")
fs.VerifyMycelia()
fs.InitStorage() fs.InitStorage()
// Start server code. See handlers.go for handlers' implementations. // Start server code. See handlers.go for handlers' implementations.
@ -43,31 +63,20 @@ func main() {
http.ServeFile(w, rq, filepath.Join(filepath.Dir(cfg.WikiDir), "favicon.ico")) http.ServeFile(w, rq, filepath.Join(filepath.Dir(cfg.WikiDir), "favicon.ico"))
}) })
r.Queries("action", "binary", "rev", cfg.RevQuery).Path(cfg.HyphaUrl). IdempotentRouterBoiler(r, "binary", HandlerBinary)
HandlerFunc(HandlerBinary) IdempotentRouterBoiler(r, "raw", HandlerRaw)
r.Queries("action", "binary").Path(cfg.HyphaUrl). IdempotentRouterBoiler(r, "zen", HandlerZen)
HandlerFunc(HandlerBinary) IdempotentRouterBoiler(r, "view", HandlerView)
r.Queries("action", "raw", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
HandlerFunc(HandlerRaw)
r.Queries("action", "raw").Path(cfg.HyphaUrl).
HandlerFunc(HandlerRaw)
r.Queries("action", "zen", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
HandlerFunc(HandlerZen)
r.Queries("action", "zen").Path(cfg.HyphaUrl).
HandlerFunc(HandlerZen)
r.Queries("action", "view", "rev", cfg.RevQuery).Path(cfg.HyphaUrl).
HandlerFunc(HandlerView)
r.Queries("action", "view").Path(cfg.HyphaUrl).
HandlerFunc(HandlerView)
r.Queries("action", "edit").Path(cfg.MyceliumUrl + cfg.HyphaUrl).
HandlerFunc(HandlerEdit)
r.Queries("action", "edit").Path(cfg.HyphaUrl). r.Queries("action", "edit").Path(cfg.HyphaUrl).
HandlerFunc(HandlerEdit) HandlerFunc(HandlerEdit)
r.Queries("action", "update").Path(cfg.HyphaUrl).Methods("POST"). r.Queries("action", "update").Path(cfg.MyceliumUrl + cfg.HyphaUrl).
HandlerFunc(HandlerUpdate) Methods("POST").HandlerFunc(HandlerUpdate)
r.Queries("action", "update").Path(cfg.HyphaUrl).
Methods("POST").HandlerFunc(HandlerUpdate)
r.HandleFunc(cfg.HyphaUrl, HandlerView) r.HandleFunc(cfg.HyphaUrl, HandlerView)

View File

@ -85,7 +85,7 @@ type Layout struct {
} }
func layout(name string) *Layout { func layout(name string) *Layout {
h := fs.Hs.Open(path.Join(cfg.TemplatesDir, cfg.Theme, name+".html")).OnRevision("0") h := fs.Hs.Open(path.Join(fs.SystemMycelium, "theme", cfg.Theme, name+".html")).OnRevision("0")
if h.Invalid { if h.Invalid {
return &Layout{nil, nil, true, h.Err} return &Layout{nil, nil, true, h.Err}
} }

View File

@ -1,9 +0,0 @@
{
"address": "127.0.0.1:1737",
"theme": "default-light",
"site-title": "🍄 MycorrhizaWiki",
"title-templates": {
"edit-hypha": "Edit %s at MycorrhizaWiki",
"view-hypha": "%s at MycorrhizaWiki"
}
}

View File

@ -1,4 +0,0 @@
<h1 class="page__title">{{ . }}</h1>
<p class="msg_hypha-does-not-exist msg">
The hypha you are trying to access does not exist yet. Why not <a href="?action=edit">create</a> it?
</p>

31
wiki/config.json Normal file
View File

@ -0,0 +1,31 @@
{
"address": "127.0.0.1:1737",
"theme": "default-light",
"site-title": "🍄 MycorrhizaWiki",
"title-templates": {
"edit-hypha": "Edit %s at MycorrhizaWiki",
"view-hypha": "%s at MycorrhizaWiki"
},
"mycelia": [
{
"names": ["main"],
"type": "main"
},
{
"names": ["sys", "system"],
"type": "system"
},
{
"names": ["spec", "special"],
"type": "special"
},
{
"names": ["user","u"],
"type": "user"
},
{
"names": ["tag", "t"],
"type": "tag"
}
]
}

View File

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 427 KiB

After

Width:  |  Height:  |  Size: 427 KiB

View File

Before

Width:  |  Height:  |  Size: 608 KiB

After

Width:  |  Height:  |  Size: 608 KiB

View File

@ -0,0 +1,85 @@
In MycorrhizaWiki **mycelia** are used to organize [hyphae](hyphae) in related namespaces.
## Mycelium traits
- Every mycelium has any number (0..∞) of hyphae.
- Every hypha is part of one mycelium.
- Every mycelium has one canonical name and any number of synonyms.
- Every wiki has one main mycelium.
- They are named by the same scheme as hyphae but they have a colon prefix.
## Mycelium URL
- Address a hypha in a particular mycelium: `/:sys/about`.
- Address subpage of the hypha above: `/:sys/about/more`
- Address a hypha in the main mycelium: `/How/does it work`.
- Address a hypha in the main mycelium explicitly: `/:main/How/does it work`.
## Mycelium configuration
In your `config.json`, in `"mycelia"` field there is an array of objects.
```
{
...
"mycelia": [
{
"names": ["main"],
"type": "main"
},
{
"names": ["sys", "system"],
"type": "system"
},
{
"names": ["spec", "special"],
"type": "special"
},
{
"names": ["user","u"],
"type": "user"
},
{
"names": ["tag", "t"],
"type": "tag"
}
]
...
}
```
Each object reprents a mycelium. You can set all their names there. First name in each `"names"` array is a canonical name for the mycelium.
Field `"type"` sets the mycelium's type. There are such types:
| **Type** | **Description** |
| `main` | The main mycelium. There must be exactly one such mycelium in a wiki. |
| `system` | Things like scripts, styles and templates go here. There must be exactly one such mycelium in a wiki. |
| `special` | Things like utility hyphae and plugin pages go here. It is optional because there are no hyphae or plugins now. |
| `user` | Userpages. It is optional because there are no users now. |
| `tag` | Pages describing tags. It is optional because there are no tags now. |
| `other` | Mycelia without any additional meaning added by the engine. There can be any number of them. |
## How are they stored in the filesystem.
For example, `wiki` is your wiki directory and you have configured the mycelia like in the example above. You should have structure like that:
```
wiki/
config.json ← your configuration
favicon.ico ← your site icon
main/ ← :main, <empty prefix>
...most of content goes here
sys/ ← :sys
...themes go here
spec/ ← :spec
...something goes here
user/ ← :user, :u
...user pages go here
tag/ ← :tag, :t
...pages describing tags go here
```
There are usual hypha directories inside those mycelial directories.
## Code
- Things related to reading the `config.json` go to the `cfg` module.
- Most of code related to mycelia is in the `fs` module.
- And also check out `handlers.go` and `main.go` for routing of mycelia.

View File

@ -0,0 +1,21 @@
{
"views": 0,
"deleted": false,
"revisions": {
"1": {
"tags": [
""
],
"name": "Mycelia",
"comment": "Update Help/Mycelia",
"author": "",
"time": 1593541268,
"text_mime": "text/markdown",
"binary_mime": "",
"text_name": "1.markdown",
"binary_name": ""
}
},
"Invalid": false,
"Err": null
}

View File

@ -1,7 +1,7 @@
<html> <html>
<head> <head>
<title>{{ .Title }}</title> <title>{{ .Title }}</title>
<link rel="stylesheet" href="/Templates/default-light/main.css?action=raw"> <link rel="stylesheet" href="/sys/theme/default-light/main.css?action=raw">
</head> </head>
<body> <body>
<header class="header"> <header class="header">
@ -20,6 +20,6 @@
<footer> <footer>
<p>This website runs <a href='https://github.com/bouncepaw/mycorrhiza'>MycorrhizaWiki</a></p> <p>This website runs <a href='https://github.com/bouncepaw/mycorrhiza'>MycorrhizaWiki</a></p>
</footer> </footer>
<script src="/Templates/default-light/main.js?action=raw"></script> <script src="/sys/theme/default-light/main.js?action=raw"></script>
</body> </body>
</html> </html>

View File

@ -12,6 +12,7 @@ header a, header a:visited { color: black; text-decoration:none; }
header a:active, header a:hover { color: #005f87; } header a:active, header a:hover { color: #005f87; }
h1, h2, h3, h4, h5, h6 { margin: 0.5em 0 0.25em; } h1, h2, h3, h4, h5, h6 { margin: 0.5em 0 0.25em; }
code { background-color: #f4f4f4; }
.page { line-height: 1.666; max-width: 40rem; hyphens: auto; } .page { line-height: 1.666; max-width: 40rem; hyphens: auto; }
.page img { max-width:100%; } .page img { max-width:100%; }
.page pre { white-space: break-spaces; } .page pre { white-space: break-spaces; }

View File

@ -0,0 +1,6 @@
<article class="page page404">
<h1 class="page__title">{{ . }}</h1>
<p class="msg_hypha-does-not-exist msg">
The hypha you are trying to access does not exist yet. Why not <a href="?action=edit">create</a> it?
</p>
</article>