mirror of
https://github.com/osmarks/mycorrhiza.git
synced 2025-04-14 23:03:17 +00:00
Make mycelia somewhat working
This commit is contained in:
parent
82c8b7ee27
commit
33e7eddfd4
@ -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 (
|
||||
|
@ -92,5 +92,5 @@ func navitreeEntry(name, class string) string {
|
||||
return fmt.Sprintf(`<li class="navitree__entry %s">
|
||||
<a class="navitree__link" href="/%s">%s</a>
|
||||
</li>
|
||||
`, class, util.DisplayToCanonical(name), filepath.Base(name))
|
||||
`, class, ":"+util.DisplayToCanonical(name), filepath.Base(name))
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ func (h *Hypha) asHtml() (string, error) {
|
||||
`
|
||||
// What about using <figure>?
|
||||
if h.hasBinaryData() {
|
||||
ret += fmt.Sprintf(`<img src="/%s?action=binary&rev=%d" class="page__amnt"/>`, util.DisplayToCanonical(rev.FullName), rev.Id)
|
||||
ret += fmt.Sprintf(`<img src="/:%s?action=binary&rev=%d" class="page__amnt"/>`, util.DisplayToCanonical(rev.FullName), rev.Id)
|
||||
}
|
||||
|
||||
contents, err := ioutil.ReadFile(rev.TextPath)
|
||||
|
46
fs/hypha.go
46
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
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
}
|
@ -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).
|
||||
|
4
main.go
4
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.
|
||||
|
107
mycelium/mycelium.go
Normal file
107
mycelium/mycelium.go
Normal file
@ -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:"
|
||||
}
|
@ -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}
|
||||
}
|
||||
|
22
util/util.go
22
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)
|
||||
}
|
||||
|
11
wiki/sys/theme/2.markdown
Normal file
11
wiki/sys/theme/2.markdown
Normal file
@ -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.
|
11
wiki/sys/theme/3.markdown
Normal file
11
wiki/sys/theme/3.markdown
Normal file
@ -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.
|
@ -1,7 +1,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ .Title }}</title>
|
||||
<link rel="stylesheet" href="/sys/theme/default-light/main.css?action=raw">
|
||||
<link rel="stylesheet" href="/:sys/theme/default-light/main.css?action=raw">
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
@ -20,6 +20,6 @@
|
||||
<footer>
|
||||
<p>This website runs <a href='https://github.com/bouncepaw/mycorrhiza'>MycorrhizaWiki</a></p>
|
||||
</footer>
|
||||
<script src="/sys/theme/default-light/main.js?action=raw"></script>
|
||||
<script src="/:sys/theme/default-light/main.js?action=raw"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -15,10 +15,10 @@ 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 img { max-width:100%; }
|
||||
.page pre { white-space: break-spaces; }
|
||||
.page pre { white-space: break-spaces; background-color: #f4f4f4; }
|
||||
.page__title { font-size: 2rem; margin: 0; }
|
||||
|
||||
footer { padding: 1rem 0; font-size: .8rem; bottom: 0; position: absolute; }
|
||||
footer { padding: 1rem 0; font-size: .8rem; }
|
||||
footer a, footer a:visited { color: black; }
|
||||
/* Sidebar section */
|
||||
.sidebar { padding: 1rem 0; background: #f4f4f4; }
|
||||
|
@ -14,8 +14,32 @@
|
||||
"binary_mime": "",
|
||||
"text_name": "1.markdown",
|
||||
"binary_name": ""
|
||||
},
|
||||
"2": {
|
||||
"tags": [
|
||||
""
|
||||
],
|
||||
"name": "Theme",
|
||||
"comment": "Update Sys/Theme",
|
||||
"author": "",
|
||||
"time": 1593802668,
|
||||
"text_mime": "text/markdown",
|
||||
"binary_mime": "",
|
||||
"text_name": "2.markdown",
|
||||
"binary_name": ""
|
||||
},
|
||||
"3": {
|
||||
"tags": [
|
||||
""
|
||||
],
|
||||
"name": "Theme",
|
||||
"comment": "Update Sys/Theme",
|
||||
"author": "",
|
||||
"time": 1593802769,
|
||||
"text_mime": "text/markdown",
|
||||
"binary_mime": "",
|
||||
"text_name": "3.markdown",
|
||||
"binary_name": ""
|
||||
}
|
||||
},
|
||||
"Invalid": false,
|
||||
"Err": null
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user