Commit 3fe70fc8 authored by Dan Allen's avatar Dan Allen
Browse files

resolves #1115 require YAML tag on attribute or site key value to enable YAML parsing

parent 661824d6
Loading
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ This project utilizes semantic versioning.

* *playbook-builder*: Allow site URL to be unset using `--url` CLI option or `URL` environment variable with empty value or ~ (#1125)
* *playbook-builder*: Rework `buildPlaybook` so it only processes args and env once
* *playbook-builder*: Require YAML tag on attribute or site key value to enable YAML parsing; don't strip quotes around value (#1115)
* *playbook-builder*: Use YAML serialization internally for map and primitive-map values read from arguments (i.e., CLI options)
* *content-aggregator*: Skip check for remote branches in non-managed repository with no remote (#1179)
* *content-aggregator*: Skip check for local branches in managed repository (#1111)
+55 −3
Original line number Diff line number Diff line
@@ -282,12 +282,64 @@ The generated site's title will be _My Docs_.
=== Name=value values

The `--attribute` and `--key` options accept values in the form `name=value`, where `name` represents the name of the AsciiDoc attribute or API key, respectively.
In <<ex-name-value-pair>>, the attribute `page-team` is assigned the value `Coco B`.
The `@` at the end of the value indicates that the attribute is xref:ROOT:component-attributes.adoc#soft-set[soft set].
In <<ex-name-value-pair>>, the attribute `attribute-missing` is assigned the value `skip`.

.Assign a name=value value to an option
[#ex-name-value-pair]
 $ antora --attribute page-team='Coco B@' antora-playbook
 $ antora --attribute attribute-missing=skip antora-playbook

Antora treats all the characters that follow the equals sign (`=`) as the attribute's value.
It does not apply any escape processing to that part of the value.
(Only the shell does that).
Antora, itself, uses the text as entered.
If the equals sign is not present (e.g., `page-pagination`), Antora assumes the value is an empty string.

It may be necessary to apply shell escaping to the option value if it contains reserved shell characters.
For example, if the attribute value contains a space, that space must be escaped (otherwise, the antora call will fail).
The simplest way to escape reserved shell characters is to enclose the entire value in double quotes.

In <<ex-name-value-pair-esc>>, the attribute `page-toctitle` is assigned the value `Table of Contents`.

.Assign an attribute value that contains spaces
[#ex-name-value-pair-esc]
 $ antora --attribute "page-toctitle=Table of Contents" antora-playbook

By the time Antora receives the value of the `--attribute` option, the double quotes will have been removed (by the shell).

[#type-coercion]
==== Type coercion

Antora assumes that any attribute or key value that's assigned using a command line option is a string.
If you want that value to be coerced to another type, you need to prefix it with a YAML type tag (e.g., `!!int`).
The tag unlocks the automatic functionality you get when assigning an attribute in a YAML file.

In <<ex-name-value-pair-type>>, the attribute `feed-limit` is assign the integer 50.

.Coerce the value of an attribute
[#ex-name-value-pair-type]
 $ antora --attribute 'feed-limit=!!int 50' antora-playbook

NOTE: In this case, single quotes are needed to escape the `!` character.
Depending on which shell you are using, you may need to choose a different escaping strategy.
 
You can use the following type tags:

[cols=a;a]
|===
|
* !!auto
* !!str
* !!bool
* !!int
|
* !!float
* !!seq
* !!map
* !!null
|===

Note that when AsciiDoc attribues are referenced in content, the value is always coerced back to a string.
However, your extension may benefit from being able to use other types.

[#boolean-value]
=== Boolean values
+1 −1
Original line number Diff line number Diff line
@@ -580,7 +580,7 @@ describe('cli', () => {
  it('should pass attributes defined using options to AsciiDoc processor', () => {
    playbookSpec.asciidoc = { attributes: { idprefix: '' } }
    fs.writeFileSync(playbookFile, toJSON(playbookSpec))
    const args = 'generate antora-playbook --attribute sectanchors=~ --attribute experimental --quiet'.split(' ')
    const args = 'generate antora-playbook --attribute sectanchors=!!null --attribute experimental --quiet'.split(' ')
    // Q: how do we assert w/ kapok when there's no output; use promise as workaround
    return new Promise((resolve) => runAntora(args).on('exit', resolve)).then((exitCode) => {
      expect(exitCode).to.equal(0)
+1 −1
Original line number Diff line number Diff line
@@ -137,7 +137,7 @@ function importArguments (config, args) {
    const argFormat = config.getSchema(configKey).format
    let argValStr = argVal
    if (argFormat === 'map' || argFormat === 'primitive-map') {
      const dumpOpts = { condenseFlow: true, flowLevel: 0, quotingType: '"' }
      const dumpOpts = { condenseFlow: true, flowLevel: 0, noCompatMode: true, quotingType: '"' }
      argValStr = yaml.dump(Array.isArray(argVal) ? argVal : [argVal], dumpOpts)
    } else if (Array.isArray(argVal)) {
      argValStr = argVal.join(',')
+30 −16
Original line number Diff line number Diff line
@@ -5,10 +5,10 @@ const json = require('json5')
const toml = require('@iarna/toml')
const yaml = require('js-yaml')

const ARG_RX = /(?:([^=]+)|(?==))(?:$|=(|("|').*?\3|.*)(?:$))/
const PRIMITIVE_TYPES = [Boolean, Number, String]
const COERCE_SCHEMA = yaml.FAILSAFE_SCHEMA
const YAML_SCHEMA = yaml.CORE_SCHEMA.extend({ implicit: [yaml.types.merge] })
const YAML_PREFIX_RX = new RegExp('!!((?:auto|str|bool|int|float|seq|map)(?= )|null(?=$))(?: |$)')

/**
 * A convict function wrapper that registers custom formats and parsers and
@@ -54,16 +54,22 @@ function registerFormats (convict) {
      const accum = config.has(name) ? config.get(name) : {}
      const entries = yaml.load(val, { schema: COERCE_SCHEMA })
      for (const entry of Array.isArray(entries) ? entries : [entries]) {
        let k, v, match
        if (!(match = entry.match(ARG_RX))) continue
        ;[, k, v = ''] = match
        if (!k) continue
        let k = entry
        let v = ''
        const equalsIdx = entry.indexOf('=')
        if (~equalsIdx) {
          if (!(k = entry.slice(0, equalsIdx))) continue
          v = entry.slice(equalsIdx + 1)
        }
        let parsed = v
        if (v && v !== '-') {
        if (v) {
          const match = v.match(YAML_PREFIX_RX)
          if (match) {
            try {
            parsed = yaml.load(v, { schema: yaml.CORE_SCHEMA })
              parsed = yaml.load(match[1] === 'auto' ? v.slice(7) : v, { schema: yaml.CORE_SCHEMA })
            } catch {}
          }
        }
        accum[k] = parsed
      }
      return accum
@@ -87,14 +93,22 @@ function registerFormats (convict) {
      const accum = config.has(name) ? config.get(name) : {}
      const entries = yaml.load(val, { schema: COERCE_SCHEMA })
      for (const entry of Array.isArray(entries) ? entries : [entries]) {
        let k, v, match
        if (!(match = entry.match(ARG_RX))) continue
        ;[, k, v = ''] = match
        if (!k) continue
        let k = entry
        let v = ''
        const equalsIdx = entry.indexOf('=')
        if (~equalsIdx) {
          if (!(k = entry.slice(0, equalsIdx))) continue
          v = entry.slice(equalsIdx + 1)
        }
        let parsed = v
        if (v && v !== '-') {
          parsed = yaml.load(v, { schema: yaml.CORE_SCHEMA })
          if (parsed && PRIMITIVE_TYPES.indexOf(parsed.constructor) < 0) parsed = v
        if (v) {
          const match = v.match(YAML_PREFIX_RX)
          if (match) {
            try {
              parsed = yaml.load(match[1] === 'auto' ? v.slice(7) : v, { schema: yaml.CORE_SCHEMA })
              if (parsed && !~PRIMITIVE_TYPES.indexOf(parsed.constructor)) parsed = v
            } catch {}
          }
        }
        accum[~k.indexOf('-') ? k.replace(/-/g, '_') : k] = parsed
      }
Loading