Skip to content

Commit

Permalink
add kroki-plantuml-include-paths attribute for PlantUML preprocessor
Browse files Browse the repository at this point in the history
  • Loading branch information
anb0s committed Feb 16, 2021
1 parent 3216664 commit 7018e79
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 16 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ Consult the [Kroki documentation](https://kroki.io/#support) to find out which f
| `kroki-fetch-diagram` | Define if we should download (and save on the disk) the images from the Kroki server.<br/>This feature is not available when running in the browser. | `false`
| `kroki-http-method` | Define how we should get the image from the Kroki server. Possible values:<br/><ul><li>`get`: always use GET requests</li><li>`post`: always use POST requests</li><li>`adaptive`: use a POST request if the URI length is longer than `kroki-max-uri-length` (default 4000) characters, otherwise use a GET request</li></ul> | `adaptive` |
| `kroki-plantuml-include` | A file that will be included at the top of all PlantUML diagrams as if `!include file` was used. This can be useful when you want to define a common skin for all your diagrams. The value can be a path or a URL. | |
| `kroki-plantuml-include-paths` | Search path(s) that will be used to resolve `!include file` additionally to current diagram directory, similar to PlantUML property [plantuml.include.path](https://plantuml.com/de/preprocessing). Please use directory delimiter `;` (Windows) or `:` (Unix) for multiple paths, e.g.: `"c:/docu/styles;c:/docu/library"` or `"~/docu/styles:~/docu/library"` | |
| `kroki-max-uri-length` | Define the max URI length before using a POST request when using `adaptive` HTTP method (`kroki-http-method`) | `4000`

**❗ IMPORTANT:**
Expand Down
9 changes: 5 additions & 4 deletions src/asciidoctor-kroki.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,12 @@ const processKroki = (processor, parent, attrs, diagramType, diagramText, contex
if (diagramType === 'vegalite') {
diagramText = require('./preprocess.js').preprocessVegaLite(diagramText, context, diagramDir)
} else if (diagramType === 'plantuml' || diagramType === 'c4plantuml') {
const plantUmlInclude = doc.getAttribute('kroki-plantuml-include')
if (plantUmlInclude) {
diagramText = `!include ${plantUmlInclude}\n${diagramText}`
const plantUmlIncludeFile = doc.getAttribute('kroki-plantuml-include')
if (plantUmlIncludeFile) {
diagramText = `!include ${plantUmlIncludeFile}\n${diagramText}`
}
diagramText = require('./preprocess.js').preprocessPlantUML(diagramText, context, diagramDir)
const plantUmlIncludePaths = doc.getAttribute('kroki-plantuml-include-paths')
diagramText = require('./preprocess.js').preprocessPlantUML(diagramText, context, plantUmlIncludePaths, diagramDir)
}
}
const blockId = attrs.id
Expand Down
42 changes: 30 additions & 12 deletions src/preprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,15 @@ function removePlantUmlTags (diagramText) {
/**
* @param {string} diagramText
* @param {any} context
* @param {string} diagramIncludePaths - predefined include paths (can be null)
* @param {string} diagramDir - diagram base directory
* @returns {string}
*/
module.exports.preprocessPlantUML = function (diagramText, context, diagramDir = '') {
module.exports.preprocessPlantUML = function (diagramText, context, diagramIncludePaths = '', diagramDir = '') {
const includeOnce = []
const includeStack = []
diagramText = preprocessPlantUmlIncludes(diagramText, diagramDir, includeOnce, includeStack, context.vfs)
const includePaths = diagramIncludePaths ? diagramIncludePaths.split(path.delimiter) : []
diagramText = preprocessPlantUmlIncludes(diagramText, diagramDir, includeOnce, includeStack, includePaths, context.vfs)
return removePlantUmlTags(diagramText)
}

Expand All @@ -98,10 +100,11 @@ module.exports.preprocessPlantUML = function (diagramText, context, diagramDir =
* @param {string} dirPath
* @param {string[]} includeOnce
* @param {string[]} includeStack
* @param {string[]} includePaths
* @param {any} vfs
* @returns {string}
*/
function preprocessPlantUmlIncludes (diagramText, dirPath, includeOnce, includeStack, vfs) {
function preprocessPlantUmlIncludes (diagramText, dirPath, includeOnce, includeStack, includePaths, vfs) {
// see: http://plantuml.com/en/preprocessing
const regExInclude = /^\s*!(include(?:_many|_once|url|sub)?)\s+((?:(?<=\\)[ ]|[^ ])+)(.*)/
const regExTrailingComment = /^\s+[#|\\/']/
Expand All @@ -119,7 +122,7 @@ function preprocessPlantUmlIncludes (diagramText, dirPath, includeOnce, includeS
const trailingContent = args[2]
const url = urlSub[0].replace(/\\ /g, ' ')
const sub = urlSub[1]
const result = readPlantUmlInclude(url, dirPath, includeStack, vfs)
const result = readPlantUmlInclude(url, [dirPath, ...includePaths], includeStack, vfs)
if (result.skip) {
return line
}
Expand All @@ -142,7 +145,7 @@ function preprocessPlantUmlIncludes (diagramText, dirPath, includeOnce, includeS
text = getPlantUmlTextOrFirstBlock(text)
}
includeStack.push(result.filePath)
text = preprocessPlantUmlIncludes(text, path.dirname(result.filePath), includeOnce, includeStack, vfs)
text = preprocessPlantUmlIncludes(text, path.dirname(result.filePath), includeOnce, includeStack, includePaths, vfs)
includeStack.pop()
if (trailingContent.match(regExTrailingComment)) {
return text + trailingContent
Expand All @@ -161,15 +164,33 @@ function preprocessPlantUmlIncludes (diagramText, dirPath, includeOnce, includeS
return diagramProcessed.join('\n')
}

/**
* @param {string} includeFile - relative or absolute include file
* @param {string[]} includePaths - array with include paths
* @param {any} vfs
* @returns {string} the found file or include file path
*/
function resolveIncludeFile (includeFile, 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
}
}
return filePath
}

/**
* @param {string} url
* @param {string} dirPath
* @param {string[]} includePaths
* @param {string[]} includeStack
* @param {any} vfs
* @returns {any}
*/
function readPlantUmlInclude (url, dirPath, includeStack, vfs) {
const exists = typeof vfs !== 'undefined' && typeof vfs.exists === 'function' ? vfs.exists : require('./node-fs.js').exists
function readPlantUmlInclude (url, includePaths, includeStack, vfs) {
const read = typeof vfs !== 'undefined' && typeof vfs.read === 'function' ? vfs.read : require('./node-fs.js').read
let skip = false
let text = ''
Expand All @@ -191,10 +212,7 @@ function readPlantUmlInclude (url, dirPath, includeStack, vfs) {
skip = true
}
} else {
filePath = path.join(dirPath, url)
if (!exists(filePath)) {
filePath = url
}
filePath = resolveIncludeFile(url, 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)
Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/plantuml/diagrams/hello-with-style.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
!include style.iuml
Bob->Alice: Hello
4 changes: 4 additions & 0 deletions test/fixtures/plantuml/styles/general.iuml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
skinparam DefaultFontName "Neucha"
skinparam BackgroundColor transparent
skinparam Shadowing false
skinparam Handwritten true
5 changes: 5 additions & 0 deletions test/fixtures/plantuml/styles/note.iuml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
skinparam Note {
BorderColor #303030
BackgroundColor #CEEEFE
FontSize 12
}
13 changes: 13 additions & 0 deletions test/fixtures/plantuml/styles/sequence.iuml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
skinparam Sequence {
TitleFontSize 12
TitleFontColor #606060
ArrowColor #303030
DividerBackgroundColor #EEEEEE
GroupBackgroundColor #EEEEEE
LifeLineBackgroundColor white
LifeLineBorderColor #303030
ParticipantBackgroundColor #FEFEFE
ParticipantBorderColor #303030
BoxLineColor #303030
BoxBackgroundColor #DDDDDD
}
3 changes: 3 additions & 0 deletions test/fixtures/plantuml/styles/style.iuml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
!include general.iuml
!include note.iuml
!include sequence.iuml
20 changes: 20 additions & 0 deletions test/test.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,26 @@ alice -> bob
expect(html).to.contain(`https://kroki.io/plantuml/svg/${encodeText(diagramText)}`)
expect(html).to.contain('<div class="imageblock sequence kroki-format-svg kroki">')
}).timeout(5000)
it('should convert a PlantUML diagram and resolve includes from configured kroki-plantuml-include-paths attribute', () => {
const file = ospath.join(__dirname, 'fixtures', 'plantuml', 'diagrams', 'hello-with-style.puml')
const diagramText = fs.readFileSync(file, 'utf8')
.replace(/^!include (.*)\r?\n/m,
fs.readFileSync(ospath.join(__dirname, 'fixtures', 'plantuml', 'styles', 'general.iuml'), 'utf8') + '\n' +
fs.readFileSync(ospath.join(__dirname, 'fixtures', 'plantuml', 'styles', 'note.iuml'), 'utf8') + '\n' +
fs.readFileSync(ospath.join(__dirname, 'fixtures', 'plantuml', 'styles', 'sequence.iuml'), 'utf8') + '\n'
)
const input = `plantuml::${file}[svg,role=sequence]`
const registry = asciidoctor.Extensions.create()
asciidoctorKroki.register(registry)
const html = asciidoctor.convert(input, {
safe: 'safe',
extension_registry: registry,
attributes: { 'kroki-plantuml-include-paths': ospath.join(__dirname, 'fixtures', 'plantuml', 'styles') },
base_dir: ospath.join(__dirname, 'fixtures')
})
expect(html).to.contain(`https://kroki.io/plantuml/svg/${encodeText(diagramText)}`)
expect(html).to.contain('<div class="imageblock sequence kroki-format-svg kroki">')
}).timeout(5000)
it('should convert a diagram with a relative path to an image', () => {
const input = `
:imagesdir: .asciidoctor/kroki
Expand Down

0 comments on commit 7018e79

Please sign in to comment.