First commit
commit
e22334952d
|
@ -0,0 +1,19 @@
|
|||
A simple MD to HTML converter
|
||||
|
||||
## Compilation
|
||||
|
||||
1. `git clone https://git.kirbylife.dev/kirbylife/nim-md-to-html`
|
||||
1. `cd nim-md-to-html`
|
||||
1. `nimble build -d:release`
|
||||
|
||||
## Usage
|
||||
|
||||
1. `nim-md-to-html <path/to/file.md> </path/to/theme.css> > index.html`
|
||||
|
||||
The theme must be a classless CSS file.
|
||||
I personally recommend [water.css](https://watercss.kognise.dev/), but on the internet there is a good variety, choose the one you like the most.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the LGPL 3.0 license.
|
|
@ -0,0 +1,16 @@
|
|||
# Package
|
||||
|
||||
version = "0.1.0"
|
||||
author = "kirbylife"
|
||||
description = "A simple MD to HTML converter"
|
||||
license = "LGPL-3.0-or-later"
|
||||
srcDir = "src"
|
||||
bin = @["md_to_html_embed"]
|
||||
|
||||
|
||||
# Dependencies
|
||||
|
||||
requires "nim >= 2.0"
|
||||
requires "markdown == 0.8.7"
|
||||
requires "glob == 0.11.3"
|
||||
requires "regex == 0.25.0"
|
|
@ -0,0 +1,85 @@
|
|||
import markdown
|
||||
import glob
|
||||
import regex
|
||||
import std/htmlparser
|
||||
import std/xmltree
|
||||
import std/strtabs
|
||||
import std/strutils
|
||||
import std/os
|
||||
import std/paths
|
||||
import std/sequtils
|
||||
import std/streams
|
||||
import std/base64
|
||||
|
||||
const MAXOUTERLIMIT = 2
|
||||
|
||||
proc applyTheme(html: XmlNode, theme: string) =
|
||||
let charset_att = {"charset": "utf-8"}.toXmlAttributes()
|
||||
let charset = newXmlTree("meta", [], charset_att)
|
||||
|
||||
let theme_att = {"style": "text/css"}.toXmlAttributes()
|
||||
let theme_content = newText(theme)
|
||||
let style = newXmlTree("style", [theme_content], theme_att)
|
||||
|
||||
let head = newXmlTree("head", [charset, style])
|
||||
|
||||
html.insert(head, 0)
|
||||
|
||||
proc bytesToBase64(bytes: seq[byte]): string =
|
||||
return " data:image/jpeg;charset=utf8;base64," & bytes.encode()
|
||||
|
||||
proc pathToBytes(path: string): seq[byte] =
|
||||
const block_size = 4096
|
||||
|
||||
var
|
||||
buffer: array[block_size, byte]
|
||||
bytes: seq[byte]
|
||||
total_bytes_readed = 0
|
||||
|
||||
let strm = openFileStream(path, fmRead)
|
||||
|
||||
while true:
|
||||
let bytes_readed = strm.readData(addr(buffer), block_size)
|
||||
if bytes_readed == 0:
|
||||
break
|
||||
bytes.add(buffer[0..<bytes_readed])
|
||||
|
||||
strm.close()
|
||||
|
||||
return bytes
|
||||
|
||||
proc embedImages(html: XmlNode, root: string) =
|
||||
for node in html.findAll("img"):
|
||||
# echo node
|
||||
if node.attrs.hasKey("src"):
|
||||
var src = node.attrs["src"]
|
||||
if src.isEmptyOrWhitespace():
|
||||
continue
|
||||
if not src.startsWith("http"):
|
||||
let search_param = root / "../".repeat(MAXOUTERLIMIT) / "**" / src
|
||||
let possible_attempts = walkGlob(search_param).toSeq()
|
||||
if possible_attempts.len() > 0:
|
||||
let img_path = possible_attempts[0]
|
||||
src = img_path.pathToBytes().bytesToBase64()
|
||||
node.attrs["src"] = src
|
||||
|
||||
# let img_bytes = pathToBytes(src)
|
||||
|
||||
when isMainModule and declared(commandLineParams):
|
||||
let
|
||||
params = commandLineParams()
|
||||
md_src = absolutePath(params[0])
|
||||
md_root = splitFile(md_src).dir
|
||||
theme_src = params[1]
|
||||
|
||||
let raw_md = readFile(md_src).replace(re2"!\[\[(.+)\]\]", "<img src=\"$1\">")
|
||||
let raw_html = markdown(raw_md, config=initGfmConfig())
|
||||
let parsed_html = parseHtml(raw_html)
|
||||
|
||||
let raw_theme = readFile(theme_src)
|
||||
|
||||
embedImages(parsed_html, md_root)
|
||||
applyTheme(parsed_html, raw_theme)
|
||||
|
||||
echo parsed_html
|
||||
|
Loading…
Reference in New Issue