diff options
-rw-r--r-- | tests/testament/htmlgen.nim | 146 | ||||
-rw-r--r-- | tests/testament/testamenthtml.templ | 327 |
2 files changed, 217 insertions, 256 deletions
diff --git a/tests/testament/htmlgen.nim b/tests/testament/htmlgen.nim index 05c24b2b5..bf26a956d 100644 --- a/tests/testament/htmlgen.nim +++ b/tests/testament/htmlgen.nim @@ -9,54 +9,33 @@ ## HTML generator for the tester. -import cgi, backend, strutils, json, os +import cgi, backend, strutils, json, os, tables, times import "testamenthtml.templ" -proc generateTestRunTabListItemPartial(outfile: File, testRunRow: JsonNode, firstRow = false) = +proc generateTestResultPanelPartial(outfile: File, testResultRow: JsonNode) = let - # The first tab gets the bootstrap class for a selected tab - firstTabActiveClass = if firstRow: "active" - else: "" - commitId = htmlQuote testRunRow["commit"].str - hash = htmlQuote(testRunRow["commit"].str) - branch = htmlQuote(testRunRow["branch"].str) - machineId = htmlQuote testRunRow["machine"].str - machineName = htmlQuote(testRunRow["machine"].str) - - outfile.generateHtmlTabListItem( - firstTabActiveClass, - commitId, - machineId, - branch, - hash, - machineName - ) - -proc generateTestResultPanelPartial(outfile: File, testResultRow: JsonNode, onlyFailing = false) = - let - trId = htmlQuote(testResultRow["category"].str & "_" & testResultRow["name"].str) + trId = htmlQuote(testResultRow["category"].str & "_" & testResultRow["name"].str). + multiReplace({".": "_", " ": "_", ":": "_"}) name = testResultRow["name"].str.htmlQuote() category = testResultRow["category"].str.htmlQuote() target = testResultRow["target"].str.htmlQuote() action = testResultRow["action"].str.htmlQuote() result = htmlQuote testResultRow["result"].str - expected = htmlQuote testResultRow["expected"].str - gotten = htmlQuote testResultRow["given"].str + expected = testResultRow["expected"].str + gotten = testResultRow["given"].str timestamp = "unknown" - var panelCtxClass, textCtxClass, bgCtxClass, resultSign, resultDescription: string + var + panelCtxClass, textCtxClass, bgCtxClass: string + resultSign, resultDescription: string case result of "reSuccess": - if onlyFailing: - return panelCtxClass = "success" textCtxClass = "success" bgCtxClass = "success" resultSign = "ok" resultDescription = "PASS" of "reIgnored": - if onlyFailing: - return panelCtxClass = "info" textCtxClass = "info" bgCtxClass = "info" @@ -71,9 +50,7 @@ proc generateTestResultPanelPartial(outfile: File, testResultRow: JsonNode, only outfile.generateHtmlTestresultPanelBegin( trId, name, target, category, action, resultDescription, - timestamp, - result, resultSign, - panelCtxClass, textCtxClass, bgCtxClass + timestamp, result, resultSign, panelCtxClass, textCtxClass, bgCtxClass ) if expected.isNilOrWhitespace() and gotten.isNilOrWhitespace(): outfile.generateHtmlTestresultOutputNone() @@ -90,7 +67,7 @@ type totalCount, successCount, ignoredCount, failedCount: int successPercentage, ignoredPercentage, failedPercentage: BiggestFloat -proc allTestResults(): AllTests = +proc allTestResults(onlyFailing = false): AllTests = result.data = newJArray() for file in os.walkFiles("testresults/*.json"): let data = parseFile(file) @@ -98,69 +75,74 @@ proc allTestResults(): AllTests = echo "[ERROR] ignoring json file that is not an array: ", file else: for elem in data: - result.data.add elem let state = elem["result"].str + inc result.totalCount if state.contains("reSuccess"): inc result.successCount elif state.contains("reIgnored"): inc result.ignoredCount - - result.totalCount = result.data.len - result.successPercentage = 100 * (result.successCount.toBiggestFloat() / result.totalCount.toBiggestFloat()) - result.ignoredPercentage = 100 * (result.ignoredCount.toBiggestFloat() / result.totalCount.toBiggestFloat()) - result.failedCount = result.totalCount - result.successCount - result.ignoredCount - result.failedPercentage = 100 * (result.failedCount.toBiggestFloat() / result.totalCount.toBiggestFloat()) - - -proc generateTestResultsPanelGroupPartial(outfile: File, allResults: JsonNode, onlyFailing = false) = + if not onlyFailing or not(state.contains("reSuccess")): + result.data.add elem + result.successPercentage = 100 * + (result.successCount.toBiggestFloat / result.totalCount.toBiggestFloat) + result.ignoredPercentage = 100 * + (result.ignoredCount.toBiggestFloat / result.totalCount.toBiggestFloat) + result.failedCount = result.totalCount - + result.successCount - result.ignoredCount + result.failedPercentage = 100 * + (result.failedCount.toBiggestFloat / result.totalCount.toBiggestFloat) + +proc generateTestResultsPanelGroupPartial(outfile: File, allResults: JsonNode) = for testresultRow in allResults: - generateTestResultPanelPartial(outfile, testresultRow, onlyFailing) + generateTestResultPanelPartial(outfile, testresultRow) -proc generateTestRunTabContentPartial(outfile: File, allResults: AllTests, testRunRow: JsonNode, onlyFailing = false, firstRow = false) = +proc generateAllTestsContent(outfile: File, allResults: AllTests, + onlyFailing = false) = + if allResults.data.len < 1: return # Nothing to do if there is no data. + # Only results from one test run means that test run environment info is the + # same for all tests let - # The first tab gets the bootstrap classes for a selected and displaying tab content - firstTabActiveClass = if firstRow: " in active" - else: "" - commitId = htmlQuote testRunRow["commit"].str - hash = htmlQuote(testRunRow["commit"].str) - branch = htmlQuote(testRunRow["branch"].str) - machineId = htmlQuote testRunRow["machine"].str - machineName = htmlQuote(testRunRow["machine"].str) - os = htmlQuote("unknown_os") - cpu = htmlQuote("unknown_cpu") - - outfile.generateHtmlTabPageBegin( - firstTabActiveClass, commitId, - machineId, branch, hash, machineName, os, cpu, + firstRow = allResults.data[0] + commit = htmlQuote firstRow["commit"].str + branch = htmlQuote firstRow["branch"].str + machine = htmlQuote firstRow["machine"].str + + outfile.generateHtmlAllTestsBegin( + machine, commit, branch, allResults.totalCount, - allResults.successCount, formatBiggestFloat(allResults.successPercentage, ffDecimal, 2) & "%", - allResults.ignoredCount, formatBiggestFloat(allResults.ignoredPercentage, ffDecimal, 2) & "%", - allResults.failedCount, formatBiggestFloat(allResults.failedPercentage, ffDecimal, 2) & "%" + allResults.successCount, + formatBiggestFloat(allResults.successPercentage, ffDecimal, 2) & "%", + allResults.ignoredCount, + formatBiggestFloat(allResults.ignoredPercentage, ffDecimal, 2) & "%", + allResults.failedCount, + formatBiggestFloat(allResults.failedPercentage, ffDecimal, 2) & "%", + onlyFailing ) - generateTestResultsPanelGroupPartial(outfile, allResults.data, onlyFailing) - outfile.generateHtmlTabPageEnd() - -proc generateTestRunsHtmlPartial(outfile: File, allResults: AllTests, onlyFailing = false) = - # Iterating the results twice, get entire result set in one go - outfile.generateHtmlTabListBegin() - if allResults.data.len > 0: - generateTestRunTabListItemPartial(outfile, allResults.data[0], true) - outfile.generateHtmlTabListEnd() - - outfile.generateHtmlTabContentsBegin() - var firstRow = true - for testRunRow in allResults.data: - generateTestRunTabContentPartial(outfile, allResults, testRunRow, onlyFailing, firstRow) - if firstRow: - firstRow = false - outfile.generateHtmlTabContentsEnd() + generateTestResultsPanelGroupPartial(outfile, allResults.data) + outfile.generateHtmlAllTestsEnd() proc generateHtml*(filename: string, onlyFailing: bool) = + let + currentTime = getTime().getLocalTime() + timestring = htmlQuote format(currentTime, "yyyy-MM-dd HH:mm:ss 'UTC'zzz") var outfile = open(filename, fmWrite) outfile.generateHtmlBegin() - generateTestRunsHtmlPartial(outfile, allTestResults(), onlyFailing) + generateAllTestsContent(outfile, allTestResults(onlyFailing), onlyFailing) - outfile.generateHtmlEnd() + outfile.generateHtmlEnd(timestring) outfile.flushFile() close(outfile) + +proc dumpJsonTestResults*(prettyPrint, onlyFailing: bool) = + var + outfile = stdout + jsonString: string + + let results = allTestResults(onlyFailing) + if prettyPrint: + jsonString = results.data.pretty() + else: + jsonString = $ results.data + + outfile.writeLine(jsonString) diff --git a/tests/testament/testamenthtml.templ b/tests/testament/testamenthtml.templ index f7477f3aa..9190f370e 100644 --- a/tests/testament/testamenthtml.templ +++ b/tests/testament/testamenthtml.templ @@ -29,7 +29,7 @@ */ /** - * + * * @param {number} index * @param {Element[]} elemArray * @param {executeForElement} executeOnItem @@ -69,17 +69,16 @@ } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) * @param {executeForElement} executeOnEachPanel */ - function wholePanelAll(tabId, category, executeOnEachPanel) { + function wholePanelAll(category, executeOnEachPanel) { var selector = "div.panel"; if (typeof category === "string" && category) { selector += "-" + category; } - var jqPanels = $(selector, $("#" + tabId)); + var jqPanels = $(selector); /** @type {Element[]} */ var elemArray = jqPanels.toArray(); @@ -87,17 +86,16 @@ } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) * @param {executeForElement} executeOnEachPanel */ - function panelBodyAll(tabId, category, executeOnEachPanelBody) { + function panelBodyAll(category, executeOnEachPanelBody) { var selector = "div.panel"; if (typeof category === "string" && category) { selector += "-" + category; } - var jqPanels = $(selector, $("#" + tabId)); + var jqPanels = $(selector); var jqPanelBodies = $("div.panel-body", jqPanels); /** @type {Element[]} */ @@ -107,35 +105,31 @@ } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) */ - function showAll(tabId, category) { - wholePanelAll(tabId, category, executeShowOnElement); + function showAll(category) { + wholePanelAll(category, executeShowOnElement); } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) */ - function hideAll(tabId, category) { - wholePanelAll(tabId, category, executeHideOnElement); + function hideAll(category) { + wholePanelAll(category, executeHideOnElement); } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) */ - function expandAll(tabId, category) { - panelBodyAll(tabId, category, executeExpandOnElement); + function expandAll(category) { + panelBodyAll(category, executeExpandOnElement); } /** - * @param {string} tabId The id of the tabpanel div to search. * @param {string} [category] Optional bootstrap panel context class (danger, warning, info, success) */ - function collapseAll(tabId, category) { - panelBodyAll(tabId, category, executeCollapseOnElement); + function collapseAll(category) { + panelBodyAll(category, executeCollapseOnElement); } </script> </head> @@ -143,176 +137,161 @@ <div class="container"> <h1>Testament Test Results <small>Nim Tester</small></h1> #end proc -#proc generateHtmlTabListBegin*(outfile: File) = - <ul class="nav nav-tabs" role="tablist"> -#end proc -#proc generateHtmlTabListItem*(outfile: File, firstTabActiveClass, commitId, -# machineId, branch, hash, machineName: string) = - <li role="presentation" class="%firstTabActiveClass"> - <a href="#tab-commit-%commitId-machine-%machineId" aria-controls="tab-commit-%commitId-machine-%machineId" role="tab" data-toggle="tab"> - %branch#%hash@%machineName - </a> - </li> -#end proc -#proc generateHtmlTabListEnd*(outfile: File) = - </ul> -#end proc -#proc generateHtmlTabContentsBegin*(outfile: File) = - <div class="tab-content"> -#end proc -#proc generateHtmlTabPageBegin*(outfile: File, firstTabActiveClass, commitId, -# machineId, branch, hash, machineName, os, cpu: string, totalCount: BiggestInt, +#proc generateHtmlAllTestsBegin*(outfile: File, machine, commit, branch: string, +# totalCount: BiggestInt, # successCount: BiggestInt, successPercentage: string, # ignoredCount: BiggestInt, ignoredPercentage: string, -# failedCount: BiggestInt, failedPercentage: string) = - <div id="tab-commit-%commitId-machine-%machineId" class="tab-pane fade%firstTabActiveClass" role="tabpanel"> - <h2>%branch#%hash@%machineName</h2> - <dl class="dl-horizontal"> - <dt>Branch</dt> - <dd>%branch</dd> - <dt>Commit Hash</dt> - <dd><code>%hash</code></dd> - <dt>Machine Name</dt> - <dd>%machineName</dd> - <dt>OS</dt> - <dd>%os</dd> - <dt title="CPU Architecture">CPU</dt> - <dd>%cpu</dd> - <dt>All Tests</dt> - <dd> - <span class="glyphicon glyphicon-th-list"></span> - %totalCount - </dd> - <dt>Successful Tests</dt> - <dd> - <span class="glyphicon glyphicon-ok-sign"></span> - %successCount (%successPercentage) - </dd> - <dt>Skipped Tests</dt> - <dd> - <span class="glyphicon glyphicon-question-sign"></span> - %ignoredCount (%ignoredPercentage) - </dd> - <dt>Failed Tests</dt> - <dd> - <span class="glyphicon glyphicon-exclamation-sign"></span> - %failedCount (%failedPercentage) - </dd> - </dl> - <div class="table-responsive"> - <table class="table table-condensed"> - <tr> - <th class="text-right" style="vertical-align:middle">All Tests</th> - <td> - <div class="btn-group"> - <button class="btn btn-default" type="button" onclick="showAll('tab-commit-%commitId-machine-%machineId');">Show All</button> - <button class="btn btn-default" type="button" onclick="hideAll('tab-commit-%commitId-machine-%machineId');">Hide All</button> - <button class="btn btn-default" type="button" onclick="expandAll('tab-commit-%commitId-machine-%machineId');">Expand All</button> - <button class="btn btn-default" type="button" onclick="collapseAll('tab-commit-%commitId-machine-%machineId');">Collapse All</button> - </div> - </td> - </tr> - <tr> - <th class="text-right" style="vertical-align:middle">Successful Tests</th> - <td> - <div class="btn-group"> - <button class="btn btn-default" type="button" onclick="showAll('tab-commit-%commitId-machine-%machineId', 'success');">Show All</button> - <button class="btn btn-default" type="button" onclick="hideAll('tab-commit-%commitId-machine-%machineId', 'success');">Hide All</button> - <button class="btn btn-default" type="button" onclick="expandAll('tab-commit-%commitId-machine-%machineId', 'success');">Expand All</button> - <button class="btn btn-default" type="button" onclick="collapseAll('tab-commit-%commitId-machine-%machineId', 'success');">Collapse All</button> - </div> - </td> - </tr> - <tr> - <th class="text-right" style="vertical-align:middle">Skipped Tests</th> - <td> - <div class="btn-group"> - <button class="btn btn-default" type="button" onclick="showAll('tab-commit-%commitId-machine-%machineId', 'info');">Show All</button> - <button class="btn btn-default" type="button" onclick="hideAll('tab-commit-%commitId-machine-%machineId', 'info');">Hide All</button> - <button class="btn btn-default" type="button" onclick="expandAll('tab-commit-%commitId-machine-%machineId', 'info');">Expand All</button> - <button class="btn btn-default" type="button" onclick="collapseAll('tab-commit-%commitId-machine-%machineId', 'info');">Collapse All</button> - </div> - </td> - </tr> - <tr> - <th class="text-right" style="vertical-align:middle">Failed Tests</th> - <td> - <div class="btn-group"> - <button class="btn btn-default" type="button" onclick="showAll('tab-commit-%commitId-machine-%machineId', 'danger');">Show All</button> - <button class="btn btn-default" type="button" onclick="hideAll('tab-commit-%commitId-machine-%machineId', 'danger');">Hide All</button> - <button class="btn btn-default" type="button" onclick="expandAll('tab-commit-%commitId-machine-%machineId', 'danger');">Expand All</button> - <button class="btn btn-default" type="button" onclick="collapseAll('tab-commit-%commitId-machine-%machineId', 'danger');">Collapse All</button> - </div> - </td> - </tr> - </table> - </div> - <div class="panel-group"> +# failedCount: BiggestInt, failedPercentage: string, onlyFailing = false) = + <dl class="dl-horizontal"> + <dt>Hostname</dt> + <dd>%machine</dd> + <dt>Git Commit</dt> + <dd><code>%commit</code></dd> + <dt title="Git Branch reference">Branch ref.</dt> + <dd>%branch</dd> + </dl> + <dl class="dl-horizontal"> + <dt>All Tests</dt> + <dd> + <span class="glyphicon glyphicon-th-list"></span> + %totalCount + </dd> + <dt>Successful Tests</dt> + <dd> + <span class="glyphicon glyphicon-ok-sign"></span> + %successCount (%successPercentage) + </dd> + <dt>Skipped Tests</dt> + <dd> + <span class="glyphicon glyphicon-question-sign"></span> + %ignoredCount (%ignoredPercentage) + </dd> + <dt>Failed Tests</dt> + <dd> + <span class="glyphicon glyphicon-exclamation-sign"></span> + %failedCount (%failedPercentage) + </dd> + </dl> + <div class="table-responsive"> + <table class="table table-condensed"> +# if not onlyFailing: + <tr> + <th class="text-right" style="vertical-align:middle">All Tests</th> + <td> + <div class="btn-group"> + <button class="btn btn-default" type="button" onclick="showAll();">Show All</button> + <button class="btn btn-default" type="button" onclick="hideAll();">Hide All</button> + <button class="btn btn-default" type="button" onclick="expandAll();">Expand All</button> + <button class="btn btn-default" type="button" onclick="collapseAll();">Collapse All</button> + </div> + </td> + </tr> + <tr> + <th class="text-right" style="vertical-align:middle">Successful Tests</th> + <td> + <div class="btn-group"> + <button class="btn btn-default" type="button" onclick="showAll('success');">Show All</button> + <button class="btn btn-default" type="button" onclick="hideAll('success');">Hide All</button> + <button class="btn btn-default" type="button" onclick="expandAll('success');">Expand All</button> + <button class="btn btn-default" type="button" onclick="collapseAll('success');">Collapse All</button> + </div> + </td> + </tr> +# end if + <tr> + <th class="text-right" style="vertical-align:middle">Skipped Tests</th> + <td> + <div class="btn-group"> + <button class="btn btn-default" type="button" onclick="showAll('info');">Show All</button> + <button class="btn btn-default" type="button" onclick="hideAll('info');">Hide All</button> + <button class="btn btn-default" type="button" onclick="expandAll('info');">Expand All</button> + <button class="btn btn-default" type="button" onclick="collapseAll('info');">Collapse All</button> + </div> + </td> + </tr> + <tr> + <th class="text-right" style="vertical-align:middle">Failed Tests</th> + <td> + <div class="btn-group"> + <button class="btn btn-default" type="button" onclick="showAll('danger');">Show All</button> + <button class="btn btn-default" type="button" onclick="hideAll('danger');">Hide All</button> + <button class="btn btn-default" type="button" onclick="expandAll('danger');">Expand All</button> + <button class="btn btn-default" type="button" onclick="collapseAll('danger');">Collapse All</button> + </div> + </td> + </tr> + </table> + </div> + <div class="panel-group"> #end proc #proc generateHtmlTestresultPanelBegin*(outfile: File, trId, name, target, category, -# action, resultDescription, timestamp, result, resultSign, +# action, resultDescription, timestamp, result, resultSign, # panelCtxClass, textCtxClass, bgCtxClass: string) = - <div id="panel-testResult-%trId" class="panel panel-%panelCtxClass"> - <div class="panel-heading" style="cursor:pointer" data-toggle="collapse" data-target="#panel-body-testResult-%trId" aria-controls="panel-body-testResult-%trId" aria-expanded="false"> - <div class="row"> - <h4 class="col-xs-3 col-sm-1 panel-title"> - <span class="glyphicon glyphicon-%resultSign-sign"></span> - <strong>%resultDescription</strong> - </h4> - <h4 class="col-xs-1 panel-title"><span class="badge">%target</span></h4> - <h4 class="col-xs-5 col-sm-7 panel-title" title="%name"><code class="text-%textCtxClass">%name</code></h4> - <h4 class="col-xs-3 col-sm-3 panel-title text-right"><span class="badge">%category</span></h4> - </div> - </div> - <div id="panel-body-testResult-%trId" class="panel-body collapse bg-%bgCtxClass"> - <dl class="dl-horizontal"> - <dt>Name</dt> - <dd><code class="text-%textCtxClass">%name</code></dd> - <dt>Category</dt> - <dd><span class="badge">%category</span></dd> - <dt>Timestamp</dt> - <dd>%timestamp</dd> - <dt>Nim Action</dt> - <dd><code class="text-%textCtxClass">%action</code></dd> - <dt>Nim Backend Target</dt> - <dd><span class="badge">%target</span></dd> - <dt>Code</dt> - <dd><code class="text-%textCtxClass">%result</code></dd> - </dl> + <div id="panel-testResult-%trId" class="panel panel-%panelCtxClass"> + <div class="panel-heading" style="cursor:pointer" data-toggle="collapse" data-target="#panel-body-testResult-%trId" aria-controls="panel-body-testResult-%trId" aria-expanded="false"> + <div class="row"> + <h4 class="col-xs-3 col-sm-1 panel-title"> + <span class="glyphicon glyphicon-%resultSign-sign"></span> + <strong>%resultDescription</strong> + </h4> + <h4 class="col-xs-1 panel-title"><span class="badge">%target</span></h4> + <h4 class="col-xs-5 col-sm-7 panel-title" title="%name"><code class="text-%textCtxClass">%name</code></h4> + <h4 class="col-xs-3 col-sm-3 panel-title text-right"><span class="badge">%category</span></h4> + </div> + </div> + <div id="panel-body-testResult-%trId" class="panel-body collapse bg-%bgCtxClass"> + <dl class="dl-horizontal"> + <dt>Name</dt> + <dd><code class="text-%textCtxClass">%name</code></dd> + <dt>Category</dt> + <dd><span class="badge">%category</span></dd> + <dt>Timestamp</dt> + <dd>%timestamp</dd> + <dt>Nim Action</dt> + <dd><code class="text-%textCtxClass">%action</code></dd> + <dt>Nim Backend Target</dt> + <dd><span class="badge">%target</span></dd> + <dt>Code</dt> + <dd><code class="text-%textCtxClass">%result</code></dd> + </dl> #end proc #proc generateHtmlTestresultOutputDetails*(outfile: File, expected, gotten: string) = - <div class="table-responsive"> - <table class="table table-condensed"> - <thead> - <tr> - <th>Expected</th> - <th>Actual</th> - </tr> - </thead> - <tbody> - <tr> - <td><pre>%expected</pre></td> - <td><pre>%gotten</pre></td> - </tr> - </tbody> - </table> - </div> + <div class="table-responsive"> + <table class="table table-condensed"> + <thead> + <tr> + <th>Expected</th> + <th>Actual</th> + </tr> + </thead> + <tbody> + <tr> + <td><pre>%expected</pre></td> + <td><pre>%gotten</pre></td> + </tr> + </tbody> + </table> + </div> #end proc #proc generateHtmlTestresultOutputNone*(outfile: File) = - <p class="sr-only">No output details</p> + <p class="sr-only">No output details</p> #end proc #proc generateHtmlTestresultPanelEnd*(outfile: File) = - </div> - </div> -#end proc -#proc generateHtmlTabPageEnd*(outfile: File) = </div> </div> #end proc -#proc generateHtmlTabContentsEnd*(outfile: File) = +#proc generateHtmlAllTestsEnd*(outfile: File) = </div> #end proc -#proc generateHtmlEnd*(outfile: File) = +#proc generateHtmlEnd*(outfile: File, timestamp: string) = + <hr /> + <footer> + <p> + Report generated by: <code>testament</code> – Nim Tester + <br /> + Made with Nim. Generated on: %timestamp + </p> + </footer> </div> </body> -</html> \ No newline at end of file +</html> |