Commit 535349d7 authored by Dan Allen's avatar Dan Allen

Merge issue-29-document-converter branch into master

resolves #29 add document converter

See merge request antora/antora-direct!21
parents 2805dd05 a09868b2
Pipeline #13293075 passed with stages
in 4 minutes and 3 seconds
......@@ -35,9 +35,14 @@ class FileCatalog {
}
findBy (options) {
const srcFilter = _.pick(options, ['component', 'module', 'module', 'family', 'subpath', 'stem'])
const srcFilter = _.pick(options, ['component', 'version', 'module', 'family', 'subpath', 'stem', 'basename'])
return _.filter(this[$files], { src: srcFilter })
}
getById ({ component, version, module, family, subpath, basename }) {
const id = [component, version, module, family, subpath, basename]
return _.get(this[$filesIndex], id)
}
}
module.exports = (playbook, corpus) => {
......
......@@ -499,4 +499,44 @@ describe('classifyContent()', () => {
expect(pages[1].src).to.include({ component: 'the-component', version: 'v4.5.6' })
})
})
describe('getById()', () => {
beforeEach(() => {
corpus = [
{
name: 'the-component',
title: 'The Component',
version: 'v1.2.3',
files: [
createFile('/modules/ROOT/assets/images/foo.png'),
createFile('/modules/ROOT/documents/page-one.adoc'),
],
},
]
})
it('should find file by coordinates', () => {
const page = classifyContent(playbook, corpus).getById({
component: 'the-component',
version: 'v1.2.3',
module: 'ROOT',
family: 'page',
subpath: '',
basename: 'page-one.adoc',
})
expect(page.path).to.equal('/modules/ROOT/documents/page-one.adoc')
})
it('should return null if nothing is found', () => {
const page = classifyContent(playbook, corpus).getById({
component: 'the-component',
version: 'v1.2.3',
module: 'ROOT',
family: 'page',
subpath: '',
basename: 'unknown-page.adoc',
})
expect(page).not.to.exist()
})
})
})
'use strict'
const asciidoctor = require('asciidoctor.js')()
const AsciidoctorIncludeProcessorExtension = require('./include-processor-extension')
const includeProcessor = new AsciidoctorIncludeProcessorExtension(asciidoctor)
const AsciidoctorXrefProcessorExtension = require('./xref-processor-extension')
const xrefProcessor = new AsciidoctorXrefProcessorExtension(asciidoctor)
module.exports = function (asciidoc, options, onInclude, onPageRef) {
includeProcessor.onInclude(onInclude)
xrefProcessor.onPageRef(onPageRef)
const ast = asciidoctor.load(asciidoc, options)
const attributes = ast.getAttributes()
const html = ast.convert()
const htmlContents = Buffer.from(html)
return { attributes, htmlContents }
}
'use strict'
const $$includeHandler = Symbol('$$includeHandler')
module.exports = class AsciidoctorIncludeProcessorExtension {
constructor (asciidoctor) {
const thisExtension = this
const Extensions = asciidoctor.Extensions
Extensions.register(function () {
this.includeProcessor(function () {
this.process((doc, reader, target, attributes) => {
const processCallback = thisExtension[$$includeHandler]
if (!processCallback) {
return
}
const include = processCallback(doc, target, doc.reader.$cursor())
if (include != null) {
reader.$push_include(include.contents, include.file, include.path, 1, attributes)
}
})
})
})
this[$$includeHandler] = null
}
onInclude (callback) {
this[$$includeHandler] = callback
}
}
'use strict'
const path = require('path')
const convertAsciiDocString = require('./asciidoctor')
const samplesdir = '$samples$'
const fragmentsdir = '$fragments$'
module.exports = async function convertDocument (file, playbookAsciidoctor, vfileCatalog) {
const options = {
safe: 'safe',
attributes: Object.assign(
{
// overridable attributes
'source-highlighter': 'highlight.js',
sectanchors: '',
idprefix: '',
idseparator: '-',
icons: 'font',
},
playbookAsciidoctor,
{
// fixed attributes
docname: file.src.stem,
docfile: file.path,
docfilesuffix: file.src.extname,
'env-site': '',
imagesdir: file.out.moduleRootPath + '/_images',
attachmentsdir: file.out.moduleRootPath + '/_attachments',
samplesdir,
fragmentsdir,
}
),
}
const { attributes, htmlContents } = convertAsciiDocString(
file.contents.toString(),
options,
(doc, target) => readInclude(file, vfileCatalog, target),
(refId, text) => transformXref(file, refId, text, vfileCatalog)
)
file.contents = htmlContents
file.asciidoc = { attributes }
return Promise.resolve()
}
function readInclude (file, vfileCatalog, target) {
const [targetFamily, ...targetPath] = target.split('/').filter((a) => a !== '')
const findOptions = {
component: file.src.component,
version: file.src.version,
module: file.src.module,
subpath: targetPath.slice(0, -1).join('/'),
basename: targetPath.slice(-1).join(''),
}
const include = { file: target, path: file.src.basename }
if (targetFamily === samplesdir) {
findOptions.family = 'sample'
} else if (targetFamily === fragmentsdir) {
findOptions.family = 'fragment'
} else {
// TODO log "Bad include"
include.contents = `+include::${target}[]+`
return include
}
const includeFile = vfileCatalog.getById(findOptions)
if (includeFile == null) {
// TODO log "Unknown include"
include.contents = `+include::${target}[]+`
return include
}
include.contents = includeFile.contents.toString()
return include
}
function transformXref (file, xref, title, vfileCatalog) {
const xrefSrc = getSourceFromXref(xref)
if (xrefSrc == null) {
// TODO log "Invalid xref"
return `<a href="#">${xref}</a>`
}
const xrefFile = vfileCatalog.getById({
component: xrefSrc.component || file.src.component,
version: xrefSrc.version || file.src.version,
module: xrefSrc.module || file.src.module,
family: 'page',
subpath: xrefSrc.subpath,
basename: xrefSrc.basename,
})
if (xrefFile == null) {
// TODO log "Unknown xref"
return `<a href="#">${xref}</a>`
}
const { dir: urlBase } = path.parse(file.pub.url)
const relativeUrl = path.relative(urlBase, xrefFile.pub.url)
return `<a href="${relativeUrl}">${title}</a>`
}
const XREF_REGEX = /^(?:(.+?)@)?(?:(?:(.+?):)?(?:(.+?))?:)?(?:(.+)\/)?(.+?)(?:#(.+?))?$/
function getSourceFromXref (xref) {
const matches = XREF_REGEX.exec(xref)
if (!matches) {
return null
}
const [, extractedVersion, component, extractedModule, subpath = '', stem, fragment] = matches
let version = extractedVersion
if (extractedVersion == null && component != null) {
// if component is defined and version undefined, it implicitly means "master"
version = 'master'
}
let module = extractedModule
if ((component != null || version != null) && extractedModule == null) {
// if component and/or version are defined and module undefined, it implicitly means "ROOT"
module = 'ROOT'
}
return {
component,
version,
module,
family: 'page',
subpath,
mediaType: 'text/asciidoc',
basename: stem + '.adoc',
stem,
extname: '.adoc',
fragment,
}
}
'use strict'
const $$pageRefHandler = Symbol('$$pageRefHandler')
module.exports = class AsciidoctorXrefProcessorExtension {
constructor (asciidoctor) {
const thisExtension = this
const Html5Converter = global.Opal.klass(asciidoctor.$$const.Converter, null, 'Html5Converter', () => {})
global.Opal.alias(Html5Converter, 'super_inline_anchor', 'inline_anchor')
global.Opal.defn(Html5Converter, '$inline_anchor', function (node) {
if (node.getType() === 'xref') {
// NOTE refId is undefined if document is self-referencing
let refId = node.getAttribute('refid')
if (
node.getAttribute('path') ||
(refId && refId.endsWith('.adoc') && (refId = refId.slice(0, -5)) !== undefined)
) {
let text
text = (text = node.$text()) === global.Opal.nil ? refId : text
return thisExtension[$$pageRefHandler](refId, text)
}
}
return this.$super_inline_anchor(node)
})
}
onPageRef (callback) {
this[$$pageRefHandler] = callback
}
}
This diff is collapsed.
......@@ -164,6 +164,12 @@ asap@~2.0.3:
version "2.0.6"
resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
asciidoctor.js@^1.5.6-preview.4:
version "1.5.6-preview.4"
resolved "https://registry.yarnpkg.com/asciidoctor.js/-/asciidoctor.js-1.5.6-preview.4.tgz#cf9c36223b0210927489eaba61eec929ab3bc5a0"
dependencies:
opal-runtime "0.11.0-integration8"
asn1@~0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
......@@ -1291,6 +1297,16 @@ glob2base@^0.0.12:
dependencies:
find-index "^0.1.1"
glob@6.0.4:
version "6.0.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "2 || 3"
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8"
......@@ -2616,6 +2632,12 @@ onetime@^2.0.0:
dependencies:
mimic-fn "^1.0.0"
opal-runtime@0.11.0-integration8:
version "0.11.0-integration8"
resolved "https://registry.yarnpkg.com/opal-runtime/-/opal-runtime-0.11.0-integration8.tgz#8442ddc8c66279c6cd004bf7a730a78646202db2"
dependencies:
glob "6.0.4"
optimist@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment