diff --git a/README.md b/README.md
index 1ff54b30..21b45034 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# 🍄 MycorrhizaWiki 0.9
+# 🍄 MycorrhizaWiki 0.10
A wiki engine.
## Building
@@ -11,12 +11,25 @@ make
# * create an executable called `mycorrhiza`. Run it with path to your wiki.
```
+## Usage
+```
+mycorrhiza [OPTIONS...] WIKI_PATH
+
+Options:
+ -home string
+ The home page (default "home")
+ -port string
+ Port to serve the wiki at (default "1737")
+ -title string
+ How to call your wiki in the navititle (default "🍄")
+```
+
## Features
* Edit pages through html forms
* Responsive design
* Works in text browsers
-* Wiki pages (called hyphae) are in gemtext
-* Everything is stored as simple files, no database required
+* Wiki pages (called hyphae) are written in mycomarkup
+* Everything is stored as simple files, no database required. You can run a wiki on almost any directory and get something to work with.
* Page trees
* Changes are saved to git
* List of hyphae page
@@ -34,4 +47,3 @@ Help is always needed. We have a [tg chat](https://t.me/mycorrhizadev) where som
* Tagging system
* Authorization
* Better history viewing
-* More markups
diff --git a/flag.go b/flag.go
new file mode 100644
index 00000000..d5d6df4c
--- /dev/null
+++ b/flag.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+ "flag"
+ "log"
+ "path/filepath"
+
+ "github.com/bouncepaw/mycorrhiza/util"
+)
+
+func init() {
+ flag.StringVar(&util.ServerPort, "port", "1737", "Port to serve the wiki at")
+ flag.StringVar(&util.HomePage, "home", "home", "The home page")
+ flag.StringVar(&util.SiteTitle, "title", "🍄", "How to call your wiki in the navititle")
+}
+
+// Do the things related to cli args and die maybe
+func parseCliArgs() {
+ flag.Parse()
+
+ args := flag.Args()
+ if len(args) == 0 {
+ log.Fatal("Error: pass a wiki directory")
+ }
+
+ var err error
+ WikiDir, err = filepath.Abs(args[0])
+ util.WikiDir = WikiDir
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if !isCanonicalName(util.HomePage) {
+ log.Fatal("Error: you must use a proper name for the homepage")
+ }
+}
diff --git a/history/history.go b/history/history.go
index c57ef96a..ec409c29 100644
--- a/history/history.go
+++ b/history/history.go
@@ -59,16 +59,16 @@ func (rev Revision) HyphaeLinks() (html string) {
}
for _, filename := range strings.Split(out.String(), "\n") {
// If filename has an ampersand:
- if strings.IndexRune(filename, '&') >= 0 {
+ if strings.IndexRune(filename, '.') >= 0 {
// Remove ampersanded suffix from filename:
- ampersandPos := strings.LastIndexByte(filename, '&')
+ ampersandPos := strings.LastIndexByte(filename, '.')
hyphaName := string([]byte(filename)[0:ampersandPos]) // is it safe?
if isNewName(hyphaName) {
// Entries are separated by commas
if len(set) > 1 {
html += `, `
}
- html += fmt.Sprintf(`%[2]s`, rev.Hash, hyphaName)
+ html += fmt.Sprintf(`%[1]s`, hyphaName)
}
}
}
@@ -77,10 +77,10 @@ func (rev Revision) HyphaeLinks() (html string) {
func (rev Revision) RecentChangesEntry() (html string) {
return fmt.Sprintf(`
-
-
%s
-
%s
-
%s
+
+
%s
+
%s
+
%s
`, rev.TimeString(), rev.Hash, rev.HyphaeLinks(), rev.Message)
}
@@ -125,6 +125,6 @@ func unixTimestampAsTime(ts string) *time.Time {
// Rename renames from `from` to `to` using `git mv`.
func Rename(from, to string) error {
log.Println(util.ShorterPath(from), util.ShorterPath(to))
- _, err := gitsh("mv", from, to)
+ _, err := gitsh("mv", "--force", from, to)
return err
}
diff --git a/history/information.go b/history/information.go
index 469d9ab9..164e3a96 100644
--- a/history/information.go
+++ b/history/information.go
@@ -39,7 +39,7 @@ func Revisions(hyphaName string) ([]Revision, error) {
"log", "--oneline", "--no-merges",
// Hash, Commiter email, Commiter time, Commit msg separated by tab
"--pretty=format:\"%h\t%ce\t%ct\t%s\"",
- "--", hyphaName+"&.*",
+ "--", hyphaName+".*",
)
revs []Revision
)
diff --git a/history/operations.go b/history/operations.go
index 4388916b..05074836 100644
--- a/history/operations.go
+++ b/history/operations.go
@@ -55,6 +55,12 @@ func (hop *HistoryOp) gitop(args ...string) *HistoryOp {
return hop
}
+// WithError appends the `err` to the list of errors.
+func (hop *HistoryOp) WithError(err error) *HistoryOp {
+ hop.Errs = append(hop.Errs, err)
+ return hop
+}
+
// WithFilesRemoved git-rm-s all passed `paths`. Paths can be rooted or not. Paths that are empty strings are ignored.
func (hop *HistoryOp) WithFilesRemoved(paths ...string) *HistoryOp {
args := []string{"rm", "--quiet", "--"}
@@ -71,7 +77,9 @@ func (hop *HistoryOp) WithFilesRenamed(pairs map[string]string) *HistoryOp {
for from, to := range pairs {
if from != "" {
os.MkdirAll(filepath.Dir(to), 0777)
- hop.gitop(append([]string{"mv"}, from, to)...)
+ if err := Rename(from, to); err != nil {
+ hop.Errs = append(hop.Errs, err)
+ }
}
}
return hop
diff --git a/http_mutators.go b/http_mutators.go
index aa669c07..beb43e60 100644
--- a/http_mutators.go
+++ b/http_mutators.go
@@ -2,13 +2,9 @@ package main
import (
"fmt"
- "io/ioutil"
"log"
"net/http"
- "os"
- "path/filepath"
- "github.com/bouncepaw/mycorrhiza/history"
"github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/util"
)
@@ -116,14 +112,13 @@ func handlerEdit(w http.ResponseWriter, rq *http.Request) {
textAreaFill, err = FetchTextPart(hyphaData)
if err != nil {
log.Println(err)
- HttpErr(w, http.StatusInternalServerError, hyphaName, "Error",
- "Could not fetch text data")
+ HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", "Could not fetch text data")
return
}
} else {
warning = `
You are creating a new hypha.
`
}
- util.HTTP200Page(w, base("Edit"+hyphaName, templates.EditHTML(hyphaName, textAreaFill, warning)))
+ util.HTTP200Page(w, base("Edit "+hyphaName, templates.EditHTML(hyphaName, textAreaFill, warning)))
}
// handlerUploadText uploads a new text part for the hypha.
@@ -133,42 +128,19 @@ func handlerUploadText(w http.ResponseWriter, rq *http.Request) {
hyphaName = HyphaNameFromRq(rq, "upload-text")
hyphaData, isOld = HyphaStorage[hyphaName]
textData = rq.PostFormValue("text")
- textDataBytes = []byte(textData)
- fullPath = filepath.Join(WikiDir, hyphaName+"&.gmi")
)
- if textData == "" {
- HttpErr(w, http.StatusBadRequest, hyphaName, "Error",
- "No text data passed")
- return
- }
- // For some reason, only 0777 works. Why?
- if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
- log.Println(err)
+ if !isOld {
+ hyphaData = &HyphaData{}
}
- if err := ioutil.WriteFile(fullPath, textDataBytes, 0644); err != nil {
- log.Println(err)
- HttpErr(w, http.StatusInternalServerError, hyphaName, "Error",
- fmt.Sprintf("Failed to write %d bytes to %s",
- len(textDataBytes), fullPath))
+ if textData == "" {
+ HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No text data passed")
return
}
- if !isOld {
- hd := HyphaData{
- textType: TextGemini,
- textPath: fullPath,
- }
- HyphaStorage[hyphaName] = &hd
- hyphaData = &hd
+ if hop := hyphaData.UploadText(hyphaName, textData, isOld); len(hop.Errs) != 0 {
+ HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
} else {
- hyphaData.textType = TextGemini
- hyphaData.textPath = fullPath
+ http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
}
- history.Operation(history.TypeEditText).
- WithFiles(fullPath).
- WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName)).
- WithSignature("anon").
- Apply()
- http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
}
// handlerUploadBinary uploads a new binary part for the hypha.
@@ -176,62 +148,29 @@ func handlerUploadBinary(w http.ResponseWriter, rq *http.Request) {
log.Println(rq.URL)
hyphaName := HyphaNameFromRq(rq, "upload-binary")
rq.ParseMultipartForm(10 << 20)
- // Read file
+
file, handler, err := rq.FormFile("binary")
if file != nil {
defer file.Close()
}
// If file is not passed:
if err != nil {
- HttpErr(w, http.StatusBadRequest, hyphaName, "Error",
- "No binary data passed")
+ HttpErr(w, http.StatusBadRequest, hyphaName, "Error", "No binary data passed")
return
}
// If file is passed:
var (
hyphaData, isOld = HyphaStorage[hyphaName]
- mimeType = MimeToBinaryType(handler.Header.Get("Content-Type"))
- ext = mimeType.Extension()
- fullPath = filepath.Join(WikiDir, hyphaName+"&"+ext)
+ mime = handler.Header.Get("Content-Type")
)
-
- data, err := ioutil.ReadAll(file)
- if err != nil {
- HttpErr(w, http.StatusInternalServerError, hyphaName, "Error",
- "Could not read passed data")
- return
- }
- if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
- log.Println(err)
- }
if !isOld {
- hd := HyphaData{
- binaryPath: fullPath,
- binaryType: mimeType,
- }
- HyphaStorage[hyphaName] = &hd
- hyphaData = &hd
- } else {
- if hyphaData.binaryPath != fullPath {
- if err := history.Rename(hyphaData.binaryPath, fullPath); err != nil {
- log.Println(err)
- } else {
- log.Println("Moved", hyphaData.binaryPath, "to", fullPath)
- }
- }
- hyphaData.binaryPath = fullPath
- hyphaData.binaryType = mimeType
+ hyphaData = &HyphaData{}
}
- if err = ioutil.WriteFile(fullPath, data, 0644); err != nil {
- HttpErr(w, http.StatusInternalServerError, hyphaName, "Error",
- "Could not save passed data")
- return
+ hop := hyphaData.UploadBinary(hyphaName, mime, file, isOld)
+
+ if len(hop.Errs) != 0 {
+ HttpErr(w, http.StatusInternalServerError, hyphaName, "Error", hop.Errs[0].Error())
+ } else {
+ http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
}
- log.Println("Written", len(data), "of binary data for", hyphaName, "to path", fullPath)
- history.Operation(history.TypeEditText).
- WithFiles(fullPath, hyphaData.binaryPath).
- WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", hyphaName, mimeType.Mime())).
- WithSignature("anon").
- Apply()
- http.Redirect(w, rq, "/page/"+hyphaName, http.StatusSeeOther)
}
diff --git a/http_readers.go b/http_readers.go
index 5843e4d2..7870d3ed 100644
--- a/http_readers.go
+++ b/http_readers.go
@@ -6,10 +6,11 @@ import (
"log"
"net/http"
"os"
+ "path/filepath"
"strings"
- "github.com/bouncepaw/mycorrhiza/gemtext"
"github.com/bouncepaw/mycorrhiza/history"
+ "github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/templates"
"github.com/bouncepaw/mycorrhiza/tree"
"github.com/bouncepaw/mycorrhiza/util"
@@ -32,11 +33,11 @@ func handlerRevision(w http.ResponseWriter, rq *http.Request) {
revHash = shorterUrl[:firstSlashIndex]
hyphaName = CanonicalName(shorterUrl[firstSlashIndex+1:])
contents = fmt.Sprintf(`
This hypha had no text at this revision.
`)
- textPath = hyphaName + "&.gmi"
+ textPath = hyphaName + ".myco"
textContents, err = history.FileAtRevision(textPath, revHash)
)
if err == nil {
- contents = gemtext.ToHtml(hyphaName, textContents)
+ contents = markup.ToHtml(hyphaName, textContents)
}
page := templates.RevisionHTML(
hyphaName,
@@ -75,7 +76,7 @@ func handlerText(w http.ResponseWriter, rq *http.Request) {
hyphaName := HyphaNameFromRq(rq, "text")
if data, ok := HyphaStorage[hyphaName]; ok {
log.Println("Serving", data.textPath)
- w.Header().Set("Content-Type", data.textType.Mime())
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
http.ServeFile(w, rq, data.textPath)
}
}
@@ -86,7 +87,7 @@ func handlerBinary(w http.ResponseWriter, rq *http.Request) {
hyphaName := HyphaNameFromRq(rq, "binary")
if data, ok := HyphaStorage[hyphaName]; ok {
log.Println("Serving", data.binaryPath)
- w.Header().Set("Content-Type", data.binaryType.Mime())
+ w.Header().Set("Content-Type", ExtensionToMime(filepath.Ext(data.binaryPath)))
http.ServeFile(w, rq, data.binaryPath)
}
}
@@ -103,7 +104,7 @@ func handlerPage(w http.ResponseWriter, rq *http.Request) {
fileContentsT, errT := ioutil.ReadFile(data.textPath)
_, errB := os.Stat(data.binaryPath)
if errT == nil {
- contents = gemtext.ToHtml(hyphaName, string(fileContentsT))
+ contents = markup.ToHtml(hyphaName, string(fileContentsT))
}
if !os.IsNotExist(errB) {
contents = binaryHtmlBlock(hyphaName, data) + contents
diff --git a/hypha.go b/hypha.go
index 74a86850..a49b785d 100644
--- a/hypha.go
+++ b/hypha.go
@@ -5,21 +5,22 @@ import (
"fmt"
"io/ioutil"
"log"
+ "mime/multipart"
"os"
"path/filepath"
"strings"
- "github.com/bouncepaw/mycorrhiza/gemtext"
"github.com/bouncepaw/mycorrhiza/history"
+ "github.com/bouncepaw/mycorrhiza/markup"
"github.com/bouncepaw/mycorrhiza/util"
)
func init() {
- gemtext.HyphaExists = func(hyphaName string) bool {
+ markup.HyphaExists = func(hyphaName string) bool {
_, hyphaExists := HyphaStorage[hyphaName]
return hyphaExists
}
- gemtext.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) {
+ markup.HyphaAccess = func(hyphaName string) (rawText, binaryBlock string, err error) {
if hyphaData, ok := HyphaStorage[hyphaName]; ok {
rawText, err = FetchTextPart(hyphaData)
if hyphaData.binaryPath != "" {
@@ -35,9 +36,54 @@ func init() {
// HyphaData represents a hypha's meta information: binary and text parts rooted paths and content types.
type HyphaData struct {
textPath string
- textType TextType
binaryPath string
- binaryType BinaryType
+}
+
+// uploadHelp is a helper function for UploadText and UploadBinary
+func (hd *HyphaData) uploadHelp(hop *history.HistoryOp, hyphaName, ext string, originalFullPath *string, isOld bool, data []byte) *history.HistoryOp {
+ var (
+ fullPath = filepath.Join(WikiDir, hyphaName+ext)
+ )
+ if err := os.MkdirAll(filepath.Dir(fullPath), 0777); err != nil {
+ return hop.WithError(err)
+ }
+
+ if err := ioutil.WriteFile(fullPath, data, 0644); err != nil {
+ return hop.WithError(err)
+ }
+ if isOld && *originalFullPath != fullPath && *originalFullPath != "" {
+ if err := history.Rename(*originalFullPath, fullPath); err != nil {
+ return hop.WithError(err)
+ }
+ log.Println("Move", *originalFullPath, "to", fullPath)
+ }
+ if !isOld {
+ HyphaStorage[hyphaName] = hd
+ }
+ *originalFullPath = fullPath
+ log.Printf("%v\n", *hd)
+ return hop.WithFiles(fullPath).
+ WithSignature("anon").
+ Apply()
+}
+
+// UploadText loads a new text part from `textData` for hypha `hyphaName` with `hd`. It must be marked if the hypha `isOld`.
+func (hd *HyphaData) UploadText(hyphaName, textData string, isOld bool) *history.HistoryOp {
+ hop := history.Operation(history.TypeEditText).WithMsg(fmt.Sprintf("Edit ‘%s’", hyphaName))
+ return hd.uploadHelp(hop, hyphaName, ".myco", &hd.textPath, isOld, []byte(textData))
+}
+
+// UploadBinary loads a new binary part from `file` for hypha `hyphaName` with `hd`. The contents have the specified `mime` type. It must be marked if the hypha `isOld`.
+func (hd *HyphaData) UploadBinary(hyphaName, mime string, file multipart.File, isOld bool) *history.HistoryOp {
+ var (
+ hop = history.Operation(history.TypeEditBinary).WithMsg(fmt.Sprintf("Upload binary part for ‘%s’ with type ‘%s’", hyphaName, mime))
+ data, err = ioutil.ReadAll(file)
+ )
+ if err != nil {
+ return hop.WithError(err).Apply()
+ }
+
+ return hd.uploadHelp(hop, hyphaName, MimeToExtension(mime), &hd.binaryPath, isOld, data)
}
// DeleteHypha deletes hypha and makes a history record about that.
@@ -61,10 +107,13 @@ func findHyphaeToRename(hyphaName string, recursive bool) []string {
return hyphae
}
-func renamingPairs(hyphaNames []string, replaceName func(string) string) map[string]string {
+func renamingPairs(hyphaNames []string, replaceName func(string) string) (map[string]string, error) {
renameMap := make(map[string]string)
for _, hn := range hyphaNames {
if hd, ok := HyphaStorage[hn]; ok {
+ if _, nameUsed := HyphaStorage[replaceName(hn)]; nameUsed {
+ return nil, errors.New("Hypha " + replaceName(hn) + " already exists")
+ }
if hd.textPath != "" {
renameMap[hd.textPath] = replaceName(hd.textPath)
}
@@ -73,7 +122,7 @@ func renamingPairs(hyphaNames []string, replaceName func(string) string) map[str
}
}
}
- return renameMap
+ return renameMap, nil
}
// word Data is plural here
@@ -94,11 +143,15 @@ func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *his
replaceName = func(str string) string {
return strings.Replace(str, hyphaName, newName, 1)
}
- hyphaNames = findHyphaeToRename(hyphaName, recursive)
- renameMap = renamingPairs(hyphaNames, replaceName)
- renameMsg = "Rename ‘%s’ to ‘%s’"
- hop = history.Operation(history.TypeRenameHypha)
+ hyphaNames = findHyphaeToRename(hyphaName, recursive)
+ renameMap, err = renamingPairs(hyphaNames, replaceName)
+ renameMsg = "Rename ‘%s’ to ‘%s’"
+ hop = history.Operation(history.TypeRenameHypha)
)
+ if err != nil {
+ hop.Errs = append(hop.Errs, err)
+ return hop
+ }
if recursive {
renameMsg += " recursively"
}
@@ -113,14 +166,14 @@ func (hd *HyphaData) RenameHypha(hyphaName, newName string, recursive bool) *his
}
// binaryHtmlBlock creates an html block for binary part of the hypha.
-func binaryHtmlBlock(hyphaName string, d *HyphaData) string {
- switch d.binaryType {
- case BinaryJpeg, BinaryGif, BinaryPng, BinaryWebp, BinarySvg, BinaryIco:
+func binaryHtmlBlock(hyphaName string, hd *HyphaData) string {
+ switch filepath.Ext(hd.binaryPath) {
+ case ".jpg", ".gif", ".png", ".webp", ".svg", ".ico":
return fmt.Sprintf(`
-
+
`, hyphaName)
- case BinaryOgg, BinaryWebm, BinaryMp4:
+ case ".ogg", ".webm", ".mp4":
return fmt.Sprintf(`
`, hyphaName)
- case BinaryMp3:
+ case ".mp3":
return fmt.Sprintf(`