diff --git a/package.json b/package.json index 1ca0d856..264ab744 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "commander": "^12.1.0", "eo2js": "^0.0.8", "fast-xml-parser": "^5.2.3", + "marked": "^4.3.0", "node": "^24.1.0", "relative": "^3.0.2", "semver": "^7.7.2", diff --git a/src/commands/docs.js b/src/commands/docs.js index 1fc83c7e..1f5ba47d 100644 --- a/src/commands/docs.js +++ b/src/commands/docs.js @@ -6,6 +6,7 @@ const fs = require('fs'); const path = require('path'); const SaxonJS = require('saxon-js'); +const { marked } = require('marked'); /** * Recursively reads all .xmir files from a directory. @@ -52,6 +53,17 @@ function transformDocument(xmir, xsl) { return html; } +/** + * Converts Markdown blocks in documentation to HTML + * @param {String} html - text of HTML file + * @return {String} HTML document + */ +function convertMarkdownToHtml(html) { + const regex = /(?]*>)(?[\s\S]*?)(?<\/div>)/gi; + const converted_html = html.replace(regex, (match, opening_tag, content, closing_tag) => `${opening_tag}${marked.parse(content)}${closing_tag}`); + return converted_html; +} + /** * Creates documentation block from given XMIR * @param {String} xmir_path - path of XMIR @@ -61,7 +73,7 @@ function createXmirHtmlBlock(xmir_path) { try { const xmir = fs.readFileSync(xmir_path).toString(); const xsl = fs.readFileSync(path.join(__dirname, '..', 'resources', 'xmir-transformer.xsl')).toString(); - return transformDocument(xmir, xsl); + return convertMarkdownToHtml(transformDocument(xmir, xsl)); } catch(error) { throw new Error(`Error while applying XSL to XMIR: ${error.message}`, error); } @@ -75,37 +87,38 @@ function createXmirHtmlBlock(xmir_path) { * @return {String} HTML of the package */ function generatePackageHtml(name, xmir_htmls, css_path) { - const title = `

Package ${name} documentation

`; + xmir_htmls = xmir_htmls.filter(item => item !== '
'); + const cur_date = new Date(); return ` + - ${title} - ${xmir_htmls.join('\n')} +
+
+ +
+ ${xmir_htmls.join('\n')} +
`; } /** * Wraps given html body + * @param {String} name - File name * @param {String} html - HTML body * @param {String} css_path - CSS file path * @return {String} Ready HTML */ -function wrapHtml(html, css_path) { - return ` - - - - - - - ${html} - - - `; +function wrapHtml(name, html, css_path) { + return generatePackageHtml(name, [html], css_path); } /** @@ -128,7 +141,7 @@ module.exports = async function(opts) { const xmir_html = createXmirHtmlBlock(xmir); const html_app = path.join(output, path.dirname(relative),`${name}.html`); fs.mkdirSync(path.dirname(html_app), {recursive: true}); - fs.writeFileSync(html_app, wrapHtml(xmir_html, css)); + fs.writeFileSync(html_app, wrapHtml(name, xmir_html, css)); const packages = path.dirname(relative).split(path.sep).join('.'); const html_package = path.join(output, `package_${packages}.html`); if (!(packages in packages_info)) { @@ -143,10 +156,10 @@ module.exports = async function(opts) { for (const package_name of Object.keys(packages_info)) { fs.mkdirSync(path.dirname(packages_info[package_name].path), {recursive: true}); fs.writeFileSync(packages_info[package_name].path, - generatePackageHtml(package_name, packages_info[package_name].xmir_htmls, css)); + generatePackageHtml(`${package_name} package`, packages_info[package_name].xmir_htmls, css)); } const packages = path.join(output, 'packages.html'); - fs.writeFileSync(packages, generatePackageHtml('', all_xmir_htmls, css)); + fs.writeFileSync(packages, generatePackageHtml('overall package', all_xmir_htmls, css)); console.info('Documentation generation completed in the %s directory', output); } catch (error) { console.error('Error generating documentation:', error); diff --git a/src/resources/xmir-transformer.xsl b/src/resources/xmir-transformer.xsl index ca20d3cf..f07ab127 100644 --- a/src/resources/xmir-transformer.xsl +++ b/src/resources/xmir-transformer.xsl @@ -4,9 +4,10 @@ * SPDX-License-Identifier: MIT --> + -
- +
+
@@ -17,7 +18,7 @@ -

+

( @@ -25,15 +26,15 @@ , )

-

+

-

+
-
+
diff --git a/test/commands/test_docs.js b/test/commands/test_docs.js index 462e0c99..f3435ad3 100644 --- a/test/commands/test_docs.js +++ b/test/commands/test_docs.js @@ -108,4 +108,73 @@ describe('docs', () => { assert(!test_content.includes('Not docs'), `Unnecessary comment found in ${test_html}`); done(); }); + /** + * Tests that the 'docs' command does not generate test to HTML. + * @param {Mocha.Done} done - Mocha callback signaling asynchronous completion + */ + it('does not generate tests to HTML', (done) => { + const sample = parsed; + fs.mkdirSync(sample, {recursive: true}); + const xmir = path.join(sample, 'test.xmir'); + fs.writeFileSync(xmir, fs.readFileSync(path.join(__dirname, '..', 'resources', 'test4.xmir')).toString()); + runSync([ + 'docs', + '--verbose', + '-s', path.resolve(home, 'src'), + '-t', home, + ]); + assert(fs.existsSync(docs), 'Expected the docs directory to be created but it is missing'); + const test_html = path.join(docs, 'test.html'); + assert(fs.existsSync(test_html), `Expected file ${test_html} but it was not created`); + const test_content = fs.readFileSync(test_html); + assert(!test_content.includes('Tests this comment is not in docs.'), `Unnecessary comment found in ${test_html}`); + done(); + }); + /** + * Tests that the 'docs' command does not generate completely empty HTML for empty docblocks. + * @param {Mocha.Done} done - Mocha callback signaling asynchronous completion + */ + it('does not generate empty HTML for empty docblocks', (done) => { + const sample = parsed; + fs.mkdirSync(sample, {recursive: true}); + const xmir = path.join(sample, 'test.xmir'); + fs.writeFileSync(xmir, fs.readFileSync(path.join(__dirname, '..', 'resources', 'test5.xmir')).toString()); + runSync([ + 'docs', + '--verbose', + '-s', path.resolve(home, 'src'), + '-t', home, + ]); + assert(fs.existsSync(docs), 'Expected the docs directory to be created but it is missing'); + const test_html = path.join(docs, 'test.html'); + assert(fs.existsSync(test_html), `Expected file ${test_html} but it was not created`); + const test_content = fs.readFileSync(test_html).toString(); + const text_only = test_content.replace(/<[^>]*>/g, '') + .replace(/\s+/g, ''); + assert(text_only.length > 0); + done(); + }); + /** + * Tests that the 'docs' command generates markdown correctly. + * @param {Mocha.Done} done - Mocha callback signaling asynchronous completion + */ + it('generates markdown correctly', (done) => { + const sample = parsed; + fs.mkdirSync(sample, {recursive: true}); + const xmir = path.join(sample, 'test.xmir'); + fs.writeFileSync(xmir, fs.readFileSync(path.join(__dirname, '..', 'resources', 'test6.xmir')).toString()); + runSync([ + 'docs', + '--verbose', + '-s', path.resolve(home, 'src'), + '-t', home, + ]); + assert(fs.existsSync(docs), 'Expected the docs directory to be created but it is missing'); + const test_html = path.join(docs, 'test.html'); + assert(fs.existsSync(test_html), `Expected file ${test_html} but it was not created`); + const test_content = fs.readFileSync(test_html); + assert(test_content.includes('Strong test'), `Markdown not processed correctly in ${test_html}`); + assert(test_content.includes('Code test'), `Markdown not processed correctly in ${test_html}`); + done(); + }); }); diff --git a/test/resources/test4.xmir b/test/resources/test4.xmir new file mode 100644 index 00000000..44b95c8b --- /dev/null +++ b/test/resources/test4.xmir @@ -0,0 +1,56 @@ + + + + # Says hello to Jeff. +[] &gt; app + # Tests this comment is not in docs. + [] +&gt; some-check + QQ.io.stdout &gt; @ + &quot;Test passed!&quot; + + + Says hello to Jeff. + Tests this comment is not in docs. + + + + + + + 54-65-73-74-20-70-61-73-73-65-64-21 + + + + + + + validate-before-stars + resolve-before-stars + wrap-method-calls + const-to-dataized + stars-to-tuples + vars-float-up + move-voids-up + validate-objects-count + build-fqns + expand-qqs + expand-aliases + resolve-aliases + add-default-package + roll-bases + cti-adds-errors + decorate + add-as-attributes-inside-application + st-hex + + diff --git a/test/resources/test5.xmir b/test/resources/test5.xmir new file mode 100644 index 00000000..7757c120 --- /dev/null +++ b/test/resources/test5.xmir @@ -0,0 +1,49 @@ + + + + [args] &gt; app + QQ.io.stdout &gt; @ + &quot;Hello, world!\n&quot; + + + + + + + + 48-65-6C-6C-6F-2C-20-77-6F-72-6C-64-21-0A + + + + + + validate-before-stars + resolve-before-stars + wrap-method-calls + const-to-dataized + stars-to-tuples + vars-float-up + move-voids-up + validate-objects-count + build-fqns + expand-qqs + expand-aliases + resolve-aliases + add-default-package + roll-bases + cti-adds-errors + decorate + add-as-attributes-inside-application + st-hex + + diff --git a/test/resources/test6.xmir b/test/resources/test6.xmir new file mode 100644 index 00000000..4eb35495 --- /dev/null +++ b/test/resources/test6.xmir @@ -0,0 +1,54 @@ + + + + # **Strong test** +# `Code test` +[args] &gt; app + QQ.io.stdout &gt; @ + &quot;Test passed!&quot; + + + **Strong test**\n`Code test` + + + + + + + + 54-65-73-74-20-70-61-73-73-65-64-21 + + + + + + validate-before-stars + resolve-before-stars + wrap-method-calls + const-to-dataized + stars-to-tuples + vars-float-up + move-voids-up + validate-objects-count + build-fqns + expand-qqs + expand-aliases + resolve-aliases + add-default-package + roll-bases + cti-adds-errors + decorate + add-as-attributes-inside-application + st-hex + +