diff --git a/go.mod b/go.mod index 48a716c..7863c69 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/bouncepaw/mycorrhiza go 1.14 require ( + github.com/gomarkdown/markdown v0.0.0-20200609195525-3f9352745725 // indirect github.com/gorilla/mux v1.7.4 github.com/sirupsen/logrus v1.6.0 // indirect gopkg.in/ini.v1 v1.57.0 diff --git a/go.sum b/go.sum index 5c85cbd..25221e8 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gomarkdown/markdown v0.0.0-20200609195525-3f9352745725 h1:X6sZdr+t2E2jwajTy/FfXbmAKPFTYxEq9hiFgzMiuPQ= +github.com/gomarkdown/markdown v0.0.0-20200609195525-3f9352745725/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -6,6 +8,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ= golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= diff --git a/hypha.go b/hypha.go index 4151491..cd0b4d3 100644 --- a/hypha.go +++ b/hypha.go @@ -1,7 +1,9 @@ package main import ( + "errors" "fmt" + "strconv" ) type Hypha struct { @@ -36,39 +38,29 @@ func (h Hypha) String() string { revbuf) } -type Revision struct { - // Revision is hypha's state at some point in time. Future revisions are not really supported. Most data here is stored in m.ini. - Id int - // Name used at this revision - Name string `json:"name"` - // Present in every hypha. Stored in t.txt. - TextPath string - // In at least one markup. Supported ones are "myco", "html", "md", "plain" - Markup string `json:"markup"` - // Some hyphæ have binary contents such as images. Their presence change hypha's behavior in a lot of ways (see methods' implementations). If stored, it is stored in b (filename "b") - BinaryPath string - // To tell what is meaning of binary content, mimeType for them is stored. If the hypha has no binary content, this field must be "application/x-hypha" - MimeType string `json:"mimeType"` - // Every revision was created at some point. This field stores the creation time of the latest revision - RevisionTime int `json:"createdAt"` - // Every hypha has any number of tags - Tags []string `json:"tags"` - // Current revision is authored by someone - RevisionAuthor string `json:"author"` - // and has a comment in plain text - RevisionComment string `json:"comment"` - // Rest of fields are ignored +func GetRevision(hyphae map[string]*Hypha, hyphaName string, rev string) (Revision, error) { + for name, _ := range hyphae { + if name == hyphaName { + for _, r := range hyphae[name].Revisions { + id, err := strconv.Atoi(rev) + if err != nil { + return Revision{}, err + } + if r.Id == id { + return r, nil + } + } + } + } + return Revision{}, errors.New("Some error idk") } -func (h Revision) String() string { - return fmt.Sprintf(`Revision %v created at %v { - name: %v - textPath: %v - markup: %v - binaryPath: %v - mimeType: %v - tags: %v - revisionAuthor: %v - revisionComment: %v -}`, h.Id, h.RevisionTime, h.Name, h.TextPath, h.Markup, h.BinaryPath, h.MimeType, h.Tags, h.RevisionAuthor, h.RevisionComment) +// `rev` is the id of revision to render. If it = 0, the last one is rendered. If the revision is not found, an error is returned. +func (h Hypha) Render(hyphae map[string]*Hypha, rev int) (ret string, err error) { + for _, r := range h.Revisions { + if r.Id == rev { + return r.Render(hyphae) + } + } + return "", errors.New("Revision was not found") } diff --git a/main.go b/main.go index abe480b..3cde01b 100644 --- a/main.go +++ b/main.go @@ -2,8 +2,14 @@ package main import ( "fmt" + "github.com/gorilla/mux" + "io/ioutil" + "log" + "net/http" "os" "path/filepath" + // "strconv" + "time" ) var rootWikiDir string @@ -30,5 +36,56 @@ func main() { hyphae := hyphaeAsMap(recurFindHyphae(rootWikiDir)) setRelations(hyphae) - fmt.Println(hyphae) + // Start server code + r := mux.NewRouter() + r.HandleFunc("/showHyphae", func(w http.ResponseWriter, r *http.Request) { + for _, h := range hyphae { + fmt.Fprintln(w, h) + } + }) + r.Queries( + "action", "getBinary", + "rev", "{rev:[\\d]+}", + ).Path("/{hypha:" + hyphaPattern + "}"). + HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + rev, err := GetRevision(hyphae, vars["hypha"], vars["rev"]) + if err != nil { + log.Println("Failed to show image of", rev.FullName) + } + fileContents, err := ioutil.ReadFile(rev.BinaryPath) + if err != nil { + log.Println("Failed to show image of", rev.FullName) + } + log.Println("Contents:", fileContents[:10], "...") + w.Header().Set("Content-Type", rev.MimeType) + // w.Header().Set("Content-Length", strconv.Itoa(len(fileContents))) + w.WriteHeader(http.StatusOK) + w.Write(fileContents) + log.Println("Showing image of", rev.FullName) + }) + + r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(http.StatusOK) + for _, v := range hyphae { + log.Println("Rendering latest revision of hypha", v.Name) + html, err := v.Render(hyphae, 0) + if err != nil { + fmt.Fprintln(w, err) + } + fmt.Fprintln(w, html) + } + }) + http.Handle("/", r) + + srv := &http.Server{ + Handler: r, + Addr: "127.0.0.1:8000", + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + log.Fatal(srv.ListenAndServe()) } diff --git a/revision.go b/revision.go new file mode 100644 index 0000000..abf283e --- /dev/null +++ b/revision.go @@ -0,0 +1,79 @@ +package main + +import ( + "errors" + "fmt" + "github.com/gomarkdown/markdown" + "io/ioutil" +) + +type Revision struct { + // Revision is hypha's state at some point in time. Future revisions are not really supported. Most data here is stored in m.ini. + Id int + // Name used at this revision + Name string `json:"name"` + // Name of hypha + FullName string + // Present in every hypha. Stored in t.txt. + TextPath string + // In at least one markup. Supported ones are "myco", "html", "md", "plain" + Markup string `json:"markup"` + // Some hyphæ have binary contents such as images. Their presence change hypha's behavior in a lot of ways (see methods' implementations). If stored, it is stored in b (filename "b") + BinaryPath string + BinaryRequest string + // To tell what is meaning of binary content, mimeType for them is stored. If the hypha has no binary content, this field must be "application/x-hypha" + MimeType string `json:"mimeType"` + // Every revision was created at some point. This field stores the creation time of the latest revision + RevisionTime int `json:"createdAt"` + // Every hypha has any number of tags + Tags []string `json:"tags"` + // Current revision is authored by someone + RevisionAuthor string `json:"author"` + // and has a comment in plain text + RevisionComment string `json:"comment"` +} + +func (h Revision) String() string { + return fmt.Sprintf(`Revision %v created at %v { + name: %v + textPath: %v + markup: %v + binaryPath: %v + mimeType: %v + tags: %v + revisionAuthor: %v + revisionComment: %v +}`, h.Id, h.RevisionTime, h.Name, h.TextPath, h.Markup, h.BinaryPath, h.MimeType, h.Tags, h.RevisionAuthor, h.RevisionComment) +} + +// This method is meant to be called only by Hypha#Render. +func (r Revision) Render(hyphae map[string]*Hypha) (ret string, err error) { + ret += `
+` + // If it is a binary hypha (we support only images for now): + // TODO: support things other than images. + if r.MimeType != "application/x-hypha" { + ret += fmt.Sprintf(``, r.BinaryRequest) + } + + contents, err := ioutil.ReadFile(r.TextPath) + if err != nil { + return "", err + } + + // TODO: support more markups. + // TODO: support mycorrhiza extensions like transclusion. + switch r.Markup { + case "plain": + ret += fmt.Sprintf(`
%s
`, contents) + case "md": + html := markdown.ToHTML(contents, nil, nil) + ret += string(html) + default: + return "", errors.New("Unsupported markup: " + r.Markup) + } + + ret += ` +
` + return ret, nil +} diff --git a/walk.go b/walk.go index ddd5afb..2587210 100644 --- a/walk.go +++ b/walk.go @@ -96,7 +96,7 @@ func recurFindHyphae(fullPath string) (hyphae []*Hypha) { // Fill in every revision for _, possibleRevisionPath := range possibleRevisionPaths { - rev, err := makeRevision(possibleRevisionPath) + rev, err := makeRevision(possibleRevisionPath, h.Name) if err == nil { h.Revisions = append(h.Revisions, rev) } @@ -119,7 +119,7 @@ func recurFindHyphae(fullPath string) (hyphae []*Hypha) { return hyphae } -func makeRevision(fullPath string) (r Revision, err error) { +func makeRevision(fullPath string, fullName string) (r Revision, err error) { // fullPath is expected to be a path to a dir. // Revision directory must have at least `m.json` and `t.txt` files. var ( @@ -159,7 +159,7 @@ func makeRevision(fullPath string) (r Revision, err error) { return r, err } - r = Revision{} + r = Revision{FullName: fullName} err = json.Unmarshal(mJsonContents, &r) if err != nil { fmt.Println(fullPath, ">\tError:", err) @@ -174,6 +174,7 @@ func makeRevision(fullPath string) (r Revision, err error) { // Do not check for binary file presence, attempt to read it will fail anyway if bPresent { r.BinaryPath = filepath.Join(fullPath, "b") + r.BinaryRequest = fmt.Sprintf("%s?rev=%d&action=getBinary", r.FullName, r.Id) } else { return r, errors.New("makeRevision: b file not present") }