miki

Miki: personal wiki with the fmd markup format
git clone https://wtenison.com/repo/miki.git
Log | Files | Refs | README | LICENSE

commit 83dfe3d818c4d92a62ea24e0a26482c2ab71da9f
parent 0a45c4708fa283f94989fe6324f148cfd16ce173
Author: weetat <wtenison@protonmail.com>
Date:   Wed, 26 Feb 2025 19:01:00 +0000

Render fmd to HTML and show it on the home page

Add an interpreter that walks the AST and emits HTML. MakeDirectoryPkg
looks for a .fmd file in the data directory matching the page name and
passes the rendered HTML to the template.

Diffstat:
Msrc/filesystem/filesystem.go | 32+++++++++++++++++++++++++++++---
Msrc/fmd/fmd.go | 43+++++++++++++++++++++++++++++++++++++++++++
Msrc/fmd/fmd_test.go | 26+++++++++++++-------------
Asrc/fmd/interpreter.go | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/fmd/lexer.go | 33+++++++++++++++++++--------------
Msrc/fmd/parser.go | 27+++++++--------------------
Asrc/fmd/temp.html | 13+++++++++++++
Msrc/network/network.go | 2++
Mweb/global/styles.css | 5+++++
Mweb/home/index.html | 6++++--
10 files changed, 196 insertions(+), 52 deletions(-)

diff --git a/src/filesystem/filesystem.go b/src/filesystem/filesystem.go @@ -4,9 +4,11 @@ import ( "encoding/json" "fmt" "log" + "miki/src/fmd" "os" "path/filepath" "regexp" + "strings" ) var FilePatternHTML = regexp.MustCompile("^([a-zA-Z0-9.]+).html$") @@ -25,9 +27,9 @@ type DirectorySignature struct { } type DirectoryPackage struct { - Root string // The root directory of the resource. - DependencyFiles []string // The set of valid resource files. - DependencyData IDirectoryData // Any relevant resource data. + Root string // The root directory of the resource. + DependencyFiles []string // The set of valid resource files. + DependencyData interface{} // Any relevant resource data. } type IDirectoryData interface { @@ -95,6 +97,30 @@ func MakeDirectoryPkg(resSig DirectorySignature) (DirectoryPackage, error) { // Include any sub-directory file dependencies. _resPkg.DependencyFiles = append(_resPkg.DependencyFiles, _resFileDirDeps...) + // Check for atricle entry. + // TODO: Move to secure and refined interface. + _dataFiles, _err := os.ReadDir(Dirs.DataDirectory) + if _err != nil { + _retErr := fmt.Errorf("MakeDirectoryPkg:%s", _err) + return DirectoryPackage{}, _retErr + } + + for _, _df := range _dataFiles { + _rootFile := strings.Split(resSig.Root, "/") + _rootDataFile := _rootFile[len(_rootFile)-1] + ".fmd" + + if _df.Name() == _rootDataFile { + log.Printf("INFO\tMakeDirectoryPkg:df:%s, rootDataFile:%s", _df.Name(), _rootDataFile) + var _DataContainer struct { + PageContent string + } + _DataContainer.PageContent = fmd.GetFMDToHTML(filepath.Join(Dirs.DataDirectory, _rootDataFile)) + + // log.Printf("INFO\tMakeDirectoryPkg:dataContainer.pageContent:%s", _DataContainer.PageContent) + _resPkg.DependencyData = _DataContainer + } + } + // log.Printf("INFO\tMakeDirectoryPkg:%v\n", _resPkg) return _resPkg, nil } diff --git a/src/fmd/fmd.go b/src/fmd/fmd.go @@ -1,2 +1,45 @@ package fmd +import ( + "bytes" + "log" + "os" +) + +func RenderFMD(filePath string) { + _fileBytes, _err := os.ReadFile(filePath) + if _err != nil { + log.Println("_ERROR\tRenderFMD:ReadFile:", filePath, ":", _err) + } + + _l := MakeLexer("RenderLexer", _fileBytes) + + _tokens := _l.Exec() + + _p := MakeParser(_tokens) + + _ast := _p.Exec() + + _outputBytes := bytes.NewBufferString(interpret(_ast)) + + if _err = os.WriteFile("temp.html", _outputBytes.Bytes(), 0666); _err != nil { + log.Println("_ERROR\tRenderFMD:os.WriteFile:", filePath, ":", _err) + } +} + +func GetFMDToHTML(filePath string) string { + _fileBytes, _err := os.ReadFile(filePath) + if _err != nil { + log.Println("_ERROR\tRenderFMD:ReadFile:", filePath, ":", _err) + } + + _l := MakeLexer("RenderLexer", _fileBytes) + + _tokens := _l.Exec() + + _p := MakeParser(_tokens) + + _ast := _p.Exec() + + return interpret(_ast) +} diff --git a/src/fmd/fmd_test.go b/src/fmd/fmd_test.go @@ -1,26 +1,26 @@ package fmd import ( - "os" "testing" ) func TestFMD(t *testing.T) { _path := ("/home/weetat/MIKI/data/ents/home.fmd") - t.Log("Path:", _path) - _input, _err := os.ReadFile(_path) - if _err != nil { - t.Error("_ERROR\tcannot open file:", _err.Error()) - return - } + RenderFMD(_path) + // t.Log("Path:", _path) + // _input, _err := os.ReadFile(_path) + // if _err != nil { + // t.Error("_ERROR\tcannot open file:", _err.Error()) + // return + // } - _l := MakeLexer("MyLexer", _input) - _tokens := _l.Exec() + // _l := MakeLexer("MyLexer", _input) + // _tokens := _l.Exec() - t.Log("Tokens:", _tokens) + // // t.Log("Tokens:", _tokens) - _p := MakeParser(_tokens) - _ast := _p.Exec() + // _p := MakeParser(_tokens) + // _ast := _p.Exec() - t.Log("AST:", _ast) + // t.Log(interpret(_ast)) } diff --git a/src/fmd/interpreter.go b/src/fmd/interpreter.go @@ -0,0 +1,61 @@ +package fmd + +import ( + "strings" +) + +func interpret(n node) string { + var _res strings.Builder + + switch n.nType { + case nodeFMD: + for _, _child := range n.children { + _res.WriteString(interpret(_child)) + } + case nodeCommand: + switch n.value { + case "b": + return "<b>" + interpret(n.children[0]) + "</b>" + case "i": + return "<i>" + interpret(n.children[0]) + "</i>" + case "h1": + return "<h1>" + interpret(n.children[0]) + "</h1>" + case "h2": + return "<h2>" + interpret(n.children[0]) + "</h2>" + case "h3": + return "<h3>" + interpret(n.children[0]) + "</h3>" + case "h4": + return "<h4>" + interpret(n.children[0]) + "</h4>" + case "h5": + return "<h5>" + interpret(n.children[0]) + "</h5>" + case "h6": + return "<h6>" + interpret(n.children[0]) + "</h6>" + case "@": + return "<a>" + interpret(n.children[0]) + "</a>" + case "img": + { + _child := interpret(n.children[0]) + _src := strings.Split(_child, "/") + _alt := _src[len(_src)-1] + return "<img src=\"" + _child + "\" alt=\"" + _alt + "\">" + "</img>" + } + case "col": + return "<div class=\"__mcol\">" + interpret(n.children[0]) + "</div>" + default: + return "<div>" + interpret(n.children[0]) + "</div>" + } + case nodeBlock: + _res.WriteString("<div>") + for _, _child := range n.children { + _res.WriteString(interpret(_child)) + } + _res.WriteString("</div>") + case nodeNewline: + return n.value + case nodeSpace: + return n.value + case nodeLiteral: + return n.value + } + return _res.String() +} diff --git a/src/fmd/lexer.go b/src/fmd/lexer.go @@ -2,7 +2,6 @@ package fmd import ( "slices" - "strings" ) /* Reference source: Lexical Scanning in Go - Rob Pike, @@ -11,15 +10,20 @@ https://www.youtube.com/watch?v=HxaD_trXwRE */ type tokenType int const ( + // WORDS TokenLiteral tokenType = iota // 0. (letters, numbers, symbols)+ TokenKeyword // 1. (valid keywords) - TokenSpace // 2. " " - TokenNewline // 3. \n - TokenTab // 4. \t - TokenEscape // 5. \ - TokenColon // 6. : - TokenLBrace // 7. { - TokenRBrace // 8. } + + // SPACING + TokenSpace // 2. " " + TokenNewline // 3. \n + TokenTab // 4. \t + TokenEscape // 5. \ + + // OPERATORS + TokenColon // 6. : + TokenLBrace // 7. { + TokenRBrace // 8. } ) type token struct { @@ -27,8 +31,9 @@ type token struct { Value string } -// var opSymbols = []string{":", "{", "}", "\\", "\\n"} -var opKeywords = []string{"b", "i", "h1", "h2", "h3", "h4", "h5", "h6", "@", "#"} +var symbols = []string{" ", "\n", "\t", "\\", ":", "{", "}"} +var opKeywords = []string{ + "b", "i", "h1", "h2", "h3", "h4", "h5", "h6", "@", "#", "col", "img", "_"} type lexer struct { name string // for error output @@ -55,6 +60,9 @@ func (l *lexer) Exec() []token { case _currentSymbol == '\t': l.tokens = append(l.tokens, token{TokenTab, "\t"}) l.pos++ + case _currentSymbol == '\\': + l.tokens = append(l.tokens, token{TokenEscape, "\\"}) + l.pos++ case _currentSymbol == ':': l.tokens = append(l.tokens, token{TokenColon, ":"}) l.pos++ @@ -64,15 +72,12 @@ func (l *lexer) Exec() []token { case _currentSymbol == '}': l.tokens = append(l.tokens, token{TokenRBrace, "}"}) l.pos++ - case _currentSymbol == '\\': - l.tokens = append(l.tokens, token{TokenEscape, "\\"}) - l.pos++ default: { // NOTE: inlined check until good reason to abstract arrives. var _tokenString string _start := l.pos - for l.pos < len(l.input) && !strings.Contains(":{} \\\n", string(l.input[l.pos])) { + for l.pos < len(l.input) && !slices.Contains(symbols, string(l.input[l.pos])) { l.pos++ } diff --git a/src/fmd/parser.go b/src/fmd/parser.go @@ -2,7 +2,6 @@ package fmd import ( "fmt" - "log" ) type nodeType string @@ -13,6 +12,7 @@ const ( nodeCommand nodeType = "command" nodeLiteral nodeType = "literal" nodeSpace nodeType = "space" + nodeNewline nodeType = "newline" ) type node struct { @@ -32,6 +32,7 @@ func MakeParser(tokens []token) *parser { func (p *parser) Exec() node { _fmd := node{nType: nodeFMD} + for p.pos < len(p.tokens) { if p.isNextCommand() { // keyword:? _fmd.children = append(_fmd.children, p.parseCommand()) @@ -50,57 +51,47 @@ func (p *parser) parseCommand() node { p.consume(TokenColon) - log.Printf("Command:%v", _command) // keyword:{block} if p.peekNextN(TokenLBrace, 0) { p.consume(TokenLBrace) - // log.Printf("Command:branch:Block:%v", _command) _command.children = []node{p.parseBlock()} p.consume(TokenRBrace) } else { // keyword:"literal" - // log.Printf("Command:branch:Literal:%v", _command) _command.children = []node{p.parseLiteral()} } - log.Printf("Command:completed:%v", _command) return _command } func (p *parser) parseBlock() node { _block := node{nType: nodeBlock} - log.Printf("Block:%v", _block) for p.pos < len(p.tokens) && p.current().Type != TokenRBrace { + if p.isNextCommand() { // keyword:? - // log.Printf("Block:branch:Command:%v", _block) _block.children = append(_block.children, p.parseCommand()) } else if p.peekNextN(TokenSpace, 0) { // " " - // log.Printf("Block:space%v", _block) - // _block.children = append(_block.children, node{nType: nodeSpace, value: " "}) + _block.children = append(_block.children, node{nType: nodeSpace, value: " "}) p.pos++ } else if p.peekNextN(TokenNewline, 0) { // "\n" - // log.Printf("Block:newline:%v", _block) - // _block.children = append(_block.children, node{nType: nodeSpace, value: "\\n"}) + _block.children = append(_block.children, node{nType: nodeNewline, value: "\n"}) p.pos++ } else if p.peekNextN(TokenTab, 0) { // "\t" - // log.Printf("Block:tab:%v", _block) - // _block.children = append(_block.children, node{nType: nodeSpace, value: "\\n"}) + // _block.children = append(_block.children, node{nType: nodeSpace, value: "\\t"}) p.pos++ } else { // "literal" - // log.Printf("Block:branch:Literal:%v", _block) _block.children = append(_block.children, p.parseLiteral()) } + } - log.Printf("Block:completed:%v", _block) return _block } func (p *parser) parseLiteral() node { _literal := node{nType: nodeLiteral, value: p.consume(TokenLiteral).Value} - log.Printf("Literal:%v", _literal) return _literal } @@ -113,14 +104,10 @@ func (p *parser) isNextCommand() bool { func (p *parser) peekNextN(t tokenType, n int) bool { _res := p.pos+n < len(p.tokens) && p.tokens[p.pos+n].Type == t - if p.pos+n < len(p.tokens) { - // log.Printf("\tPeek:inputToken:%v, parserToken:%v", t, p.tokens[p.pos+n]) - } return _res } func (p *parser) consume(t tokenType) token { - // log.Printf("\tconsume:%v", t) if p.peekNextN(t, 0) { token := p.tokens[p.pos] p.pos++ diff --git a/src/fmd/temp.html b/src/fmd/temp.html @@ -0,0 +1,12 @@ +<h1> <b>Welcome</b> To My <i>Wiki</i> </h1> + +This is the landing page for my wiki. + +<div class="__mcol"> + <div> + <img src="data/logo.png" alt="logo.png"></img> + </div> + <div> + This is the content of the other column. + </div> +</div> +\ No newline at end of file diff --git a/src/network/network.go b/src/network/network.go @@ -41,6 +41,8 @@ func handlePageDefault(w http.ResponseWriter, r *http.Request) { _rPkg.DependencyFiles = append(_rPkg.DependencyFiles, mfs.GlobalPkg.DependencyFiles...) + // log.Printf("INFO\thandleDefaultPage:rPkg:%v", _rPkg) + _tmpl, _err := template.ParseFiles(_rPkg.DependencyFiles...) if _err != nil { log.Printf("_ERROR\thandlePageDefault(\"/\"):%s\n", _err) diff --git a/web/global/styles.css b/web/global/styles.css @@ -345,6 +345,11 @@ font-size: 0.75em; } + .__mcol { + display: flex; + flex-direction: column; + } + } @layer modifier { diff --git a/web/home/index.html b/web/home/index.html @@ -18,11 +18,13 @@ <section id="subject_"> - <section id="_card" class="__container_bg-1"> + <!-- <section id="_card" class="__container_bg-1"> - </section> + </section> --> <section id="_body"> + + {{.PageContent}} </section>