package server import ( "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/ast" "github.com/gomarkdown/markdown/parser" "vultras.su/core/bond" "vultras.su/core/bond/contents" "vultras.su/util/pp" "path/filepath" "path" "os" "fmt" "strings" "bytes" ) const htmlHead = ` Title ` const htmlFooter = ` ` type ServerOptions struct { WikiPath string WikiExt string WebPath string AddFileNavigation bool AddDocNavigation bool } type Server struct { handler bond.Handler options ServerOptions prep *pp.Preprocessor } func (srv *Server) makeMdParser() *parser.Parser { return parser.NewWithExtensions( parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock | parser.Attributes | parser.Tables, ) } func (srv *Server) makeHtmlRenderer() *html.Renderer { return html.NewRenderer( html.RendererOptions{ Flags: html.CommonFlags | html.HrefTargetBlank, }, ) } func New(opts ServerOptions) *Server { srv := &Server{} srv.handler = makeRootHandler(opts) srv.options = opts srv.prep = pp.NewPp(pp.NewTengo()) return srv } func (srv *Server) Handle(c *bond.Context) { c.Data = ContextData{ Server: srv, } srv.handler.Handle(c) } func (srv *Server) ProcessPmd(pth string, bts []byte) ([]byte, error) { prep, err := srv.prep.Process(pth, string(bts)) if err != nil { return nil, err } return []byte(prep), nil } func (srv *Server) pageHead() string { return htmlHead } func (srv *Server) pageFooter() string { return htmlFooter } type Heading struct { Id string Level int Text string } func getDocumentHeadings(doc ast.Node) []Heading { ret := []Heading{} ast.WalkFunc(doc, func(node ast.Node, entering bool) ast.WalkStatus { heading, ok := node.(*ast.Heading) if !ok { return ast.GoToNext } children := node.GetChildren() if len(children) > 0 && !entering { leaf := children[0].AsLeaf() if leaf == nil { return ast.GoToNext } ret = append(ret, Heading{ Id: heading.HeadingID, Level: heading.Level, Text: string(leaf.Literal), }) return ast.SkipChildren } return ast.GoToNext }) return ret } type HeadingTree struct { Heading Heading Children HeadingTrees } type HeadingTrees []*HeadingTree func makeHeadingTrees(hs []Heading) HeadingTrees { fmt.Println("fooooooooooooooouuuuuuuuund", len(hs), hs) if len(hs) == 0 { return nil } if len(hs) == 1 { return HeadingTrees{ &HeadingTree{ Heading: hs[0], }, } } var ( lasti int found bool ) first := hs[0] rest := hs[1:] for i, h := range rest { if first.Level >= h.Level { fmt.Println("thefound: ", first, h) lasti = i+1 found = true break } } if !found { fmt.Println("in not found") return HeadingTrees{ &HeadingTree{ Heading: first, Children: makeHeadingTrees(rest), }, } } fmt.Println("through", lasti) return append( makeHeadingTrees(hs[:lasti]), makeHeadingTrees(hs[lasti:])... , ) } func RenderHeadingTrees(trees HeadingTrees, first bool) string { var b strings.Builder fmt.Fprint(&b, "") for _, tree := range trees { fmt.Fprintf( &b, "%s", tree.Heading.Id, tree.Heading.Text, ) if len(tree.Children) > 0 { fmt.Fprint(&b, RenderHeadingTrees(tree.Children, false)) } } fmt.Fprint(&b, "") return b.String() } func (srv *Server) ProcessToHtml(urlPath, filePath string, bts []byte) ([]byte, error) { var b bytes.Buffer doc := srv.makeMdParser().Parse(bts) main_section := markdown.Render(doc, srv.makeHtmlRenderer()) fmt.Fprint(&b, srv.pageHead()) if srv.options.AddFileNavigation { fileDirPath := filepath.Dir(filePath) urlDirPath := path.Dir(urlPath) entries, err := os.ReadDir(fileDirPath) if err != nil { fmt.Println("leaving", fileDirPath) return nil, err } fmt.Fprint(&b, "
") } fmt.Fprint(&b, "
") if srv.options.AddDocNavigation { headings := getDocumentHeadings(doc) trees := makeHeadingTrees(headings) docNav := RenderHeadingTrees(trees, true) fmt.Fprint(&b, docNav) } fmt.Fprint(&b, "
") fmt.Fprintf(&b, string(main_section)) fmt.Fprintf(&b, "
") fmt.Fprintf(&b, "
") fmt.Fprint(&b, srv.pageFooter()) return b.Bytes(), nil } var makeRootHandler = func(opts ServerOptions) bond.Handler { return bond.Root(bond.Path(). Case( "web", bond.StaticDir(opts.WebPath), ).Case( "api", nil, ).Default( Func(func(c *Context){ pth := c.wikiFilePath(c.Path()) bts, err := os.ReadFile(pth) if err != nil { pth = c.wikiFilePath(c.Path()+"/") bts, err = os.ReadFile(pth) if err != nil { c.NotFound() return } } prep, err := c.ProcessPmd(pth, bts) if err != nil { c.InternalServerError(err) return } fmt.Println(c.Path(), pth) bts, err = c.ProcessToHtml(c.Path(), pth, prep) if err != nil { c.InternalServerError(err) return } c.SetContentType(contents.Html, contents.Utf8) c.W.Write(bts) }), )) }