diff --git a/cfg/config.go b/cfg/config.go
index 88ea94a..0a40da1 100644
--- a/cfg/config.go
+++ b/cfg/config.go
@@ -18,8 +18,8 @@ const (
HyphaUrl = `/{hypha:` + HyphaPattern + `}`
RevisionPattern = `[\d]+`
RevQuery = `{rev:` + RevisionPattern + `}`
- MyceliumPattern = `:` + HyphaPattern
- MyceliumUrl = `/{mycelium:` + MyceliumPattern + `}`
+ MyceliumPattern = `[^\s\d:/?&\\][^:?&\\/]*`
+ MyceliumUrl = `/:{mycelium:` + MyceliumPattern + `}`
)
var (
diff --git a/fs/genealogy.go b/fs/genealogy.go
index 96da25a..5d63c37 100644
--- a/fs/genealogy.go
+++ b/fs/genealogy.go
@@ -92,5 +92,5 @@ func navitreeEntry(name, class string) string {
return fmt.Sprintf(`
%s
-`, class, util.DisplayToCanonical(name), filepath.Base(name))
+`, class, ":"+util.DisplayToCanonical(name), filepath.Base(name))
}
diff --git a/fs/html.go b/fs/html.go
index 0289988..264473d 100644
--- a/fs/html.go
+++ b/fs/html.go
@@ -21,7 +21,7 @@ func (h *Hypha) asHtml() (string, error) {
`
// What about using ?
if h.hasBinaryData() {
- ret += fmt.Sprintf(`
`, util.DisplayToCanonical(rev.FullName), rev.Id)
+ ret += fmt.Sprintf(`
`, util.DisplayToCanonical(rev.FullName), rev.Id)
}
contents, err := ioutil.ReadFile(rev.TextPath)
diff --git a/fs/hypha.go b/fs/hypha.go
index 762955a..4d2a974 100644
--- a/fs/hypha.go
+++ b/fs/hypha.go
@@ -12,6 +12,7 @@ import (
"strings"
"time"
+ "github.com/bouncepaw/mycorrhiza/mycelium"
"github.com/bouncepaw/mycorrhiza/util"
)
@@ -32,7 +33,16 @@ func (h *Hypha) Invalidate(err error) *Hypha {
return h
}
-func (s *Storage) Open(name string) *Hypha {
+func (s *Storage) OpenFromMap(m map[string]string) *Hypha {
+ name := mycelium.NameWithMyceliumInMap(m)
+ h := s.open(name)
+ if rev, ok := m["rev"]; ok {
+ return h.OnRevision(rev)
+ }
+ return h
+}
+
+func (s *Storage) open(name string) *Hypha {
name = util.UrlToCanonical(name)
h := &Hypha{
Exists: true,
@@ -110,13 +120,18 @@ func (h *Hypha) ActionRaw(w http.ResponseWriter) *Hypha {
if h.Invalid {
return h
}
- fileContents, err := ioutil.ReadFile(h.actual.TextPath)
- if err != nil {
- return h.Invalidate(err)
+ if h.Exists {
+ fileContents, err := ioutil.ReadFile(h.actual.TextPath)
+ if err != nil {
+ return h.Invalidate(err)
+ }
+ w.Header().Set("Content-Type", h.mimeTypeForActionRaw())
+ w.WriteHeader(http.StatusOK)
+ w.Write(fileContents)
+ } else {
+ log.Println("Hypha", h.FullName, "has no actual revision")
+ w.WriteHeader(http.StatusNotFound)
}
- w.Header().Set("Content-Type", h.mimeTypeForActionRaw())
- w.WriteHeader(http.StatusOK)
- w.Write(fileContents)
return h
}
@@ -126,13 +141,18 @@ func (h *Hypha) ActionBinary(w http.ResponseWriter) *Hypha {
if h.Invalid {
return h
}
- fileContents, err := ioutil.ReadFile(h.actual.BinaryPath)
- if err != nil {
- return h.Invalidate(err)
+ if h.Exists {
+ fileContents, err := ioutil.ReadFile(h.actual.BinaryPath)
+ if err != nil {
+ return h.Invalidate(err)
+ }
+ w.Header().Set("Content-Type", h.actual.BinaryMime)
+ w.WriteHeader(http.StatusOK)
+ w.Write(fileContents)
+ } else {
+ log.Println("Hypha", h.FullName, "has no actual revision")
+ w.WriteHeader(http.StatusNotFound)
}
- w.Header().Set("Content-Type", h.actual.BinaryMime)
- w.WriteHeader(http.StatusOK)
- w.Write(fileContents)
return h
}
diff --git a/fs/mycelium.go b/fs/mycelium.go
deleted file mode 100644
index 7f87960..0000000
--- a/fs/mycelium.go
+++ /dev/null
@@ -1,56 +0,0 @@
-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")
-}
diff --git a/handlers.go b/handlers.go
index 1272121..3580eff 100644
--- a/handlers.go
+++ b/handlers.go
@@ -14,7 +14,7 @@ import (
// Boilerplate code present in many handlers. Good to have it.
func HandlerBase(w http.ResponseWriter, rq *http.Request) *fs.Hypha {
vars := mux.Vars(rq)
- return fs.Hs.Open(vars["hypha"]).OnRevision(RevInMap(vars))
+ return fs.Hs.OpenFromMap(vars).OnRevision(RevInMap(vars))
}
func HandlerRaw(w http.ResponseWriter, rq *http.Request) {
@@ -41,7 +41,7 @@ func HandlerView(w http.ResponseWriter, rq *http.Request) {
func HandlerEdit(w http.ResponseWriter, rq *http.Request) {
vars := mux.Vars(rq)
- h := fs.Hs.Open(vars["hypha"]).OnRevision("0")
+ h := fs.Hs.OpenFromMap(vars).OnRevision("0")
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write(render.HyphaEdit(h))
@@ -51,7 +51,7 @@ func HandlerUpdate(w http.ResponseWriter, rq *http.Request) {
vars := mux.Vars(rq)
log.Println("Attempt to update hypha", vars["hypha"])
h := fs.Hs.
- Open(vars["hypha"]).
+ OpenFromMap(vars).
CreateDirIfNeeded().
AddRevisionFromHttpData(rq).
WriteTextFileFromHttpData(rq).
diff --git a/main.go b/main.go
index baf04d9..ea108dc 100644
--- a/main.go
+++ b/main.go
@@ -10,6 +10,7 @@ import (
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/fs"
+ "github.com/bouncepaw/mycorrhiza/mycelium"
"github.com/gorilla/mux"
)
@@ -53,7 +54,7 @@ func main() {
log.Println("Welcome to MycorrhizaWiki α")
cfg.InitConfig(wikiDir)
log.Println("Indexing hyphae...")
- fs.VerifyMycelia()
+ mycelium.Init()
fs.InitStorage()
// Start server code. See handlers.go for handlers' implementations.
@@ -78,6 +79,7 @@ func main() {
r.Queries("action", "update").Path(cfg.HyphaUrl).
Methods("POST").HandlerFunc(HandlerUpdate)
+ r.HandleFunc(cfg.MyceliumUrl+cfg.HyphaUrl, HandlerView)
r.HandleFunc(cfg.HyphaUrl, HandlerView)
// Debug page that renders all hyphae.
diff --git a/mycelium/mycelium.go b/mycelium/mycelium.go
new file mode 100644
index 0000000..94d9e8b
--- /dev/null
+++ b/mycelium/mycelium.go
@@ -0,0 +1,107 @@
+package mycelium
+
+import (
+ "io/ioutil"
+ "log"
+ "strings"
+
+ "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
+}
+
+// Add values to the set. If a value is already there, return false.
+func addInUniqueSet(set map[string]struct{}, names []string) bool {
+ ok := true
+ for _, name := range names {
+ if _, present := set[name]; present {
+ ok = false
+ }
+ set[name] = struct{}{}
+ }
+ return ok
+}
+
+func Init() {
+ var (
+ // Used to check if there are no duplicates
+ foundNames = make(map[string]struct{})
+ 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])
+ }
+ // Confirm uniqueness of names
+ if ok := addInUniqueSet(foundNames, mycelium.Names); !ok {
+ log.Fatal("At least one name was used more than once for mycelia")
+ }
+ }
+ 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")
+}
+
+func NameWithMyceliumInMap(m map[string]string) (res string) {
+ var (
+ hyphaName, okH = m["hypha"]
+ mycelName, okM = m["mycelium"]
+ )
+ log.Println(m)
+ if !okH {
+ // It will result in an error when trying to open a hypha with such name
+ return ":::"
+ }
+ if okM {
+ res = canonicalMycelium(mycelName)
+ } else {
+ res = MainMycelium
+ }
+ return res + "/" + hyphaName
+}
+
+func canonicalMycelium(name string) string {
+ log.Println("Determining canonical mycelial name for", name)
+ name = strings.ToLower(name)
+ for _, mycel := range cfg.Mycelia {
+ for _, mycelName := range mycel.Names {
+ if mycelName == name {
+ return mycel.Names[0]
+ }
+ }
+ }
+ // This is a nonexistent mycelium. Return a name that will trigger an error
+ return ":error:"
+}
diff --git a/render/render.go b/render/render.go
index 4e33f38..a70f9f8 100644
--- a/render/render.go
+++ b/render/render.go
@@ -8,6 +8,7 @@ import (
"github.com/bouncepaw/mycorrhiza/cfg"
"github.com/bouncepaw/mycorrhiza/fs"
+ "github.com/bouncepaw/mycorrhiza/mycelium"
)
// HyphaEdit renders hypha editor.
@@ -85,7 +86,12 @@ type Layout struct {
}
func layout(name string) *Layout {
- h := fs.Hs.Open(path.Join(fs.SystemMycelium, "theme", cfg.Theme, name+".html")).OnRevision("0")
+ lytName := path.Join("theme", cfg.Theme, name+".html")
+ h := fs.Hs.OpenFromMap(map[string]string{
+ "mycelium": mycelium.SystemMycelium,
+ "hypha": lytName,
+ "rev": "0",
+ })
if h.Invalid {
return &Layout{nil, nil, true, h.Err}
}
diff --git a/util/util.go b/util/util.go
index de78835..7d07b11 100644
--- a/util/util.go
+++ b/util/util.go
@@ -5,12 +5,28 @@ import (
"unicode"
)
+func addColonPerhaps(name string) string {
+ if strings.HasPrefix(name, ":") {
+ return name
+ }
+ return ":" + name
+}
+
+func removeColonPerhaps(name string) string {
+ if strings.HasPrefix(name, ":") {
+ return name[1:]
+ }
+ return name
+}
+
func UrlToCanonical(name string) string {
- return strings.ToLower(strings.ReplaceAll(name, " ", "_"))
+ return removeColonPerhaps(
+ strings.ToLower(strings.ReplaceAll(name, " ", "_")))
}
func DisplayToCanonical(name string) string {
- return strings.ToLower(strings.ReplaceAll(name, " ", "_"))
+ return removeColonPerhaps(
+ strings.ToLower(strings.ReplaceAll(name, " ", "_")))
}
func CanonicalToDisplay(name string) (res string) {
@@ -29,5 +45,5 @@ func CanonicalToDisplay(name string) (res string) {
}
res += string(ch)
}
- return res
+ return addColonPerhaps(res)
}
diff --git a/wiki/sys/theme/2.markdown b/wiki/sys/theme/2.markdown
new file mode 100644
index 0000000..7ddb176
--- /dev/null
+++ b/wiki/sys/theme/2.markdown
@@ -0,0 +1,11 @@
+# Themes
+
+In MycorrhizaWiki, themes are used to specify what is shown to a user, the front-end. So, a theme consists of:
+
+- **HTML templates.** Data is inserted into them.
+- **CSS.**
+- **JS.**
+
+Your HTML template structure must be identical to the one. You can include any CSS and JS in your themes.
+
+See [default-light](:sys/theme/default-light) theme for more information. This is the only theme that is guaranteed to work.
diff --git a/wiki/sys/theme/3.markdown b/wiki/sys/theme/3.markdown
new file mode 100644
index 0000000..275da3d
--- /dev/null
+++ b/wiki/sys/theme/3.markdown
@@ -0,0 +1,11 @@
+# Themes
+
+In MycorrhizaWiki, themes are used to specify what is shown to a user, the front-end. So, a theme consists of:
+
+- **HTML templates.** Data is inserted into them.
+- **CSS.**
+- **JS.**
+
+Your HTML template structure must be identical to the one. You can include any CSS and JS in your themes.
+
+See [default-light](/:sys/theme/default-light) theme for more information. This is the only theme that is guaranteed to work.
diff --git a/wiki/sys/theme/default-light/base.html/1.html b/wiki/sys/theme/default-light/base.html/1.html
index 5c6165b..54bc2f7 100644
--- a/wiki/sys/theme/default-light/base.html/1.html
+++ b/wiki/sys/theme/default-light/base.html/1.html
@@ -1,7 +1,7 @@
{{ .Title }}
-
+