Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
* xref:model5/asciidoctorj/revealjs.adoc[]
* xref:model5/asciidoctorj/diagram.adoc[]
* xref:model5/asciidoctorj/kroki.adoc[]
* xref:model5/asciidoctorj/groovydsl.adoc[]
* xref:model5/asciidoctorj/engine-options.adoc[]
* xref:model5/asciidoctorj/configurations.adoc[]
* xref:model5/asciidoctorj/adding-an-external-backend.adoc[]
* xref:model5/asciidoctorj/adding-an-external-extension.adoc[]
* xref:model5/asciidoctorj/engine-options.adoc[]

.Asciidoctor.js
* xref:model5/asciidoctorjs/html.adoc[]
Expand Down
27 changes: 27 additions & 0 deletions docs/modules/ROOT/pages/model5/asciidoctorj/configurations.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
= Dependency configurations

Configurations play a major role in managing the classpath that is seen by {asciidoctorj-name} engines.
Knowing what they are can help a build script author manage cases special to their environment.

.Types
* The toolchain itself has a configuration that manages the {asciidoctorj-name} and JRuby dependencies.
* Most output formatters and extensions that are registered on a toolchain also have their own configurations.
* GEMs are handled in their own configurations and are always directly associated with the toolchain, even if
the GEMs are from output formatters or extensions.
The reason is that GEMs are bundled into a GemJar and passed in that way on the classpath.

All the above configurations combine to form the final classpath that the {asciidoctorj-name} engine sees when executing.

The configuration for a toolchain is named `asciidoctorjEngine<TOOLCHAIN-NAME>`.
For a toolchain named `asciidoctorj`, the associated configuration is called `asciidoctorjEngineAsciidoctorj`.

The configuration for an output formatter associated with a toolchain is named `asciidoctorjOutputFormatter<TOOLCHAIN-NAME><FORMATTER-NAME>`.
For instance, the PDF output formatter which might be called `pdf` and associated with a toolchain called `asciidoctorj`, will have a configuration `asciidoctorjOutputFormatterAsciidoctorjPdf`.

The configuration for an extension associated with a toolchain is named `asciidoctorjExtension<TOOLCHAIN-NAME><EXTENSION-NAME>`.
For instance, the Kroki extension which might be called `kroki` and associated with a toolchain called `asciidoctorj`, will have a configuration `asciidoctorjExtensionAsciidoctorjKroki`.

The configurations for GEMs are named `asciidocGems<TOOLCHAIN-NAME>`.
For instance the configuration for the toolchain named `asciidoctorj` will be `asciidocGemsAsciidoctorj`.


48 changes: 48 additions & 0 deletions docs/modules/ROOT/pages/model5/asciidoctorj/groovydsl.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
= Adding extensions using Groovy

It is possible to write extensions inline using a https://github.com/asciidoctor/asciidoctorj-groovy-dsl[Groovy DSL].
Two approaches are supported: inline strings and files.

[tabs]
====
Groovy::
+
--
[source,groovy]
----
import org.asciidoctor.gradle.model5.jvm.extensions.AsciidoctorjGroovyDslExtension

asciidoc {
toolchains {
aciidoctorj {
asciidocExtensions {
groovydsl(AsciidoctorjGroovyDslExtension) {
fromString (''' // <.>
block(name: "BIG", contexts: [":paragraph"]) {
parent, reader, attributes ->
def upperLines = reader.readLines()
.collect {it.toUpperCase()}
.inject("") {a, b -> a + '\n' + b}

createBlock(parent, "paragraph", [upperLines], attributes, [:])
}
''')

fromFile('src/docs/asciidoc/myExtension.groovy') // <.>
}
}
}
}
}
----
<.> Add an inline extension.
Anything that can be lazy-evaluated as a string can be passed.
<.> An extension in a Groovy source file.
Anything that can be lazy-evaluated to a file can be passed.
--
====

NOTE: In versions prior to 4.0, there was a way of using closures to define extensions.
With advancements in Gradle, it has actually become problematic to serialize those closures over classloader boundaries.
The second problem is that it unnecessarily drags in a number of dependencies onto the build plugin classpath.
These are the two reasons why we no longer support the closure approach for inline extensions.
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ asciidoctorjDiagramPlantUml = "1.2025.3"
# tag::hacking-asciidoctorj-output-formatter[]
asciidoctorjEpub = "2.2.0"
# end::hacking-asciidoctorj-output-formatter[]
asciidoctorjGroovyDsl = "2.0.2"
asciidoctorjGroovyDsl = "3.0.0-beta.1"
asciidoctorjPdf = "2.3.19"
asciidoctorjRevealjs = "5.2.0"
asciidoctorjs = "3.0.0"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,11 @@ public interface AsciidoctorConversionSettings {
* @return Provider to templates.
*/
Provider<ConversionTemplate> getTemplates();

/**
* A collection of scripts, grouped by implementation language.
*
* @return Provider to the collection. Can be empty.
*/
Provider<Map<String,ScriptCollection>> getScriptCollections();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright 2013 - 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.asciidoctor.gradle.model5.core;

import org.gradle.api.provider.Provider;

import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
* A collection of scripts that can be used for extension.
*
* @author Schalk W. Cronjé
* @since 5.0
*/
public interface ScriptCollection {
/**
* A set of scripts.
*
* @return List of scripts.
*/
Provider<Set<String>> getScripts();

/**
* A set of files containing scripts.
*
* @return Collection of files locations.
*/
Provider<Set<File>> getScriptFiles();

/**
* The language of the script collection.
*
* @return The language used for the scripts.
*/
String getScriptType();
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@
*/
public interface AsciidoctorExtension extends Named, AsciidoctorRequires, HasAttributeProvider, HasDisplayType {
/**
* Additional items to add to the classpath when a conversion involving the output formatter is executed.
* Additional items to add to the classpath when a conversion involving the extension is executed.
*
* <p>
* The classpath is empty by default.
* </p>
*
* @return Classpath. Can be {@code null} to indicate that the formatter does not support additional classpath.
* @return Classpath. Can be {@code null} to indicate that the extension does not support additional classpath.
*/
FileCollection getClasspath();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Copyright 2013 - 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.asciidoctor.gradle.model5.core.extensions;

import org.asciidoctor.gradle.model5.core.ScriptCollection;
import org.gradle.api.provider.Provider;

import java.util.Map;

/**
* An interface for providing scripted extensions.
*
* @author Schalk W. Cronjé
* @since 5.0
*/
public interface ScriptedExtensions {
/**
* Clears all registered scripts in the extensions.
*/
void clearScripts();

/**
* Adds an extension from string content.
*
* @param ext Anything that can be lazy-evaluated to a string
*/
void fromString(Object ext);

/**
* Adds an extension from content in a file.
*
* @param ext Anything that can be lazy-evaluated to a file
*/
void fromFile(Object ext);

/**
* Returns a list of scripted extensions.
*
* @return Not {@code null}, but can be empty.
*/
ScriptCollection getScriptedExtensions();
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import org.asciidoctor.gradle.model5.core.AsciidoctorConversionSettings
import org.asciidoctor.gradle.model5.core.AsciidoctorNamedBackend
import org.asciidoctor.gradle.model5.core.ConversionTemplate
import org.asciidoctor.gradle.model5.core.DocType
import org.asciidoctor.gradle.model5.core.ScriptCollection
import org.gradle.api.file.Directory
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.MapProperty
Expand Down Expand Up @@ -51,6 +52,7 @@ class DefaultAsciidoctorConversionSettings implements AsciidoctorConversionSetti
final SetProperty<Pattern> fatalWarnings
final Property<Boolean> embedded
final Property<ConversionTemplate> templates
final MapProperty<String, ScriptCollection> scriptCollections

@Inject
DefaultAsciidoctorConversionSettings(ObjectFactory objectFactory) {
Expand All @@ -65,6 +67,7 @@ class DefaultAsciidoctorConversionSettings implements AsciidoctorConversionSetti
this.fatalWarnings = objectFactory.setProperty(Pattern)
this.embedded = objectFactory.property(Boolean).convention(false)
this.templates = objectFactory.property(ConversionTemplate)
this.scriptCollections = objectFactory.mapProperty(String, ScriptCollection)
}

void updateFrom(DefaultAsciidoctorConversionSettings other) {
Expand All @@ -79,5 +82,6 @@ class DefaultAsciidoctorConversionSettings implements AsciidoctorConversionSetti
fatalWarnings.set(other.fatalWarnings)
embedded.set(other.embedded)
templates.set(other.templates)
scriptCollections.set(other.scriptCollections)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2013 - 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.asciidoctor.gradle.model5.core.internal

import groovy.transform.CompileStatic
import org.asciidoctor.gradle.model5.core.ScriptCollection
import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.ysb33r.grolifant5.api.core.ConfigCacheSafeOperations

import javax.inject.Inject

/**
* Implementation of {@link ScriptCollection}.
*
* @author Schalk W. Cronjé
*
* @since 5.0
*/
@CompileStatic
class DefaultScriptCollection implements ScriptCollection {

final String scriptType
private final ConfigCacheSafeOperations ccso
private final SetProperty<String> scripts
private final SetProperty<File> scriptFiles

@Inject
DefaultScriptCollection(String language, Project project) {
this.ccso = ConfigCacheSafeOperations.from(project)
this.scripts = project.objects.setProperty(String)
this.scriptFiles = project.objects.setProperty(File)
this.scriptType = language
}

void clear() {
this.scripts.set([])
this.scriptFiles.set([])
}

void addScript(Object script) {
this.scripts.add(ccso.stringTools().provideString(script))
}

void addScriptFile(Object file) {
this.scriptFiles.add(ccso.fsOperations().provideFile(file))
}

void addScripts(Provider<? extends Iterable<? extends String>> more) {
this.scripts.addAll(more)
}

void addScriptFiles(Provider<? extends Iterable<? extends File>> more) {
this.scriptFiles.addAll(more)
}

@Override
Provider<Set<String>> getScripts() {
this.scripts
}

@Override
Provider<Set<File>> getScriptFiles() {
this.scriptFiles
}
}
Loading
Loading