diff --git a/packages/@power-doctest/asciidoctor/README.md b/packages/@power-doctest/asciidoctor/README.md index 0136864..ed587c4 100644 --- a/packages/@power-doctest/asciidoctor/README.md +++ b/packages/@power-doctest/asciidoctor/README.md @@ -1,6 +1,6 @@ # @power-doctest/asciidoctor -A power-doctest Runner using for Asciidoctor. +A [Asciidoctor](https://asciidoctor.org/) parser for power-doctest. ## Install diff --git a/packages/@power-doctest/asciidoctor/package.json b/packages/@power-doctest/asciidoctor/package.json index 10ef823..654e4c7 100644 --- a/packages/@power-doctest/asciidoctor/package.json +++ b/packages/@power-doctest/asciidoctor/package.json @@ -1,7 +1,7 @@ { "name": "@power-doctest/asciidoctor", "version": "1.0.0", - "description": "A power-doctest Runner using for Asciidoctor.", + "description": "A Asciidoctor parser for power-doctest.", "keywords": [ "asciidoc", "asciidoctor", @@ -30,6 +30,7 @@ }, "scripts": { "build": "cross-env NODE_ENV=production tsc -p .", + "updateSnapshot": "cross-env UPDATE_SNAPSHOT=1 npm test", "clean": "rimraf lib/", "prettier": "prettier --write \"**/*.{js,jsx,ts,tsx,css}\"", "prepublish": "npm run --if-present build", @@ -42,6 +43,7 @@ "tabWidth": 4 }, "dependencies": { + "@power-doctest/types": "^1.0.0", "asciidoctor": "^2.0.3" }, "devDependencies": { diff --git a/packages/@power-doctest/asciidoctor/src/index.ts b/packages/@power-doctest/asciidoctor/src/index.ts index 5f52bc4..861cbe8 100644 --- a/packages/@power-doctest/asciidoctor/src/index.ts +++ b/packages/@power-doctest/asciidoctor/src/index.ts @@ -1,7 +1,100 @@ +import { ParsedCode, ParsedResults, ParserArgs } from "@power-doctest/types"; +import * as fs from "fs"; +import * as path from "path"; + const Asciidoctor = require("asciidoctor"); const asciidoctor = Asciidoctor(); +type Attributes = { + [index: string]: string; +} +const getState = (attributes: Attributes): "none" | "enabled" | "disabled" => { + const state = attributes["doctest-state"]; + if (!state) { + return "none"; + } + if (/enable(d)?/.test(state)) { + return "enabled"; + } else if (/disable(d)?/.test(state)) { + return "disabled"; + } + return "none"; +}; + +const getMeta = (attributes: Attributes): {} | undefined => { + const meta = attributes["doctest-meta"]; + if (!meta) { + return; + } + try { + return JSON.parse(meta); + } catch (error) { + // parse error + throw new Error(`Can not parsed. doctest-meta={...} should be JSON object: ${error}`); + } +}; + +const getOptions = (attributes: Attributes): {} | undefined => { + const meta = attributes["doctest-options"]; + if (!meta) { + return; + } + try { + return JSON.parse(meta); + } catch (error) { + // parse error + throw new Error(`Can not parsed. doctest-options={...} should be JSON object: ${error}`); + } +}; + +// inlining include:: +const inlineCode = (code: string, baseFilePath: string): string => { + // include:: -> link: + const pattern = /link:(.+)\[.*?]/; + const dirName = path.dirname(baseFilePath); + return code.replace(pattern, (all, filePath) => { + const fileName = path.resolve(dirName, filePath); + if (fs.existsSync(fileName)) { + return fs.readFileSync(fileName, "utf-8"); + } + return all; + }); +}; -export function parse(code: string) { - const doc = asciidoctor.load(code); - return doc; +export function parse(args: ParserArgs): ParsedResults { + const doc = asciidoctor.load(args.content); + return doc.getBlocks() + .filter((block: any) => { + const attributes = block.getAttributes(); + return attributes.style === "source" && (attributes.language === "js" || attributes.language === "javascript"); + }) + .map((block: any) => { + const lineNumber: number = block.document.getReader().lineno; + const attributes: {} = block.getAttributes(); + const code: string = block.getSource(); + const lines: string[] = block.getSourceLines(); + const meta = getMeta(attributes); + const doctestOptions = getOptions(attributes); + const parsedCode: ParsedCode = { + code: inlineCode(code, args.filePath), + state: getState(attributes), + location: { + start: { + line: lineNumber, + column: 0 + }, + end: { + line: lineNumber + lines.length, + column: 0 + } + }, + metadata: meta, + doctestOptions: doctestOptions ? { + filePath: args.filePath, + ...doctestOptions + } : { + filePath: args.filePath + } + }; + return parsedCode; + }); } diff --git a/packages/@power-doctest/asciidoctor/test/index.test.ts b/packages/@power-doctest/asciidoctor/test/index.test.ts deleted file mode 100644 index d24fa7b..0000000 --- a/packages/@power-doctest/asciidoctor/test/index.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { parse } from "../src"; - -describe("asciidoctor", function() { - it("should parse", () => { - const doc = parse(`.app.rb -[#src-listing] -[source,ruby, doctest=true] ----- -require 'sinatra' - -get '/hi' do - "Hello World!" -end -include:a.md -----`); - console.log(doc.getBlocks() - .filter((block: any) => { - const attributes = block.getAttributes(); - console.log(attributes); - return attributes.style === "source" && attributes.language === "ruby" - }) - .map((block: any) => { - return block.getSource() - })); - - }); -}); diff --git a/packages/@power-doctest/asciidoctor/test/snapshot.test.ts b/packages/@power-doctest/asciidoctor/test/snapshot.test.ts new file mode 100644 index 0000000..e1a56ba --- /dev/null +++ b/packages/@power-doctest/asciidoctor/test/snapshot.test.ts @@ -0,0 +1,49 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as assert from "assert"; +// transform function +import { parse } from "../src"; + +const fixturesDir = path.join(__dirname, "snapshots"); + +const trimUndefinedProperty = (o: T, baseDir: string): T => { + return JSON.parse(stringify(o, baseDir)); +}; +const stringify = (o: {}, baseDir: string): string => { + return JSON.stringify(o, (key: string, value: any) => { + if (key === "filePath" && typeof value === "string") { + return path.relative(baseDir, value); + } else { + return value; + } + }, 4); +}; +describe("Snapshot testing", () => { + fs.readdirSync(fixturesDir) + .map(caseName => { + const normalizedTestName = caseName.replace(/-/g, " "); + it(`Test ${normalizedTestName}`, async function() { + const fixtureDir = path.join(fixturesDir, caseName); + const actualFilePath = path.join(fixtureDir, "input.adoc"); + const actualContent = fs.readFileSync(actualFilePath, "utf-8"); + const results = parse({ + content: actualContent, + filePath: actualFilePath + }); + const expectedFilePath = path.join(fixtureDir, "output.json"); + // Usage: update snapshots + // UPDATE_SNAPSHOT=1 npm test + if (!fs.existsSync(expectedFilePath) || process.env.UPDATE_SNAPSHOT) { + fs.writeFileSync(expectedFilePath, stringify(results, fixtureDir)); + this.skip(); // skip when updating snapshots + return; + } + // compare input and output + const expectedContent = JSON.parse(fs.readFileSync(expectedFilePath, "utf-8")); + assert.deepStrictEqual( + trimUndefinedProperty(results, fixtureDir), + expectedContent + ); + }); + }); +}); diff --git a/packages/@power-doctest/asciidoctor/test/snapshots/disabled/input.adoc b/packages/@power-doctest/asciidoctor/test/snapshots/disabled/input.adoc new file mode 100644 index 0000000..e3c6848 --- /dev/null +++ b/packages/@power-doctest/asciidoctor/test/snapshots/disabled/input.adoc @@ -0,0 +1,5 @@ +[source,javascript,doctest-state="disabled"] +---- +const str = "string"; +console.log(str); +---- diff --git a/packages/@power-doctest/asciidoctor/test/snapshots/enabled/input.adoc b/packages/@power-doctest/asciidoctor/test/snapshots/enabled/input.adoc new file mode 100644 index 0000000..8518406 --- /dev/null +++ b/packages/@power-doctest/asciidoctor/test/snapshots/enabled/input.adoc @@ -0,0 +1,5 @@ +[source,javascript,doctest-state="enabled"] +---- +const str = "string"; +console.log(str); +---- diff --git a/packages/@power-doctest/asciidoctor/test/snapshots/js/input.adoc b/packages/@power-doctest/asciidoctor/test/snapshots/js/input.adoc new file mode 100644 index 0000000..6443acf --- /dev/null +++ b/packages/@power-doctest/asciidoctor/test/snapshots/js/input.adoc @@ -0,0 +1,5 @@ +[source,js] +---- +const str = "string"; +console.log(str); +---- diff --git a/packages/@power-doctest/asciidoctor/test/snapshots/meta/input.adoc b/packages/@power-doctest/asciidoctor/test/snapshots/meta/input.adoc new file mode 100644 index 0000000..c626d0e --- /dev/null +++ b/packages/@power-doctest/asciidoctor/test/snapshots/meta/input.adoc @@ -0,0 +1,5 @@ +[source,javascript,doctest-meta={ "ECMAScript": 2017 }] +---- +const str = "string"; +console.log(str); +---- diff --git a/packages/@power-doctest/asciidoctor/test/snapshots/options/input.adoc b/packages/@power-doctest/asciidoctor/test/snapshots/options/input.adoc new file mode 100644 index 0000000..b24d3ba --- /dev/null +++ b/packages/@power-doctest/asciidoctor/test/snapshots/options/input.adoc @@ -0,0 +1,5 @@ +[source,javascript,doctest-options={ "runMode": "any" }] +---- +const str = "string"; +console.log(str); +---- diff --git a/packages/@power-doctest/asciidoctor/test/snapshots/simple/input.adoc b/packages/@power-doctest/asciidoctor/test/snapshots/simple/input.adoc new file mode 100644 index 0000000..f8b50d6 --- /dev/null +++ b/packages/@power-doctest/asciidoctor/test/snapshots/simple/input.adoc @@ -0,0 +1,5 @@ +[source,javascript] +---- +const str = "string"; +console.log(str); +---- diff --git a/packages/@power-doctest/asciidoctor/test/snapshots/source-include/input.adoc b/packages/@power-doctest/asciidoctor/test/snapshots/source-include/input.adoc new file mode 100644 index 0000000..db602f3 --- /dev/null +++ b/packages/@power-doctest/asciidoctor/test/snapshots/source-include/input.adoc @@ -0,0 +1,4 @@ +[source,javascript] +---- +include::source.js[] +---- diff --git a/packages/@power-doctest/asciidoctor/test/snapshots/source-include/source.js b/packages/@power-doctest/asciidoctor/test/snapshots/source-include/source.js new file mode 100644 index 0000000..395812b --- /dev/null +++ b/packages/@power-doctest/asciidoctor/test/snapshots/source-include/source.js @@ -0,0 +1 @@ +console.log("from source.js");