all repos — hastyscribe @ 609d522530875ffe28a12b01754cbe421cea5325

A professional markdown compiler.

Refactoring

- TMDMetaData now uses default object fields for initialization and is
  now an `out` parameter of `markdown.md`.
- `sequtils.mapIt` consecutive usage merged.
- Image download warning now ouputs one log mark for the message.
- `compileDocument` refactored to not use accumulation variables.
- `hastyscribe_logo` svg and img tag now prepared at compile time.
- Log level `lvNotice` now uses a dedicated color
- Normal log color changed from `fgWhite` to `fgDefault`
- `utils.encode_image` now uses `strutils.multireplace`
Zoom zoomrmc+git@gmail.com
Sat, 16 Sep 2023 16:55:04 +0400
commit

609d522530875ffe28a12b01754cbe421cea5325

parent

81342890eda6ba6e10748e81b8478afbad62f313

M src/hastyscribe.nimsrc/hastyscribe.nim

@@ -3,7 +3,6 @@ macros,

os, parseopt, strutils, - sequtils, times, pegs, xmltree,

@@ -15,6 +14,7 @@ ]

from nimquery import querySelectorAll from std/htmlparser import parseHtml +from std/sequtils import mapIt import hastyscribepkg/niftylogger,

@@ -124,10 +124,9 @@ elif imgfile.startsWith(peg"'http' 's'? '://'"):

try: let client = newHttpClient() imgcontent = encode_image(client.getContent(imgfile), imgformat) - except CatchableError: - warn "Unable to download '" & imgfile & "'" - warn " Reason: " & getCurrentExceptionMsg() - warn " -> Image will be linked instead" + except CatchableError as e: + warn "Unable to download '$1'\n Reason: $2\n" % [imgfile, e.msg] & + " -> Image will be linked instead" continue else: imgcontent = encode_image_file(current_dir & imgfile, imgformat)

@@ -367,18 +366,15 @@ result = ""

let html = document.parseHtml # Check icons let iconRules = html.querySelectorAll("span[class^=fa-]") - .mapIt(it.attr("class")) - .mapIt(getTableValue(hs.iconStyles, it, "Icon")) + .mapIt(getTableValue(hs.iconStyles, it.attr("class"), "Icon")) result &= iconRules.join("\n") # Check badges let badgeRules = html.querySelectorAll("span[class^=badge-]") - .mapIt(it.attr("class")) - .mapIt(getTableValue(hs.badgeStyles, it, "Badge")) + .mapIt(getTableValue(hs.badgeStyles, it.attr("class"), "Badge")) result &= badgeRules.join("\n") # Check notes let noteRules = html.querySelectorAll("div.tip, div.warning, div.note, div.sidebar") - .mapIt(it.attr("class")) - .mapIt(getTableValue(hs.noteStyles, it, "Note")) + .mapIt(getTableValue(hs.noteStyles, it.attr("class"), "Note")) result &= noteRules.join("\n") # Check links let linkHrefs = html.querySelectorAll("a[href]")

@@ -387,7 +383,7 @@ var linkRules = newSeq[string]()

# Add #document-top rule because it is always needed and added at the end. linkRules.add hs.linkStyles["^='#document-top"] for href in linkHrefs: - for key in hs.linkStyles.keys.toSeq: + for key in hs.linkStyles.keys: if not linkRules.contains(hs.linkStyles[key]): let op = key[0..1] let value = key[3..^1] # Skip first '

@@ -408,8 +404,7 @@

# Public API proc compileFragment*(hs: var HastyScribe, input, dir: string, toc = false): string {.discardable.} = - hs.options.input = input - hs.document = hs.options.input + hs.document = input # Parse transclusions, fields, snippets, and macros hs.document = hs.preprocess(hs.document, dir) # Process markdown

@@ -420,68 +415,50 @@ hs.document = hs.document.md(flags)

return hs.document proc compileDocument*(hs: var HastyScribe, input, dir: string): string {.discardable.} = - hs.options.input = input - hs.document = hs.options.input + hs.document = input # Load style rules to be included on-demand hs.load_styles() # Parse transclusions, fields, snippets, and macros hs.document = hs.preprocess(hs.document, dir) - # Document Variables - var - main_css_tag = "" - optional_css_tag = "" - user_css_tag = "" - user_js_tag = "" - watermark_css_tag = "" - headings = " class=\"headings\"" - author_footer = "" - title_tag = "" - header_tag = "" - toc = "" - metadata = TMDMetaData(title:"", author:"", date:"", toc:"", css:"") - let logo_datauri = encode_image(hastyscribe_logo, "svg") - let hastyscribe_svg = """ - <img src="$#" width="80" height="23" alt="HastyScribe"> - """ % [logo_datauri] # Process markdown + var metadata: TMDMetaData hs.document = hs.document.md(0, metadata) - # Manage metadata - if metadata.author != "": - author_footer = "<span class=\"copy\"></span> " & metadata.author & " &ndash;" - if metadata.title != "": - title_tag = "<title>" & metadata.title & "</title>" - header_tag = "<div id=\"header\"><h1>" & metadata.title & "</h1></div>" - else: - title_tag = "" - header_tag = "" - if hs.options.toc and metadata.toc != "": - toc = "<div id=\"toc\">" & metadata.toc & "</div>" - else: - headings = "" - toc = "" - - if hs.options.css != "": - user_css_tag = hs.options.css.readFile.style_tag + # Document Variables + const hastyscribe_img = """ +<img src="$#" width="80" height="23" alt="HastyScribe"> +""" % encode_image(hastyscribe_logo, "svg") + let + (headings, toc) = if hs.options.toc and metadata.toc != "": + (" class=\"headings\"", "<div id=\"toc\">" & metadata.toc & "</div>") + else: ("", "") + user_css_tag = if hs.options.css == "": "" else: + hs.options.css.readFile.style_tag + user_js_tag = if hs.options.js == "": "" else: + "<script type=\"text/javascript\">\n" & hs.options.js.readFile & "\n</script>" + watermark_css_tag = if hs.options.watermark == "": "" else: + watermark_css(hs.options.watermark) - if hs.options.js != "": - user_js_tag = "<script type=\"text/javascript\">\n" & hs.options.js.readFile & "\n</script>" + # Manage metadata + author_footer = if metadata.author == "": "" else: + "<span class=\"copy\"></span> " & metadata.author & " &ndash;" + title_tag = if metadata.title == "": "" else: + "<title>" & metadata.title & "</title>" + header_tag = if metadata.title == "": "" else: + "<div id=\"header\"><h1>" & metadata.title & "</h1></div>" - if hs.options.watermark != "": - watermark_css_tag = watermark_css(hs.options.watermark) + (main_css_tag, optional_css_tag) = if hs.options.embed: + (stylesheet.style_tag, hs.create_optional_css(hs.document)) + else: + ("", "") # Date parsing and validation var timeinfo: DateTime = local(getTime()) - try: timeinfo = parse(metadata.date, "yyyy-MM-dd") except CatchableError: timeinfo = parse(getDateStr(), "yyyy-MM-dd") - - if hs.options.embed: - main_css_tag = stylesheet.style_tag - optional_css_tag = hs.create_optional_css(hs.document) hs.document = """<!doctype html> <html lang="en">

@@ -507,26 +484,26 @@ $body

</div> <div id="footer"> <p>$author_footer $date</p> - <p><span>Powered by</span> <a href="https://h3rald.com/hastyscribe" class="hastyscribe-logo">$hastyscribe_svg</a></p> + <p><span>Powered by</span> <a href="https://h3rald.com/hastyscribe" class="hastyscribe-logo">$hastyscribe_img</a></p> </div> </div> $js </body>""" % [ - "title_tag", title_tag, - "header_tag", header_tag, - "author", metadata.author, - "author_footer", author_footer, - "date", timeinfo.format("MMMM d, yyyy"), - "toc", toc, - "main_css_tag", main_css_tag, - "hastyscribe_svg", hastyscribe_svg, - "optional_css_tag", optional_css_tag, - "user_css_tag", user_css_tag, - "headings", headings, - "body", hs.document, - "internal_css_tag", metadata.css, - "watermark_css_tag", watermark_css_tag, - "js", user_js_tag] + "title_tag", title_tag, + "header_tag", header_tag, + "author", metadata.author, + "author_footer", author_footer, + "date", timeinfo.format("MMMM d, yyyy"), + "toc", toc, + "main_css_tag", main_css_tag, + "hastyscribe_img", hastyscribe_img, + "optional_css_tag", optional_css_tag, + "user_css_tag", user_css_tag, + "headings", headings, + "body", hs.document, + "internal_css_tag", metadata.css, + "watermark_css_tag", watermark_css_tag, + "js", user_js_tag] if hs.options.embed: hs.embed_images(dir) hs.document = add_jump_to_top_links(hs.document)
M src/hastyscribepkg/markdown.nimsrc/hastyscribepkg/markdown.nim

@@ -1,65 +1,65 @@

-const +const MKDIO_D* = true -type +type MMIOT* = int mkd_flag_t* = cuint {.push importc, cdecl.} # line builder for markdown() -# +# proc mkd_in*(a2: ptr FILE; a3: mkd_flag_t): ptr MMIOT -# assemble input from a file +# assemble input from a file proc mkd_string*(a2: cstring; a3: cint; a4: mkd_flag_t): ptr MMIOT -# assemble input from a buffer +# assemble input from a buffer # line builder for github flavoured markdown -# +# proc gfm_in*(a2: ptr FILE; a3: mkd_flag_t): ptr MMIOT -# assemble input from a file +# assemble input from a file proc gfm_string*(a2: cstring; a3: cint; a4: mkd_flag_t): ptr MMIOT -# assemble input from a buffer +# assemble input from a buffer proc mkd_basename*(a2: ptr MMIOT; a3: cstring) proc mkd_initialize*() proc mkd_with_html5_tags*() proc mkd_shlib_destructor*() # compilation, debugging, cleanup -# +# proc mkd_compile*(a2: ptr MMIOT; a3: mkd_flag_t): cint proc mkd_cleanup*(a2: ptr MMIOT) # markup functions -# +# proc mkd_dump*(a2: ptr MMIOT; a3: ptr FILE; a4: cint; a5: cstring): cint proc markdown*(a2: ptr MMIOT; a3: ptr FILE; a4: mkd_flag_t): cint proc mkd_line*(a2: cstring; a3: cint; a4: cstringArray; a5: mkd_flag_t): cint -type +type mkd_sta_function_t* = proc (a2: cint; a3: pointer): cint -proc mkd_string_to_anchor*(a2: cstring; a3: cint; a4: mkd_sta_function_t; +proc mkd_string_to_anchor*(a2: cstring; a3: cint; a4: mkd_sta_function_t; a5: pointer; a6: cint) proc mkd_xhtmlpage*(a2: ptr MMIOT; a3: cint; a4: ptr FILE): cint # header block access -# +# proc mkd_doc_title*(a2: ptr MMIOT): cstring proc mkd_doc_author*(a2: ptr MMIOT): cstring proc mkd_doc_date*(a2: ptr MMIOT): cstring # compiled data access -# +# proc mkd_document*(a2: ptr MMIOT; a3: cstringArray): cint proc mkd_toc*(a2: ptr MMIOT; a3: cstringArray): cint proc mkd_css*(a2: ptr MMIOT; a3: cstringArray): cint proc mkd_xml*(a2: cstring; a3: cint; a4: cstringArray): cint # write-to-file functions -# +# proc mkd_generatehtml*(a2: ptr MMIOT; a3: ptr FILE): cint proc mkd_generatetoc*(a2: ptr MMIOT; a3: ptr FILE): cint proc mkd_generatexml*(a2: cstring; a3: cint; a4: ptr FILE): cint proc mkd_generatecss*(a2: ptr MMIOT; a3: ptr FILE): cint -const +const mkd_style* = mkd_generatecss proc mkd_generateline*(a2: cstring; a3: cint; a4: ptr FILE; a5: mkd_flag_t): cint -const +const mkd_text* = mkd_generateline # url generator callbacks -# -type +# +type mkd_callback_t* = proc (a2: cstring; a3: cint; a4: pointer): cstring mkd_free_t* = proc (a2: cstring; a3: pointer) proc mkd_e_url*(a2: pointer; a3: mkd_callback_t)

@@ -67,7 +67,7 @@ proc mkd_e_flags*(a2: pointer; a3: mkd_callback_t)

proc mkd_e_free*(a2: pointer; a3: mkd_free_t) proc mkd_e_data*(a2: pointer; a3: pointer) # version#. -# +# var markdown_version*: ptr char proc mkd_mmiot_flags*(a2: ptr FILE; a3: ptr MMIOT; a4: cint) proc mkd_flags_are*(a2: ptr FILE; a3: mkd_flag_t; a4: cint)

@@ -75,8 +75,8 @@ proc mkd_ref_prefix*(a2: ptr MMIOT; a3: cstring)

{.pop.} # special flags for markdown() and mkd_text() -# -const +# +const MKD_NOLINKS* = 0x00000001 MKD_NOIMAGE* = 0x00000002 MKD_NOPANTS* = 0x00000004

@@ -111,24 +111,24 @@ MKD_EMBED* = MKD_NOLINKS or MKD_NOIMAGE or MKD_TAGTEXT

## High Level API -import +import std/pegs -const - DefaultFlags = MKD_TOC or MKD_1_COMPAT or MKD_EXTRA_FOOTNOTE or MKD_DLEXTRA or MKD_FENCEDCODE or MKD_GITHUBTAGS or MKD_URLENCODEDANCHOR or MKD_LATEX +const + DefaultFlags = MKD_TOC or MKD_1_COMPAT or MKD_EXTRA_FOOTNOTE or MKD_DLEXTRA or MKD_FENCEDCODE or MKD_GITHUBTAGS or MKD_URLENCODEDANCHOR or MKD_LATEX -type TMDMetaData* = object - title*: string - author*: string - date*: string - toc*: string - css*: string +type TMDMetaData* = object + title*: string = "" + author*: string = "" + date*: string = "" + toc*: string = "" + css*: string = "" proc md*(s: string, f = 0): string = var flags: uint32 if (f == 0): flags = DefaultFlags - else: + else: flags = uint32(f) var str = cstring(s&" ") var mmiot = mkd_string(str, cint(str.len-1), flags)

@@ -139,11 +139,12 @@ result = cstringArrayToSeq(res)[0]

mkd_cleanup(mmiot) return -proc md*(s: string, f = 0, data: var TMDMetadata): string = +proc md*(s: string, f = 0; data: out TMDMetaData): string = + data = default(TMDMetaData) var flags: uint32 if (f == 0): flags = DefaultFlags - else: + else: flags = uint32(f) # Check if Pandoc style metadata is present var valid_metadata = false

@@ -152,7 +153,7 @@ let peg_pandoc = peg"""

definition <- ^{line} {line}? {line}? line <- '\%' @ \n """ - var matches: array[0..2, string] + var matches: array[0..2, string] let (s, e) = contents.findBounds(peg_pandoc, matches) # the pattern must start at the beginning of the file if s == 0:

@@ -160,7 +161,7 @@ if matches[0] != "" and matches[1] != "" and matches[2] != "":

valid_metadata = true else: # incomplete metadata, remove the whole pandoc section to not confuse discount - contents = contents[e-1 .. ^1] + contents = contents[e-1 .. ^1] var str = cstring(contents) var mmiot = mkd_string(str, cint(str.len), flags) if valid_metadata:

@@ -173,18 +174,15 @@ if (int(flags) and MKD_TOC) == MKD_TOC:

var toc = allocCStringArray(@[""]) if mkd_toc(mmiot, toc) > 0: data.toc = cstringArrayToSeq(toc)[0] - else: - data.toc = "" # Process CSS - var css = allocCStringArray(newSeq[string](10)) - if mkd_css(mmiot, css) > 0: - data.css = cstringArrayToSeq(css)[0] - else: - data.css = "" + data.css = block: + var css = allocCStringArray(newSeq[string](10)) + if mkd_css(mmiot, css) > 0: cstringArrayToSeq(css)[0] + else: "" # Generate HTML - var res = allocCStringArray([""]) - if mkd_document(mmiot, res) > 0: - result = cstringArrayToSeq(res)[0] - else: - result = "" + let html = block: + var res = allocCStringArray([""]) + if mkd_document(mmiot, res) > 0: cstringArrayToSeq(res)[0] + else: "" mkd_cleanup(mmiot) + html
M src/hastyscribepkg/niftylogger.nimsrc/hastyscribepkg/niftylogger.nim

@@ -1,13 +1,14 @@

-import - std/logging, - std/strutils, - std/terminal, - std/exitprocs +import std/[ + logging, + strutils, + terminal, + exitprocs, + ] if isatty(stdin): addExitProc(resetAttributes) -type +type NiftyLogger* = ref object of Logger proc logPrefix*(level: Level): tuple[msg: string, color: ForegroundColor] =

@@ -17,7 +18,7 @@ return ("---", fgMagenta)

of lvlInfo: return ("(i)", fgCyan) of lvlNotice: - return (" ", fgWhite) + return (" ", fgBlue) of lvlWarn: return ("(!)", fgYellow) of lvlError:

@@ -25,12 +26,12 @@ return ("(!)", fgRed)

of lvlFatal: return ("(x)", fgRed) else: - return (" ", fgWhite) + return (" ", fgDefault) method log*(logger: NiftyLogger; level: Level; args: varargs[string, `$`]) = var f = stdout if level >= getLogFilter() and level >= logger.levelThreshold: - if level >= lvlWarn: + if level >= lvlWarn: f = stderr let ln = substituteLog(logger.fmtStr, level, args) let prefix = level.logPrefix()
M src/hastyscribepkg/utils.nimsrc/hastyscribepkg/utils.nim

@@ -1,8 +1,9 @@

-import - std/base64, - std/os, - std/strutils, - std/pegs +import std/[ + base64, + os, + strutils, + pegs, + ] import consts

@@ -14,19 +15,20 @@ proc style_link_tag*(css: string): string =

result = "<link rel=\"stylesheet\" href=\"$1\"/>" % [css] proc encode_image*(contents, format: string): string = - if format == "svg": - let encoded_svg = contents - .replace("\"", "'") - .replace("%", "%25") - .replace("#", "%23") - .replace("{", "%7B") - .replace("}", "%7D") - .replace("<", "%3C") - .replace(">", "%3E") - .replace(" ", "%20") - return "data:image/svg+xml,$#" % [encoded_svg] - else: - return "data:image/$format;base64,$enc_contents" % ["format", format, "enc_contents", contents.encode] + if format == "svg": + let encoded_svg = contents.multireplace([ + ("\"", "'"), + ("%", "%25"), + ("#", "%23"), + ("{", "%7B"), + ("}", "%7D"), + ("<", "%3C"), + (">", "%3E"), + (" ", "%20"), + ]) + "data:image/svg+xml,$#" % [encoded_svg] + else: + "data:image/$format;base64,$enc_contents" % ["format", format, "enc_contents", contents.encode] proc encode_image_file*(file, format: string): string = if (file.fileExists):