Skip to content

Commit

Permalink
resolves #416 resolve Antora resource using resource ids
Browse files Browse the repository at this point in the history
  • Loading branch information
ggrossetie committed Jul 8, 2023
1 parent 4edef30 commit e7a1438
Show file tree
Hide file tree
Showing 14 changed files with 3,961 additions and 3,039 deletions.
1,324 changes: 774 additions & 550 deletions dist/browser/asciidoctor-kroki.js

Large diffs are not rendered by default.

5,490 changes: 3,055 additions & 2,435 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@
"lodash": "4.17.21",
"mocha": "10.2.0",
"pacote": "12.0.2",
"puppeteer": "19.5.2",
"puppeteer": "20.8.0",
"shx": "0.3.4",
"sinon": "15.0.1",
"standard": "17.0.0"
"sinon": "15.2.0",
"standard": "17.1.0"
},
"peerDependencies": {
"@asciidoctor/core": "~2.2"
Expand All @@ -73,5 +73,8 @@
},
"engines": {
"node": ">=10"
},
"volta": {
"node": "16.20.1"
}
}
20 changes: 11 additions & 9 deletions src/antora-adapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ module.exports = (file, contentCatalog, vfs) => {
} else {
baseReadFn = vfs.read
}
let baseDirnameFn
if (typeof vfs === 'undefined' || typeof vfs.dirname !== 'function') {
baseDirnameFn = require('./node-fs').dirname
let baseParseFn
if (typeof vfs === 'undefined' || typeof vfs.parse !== 'function') {
baseParseFn = require('./node-fs').parse
} else {
baseDirnameFn = vfs.dirname
baseParseFn = vfs.parse
}
let baseExistsFn
if (typeof vfs === 'undefined' || typeof vfs.exists !== 'function') {
Expand All @@ -38,17 +38,19 @@ module.exports = (file, contentCatalog, vfs) => {
})
}
},
read: (resourceId, format) => {
const target = contentCatalog.resolveResource(resourceId, file.src)
read: (resourceId, format, hash) => {
const ctx = hash || file.src
const target = contentCatalog.resolveResource(resourceId, ctx, ctx.family)
return target ? target.contents.toString() : baseReadFn(resourceId, format)
},
exists: (resourceId) => {
const target = contentCatalog.resolveResource(resourceId, file.src)
return target ? true : baseExistsFn(resourceId)
},
dirname: (resourceId) => {
const target = contentCatalog.resolveResource(resourceId, file.src)
return target ? ospath.dirname(target.src.abspath || target.src.path) : baseDirnameFn(resourceId)
parse: (resourceId, hash) => {
const ctx = hash || file.src
const target = contentCatalog.resolveResource(resourceId, ctx, ctx.family)
return target ? target.src : baseParseFn(resourceId)
}
}
}
33 changes: 21 additions & 12 deletions src/asciidoctor-kroki.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,17 @@ const BUILTIN_ATTRIBUTES = [
const wrapError = (err, message) => {
const errWrapper = new Error(message)
errWrapper.stack += `\nCaused by: ${err.stack || 'unknown'}`
errWrapper.package = 'asciidoctor-kroki'
return errWrapper
const result = {
err: {
package: 'asciidoctor-kroki',
message,
stack: errWrapper.stack
}
}
result.$inspect = function () {
return JSON.stringify(this.err)
}
return result
}

const createImageSrc = (doc, krokiDiagram, target, vfs, krokiClient) => {
Expand Down Expand Up @@ -86,7 +95,7 @@ function getOption (attrs, document) {
}
}

const processKroki = (processor, parent, attrs, diagramType, diagramText, context, diagramDir) => {
const processKroki = (processor, parent, attrs, diagramType, diagramText, context, resource) => {
const doc = parent.getDocument()
// If "subs" attribute is specified, substitute accordingly.
// Be careful not to specify "specialcharacters" or your diagram code won't be valid anymore!
Expand All @@ -96,14 +105,14 @@ const processKroki = (processor, parent, attrs, diagramType, diagramText, contex
}
if (doc.getSafe() < SAFE_MODE_SECURE) {
if (diagramType === 'vegalite') {
diagramText = require('./preprocess.js').preprocessVegaLite(diagramText, context, diagramDir)
diagramText = require('./preprocess.js').preprocessVegaLite(diagramText, context, (resource && resource.dir) || '')
} else if (diagramType === 'plantuml' || diagramType === 'c4plantuml') {
const plantUmlIncludeFile = doc.getAttribute('kroki-plantuml-include')
if (plantUmlIncludeFile) {
diagramText = `!include ${plantUmlIncludeFile}\n${diagramText}`
}
const plantUmlIncludePaths = doc.getAttribute('kroki-plantuml-include-paths')
diagramText = require('./preprocess.js').preprocessPlantUML(diagramText, context, plantUmlIncludePaths, diagramDir)
diagramText = require('./preprocess.js').preprocessPlantUML(diagramText, context, plantUmlIncludePaths, resource)
}
}
const blockId = attrs.id
Expand Down Expand Up @@ -174,8 +183,8 @@ function diagramBlock (context) {
try {
return processKroki(this, parent, attrs, diagramType, diagramText, context)
} catch (err) {
const errWrapper = wrapError(err, `Skipping ${diagramType} block.`)
parent.getDocument().getLogger().warn({ err: errWrapper })
const errorMessage = wrapError(err, `Skipping ${diagramType} block.`)
parent.getDocument().getLogger().warn(errorMessage)
attrs.role = role ? `${role} kroki-error` : 'kroki-error'
return this.createBlock(parent, attrs['cloaked-context'], diagramText, attrs)
}
Expand All @@ -200,7 +209,7 @@ function diagramBlockMacro (name, context) {
target = startDir !== '.' ? doc.normalizeWebPath(target, startDir) : target
}
} else {
if (typeof vfs === 'undefined' || typeof vfs.read !== 'function') {
if (vfs === undefined || typeof vfs.read !== 'function') {
vfs = require('./node-fs.js')
target = parent.normalizeSystemPath(target)
}
Expand All @@ -209,11 +218,11 @@ function diagramBlockMacro (name, context) {
const diagramType = name
try {
const diagramText = vfs.read(target)
const diagramDir = vfs.dirname(target)
return processKroki(this, parent, attrs, diagramType, diagramText, context, diagramDir)
const resource = (typeof vfs.parse === 'function' && vfs.parse(target)) || { dir: '' }
return processKroki(this, parent, attrs, diagramType, diagramText, context, resource)
} catch (err) {
const errWrapper = wrapError(err, `Skipping ${diagramType} block.`)
parent.getDocument().getLogger().warn({ err: errWrapper })
const errorMessage = wrapError(err, `Skipping ${diagramType} block.`)
parent.getDocument().getLogger().warn(errorMessage)
attrs.role = role ? `${role} kroki-error` : 'kroki-error'
return this.createBlock(parent, 'paragraph', `${err.message} - ${diagramType}::${target}[]`, attrs)
}
Expand Down
7 changes: 5 additions & 2 deletions src/node-fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ module.exports = {
}
return fs.readFileSync(path, encoding)
},
dirname: (resourceId) => {
return path.dirname(resourceId)
parse: (resourceId) => {
return {
dir: path.dirname(resourceId),
path: resourceId
}
}
}
43 changes: 25 additions & 18 deletions src/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,29 +84,29 @@ function removePlantUmlTags (diagramText) {
* @param {string} diagramText
* @param {any} context
* @param {string} diagramIncludePaths - predefined include paths (can be null)
* @param {string} diagramDir - diagram base directory
* @param {{[key: string]: string}} resource - diagram resource identity
* @returns {string}
*/
module.exports.preprocessPlantUML = function (diagramText, context, diagramIncludePaths = '', diagramDir = '') {
module.exports.preprocessPlantUML = function (diagramText, context, diagramIncludePaths = '', resource = { dir: '' }) {
const logger = 'logger' in context ? context.logger : console
const includeOnce = []
const includeStack = []
const includePaths = diagramIncludePaths ? diagramIncludePaths.split(path.delimiter) : []
diagramText = preprocessPlantUmlIncludes(diagramText, diagramDir, includeOnce, includeStack, includePaths, context.vfs, logger)
diagramText = preprocessPlantUmlIncludes(diagramText, resource, includeOnce, includeStack, includePaths, context.vfs, logger)
return removePlantUmlTags(diagramText)
}

/**
* @param {string} diagramText
* @param {string} dirPath
* @param {{[key: string]: string}} resource
* @param {string[]} includeOnce
* @param {string[]} includeStack
* @param {string[]} includePaths
* @param {any} vfs
* @param {any} logger
* @returns {string}
*/
function preprocessPlantUmlIncludes (diagramText, dirPath, includeOnce, includeStack, includePaths, vfs, logger) {
function preprocessPlantUmlIncludes (diagramText, resource, includeOnce, includeStack, includePaths, vfs, logger) {
// See: http://plantuml.com/en/preprocessing
// Please note that we cannot use lookbehind for compatibility reasons with Safari: https://caniuse.com/mdn-javascript_builtins_regexp_lookbehind_assertion objects are stateful when they have the global flag set (e.g. /foo/g).
// const regExInclude = /^\s*!(include(?:_many|_once|url|sub)?)\s+((?:(?<=\\)[ ]|[^ ])+)(.*)/
Expand All @@ -126,7 +126,7 @@ function preprocessPlantUmlIncludes (diagramText, dirPath, includeOnce, includeS
const trailingContent = target.comment
const url = urlSub[0].replace(/\\ /g, ' ').replace(/\s+$/g, '')
const sub = urlSub[1]
const result = readPlantUmlInclude(url, [dirPath, ...includePaths], includeStack, vfs, logger)
const result = readPlantUmlInclude(url, resource, includePaths, includeStack, vfs, logger)
if (result.skip) {
return line
}
Expand All @@ -149,7 +149,8 @@ function preprocessPlantUmlIncludes (diagramText, dirPath, includeOnce, includeS
text = getPlantUmlTextOrFirstBlock(text)
}
includeStack.push(result.filePath)
text = preprocessPlantUmlIncludes(text, path.dirname(result.filePath), includeOnce, includeStack, includePaths, vfs, logger)
const parse = typeof vfs !== 'undefined' && typeof vfs.parse === 'function' ? vfs.parse : require('./node-fs.js').parse
text = preprocessPlantUmlIncludes(text, parse(result.filePath, resource), includeOnce, includeStack, includePaths, vfs, logger)
includeStack.pop()
if (trailingContent !== '') {
return text + ' ' + trailingContent
Expand All @@ -170,21 +171,26 @@ function preprocessPlantUmlIncludes (diagramText, dirPath, includeOnce, includeS

/**
* @param {string} includeFile - relative or absolute include file
* @param {{[key: string]: string}} resource
* @param {string[]} includePaths - array with include paths
* @param {any} vfs
* @returns {string} the found file or include file path
*/
function resolveIncludeFile (includeFile, includePaths, vfs) {
function resolveIncludeFile (includeFile, resource, includePaths, vfs) {
const exists = typeof vfs !== 'undefined' && typeof vfs.exists === 'function' ? vfs.exists : require('./node-fs.js').exists
let filePath = includeFile
for (let i = 0; i < includePaths.length; i++) {
const localFilePath = path.join(includePaths[i], includeFile)
if (exists(localFilePath)) {
filePath = localFilePath
break
if (resource.dir) {
let filePath = includeFile
for (const includePath of [resource.dir, ...includePaths]) {
const localFilePath = path.join(includePath, includeFile)
if (exists(localFilePath)) {
filePath = localFilePath
break
}
}
return filePath
}
return filePath
// antora resource id
return includeFile
}

function parseTarget (value) {
Expand All @@ -206,13 +212,14 @@ function parseTarget (value) {

/**
* @param {string} url
* @param {{[key: string]: string}} resource
* @param {string[]} includePaths
* @param {string[]} includeStack
* @param {any} vfs
* @param {any} logger
* @returns {any}
*/
function readPlantUmlInclude (url, includePaths, includeStack, vfs, logger) {
function readPlantUmlInclude (url, resource, includePaths, includeStack, vfs, logger) {
const read = typeof vfs !== 'undefined' && typeof vfs.read === 'function' ? vfs.read : require('./node-fs.js').read
let skip = false
let text = ''
Expand All @@ -234,13 +241,13 @@ function readPlantUmlInclude (url, includePaths, includeStack, vfs, logger) {
skip = true
}
} else {
filePath = resolveIncludeFile(url, includePaths, vfs)
filePath = resolveIncludeFile(url, resource, includePaths, vfs)
if (includeStack.includes(filePath)) {
const message = `Preprocessing of PlantUML include failed, because recursive reading already included referenced file '${filePath}'`
throw new Error(message)
} else {
try {
text = read(filePath)
text = read(filePath, 'utf8', resource)
} catch (e) {
// Includes a local file that cannot be found but might be resolved by the Kroki server
logger.info(`Skipping preprocessing of PlantUML include, because reading the referenced local file '${filePath}' caused an error:\n${e}`)
Expand Down
28 changes: 28 additions & 0 deletions test/antora/site-remote.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
runtime:
cache_dir: ./.cache/antora

site:
title: Antora x Kroki
url: http://example.com
start_page: antora-kroki::index.adoc

content:
sources:
- url: https://github.com/ggrossetie/asciidoctor-kroki.git
branches: master
start_path: test/antora/docs

asciidoc:
extensions:
- ./../../src/asciidoctor-kroki.js
attributes:
allow-uri-read: true

ui:
bundle:
url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/master/raw/build/ui-bundle.zip?job=bundle-stable
snapshot: true

output:
dir: ./public
clean: true
30 changes: 28 additions & 2 deletions test/antora/test.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global describe it beforeEach */
/* global describe it beforeEach before */
const fs = require('fs')
const cheerio = require('cheerio')
const chai = require('chai')
Expand All @@ -8,7 +8,7 @@ chai.use(require('dirty-chai'))

const generateSite = require('@antora/site-generator-default')

describe('Antora integration', function () {
describe('Antora integration (local)', function () {
this.timeout(50000)
before(async () => {
fs.rmSync(`${__dirname}/public`, { recursive: true, force: true })
Expand All @@ -33,3 +33,29 @@ describe('Antora integration', function () {
expect(diagramContents).includes('bob')
})
})

describe('Antora integration (remote)', function () {
this.timeout(50000)
before(async () => {
fs.rmSync(`${__dirname}/public`, { recursive: true, force: true })
await generateSite([`--playbook=${__dirname}/site-remote.yml`])
})
it('should generate a site with diagrams', () => {
const $ = cheerio.load(fs.readFileSync(`${__dirname}/public/antora-kroki/source-location.html`))
const imageElements = $('img')
expect(imageElements.length).to.equal(7)
imageElements.each((i, imageElement) => {
const src = $(imageElement).attr('src')
expect(src).to.startWith('_images/ab-')
})
})
it('should resolve included diagrams when using plantuml::partial$xxx.puml[] macro', async () => {
const $ = cheerio.load(fs.readFileSync(`${__dirname}/public/antora-kroki/source-location.html`))
const imageElement = $('img[alt*=ab-inc-partial-1]')
expect(imageElement.length).to.equal(1)
const src = imageElement.attr('src')
const diagramContents = fs.readFileSync(`${__dirname}/public/antora-kroki/${src}`).toString()
expect(diagramContents).includes('alice')
expect(diagramContents).includes('bob')
})
})
8 changes: 4 additions & 4 deletions test/browser/run.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/* eslint-env node, es6 */
const path = require('path')
const path = require('node:path')
const puppeteer = require('puppeteer')

// puppeteer options
const opts = {
headless: true,
headless: 'new',
timeout: 10000,
args: [ '--allow-file-access-from-files', '--no-sandbox' ]
args: ['--allow-file-access-from-files', '--no-sandbox']
}

const log = async (msg) => {
Expand All @@ -22,7 +22,7 @@ const log = async (msg) => {
log = console[msg.type()]
}
if (args.length === 0) {
log.apply(this, [msg._text])
log.apply(this, [msg._text])
} else {
log.apply(this, args)
}
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/expected/alice-bluegray.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion test/fixtures/expected/cars-repeated-charts.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion test/fixtures/expected/chart.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e7a1438

Please sign in to comment.