asciidoctor / asciidoctor-gradle-plugin

A Gradle plugin that uses Asciidoctor via JRuby to process AsciiDoc source files within the project.
https://asciidoctor.github.io/asciidoctor-gradle-plugin/
Apache License 2.0
287 stars 122 forks source link

AsciidoctorJS require not working #681

Open jens-auer opened 1 year ago

jens-auer commented 1 year ago

I am trying to require a custom extension for AsciidoctorJS with the gradle plugin. I couldn't make this work, so I prepared a small example which uses the emoji extension from public npm repository to rule out problems with my local extension.

The extenstion is installed with npm install:

npm ls
jens@ /home/jens
├── @asciidoctor/core@2.2.6
├── @asciidoctor/docbook-converter@2.0.0
├── asciidoctor-emoji@0.4.2
├── asciidoctor-pdf@1.0.0-alpha.16
├── asciidoctor.js@1.5.9
├── asciidoctor@2.2.6
├── node-pty@0.10.1
├── opal-compiler@2.3.0

and I installed it manually in the npm folder generated when running gradle asciidoctor task

asciidoctorjs-test/build/.npm/test$ npm ls
test-asciidoctor@unspecified /home/jens/SWP-Updt/asciidoctorjs-test/build/.npm/test
├── asciidoctor-emoji@0.4.2
└── asciidoctor@2.0.2

When I now try to require the emoji extensions in my gradle build script

import org.asciidoctor.gradle.js.nodejs.AsciidoctorTask

plugins {
    id("org.asciidoctor.js.convert") version "3.3.2"
}

asciidoctorjs { 
  require '', 'asciidoctor-emoji', ''
}

asciidoctor {
  sourceDir  file('.')
  outputDir  file('build')
  sources {
    include 'test.adoc'
  }
}

because a) the documentation says "Additional NPM modules can be added via asciidoctorj.require."

and AsciidoctorJSExtension has

    /** Adds an additional NPM package that is required
     *
     * @param name Name of package
     * @param tag Tag of package
     */
    void require(final String name, final String tag) {
        this.additionalRequires.add(new NpmDependency(name, tag))
    }

    /** Adds an additional NPM package that is required
     *
     * @param scope Scope of package
     * @param name Name of package
     * @param tag Tag of package
     */
    void require(final String scope, final String name, final String tag) {
        this.additionalRequires.add(new NpmDependency(scope, name, tag))
    }

However, the emoji macro in my test.adoc

I emoji:heart[1x] Asciidoctor.js!

test

does not work.

I tried to understand how the mechanism works, but from what I can see in the code the extension should be added to additionalRequires

    void require(final String scope, final String name, final String tag) {
        this.additionalRequires.add(new NpmDependency(scope, name, tag))
    }

but the only references to the field are in

From these, only getAllAdditionalRequires has a return value that uses additionalRequires, but this functions seems to be not used as I could not find any reference. Even getRequires does not consider additionalRequires. Is this intended?

The runner gets passed

        new AsciidoctorJSRunner(
            nodejs.resolvableNodeExecutable.executable,
            project,
            asciidoctorjsExe,
            backend,
            asciidoctorjs.safeMode,
            lang.present ? getBaseDir(lang.get()) : getBaseDir(),
            lang.present ? getOutputDirFor(backend, lang.get()) : getOutputDirFor(backend),
            attributes,
            asciidoctorjs.requires,
            Optional.empty(),
            logDocuments
        )

I then tried to use requires as for AsciidoctorJ and added it to plugin and task configuration

import org.asciidoctor.gradle.js.nodejs.AsciidoctorTask

plugins {
    id("org.asciidoctor.js.convert") version "3.3.2"
}

asciidoctorjs { 
  require '', 'asciidoctor-emoji'
  requires ['asciidoctor-emoji']
  println requires
}

asciidoctor {
asciidoctorjs { 
  require '', 'asciidoctor-emoji'
  requires ['asciidoctor-emoji']
  println requires
}
  sourceDir  file('.')
  outputDir  file('build')
  sources {
    include 'test.adoc'
  }
}

However no result. I would be really grateful for some help or guidance. A RTFM with a working example would be very appreciated.

jens-auer commented 1 year ago

I think it is a bug. AsciidoctorJSRunner gets passed asciidoctorjs.requires, but the property is implemented as

    Set<String> getRequires() {
        List<String> reqs = [].toSet()

        final String docbook = moduleVersion(modules.docbook)
        if (docbook) {
            reqs.add(packageDescriptorFor(modules.docbook).toString())
        }

       reqs
    }

This returns a new set for every invocation, explaining why the set cannot be modified with add or addAll. It also totally ignores modules in additionalRequires added with require.

I think the implementation should be more like

    Set<String> getRequires() {
        List<String> reqs = getAllAdditionalRequires().collect { asModuleVersionString(it) }

        final String docbook = moduleVersion(modules.docbook)
        if (docbook) {
            reqs.add(packageDescriptorFor(modules.docbook).toString())
        }

        Set.copyOf( reqs )
    }

    private String asModuleVersionString(NpmDependency dep) {
        final String scope = it.scope
        final String name = it.packageName
        final String version = it.tag

        final String r = (scope != null && !scope.isEmpty()) ? ("@${scope}/${name}") : name

        if (version != null && !version.isEmpty()) {
            r + "@${version}"
        } else {
            r
        }
    }