all repos — hastyscribe @ fcb9a91bc3cb746744dc47a0beb293cf55ed9350

A professional markdown compiler.

Implemented fragment compilation; refactoring.
h3rald h3rald@h3rald.com
Sun, 23 Oct 2016 14:27:26 +0200
commit

fcb9a91bc3cb746744dc47a0beb293cf55ed9350

parent

a4799ca4fa2d0849d318e578461681a9bab98bea

A build_guide

@@ -0,0 +1,1 @@

+./hastyscribe doc/HastyScribe_UserGuide.md --field/version=1.4.0
A consts.nim

@@ -0,0 +1,30 @@

+ +const + stylesheet* = "assets/styles/hastyscribe.css".slurp + hastyscribe_font* = "assets/fonts/hastyscribe.woff".slurp + fontawesome_font* = "assets/fonts/fontawesome-webfont.woff".slurp + sourcecodepro_font* = "assets/fonts/SourceCodePro-Regular.ttf.woff".slurp + sourcesanspro_font* = "assets/fonts/SourceSansPro-Regular.ttf.woff".slurp + sourcesanspro_bold_font* = "assets/fonts/SourceSansPro-Bold.ttf.woff".slurp + sourcesanspro_it_font* = "assets/fonts/SourceSansPro-It.ttf.woff".slurp + sourcesanspro_boldit_font* = "assets/fonts/SourceSansPro-BoldIt.ttf.woff".slurp + watermark_style* = """ +#container { + position: relative; + z-index: 0; +} +#container:after { + content: ""; + opacity: 0.1; + z-index: -1; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background-image: url($1); + background-repeat: no-repeat; + background-position: center 70px; + background-attachment: fixed; +} +"""
M doc/HastyScribe_UserGuide.htmdoc/HastyScribe_UserGuide.htm

@@ -253,12 +253,12 @@

<p>The easiest way to get HastyScribe is by downloading one of the prebuilt binaries from the <a href="https://github.com/h3rald/hastyscribe/releases/download/v">Github Release Page</a>:</p> <ul> -<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.3.0/hastyscribe_v1.3.0_macos_x64.zip">HastyScribe for Mac OS X (x64)</a> &ndash; Compiled on Mac OS X Sierra (LLVM CLANG 8.0.0)</li> -<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.3.0/hastyscribe_v1.3.0_windows_x64.zip">HastyScribe for Windows (x64)</a> &ndash; Cross-compiled on Mac OS X Sierra (MinGW-w64 GCC 4.8.2)</li> -<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.3.0/hastyscribe_v1.3.0_windows_x86.zip">HastyScribe for Windows (x86)</a> &ndash; Cross-compiled on Mac OS X Sierra (MinGW-w64 GCC 4.8.2)</li> -<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.3.0/hastyscribe_v1.3.0_linux_x64.zip">HastyScribe for Linux (x64)</a> &ndash; Cross-compiled on Mac OS X Sierra (GNU GCC 4.8.1)</li> -<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.3.0/hastyscribe_v1.3.0_linux_x86.zip">HastyScribe for Linux (x86)</a> &ndash; Cross-compiled on Mac OS X Sierra (GNU GCC 4.8.1)</li> -<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.3.0/hastyscribe_v1.3.0_linux_arm.zip">HastyScribe for Linux (ARM)</a> &ndash; Cross-compiled on Mac OS X Sierra (GNU GCC 4.8.2)</li> +<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.4.0/hastyscribe_v1.4.0_macos_x64.zip">HastyScribe for Mac OS X (x64)</a> &ndash; Compiled on Mac OS X Sierra (LLVM CLANG 8.0.0)</li> +<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.4.0/hastyscribe_v1.4.0_windows_x64.zip">HastyScribe for Windows (x64)</a> &ndash; Cross-compiled on Mac OS X Sierra (MinGW-w64 GCC 4.8.2)</li> +<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.4.0/hastyscribe_v1.4.0_windows_x86.zip">HastyScribe for Windows (x86)</a> &ndash; Cross-compiled on Mac OS X Sierra (MinGW-w64 GCC 4.8.2)</li> +<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.4.0/hastyscribe_v1.4.0_linux_x64.zip">HastyScribe for Linux (x64)</a> &ndash; Cross-compiled on Mac OS X Sierra (GNU GCC 4.8.1)</li> +<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.4.0/hastyscribe_v1.4.0_linux_x86.zip">HastyScribe for Linux (x86)</a> &ndash; Cross-compiled on Mac OS X Sierra (GNU GCC 4.8.1)</li> +<li><a href="https://github.com/h3rald/hastyscribe/releases/download/v1.4.0/hastyscribe_v1.4.0_linux_arm.zip">HastyScribe for Linux (ARM)</a> &ndash; Cross-compiled on Mac OS X Sierra (GNU GCC 4.8.2)</li> </ul>

@@ -317,9 +317,10 @@

<ul> <li><span class="opt">--field/&lt;field&gt;=&lt;value&gt;</span> causes HastyScribe to set a custom field to a specific value.</li> <li><span class="opt">--notoc</span> causes HastyScribe to output HTML documents <em>without</em> automatically generating a Table of Contents at the start.</li> -<li><span class="opt">--user-css=&lt;file&gt;</span> causes HastyScribe inserts the contents of the specified local file as a CSS stylesheet.</li> +<li><span class="opt">--user-css=&lt;file&gt;</span> causes HastyScribe to insert the contents of the specified local file as a CSS stylesheet.</li> <li><span class="opt">--output-file=&lt;file&gt;</span> causes HastyScribe to write output to a local file (Use <span class="opt">--output-file=-</span> to output to standard output).</li> <li><span class="opt">--watermark=&lt;file&gt;</span> causes HastyScribe to embed and display an image as a watermark throughout the document.</li> +<li><span class="opt">--fragment</span> causes HastyScribe to output just an HTML fragment instead of a full document, without embedding any image, font or stylesheet.</li> </ul> </li> </ul>

@@ -402,51 +403,51 @@ </thead>

<tbody> <tr> <td><code>{{$timestamp}}</code> </td> -<td> 1476008117</td> +<td> 1477225576</td> </tr> <tr> <td><code>{{$date}}</code> </td> -<td> 2016-10-09</td> +<td> 2016-10-23</td> </tr> <tr> <td><code>{{$full-date}}</code> </td> -<td> Sunday, October 9, 2016</td> +<td> Sunday, October 23, 2016</td> </tr> <tr> <td><code>{{$long-date}}</code> </td> -<td> October 9, 2016</td> +<td> October 23, 2016</td> </tr> <tr> <td><code>{{$medium-date}}</code> </td> -<td> Oct 9, 2016</td> +<td> Oct 23, 2016</td> </tr> <tr> <td><code>{{$short-date}}</code> </td> -<td> 10/9/16</td> +<td> 10/23/16</td> </tr> <tr> <td><code>{{$short-time}}</code> </td> -<td> 12:15 PM</td> +<td> 14:26 PM</td> </tr> <tr> <td><code>{{$short-time-24}}</code> </td> -<td> 12:15</td> +<td> 14:26</td> </tr> <tr> <td><code>{{$time}}</code> </td> -<td> 12:15:17 PM</td> +<td> 14:26:16 PM</td> </tr> <tr> <td><code>{{$time-24}}</code> </td> -<td> 12:15:17</td> +<td> 14:26:16</td> </tr> <tr> <td><code>{{$day}}</code> </td> -<td> 09</td> +<td> 23</td> </tr> <tr> <td><code>{{$short-day}}</code> </td> -<td> 9</td> +<td> 23</td> </tr> <tr> <td><code>{{$month}}</code> </td>

@@ -470,7 +471,7 @@ <td> Sunday</td>

</tr> <tr> <td><code>{{$weekday-abbr}}</code> </td> -<td> 09</td> +<td> 23</td> </tr> <tr> <td><code>{{$month-name}}</code> </td>

@@ -1200,7 +1201,8 @@ : A list for unordered items. Also called _bulleted list_.

ordered list : A list for ordered items. Also called _numbered list_. alphabetical list -: Technically speaking just an ordered list, but formatted with letters instead of numbers +: Technically speaking just an ordered list, but formatted with letters instead + of numbers definition list : A list of terms and definitions. </code></pre>

@@ -1213,7 +1215,8 @@ <dd>A list for unordered items. Also called <em>bulleted list</em>.</dd>

<dt>ordered list </dt> <dd>A list for ordered items. Also called <em>numbered list</em>.</dd> <dt>alphabetical list</dt> -<dd>Technically speaking just an ordered list, but formatted with letters instead of numbers</dd> +<dd>Technically speaking just an ordered list, but formatted with letters instead +of numbers</dd> <dt>definition list</dt> <dd>A list of terms and definitions.</dd> </dl>

@@ -1225,7 +1228,8 @@ A list for unordered items. Also called _bulleted list_.

=ordered list= A list for ordered items. Also called _numbered list_. =alphabetical list= - Technically speaking just an ordered list, but formatted with letters instead of numbers + Technically speaking just an ordered list, but formatted with letters instead + of numbers =definition list= A list of terms and definitions. </code></pre>

@@ -1297,7 +1301,8 @@

<pre><code>&gt; %sidebar% &gt; This is a _sidebar_ &gt; -&gt; Although not always placed on the side of the page, _sidebars_ contain additional content and asides. +&gt; Although not always placed on the side of the page, _sidebars_ contain +&gt; additional content and asides. </code></pre> <p>Produces the following output:</p>

@@ -1366,7 +1371,7 @@

<p style="text-align:center;"> <img src="data:image/png;base64,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" alt="Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License" /></p> </div> <div id="footer"> - <p><span class="copy"></span> Fabio Cevasco &ndash; October 9, 2016</p> + <p><span class="copy"></span> Fabio Cevasco &ndash; October 23, 2016</p> <p><span>Powered by</span> <a href="https://h3rald.com/hastyscribe"><span class="hastyscribe"></span></a></p> </div> </div>
M doc/HastyScribe_UserGuide.mddoc/HastyScribe_UserGuide.md

@@ -143,9 +143,10 @@ * _filename-or-glob-expression_ is a valid file or [glob](http://en.wikipedia.org/wiki/Glob_(programming)) expression that will be compiled into HTML.

* The following options are supported: * [\-\-field/&lt;field&gt;=&lt;value&gt;](class:opt) causes {{hs}} to set a custom field to a specific value. * [\-\-notoc](class:opt) causes {{hs}} to output HTML documents _without_ automatically generating a Table of Contents at the start. - * [\-\-user-css=&lt;file&gt;](class:opt) causes {{hs}} inserts the contents of the specified local file as a CSS stylesheet. + * [\-\-user-css=&lt;file&gt;](class:opt) causes {{hs}} to insert the contents of the specified local file as a CSS stylesheet. * [\-\-output-file=&lt;file&gt;](class:opt) causes {{hs}} to write output to a local file (Use [\-\-output-file=-](class:opt) to output to standard output). * [\-\-watermark=&lt;file&gt;](class:opt) causes {{hs}} to embed and display an image as a watermark throughout the document. + * [\-\-fragment](class:opt) causes {{hs}} to output just an HTML fragment instead of a full document, without embedding any image, font or stylesheet. ### Linux/OSX Examples

@@ -655,7 +656,8 @@ : A list for unordered items. Also called _bulleted list_.

ordered list : A list for ordered items. Also called _numbered list_. alphabetical list -: Technically speaking just an ordered list, but formatted with letters instead of numbers +: Technically speaking just an ordered list, but formatted with letters instead + of numbers definition list : A list of terms and definitions. ~~~

@@ -667,7 +669,8 @@ : A list for unordered items. Also called _bulleted list_.

ordered list : A list for ordered items. Also called _numbered list_. alphabetical list -: Technically speaking just an ordered list, but formatted with letters instead of numbers +: Technically speaking just an ordered list, but formatted with letters instead + of numbers definition list : A list of terms and definitions.

@@ -679,7 +682,8 @@ A list for unordered items. Also called _bulleted list_.

=ordered list= A list for ordered items. Also called _numbered list_. =alphabetical list= - Technically speaking just an ordered list, but formatted with letters instead of numbers + Technically speaking just an ordered list, but formatted with letters instead + of numbers =definition list= A list of terms and definitions. ~~~

@@ -759,7 +763,8 @@ ~~~

> %sidebar% > This is a _sidebar_ > -> Although not always placed on the side of the page, _sidebars_ contain additional content and asides. +> Although not always placed on the side of the page, _sidebars_ contain +> additional content and asides. ~~~ {{output-text}}
M hastyscribe.nimhastyscribe.nim

@@ -11,217 +11,90 @@ tables,

httpclient import - config + config, + consts, + utils -let usage* = " HastyScribe v" & version & " - Self-contained Markdown Compiler" & """ - (c) 2013-2016 Fabio Cevasco +type + HastyOptions* = object + toc: bool + input: string + output: string + css: string + watermark: string + fragment: bool + HastyFields* = Table[string, proc():string] + HastySnippets* = Table[string, string] + HastyMacros* = Table[string, string] + HastyScribe* = object + options: HastyOptions + fields: HastyFields + snippets: HastySnippets + macros: HastyMacros + document: string - Usage: - hastyscribe <markdown_file_or_glob> [options] - Arguments: - markdown_file_or_glob The markdown (or glob expression) file to compile into HTML. - Options: - --field/<field>=<value> Define a new field called <field> with value <value>. - --notoc Do not generate a Table of Contents. - --user-css=<file> Insert contents of <file> as a CSS stylesheet. - --output-file=<file> Write output to <file>. - (Use "--output-file=-" to output to stdout) - --watermark=<file> Use the image in <file> as a watermark.""" +proc initFields(fields: HastyFields): HastyFields = + result = initTable[string, proc():string]() + for key, value in fields.pairs: + result[key] = value + var now = getTime().getLocalTime() + result["timestamp"] = proc():string = + return $now.toTime.toSeconds().int + result["date"] = proc():string = + return now.format("yyyy-MM-dd") + result["full-date"] = proc():string = + return now.format("dddd, MMMM d, yyyy") + result["long-date"] = proc():string = + return now.format("MMMM d, yyyy") + result["medium-date"] = proc():string = + return now.format("MMM d, yyyy") + result["short-date"] = proc():string = + return now.format("M/d/yy") + result["short-time-24"] = proc():string = + return now.format("HH:mm") + result["short-time"] = proc():string = + return now.format("HH:mm tt") + result["time-24"] = proc():string = + return now.format("HH:mm:ss") + result["time"] = proc():string = + return now.format("HH:mm:ss tt") + result["day"] = proc():string = + return now.format("dd") + result["month"] = proc():string = + return now.format("MM") + result["year"] = proc():string = + return now.format("yyyy") + result["short-day"] = proc():string = + return now.format("d") + result["short-month"] = proc():string = + return now.format("M") + result["short-year"] = proc():string = + return now.format("yy") + result["weekday"] = proc():string = + return now.format("dddd") + result["weekday-abbr"] = proc():string = + return now.format("dd") + result["month-name"] = proc():string = + return now.format("MMMM") + result["month-name-abbr"] = proc():string = + return now.format("MMM") + result["timezone-offset"] = proc():string = + return now.format("zzz") + result["timezone"] = proc():string = + return now.format("ZZZ") - - -var generate_toc* = true -var output_file*: string -var user_css*: string -var watermark*: string +proc newHastyScribe*(options: HastyOptions, fields: HastyFields): HastyScribe = + return HastyScribe(options: options, fields: initFields(fields), snippets: initTable[string, string](), macros: initTable[string, string](), document: "") -const - stylesheet* = "assets/styles/hastyscribe.css".slurp - hastyscribe_font* = "assets/fonts/hastyscribe.woff".slurp - fontawesome_font* = "assets/fonts/fontawesome-webfont.woff".slurp - sourcecodepro_font* = "assets/fonts/SourceCodePro-Regular.ttf.woff".slurp - sourcesanspro_font* = "assets/fonts/SourceSansPro-Regular.ttf.woff".slurp - sourcesanspro_bold_font* = "assets/fonts/SourceSansPro-Bold.ttf.woff".slurp - sourcesanspro_it_font* = "assets/fonts/SourceSansPro-It.ttf.woff".slurp - sourcesanspro_boldit_font* = "assets/fonts/SourceSansPro-BoldIt.ttf.woff".slurp - watermark_style* = """ -#container { - position: relative; - z-index: 0; -} -#container:after { - content: ""; - opacity: 0.1; - z-index: -1; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - background-image: url($1); - background-repeat: no-repeat; - background-position: center 70px; - background-attachment: fixed; -} -""" +# Utility Procedures -let - peg_imgformat = peg"i'.png' / i'.jpg' / i'.jpeg' / i'.gif' / i'.svg' / i'.bmp' / i'.webp' @$" - peg_img = peg""" +proc embed_images(hs: var HastyScribe, dir: string) = + let peg_img = peg""" image <- '<img' \s+ 'src=' ["] {file} ["] file <- [^"]+ """ - peg_snippet_def = peg""" - definition <- '{{' \s* {id} \s* '->' {@} '}}' - id <- [a-zA-Z0-9_-]+ - """ - peg_snippet = peg""" - snippet <- '{{' \s* {id} \s* '}}' - id <- [a-zA-Z0-9_-]+ - """ - peg_field = peg""" - field <- '{{' \s* '$' {id} \s* '}}' - id <- [a-zA-Z0-9_-]+ - """ - peg_macro_def = peg""" - definition <- '{#' \s* {id} \s* '->' {@} '#}' - id <- [a-zA-Z0-9_-]+ - """ - peg_macro_instance = peg""" - instance <- "{#" \s* {id} \s* "||" \s* {@} "#}" - id <- [a-zA-Z0-9_-]+ - """ - -var FIELDS = initTable[string, proc():string]() - - -var NOW:TimeInfo = getTime().getLocalTime() - -FIELDS["timestamp"] = proc():string = - return $NOW.toTime.toSeconds().int - - - -FIELDS["date"] = proc():string = - return $NOW.format("yyyy-MM-dd") - -FIELDS["full-date"] = proc():string = - return $NOW.format("dddd, MMMM d, yyyy") - -FIELDS["long-date"] = proc():string = - return $NOW.format("MMMM d, yyyy") - -FIELDS["medium-date"] = proc():string = - return $NOW.format("MMM d, yyyy") - -FIELDS["short-date"] = proc():string = - return $NOW.format("M/d/yy") - - - -FIELDS["short-time-24"] = proc():string = - return $NOW.format("HH:mm") - -FIELDS["short-time"] = proc():string = - return $NOW.format("HH:mm tt") - -FIELDS["time-24"] = proc():string = - return $NOW.format("HH:mm:ss") - -FIELDS["time"] = proc():string = - return $NOW.format("HH:mm:ss tt") - - - -FIELDS["day"] = proc():string = - return $NOW.format("dd") - -FIELDS["month"] = proc():string = - return $NOW.format("MM") - -FIELDS["year"] = proc():string = - return $NOW.format("yyyy") - -FIELDS["short-day"] = proc():string = - return $NOW.format("d") - -FIELDS["short-month"] = proc():string = - return $NOW.format("M") - -FIELDS["short-year"] = proc():string = - return $NOW.format("yy") - - - -FIELDS["weekday"] = proc():string = - return $NOW.format("dddd") - -FIELDS["weekday-abbr"] = proc():string = - return $NOW.format("dd") - - - -FIELDS["month-name"] = proc():string = - return $NOW.format("MMMM") - -FIELDS["month-name-abbr"] = proc():string = - return $NOW.format("MMM") - - - -FIELDS["timezone-offset"] = proc():string = - return $NOW.format("zzz") - -FIELDS["timezone"] = proc():string = - return $NOW.format("ZZZ") - - - -# Procedures - -proc parse_date*(date: string, timeinfo: var TimeInfo): bool = - var parts = date.split('-').map(proc(i:string): int = - try: - i.parseInt - except: - 0 - ) - if parts.len < 3: - return false - try: - timeinfo = TimeInfo(year: parts[0], month: Month(parts[1]-1), monthday: parts[2]) - # Fix invalid dates (e.g. Feb 31st -> Mar 3rd) - timeinfo = getLocalTime(timeinfo.toTime); - return true - except: - return false - -proc style_tag*(css: string): string = - result = "<style>$1</style>" % [css] - -proc encode_image*(contents, format: string): string = - let enc_contents = contents.encode(contents.len*3) - return "data:image/$format;base64,$enc_contents" % ["format", format, "enc_contents", enc_contents] - -proc encode_image_file*(file, format: string): string = - if (file.existsFile): - let contents = file.readFile - return encode_image(contents, format) - else: - stderr.writeLine("Warning: image '" & file & "' not found.") - return file - -proc encode_font*(font, format: string): string = - let enc_contents = font.encode(font.len*3) - return "data:application/$format;charset=utf-8;base64,$enc_contents" % ["format", format, "enc_contents", enc_contents] - - -proc image_format*(imgfile: string): string = - return imgfile.substr(imgfile.find(peg_imgformat)+1, imgfile.len-1) - -proc embed_images*(document, dir: string): string = var current_dir:string if dir.len == 0: current_dir = ""

@@ -229,8 +102,8 @@ else:

current_dir = dir & "/" type TImgTagStart = array[0..0, string] - var doc = document - for img in findAll(document, peg_img): + var doc = hs.document + for img in findAll(hs.document, peg_img): var matches:TImgTagStart discard img.match(peg_img, matches) let imgfile = matches[0]

@@ -251,42 +124,22 @@ else:

imgcontent = encode_image_file(current_dir & imgfile, imgformat) let imgrep = img.replace("\"" & img_file & "\"", "\"" & imgcontent & "\"") doc = doc.replace(img, imgrep) - return doc + hs.document = doc -proc watermark_css*(imgfile: string): string = - if imgfile.isNil: - result = "" - else: - let img = imgfile.encode_image_file(imgfile.image_format) - result = (watermark_style % [img]).style_tag - -proc add_jump_to_top_links*(document: string): string = - return document.replacef(peg"{'</h' [23456] '>'}", "<a href=\"#document-top\" title=\"Go to top\"></a>$1") +proc add_jump_to_top_links(hs: var HastyScribe) = + hs.document = hs.document.replacef(peg"{'</h' [23456] '>'}", "<a href=\"#document-top\" title=\"Go to top\"></a>$1") -proc create_font_face*(font, family, style: string, weight: int): string= - return """ - @font-face { - font-family:"$family"; - src:url($font) format('woff'); - font-style:$style; - font-weight:$weight; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - } - """ % ["family", family, "font", encode_font(font, "x-font-woff"), "style", style, "weight", $weight] - -var fonts* = [ - create_font_face(hastyscribe_font, "HastyScribe", "normal", 400), - create_font_face(fontawesome_font, "FontAwesome", "normal", 400), - create_font_face(sourcecodepro_font, "Source Code Pro", "normal", 400), - create_font_face(sourcesanspro_font, "Source Sans Pro", "normal", 400), - create_font_face(sourcesanspro_bold_font, "Source Sans Pro", "normal", 800), - create_font_face(sourcesanspro_it_font, "Source Sans Pro", "italic", 400), - create_font_face(sourcesanspro_boldit_font, "Source Sans Pro", "italic", 800) +proc embed_fonts(): string= + let fonts = @[ + create_font_face(hastyscribe_font, "HastyScribe", "normal", 400), + create_font_face(fontawesome_font, "FontAwesome", "normal", 400), + create_font_face(sourcecodepro_font, "Source Code Pro", "normal", 400), + create_font_face(sourcesanspro_font, "Source Sans Pro", "normal", 400), + create_font_face(sourcesanspro_bold_font, "Source Sans Pro", "normal", 800), + create_font_face(sourcesanspro_it_font, "Source Sans Pro", "italic", 400), + create_font_face(sourcesanspro_boldit_font, "Source Sans Pro", "italic", 800) ] - -proc embed_fonts*(): string= return style_tag(fonts.join); # Macro Definition:

@@ -294,15 +147,22 @@ # {{#test -> This is a $1}}

# # Macro Usage: # {{#test||simple test}} -proc parse_macros*(document: string): string = - var macros:Table[string, string] = initTable[string, string]() - var doc = document - for def in findAll(document, peg_macro_def): +proc parse_macros(hs: var HastyScribe) = + let peg_macro_def = peg""" + definition <- '{#' \s* {id} \s* '->' {@} '#}' + id <- [a-zA-Z0-9_-]+ + """ + let peg_macro_instance = peg""" + instance <- "{#" \s* {id} \s* "||" \s* {@} "#}" + id <- [a-zA-Z0-9_-]+ + """ + var doc = hs.document + for def in findAll(hs.document, peg_macro_def): var matches: array[0..1, string] discard def.match(peg_macro_def, matches) let id = matches[0].strip let value = matches[1].strip - macros[id] = value + hs.macros[id] = value doc = doc.replace(def, "") for instance in findAll(doc, peg_macro_instance): var matches: array[0..1, string]

@@ -310,92 +170,105 @@ discard instance.match(peg_macro_instance, matches)

let id = matches[0].strip let value = matches[1].strip let params = value.split("||") - if macros.hasKey(id): + if hs.macros.hasKey(id): try: - doc = doc.replace(instance, macros[id] % params) + doc = doc.replace(instance, hs.macros[id] % params) except: stderr.writeLine "Warning: Incorrect number of parameters specified for macro '$1'\n -> Instance: $2" % [id, instance] else: stderr.writeLine "Warning: Macro '" & id & "' not defined." doc = doc.replace(instance, "") - return doc + hs.document = doc # Field Usage: # {{$timestamp}} - -proc parse_fields*(document: string): string = - NOW = getTime().getLocalTime() - var doc = document - for field in document.findAll(peg_field): +proc parse_fields(hs: var HastyScribe) = + let peg_field = peg""" + field <- '{{' \s* '$' {id} \s* '}}' + id <- [a-zA-Z0-9_-]+ + """ + var doc = hs.document + for field in hs.document.findAll(peg_field): var matches:array[0..0, string] discard field.match(peg_field, matches) var id = matches[0].strip - if FIELDS.hasKey(id): - doc = doc.replace(field, FIELDS[id]()) + if hs.fields.hasKey(id): + doc = doc.replace(field, hs.fields[id]()) else: stderr.writeLine "Warning: Field '" & id & "' not defined." doc = doc.replace(field, "") - return doc + hs.document = doc # Snippet Definition: # {{test -> My test snippet}} # # Snippet Usage: # {{test}} - -proc parse_snippets*(document: string): string = - var snippets:Table[string, string] = initTable[string, string]() +proc parse_snippets(hs: var HastyScribe) = + let peg_snippet_def = peg""" + definition <- '{{' \s* {id} \s* '->' {@} '}}' + id <- [a-zA-Z0-9_-]+ + """ + let peg_snippet = peg""" + snippet <- '{{' \s* {id} \s* '}}' + id <- [a-zA-Z0-9_-]+ + """ type TSnippetDef = array[0..1, string] TSnippet = array[0..0, string] - var doc = document - for def in findAll(document, peg_snippet_def): + var doc = hs.document + for def in findAll(hs.document, peg_snippet_def): var matches:TSnippetDef discard def.match(peg_snippet_def, matches) var id = matches[0].strip var value = matches[1].strip(true, false) - snippets[id] = value + hs.snippets[id] = value doc = doc.replace(def, value) - for snippet in findAll(document, peg_snippet): + for snippet in findAll(hs.document, peg_snippet): var matches:TSnippet discard snippet.match(peg_snippet, matches) var id = matches[0].strip - if snippets[id] == nil: + if hs.snippets[id] == nil: stderr.writeLine "Warning: Snippet '" & id & "' not defined." doc = doc.replace(snippet, "") else: - doc = doc.replace(snippet, snippets[id]) - return doc - -proc compile*(input_file: string) = - let inputsplit = input_file.splitFile - - # Output file name - if output_file == nil: - output_file = inputsplit.dir/inputsplit.name & ".htm" - - var source = input_file.readFile + doc = doc.replace(snippet, hs.snippets[id]) + hs.document = doc +proc compileFragment*(hs: var HastyScribe, input: string): string {.discardable.} = + hs.options.input = input + hs.document = hs.options.input # Parse fields, snippets, and macros - source = parse_fields(source) - source = parse_snippets(source) - source = parse_macros(source) + hs.parse_fields() + hs.parse_snippets() + hs.parse_macros() + # Process markdown + hs.document = hs.document.md(MKD_EXTRA_FOOTNOTE) + return hs.document +proc compileDocument*(hs: var HastyScribe, input, dir: string): string {.discardable.} = + hs.options.input = input + hs.document = hs.options.input + # Parse fields, snippets, and macros + hs.parse_fields() + hs.parse_snippets() + hs.parse_macros() # Document Variables - var metadata = TMDMetaData(title:"", author:"", date:"", toc:"", css:"") - var body = source.md(MKD_DOTOC or MKD_EXTRA_FOOTNOTE, metadata) - var main_css_tag = stylesheet.style_tag - var user_css_tag = "" - var watermark_css_tag = "" - var headings = " class=\"headings\"" - var author_footer = "" - + var + main_css_tag = stylesheet.style_tag + user_css_tag = "" + watermark_css_tag = "" + headings = " class=\"headings\"" + author_footer = "" + title_tag = "" + header_tag = "" + toc = "" + metadata = TMDMetaData(title:"", author:"", date:"", toc:"", css:"") + # Process markdown + hs.document = hs.document.md(MKD_DOTOC or MKD_EXTRA_FOOTNOTE, metadata) # Manage metadata if metadata.author != "": author_footer = "<span class=\"copy\"></span> " & metadata.author & " &ndash;" - - var title_tag, header_tag, toc: string - if metadata.title != "": title_tag = "<title>" & metadata.title & "</title>" header_tag = "<div id=\"header\"><h1>" & metadata.title & "</h1></div>"

@@ -403,17 +276,17 @@ else:

title_tag = "" header_tag = "" - if generate_toc == true and metadata.toc != "": + if hs.options.toc and metadata.toc != "": toc = "<div id=\"toc\">" & metadata.toc & "</div>" else: headings = "" toc = "" - if user_css != nil: - user_css_tag = user_css.readFile.style_tag + if not hs.options.css.isNil: + user_css_tag = hs.options.css.readFile.style_tag - if not watermark.isNil: - watermark_css_tag = watermark_css(watermark) + if not hs.options.watermark.isNil: + watermark_css_tag = watermark_css(hs.options.watermark) # Date parsing and validation var timeinfo: TimeInfo = getLocalTime(getTime())

@@ -421,7 +294,7 @@

if parse_date(metadata.date, timeinfo) == false: discard parse_date(getDateStr(), timeinfo) - var document = """<!doctype html> + hs.document = """<!doctype html> <html lang="en"> <head> $title_tag

@@ -448,22 +321,58 @@ <p>$author_footer $date</p>

<p><span>Powered by</span> <a href="https://h3rald.com/hastyscribe"><span class="hastyscribe"></span></a></p> </div> </div> -</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, "user_css_tag", user_css_tag, "headings", headings, "body", body, +</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, "user_css_tag", user_css_tag, "headings", headings, "body", hs.document, "fonts_css_tag", embed_fonts(), "internal_css_tag", metadata.css, "watermark_css_tag", watermark_css_tag] - document = embed_images(document, inputsplit.dir) - document = add_jump_to_top_links(document) - if output_file != "-": - output_file.writeFile(document) + hs.embed_images(dir) + hs.add_jump_to_top_links() + return hs.document + +proc compile*(hs: var HastyScribe, input_file: string) = + let inputsplit = input_file.splitFile + var input = input_file.readFile + var output: string + + if hs.options.output.isNil: + output = inputsplit.dir/inputsplit.name & ".htm" else: - stdout.write(document) + output = hs.options.output + + if hs.options.fragment: + hs.compileFragment(input) + else: + hs.compileDocument(input, inputsplit.dir) + if output != "-": + output.writeFile(hs.document) + else: + stdout.write(hs.document) ### MAIN when isMainModule: - var input = "" - var files = @[""] + let usage = " HastyScribe v" & version & " - Self-contained Markdown Compiler" & """ - discard files.pop + (c) 2013-2016 Fabio Cevasco + + Usage: + hastyscribe <markdown_file_or_glob> [options] + + Arguments: + markdown_file_or_glob The markdown (or glob expression) file to compile into HTML. + Options: + --field/<field>=<value> Define a new field called <field> with value <value>. + --notoc Do not generate a Table of Contents. + --user-css=<file> Insert contents of <file> as a CSS stylesheet. + --output-file=<file> Write output to <file>. + (Use "--output-file=-" to output to stdout) + --watermark=<file> Use the image in <file> as a watermark. + --fragment If specified, an HTML fragment will be generated, without + embedding images, fonts, or stylesheets. """ + + + var input = "" + var files = newSeq[string](0) + var options = HastyOptions(toc: true, output: nil, css: nil, watermark: nil, fragment: false) + var fields = initTable[string, proc():string]() # Parse Parameters

@@ -474,17 +383,19 @@ input = key

of cmdLongOption: case key of "notoc": - generate_toc = false + options.toc = false of "user-css": - user_css = val + options.css = val of "watermark": - watermark = val + options.watermark = val of "output-file": - output_file = val + options.output = val + of "fragment": + options.fragment = true else: if key.startsWith("field/"): let val = val - FIELDS[key.replace("field/", "")] = proc(): string = + fields[key.replace("field/", "")] = proc(): string = return val discard else:

@@ -492,10 +403,13 @@ discard

if input == "": quit(usage, 1) - elif user_css == "": + elif options.css == "": quit(usage, 4) - elif output_file == "": + elif options.output == "": quit(usage, 5) + elif options.watermark == "": + quit(usage, 6) + for file in walkFiles(input): files.add(file)

@@ -503,9 +417,10 @@

if files.len == 0: quit("Error: \"$1\" does not match any file" % [input], 2) else: + var hs = newHastyScribe(options, fields) try: for file in files: - compile(file) + hs.compile(file) except IOError: let msg = getCurrentExceptionMsg() quit("Error: $1" % [msg], 3)
M hastyscribe.nimblehastyscribe.nimble

@@ -1,6 +1,6 @@

[Package] name = "hastyscribe" -version = "1.3.0" +version = "1.4.0" author = "Fabio Cevasco" description = "Self-contained markdown compiler generating self-contained HTML documents" license = "MIT"
A utils.nim

@@ -0,0 +1,71 @@

+import + base64, + os, + strutils, + sequtils, + pegs, + times + +import + consts + +proc parse_date*(date: string, timeinfo: var TimeInfo): bool = + var parts = date.split('-').map(proc(i:string): int = + try: + i.parseInt + except: + 0 + ) + if parts.len < 3: + return false + try: + timeinfo = TimeInfo(year: parts[0], month: Month(parts[1]-1), monthday: parts[2]) + # Fix invalid dates (e.g. Feb 31st -> Mar 3rd) + timeinfo = getLocalTime(timeinfo.toTime); + return true + except: + return false + +proc style_tag*(css: string): string = + result = "<style>$1</style>" % [css] + +proc encode_image*(contents, format: string): string = + let enc_contents = contents.encode(contents.len*3) + return "data:image/$format;base64,$enc_contents" % ["format", format, "enc_contents", enc_contents] + +proc encode_image_file*(file, format: string): string = + if (file.existsFile): + let contents = file.readFile + return encode_image(contents, format) + else: + stderr.writeLine("Warning: image '" & file & "' not found.") + return file + +proc image_format*(imgfile: string): string = + let peg_imgformat = peg"i'.png' / i'.jpg' / i'.jpeg' / i'.gif' / i'.svg' / i'.bmp' / i'.webp' @$" + return imgfile.substr(imgfile.find(peg_imgformat)+1, imgfile.len-1) + +proc watermark_css*(imgfile: string): string = + if imgfile.isNil: + result = "" + else: + let img = imgfile.encode_image_file(imgfile.image_format) + result = (watermark_style % [img]).style_tag + +proc encode_font*(font, format: string): string = + let enc_contents = font.encode(font.len*3) + return "data:application/$format;charset=utf-8;base64,$enc_contents" % ["format", format, "enc_contents", enc_contents] + +proc create_font_face*(font, family, style: string, weight: int): string= + return """ + @font-face { + font-family:"$family"; + src:url($font) format('woff'); + font-style:$style; + font-weight:$weight; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + """ % ["family", family, "font", encode_font(font, "x-font-woff"), "style", style, "weight", $weight] + +