@antora/assembler 1.0.0-beta.9 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,17 +1,15 @@
1
1
  'use strict'
2
2
 
3
- const computeOut = require('./util/compute-out')
4
- const createAsciiDocFile = require('./util/create-asciidoc-file')
5
3
  const filterComponentVersions = require('./filter-component-versions')
6
4
  const produceAssemblyFile = require('./produce-assembly-file')
7
5
  const selectMutableAttributes = require('./select-mutable-attributes')
8
6
 
9
7
  const ATTR_REF_RX = /\\?\{(\w[\w-]*)\}/g
10
- const IMAGE_MACRO_RX = /^image::?(.+?)\[(.*?)\]$/
11
8
 
12
- function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, resolveAssemblyModel) {
13
- const { asciidoc: assemblerAsciiDocConfig, assembly: assemblyConfig } = assemblerConfig
14
- resolveAssemblyModel ??= (componentVersion) => ({
9
+ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, selectAssemblyProfile) {
10
+ const { assembly: assemblyConfig } = assemblerConfig
11
+ selectAssemblyProfile ??= (componentVersion) => ({
12
+ attributes: assemblyConfig.attributes,
15
13
  doctype: assemblyConfig.doctype,
16
14
  insertStartPage: assemblyConfig.insertStartPage,
17
15
  rootLevel: assemblyConfig.rootLevel,
@@ -21,40 +19,51 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, re
21
19
  embedReferenceStyle: assemblyConfig.embedReferenceStyle,
22
20
  linkReferenceStyle: assemblyConfig.linkReferenceStyle,
23
21
  dropExplicitXrefText: assemblyConfig.dropExplicitXrefText,
22
+ revdate: assemblyConfig.revdate,
23
+ logger: assemblyConfig.logger, // for tests
24
24
  })
25
- const assemblerAsciiDocAttributes = Object.assign({}, assemblerAsciiDocConfig.attributes)
26
- const { revdate, 'source-highlighter': sourceHighlighter } = assemblerAsciiDocAttributes
27
- delete assemblerAsciiDocAttributes.revdate
28
- delete assemblerAsciiDocAttributes['source-highlighter']
29
- const publishableFiles = contentCatalog.getFiles().filter((file) => file.out)
25
+ const baseAssemblyAttributes = assemblyConfig.attributes
26
+ const publishableFiles = contentCatalog.getFiles().filter((file) => file.out && file.pub)
30
27
  let siteRoot
31
28
  const configMdc = assemblerConfig.file ? { file: { path: assemblerConfig.file } } : {}
32
- return filterComponentVersions(contentCatalog.getComponents(), assemblerConfig.componentVersionFilter.names).reduce(
29
+ return filterComponentVersions(contentCatalog.getComponents(), assemblerConfig.componentVersionFilter).reduce(
33
30
  (accum, componentVersion) => {
34
- const assemblyModel = resolveAssemblyModel(componentVersion)
35
- if (!assemblyModel.navigation) return accum
31
+ const assemblyModel = selectAssemblyProfile(componentVersion)
32
+ const { attributes: assemblyAttributes, logger, navigation, rootLevel } = assemblyModel
33
+ if (!navigation) return accum
34
+ const contextualLogger = logger ? { warn: logger.warn.bind(logger, configMdc) } : undefined
36
35
  const { name: componentName, version, title } = componentVersion
37
36
  const componentVersionAsciiDocConfig = getAsciiDocConfigWithAsciidoctorReducerExtension(componentVersion)
37
+ let sourceHighlighter
38
+ if ('source-highlighter' in assemblyAttributes) {
39
+ sourceHighlighter = assemblyAttributes['source-highlighter']
40
+ delete assemblyAttributes['source-highlighter']
41
+ }
38
42
  const mergedAsciiDocAttributes = collateAsciiDocAttributes(
39
- Object.assign({ revdate }, componentVersionAsciiDocConfig.attributes),
40
- assemblerAsciiDocAttributes,
41
- { logger: assemblyModel.logger, mdc: configMdc }
43
+ Object.assign({}, componentVersionAsciiDocConfig.attributes),
44
+ assemblyAttributes,
45
+ contextualLogger
42
46
  )
43
47
  const mergedAsciiDocConfig = Object.assign({}, componentVersionAsciiDocConfig, {
44
48
  attributes: mergedAsciiDocAttributes,
45
49
  })
46
- assemblyModel.outDirname = computeOut.call(contentCatalog, {
47
- component: componentName,
48
- version,
49
- family: 'export',
50
- relative: '.index.adoc',
51
- }).dirname
52
- assemblyModel.filetype = assemblerAsciiDocAttributes['assembler-filetype']
50
+ assemblyModel.filetype = baseAssemblyAttributes['assembler-filetype']
51
+ const outDirname = (assemblyModel.outDirname = contentCatalog.createFile({
52
+ src: {
53
+ component: componentName,
54
+ version,
55
+ componentVersion,
56
+ module: 'ROOT',
57
+ family: 'export',
58
+ relative: 'index.adoc',
59
+ },
60
+ }).out.dirname)
61
+ assemblyModel.pubRoot = outDirname ? '/' + outDirname : ''
53
62
  assemblyModel.siteRoot =
54
63
  siteRoot === undefined
55
64
  ? (siteRoot ??= ((val) => {
56
65
  if (!val) return null
57
- if (val.charAt(val.length - 1) === '/') val = val.slice(0, val.length - 1)
66
+ if (val.charAt(val.length - 1) === '/') val = val.substring(0, val.length - 1)
58
67
  if (!val || val.charAt() === '/') return { path: val }
59
68
  return { url: val, path: extractUrlPath(val) }
60
69
  })(mergedAsciiDocAttributes['site-url'] || mergedAsciiDocAttributes['primary-site-url']))
@@ -64,21 +73,10 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, re
64
73
  if (linkRefStyle === 'absolute' && siteRoot?.url == null) linkRefStyle = 'root-relative'
65
74
  if (linkRefStyle === 'root-relative' && siteRoot?.path == null) linkRefStyle = 'relative'
66
75
  assemblyModel.linkReferenceStyle = linkRefStyle
67
- } else {
76
+ } else if (!(assemblyModel.filetype === 'pdf' && assemblyModel.linkReferenceStyle === 'relative')) {
68
77
  assemblyModel.linkReferenceStyle = 'absolute'
69
78
  }
70
- const auxiliaryImages = new Set()
71
- Object.entries(mergedAsciiDocAttributes).forEach(([name, val]) => {
72
- const match = name.endsWith('-image') && val.startsWith('image:') && IMAGE_MACRO_RX.exec(val)
73
- if (!(match && isResourceRef(match[1]))) return
74
- // Q should we allow image to be resolved relative to component version?
75
- const image = contentCatalog.resolveResource(match[1], undefined, 'image', ['image'])
76
- if (!image?.out) return
77
- mergedAsciiDocAttributes[name] = `image:${image.out.path}[${match[2]}]`
78
- auxiliaryImages.add(image)
79
- })
80
79
  const rootEntry = { content: title }
81
- const { navigation, rootLevel } = assemblyModel
82
80
  let startPage =
83
81
  'startPage' in componentVersion
84
82
  ? componentVersion.startPage
@@ -86,23 +84,24 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, re
86
84
  if (startPage && startPage.src.component === componentName && startPage.src.version === version) {
87
85
  if (assemblyModel.insertStartPage) {
88
86
  const navtitle = startPage.asciidoc?.navtitle || rootEntry.content
89
- Object.assign(rootEntry, { navtitle, url: startPage.pub.url, urlType: 'internal' })
87
+ Object.assign(rootEntry, { navtitle, url: startPage.pub.url, urlType: 'internal', roles: ['page'] })
90
88
  }
91
89
  } else {
92
90
  // Q: should we always use a reference page as startPage for computing mutableAttributes?
93
- startPage = createAsciiDocFile(contentCatalog, {
91
+ startPage = contentCatalog.createFile({
94
92
  src: {
95
93
  component: componentVersion.name,
96
94
  version: componentVersion.version,
95
+ componentVersion,
97
96
  module: 'ROOT',
98
97
  family: 'page',
99
- relative: '.start-page.adoc',
98
+ relative: 'index.adoc',
100
99
  origin: (componentVersion.origins || [])[0],
101
100
  },
102
101
  })
102
+ startPage.path = ['modules', startPage.src.module, startPage.src.family + 's', startPage.src.relative].join('/')
103
103
  }
104
104
  const mutableAttributes = selectMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
105
- delete mutableAttributes.doctype // Q: should this be in selectMutableAttributes?
106
105
  prepareOutlines(navigation, rootEntry, rootLevel).reduce((any, outline) => {
107
106
  const assemblyFile = produceAssemblyFile(
108
107
  loadAsciiDoc,
@@ -115,16 +114,14 @@ function produceAssemblyFiles (loadAsciiDoc, contentCatalog, assemblerConfig, re
115
114
  assemblyModel
116
115
  )
117
116
  if (!assemblyFile) return any
118
- if (!any && auxiliaryImages.size) {
119
- const assembledAssets = assemblyFile.assembler.assembled.assets
120
- auxiliaryImages.forEach((asset) => assembledAssets.add(asset))
117
+ // NOTE restore source highlighter for conversion if defined in Assembler config
118
+ if (sourceHighlighter !== undefined) {
119
+ assemblyFile.asciidoc.attributes['source-highlighter'] = sourceHighlighter
120
+ } else if (assemblyModel.filetype !== 'html') {
121
+ delete assemblyFile.asciidoc.attributes['source-highlighter']
121
122
  }
122
- accum.push(assemblyFile)
123
- return true
123
+ return !!accum.push(assemblyFile)
124
124
  }, false)
125
- sourceHighlighter
126
- ? (mergedAsciiDocAttributes['source-highlighter'] = sourceHighlighter)
127
- : delete mergedAsciiDocAttributes['source-highlighter']
128
125
  return accum
129
126
  },
130
127
  []
@@ -150,15 +147,11 @@ function includedInNav (items, url) {
150
147
  return items.find((it) => it.url === url || includedInNav(it.items || [], url))
151
148
  }
152
149
 
153
- function isResourceRef (target) {
154
- return ~target.indexOf(':') && !(~target.indexOf('://') || (target.startsWith('data:') && ~target.indexOf(',')))
155
- }
156
-
157
150
  function prepareOutlines (navigation, rootEntry, rootLevel) {
158
151
  const singleEntry = navigation.length === 1
159
152
  const items = navigation.reduce((accum, it) => {
160
153
  if (!it.content || (singleEntry && it.url ? it.url === rootEntry.url : it.content === rootEntry.content)) {
161
- accum.push(...it.items)
154
+ it.items && accum.push(...it.items)
162
155
  } else {
163
156
  accum.push(it)
164
157
  }
@@ -166,7 +159,7 @@ function prepareOutlines (navigation, rootEntry, rootLevel) {
166
159
  }, [])
167
160
  if (rootLevel === 0) {
168
161
  if (rootEntry.url && includedInNav(items, rootEntry.url)) {
169
- for (const p of ['url', 'urlType']) delete rootEntry[p]
162
+ for (const p of ['roles', 'url', 'urlType']) delete rootEntry[p]
170
163
  }
171
164
  return [Object.assign(rootEntry, { index: true, items })]
172
165
  }
@@ -177,20 +170,18 @@ function prepareOutlines (navigation, rootEntry, rootLevel) {
177
170
  return items
178
171
  }
179
172
 
180
- function collateAsciiDocAttributes (collated, additional, { logger, mdc }) {
173
+ function collateAsciiDocAttributes (collated, additional, logger) {
181
174
  Object.entries(additional).forEach(([name, val]) => {
182
175
  if (val && val.constructor === String) {
183
176
  let alias
184
177
  val = val.replace(ATTR_REF_RX, (ref, refname) => {
185
- if (ref.charAt() === '\\') return ref.substr(1)
178
+ if (ref.charAt() === '\\') return ref.substring(1)
186
179
  const refval = collated[refname]
187
180
  if (refval == null || refval === false) {
188
181
  if (refname in collated && ref === val) {
189
182
  alias = refval
190
183
  } else if (collated['attribute-missing'] === 'warn') {
191
- if (logger) {
192
- logger.warn(mdc, "Skipping reference to missing attribute '%s' in value of '%s' attribute", refname, name)
193
- }
184
+ logger?.warn("Skipping reference to missing attribute '%s' in value of '%s' attribute", refname, name)
194
185
  }
195
186
  return ref
196
187
  }
@@ -17,6 +17,7 @@ function selectMutableAttributes (loadAsciiDoc, contentCatalog, referencePage, a
17
17
  ]
18
18
  // we could consider using an Asciidoctor extension here to grab attributes passed via the API instead
19
19
  const immutableAttributeNames = doc.attribute_overrides.$keys()['$-'](additionalMutableNames)
20
+ immutableAttributeNames.push('doctype')
20
21
  return Object.entries(doc.getAttributes()).reduce((accum, [name, val]) => {
21
22
  if (!immutableAttributeNames.includes(name)) accum[name] = val
22
23
  return accum
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ function createResourceKey ({ component, version, module: mod, family, relative }) {
4
+ return `${version}@${component}:${mod === 'ROOT' ? '' : mod}:${family === 'page' ? '' : family + '$'}${relative}`
5
+ }
6
+
7
+ module.exports = createResourceKey
@@ -0,0 +1,25 @@
1
+ 'use strict'
2
+
3
+ const HTML_RESERVED_ID_NAMES = 'content header footnotes footer footer-text premable toc toctitle'.split(' ')
4
+
5
+ function generateScopedId (componentSrc, componentVersion, separators, filetype, asXrefTarget) {
6
+ let { component, module: mod, relative } = componentSrc
7
+ let id = relative.replace(/\.adoc$/, '').replace(/[/.]/g, '-')
8
+ let { coordinate: coordinateSeparator, prefix: prefixSeparator } = separators
9
+ if (component !== componentVersion.name) {
10
+ id = [component, mod === 'ROOT' ? '' : mod, id].join(coordinateSeparator)
11
+ } else if (mod !== 'ROOT') {
12
+ if (asXrefTarget && coordinateSeparator === ':' && /(?:pass|stem)$/.test(mod) && /^[a-z]+(?:,[a-z-]+)*$/.test(id)) {
13
+ prefixSeparator = ''
14
+ mod = mod.replace(/(?:pass|stem)$/, '\\$&')
15
+ }
16
+ id = mod + coordinateSeparator + id
17
+ } else if (filetype === 'html' && HTML_RESERVED_ID_NAMES.includes(id)) {
18
+ id = prefixSeparator + id
19
+ prefixSeparator = ''
20
+ }
21
+ if (prefixSeparator && !/^[\p{Alpha}_:]/u.test(id)) id = prefixSeparator + id
22
+ return id
23
+ }
24
+
25
+ module.exports = generateScopedId
@@ -0,0 +1,150 @@
1
+ 'use strict'
2
+
3
+ const SPECIAL = { '.': true, '[': true, ']': true, '^': true, $: true, '}': true, ')': true, '|': true }
4
+ const QUANTIFIER_ALTERNATE = { '*': '.*?', '+': '\\+', '?': '.' }
5
+ const QUANTIFIERS = { '*': true, '+': true, '?': true, '@': true, '!': true }
6
+ const RS = '\x1e'
7
+
8
+ const BRACE_RANGE_RX = /^\{(?:([a-z])\.\.([a-z])|(0|[1-9][0-9]*)\.\.(0|[1-9][0-9]*))\}/
9
+
10
+ function filterCollection (candidates, patterns, opts = {}) {
11
+ const rxs = patterns.map(opts.compilePattern ?? compilePattern)
12
+ const test = opts.test ?? ((rx, candidate) => rx.test(candidate))
13
+ return candidates.filter((candidate) => {
14
+ let matched = false
15
+ for (const rx of rxs) {
16
+ let voteIfMatched = true
17
+ if (matched) {
18
+ if (!rx.negated) continue
19
+ voteIfMatched = false
20
+ } else if (rx.negated) {
21
+ voteIfMatched = false
22
+ }
23
+ if (test(rx, candidate)) matched = voteIfMatched
24
+ }
25
+ return matched
26
+ })
27
+ }
28
+
29
+ function compilePattern (str) {
30
+ const negated = str.charAt() === '!' ? typeof (str = str.substring(1)) === 'string' : false
31
+ if (str === '**') return Object.assign(new RegExp(), { globstar: true, negated })
32
+ if (str === '*') return Object.assign(new RegExp(), { star: true, negated })
33
+ const buffer = { result: '', brace: undefined, group: undefined }
34
+ let target = 'result'
35
+ let nestedTarget
36
+ for (let i = 0, len = str.length; i < len; i++) {
37
+ const c = str[i]
38
+ if (target === 'brace') {
39
+ if (c === '}') {
40
+ const braceBuffer = buffer.brace
41
+ let braceResult
42
+ if (~braceBuffer.indexOf(RS)) {
43
+ braceResult = braceBuffer === RS ? '' : '(?:' + braceBuffer.replace(/\x1e/g, '|') + ')'
44
+ } else {
45
+ const m = BRACE_RANGE_RX.exec('{' + braceBuffer.replace('\\.\\.', '..') + '}')
46
+ braceResult = m[1] != null ? `[${m[1]}-${m[2]}]` : rangeRe(+m[3], +m[4])
47
+ }
48
+ buffer[(target = nestedTarget === 'group' ? 'group' : 'result')] += braceResult
49
+ buffer.brace = nestedTarget = undefined
50
+ continue
51
+ } else if (c === ',') {
52
+ buffer.brace += RS
53
+ continue
54
+ }
55
+ } else if (target === 'group') {
56
+ if (c === ')') {
57
+ const { group: groupBuffer, groupQuantifier } = buffer
58
+ let groupResult = '(?:' + groupBuffer + ')'
59
+ if (groupQuantifier === '!') {
60
+ groupResult = '(?!' + groupResult + '.*?)'
61
+ } else if (groupQuantifier !== '@') {
62
+ groupResult += groupQuantifier
63
+ }
64
+ buffer[(target = nestedTarget === 'brace' ? 'brace' : 'result')] += groupResult
65
+ buffer.group = buffer.groupQuantifier = nestedTarget = undefined
66
+ continue
67
+ }
68
+ }
69
+ if (SPECIAL[c]) {
70
+ buffer[target] += '\\' + c
71
+ } else if (c === '(' || QUANTIFIERS[c]) {
72
+ const hasQuantifier = c !== '('
73
+ const atGroupStart = hasQuantifier ? str[i + 1] === '(' : true
74
+ if (target !== 'group' && atGroupStart && isGroup(str.substring(hasQuantifier ? i + 1 : i))) {
75
+ if (target === 'brace') nestedTarget = 'brace'
76
+ buffer[(target = 'group')] = ''
77
+ buffer.groupQuantifier = hasQuantifier ? ++i > 0 && c : ''
78
+ } else if (hasQuantifier) {
79
+ buffer[target] += (QUANTIFIER_ALTERNATE[c] ?? c) + (atGroupStart ? ++i > 0 && '\\(' : '')
80
+ } else {
81
+ buffer[target] += '\\('
82
+ }
83
+ } else if (c === '{') {
84
+ if (target !== 'brace' && isBrace(str.substring(i))) {
85
+ if (target === 'group') nestedTarget = 'group'
86
+ buffer[(target = 'brace')] = ''
87
+ } else {
88
+ buffer[target] += '\\' + c
89
+ }
90
+ } else {
91
+ buffer[target] += c
92
+ }
93
+ }
94
+ return Object.assign(new RegExp('^' + cleanup(buffer, target, nestedTarget) + '$'), { negated })
95
+ }
96
+
97
+ function cleanup (buffer, target, nestedTarget) {
98
+ if (target === 'result') return buffer.result
99
+ if (target === 'brace') {
100
+ buffer.result += '\\{' + buffer.brace.replace(/\x1e/g, ',')
101
+ buffer.brace = undefined
102
+ } else if (target === 'group') {
103
+ const groupQuantifier = buffer.groupQuantifier
104
+ buffer.result += (QUANTIFIER_ALTERNATE[groupQuantifier] ?? groupQuantifier) + '\\(' + buffer.group
105
+ buffer.group = buffer.groupQuantifier = undefined
106
+ }
107
+ return nestedTarget ? cleanup(buffer, nestedTarget) : buffer.result
108
+ }
109
+
110
+ function isBrace (str) {
111
+ const endBraceIdx = str.indexOf('}')
112
+ const candidate = ~endBraceIdx ? str.substring(0, endBraceIdx + 1) : ''
113
+ if (!candidate || ~candidate.indexOf('{', 1)) return false
114
+ return ~candidate.indexOf(',') || BRACE_RANGE_RX.test(candidate)
115
+ }
116
+
117
+ function isGroup (str) {
118
+ const endBraceIdx = str.indexOf(')')
119
+ const candidate = ~endBraceIdx ? str.substring(0, endBraceIdx + 1) : ''
120
+ return candidate ? !~candidate.indexOf('(', 1) : false
121
+ }
122
+
123
+ function rangeRe (min, max) {
124
+ if (min === max) return String(min)
125
+ min > max && ([min, max] = [max, min])
126
+ if (min < 10 && max < 10) return `[${min}${min + 1 === max ? '' : '-'}${max}]`
127
+ const result = []
128
+ let minStr, minStrLen, toStr
129
+ while (min <= max) {
130
+ let to = (minStrLen = (minStr = String(min)).length) < String(max).length ? 10 ** minStrLen - 1 : max
131
+ const limit = 10 ** minStrLen
132
+ for (let i = 10; i <= limit; i *= 10) {
133
+ const candidate = min + (i - (min % i) || i) - 1
134
+ if (candidate > max) break
135
+ to = candidate
136
+ }
137
+ toStr = String(to)
138
+ let rangesForChunk = ''
139
+ for (let i = 0, len = minStr.length; i < len; i++) {
140
+ const lhs = minStr[i]
141
+ const rhs = toStr[i]
142
+ rangesForChunk += lhs === rhs ? lhs : `[${lhs}${+lhs + 1 === +rhs ? '' : '-'}${rhs}]`
143
+ }
144
+ result.push(rangesForChunk)
145
+ min = to + 1
146
+ }
147
+ return result.length > 1 ? `(?:${result.join('|')})` : result[0]
148
+ }
149
+
150
+ module.exports = { filterCollection, compilePattern }
@@ -0,0 +1,36 @@
1
+ 'use strict'
2
+
3
+ function parseResourceRef (ref, ctx = {}, family = undefined, contentCatalog = undefined) {
4
+ const atIdx = ref.indexOf('@')
5
+ let firstColonIdx = ref.indexOf(':')
6
+ let component, version, module_
7
+ if (~atIdx && (~firstColonIdx ? atIdx < firstColonIdx : true)) {
8
+ if ((version = ref.substring(0, atIdx)) === '_') version = ''
9
+ ref = ref.substring(atIdx + 1)
10
+ if (~firstColonIdx) firstColonIdx -= atIdx + 1
11
+ }
12
+ const addColons = ~firstColonIdx ? (~ref.indexOf(':', firstColonIdx + 1) ? '' : ':') : '::'
13
+ const segments = (addColons + ref).split(':')
14
+ if ((component = segments[0])) {
15
+ module_ = segments[1] || 'ROOT'
16
+ version ??= contentCatalog?.getComponent(component)?.latest.version
17
+ } else {
18
+ component = ctx.component
19
+ version ??= ctx.version
20
+ module_ = segments[1] || ctx.module || 'ROOT'
21
+ }
22
+ let relative = segments.length > 3 ? segments.slice(2).join(':') : segments[2]
23
+ const dollarIdx = relative.indexOf('$')
24
+ if (~dollarIdx) {
25
+ family = relative.substring(0, dollarIdx) || family
26
+ relative = relative.substring(dollarIdx + 1)
27
+ }
28
+ if (relative.charAt() === '.' && relative.charAt(1) === '/') {
29
+ const ctxRelative = ctx.relative
30
+ const topic = ctxRelative ? ctxRelative.substring(0, (ctxRelative.lastIndexOf('/') + 1 || 1) - 1) : undefined
31
+ relative = (topic ? topic + '/' : '') + relative.substring(2)
32
+ }
33
+ return { component, version, module: module_, family, relative }
34
+ }
35
+
36
+ module.exports = parseResourceRef
@@ -0,0 +1,36 @@
1
+ 'use strict'
2
+
3
+ const path = require('node:path/posix')
4
+
5
+ function resolveEmbedTarget (resource, outDirname, referenceStyle, escapeForInline) {
6
+ const target =
7
+ referenceStyle === 'output-relative' ? resource.out.path : path.relative(outDirname + '/', resource.out.path)
8
+ return escapeForInline ? target.replace(/_/g, '{underscore}') : target
9
+ }
10
+
11
+ function resolveLinkTarget (resource, siteRoot, pubRoot, referenceStyle, escapeForInline, ensurePrefix = true) {
12
+ let target
13
+ if (resource.site?.url) {
14
+ target = ['', resource.pub.url]
15
+ } else {
16
+ switch (referenceStyle) {
17
+ case 'absolute':
18
+ target = ['', siteRoot.url + resource.pub.url]
19
+ break
20
+ case 'root-relative':
21
+ target = [ensurePrefix ? 'link:' : '', siteRoot.path + resource.pub.url]
22
+ break
23
+ default:
24
+ target = [ensurePrefix ? 'link:' : '', computeRelativeUrl(pubRoot + '/', resource.pub.url)]
25
+ }
26
+ }
27
+ if (escapeForInline) target[1] = target[1].replace(/_/g, '{underscore}')
28
+ return target.join('')
29
+ }
30
+
31
+ function computeRelativeUrl (from, to) {
32
+ const rel = path.relative(from, to)
33
+ return to.charAt(to.length - 1) === '/' ? rel + '/' : rel
34
+ }
35
+
36
+ module.exports = { resolveEmbedTarget, resolveLinkTarget }