add gradle-plugin-asciidoctor, update gradle to 7.3.2

main
Jörg Prante 2 years ago
parent 5b21e376cd
commit 6e1e4831c4

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

@ -0,0 +1,47 @@
plugins {
id 'java-gradle-plugin'
id 'com.gradle.plugin-publish' version '0.18.0'
}
apply plugin: 'java-gradle-plugin'
apply plugin: 'com.gradle.plugin-publish'
apply from: rootProject.file('gradle/compile/groovy.gradle')
dependencies {
api gradleApi()
implementation "org.asciidoctor:asciidoctorj:${project.property('asciidoctorj.version')}"
implementation "org.jruby:jruby:${project.property('jruby.version')}"
testImplementation "org.spockframework:spock-core:${project.property('spock.version')}"
testImplementation "org.jsoup:jsoup:${project.property('jsoup.version')}"
}
gradlePlugin {
plugins {
asciidoctorPlugin {
id = 'org.xbib.gradle.plugin.asciidoctor'
implementationClass = 'org.xbib.gradle.plugin.asciidoctor.AsciidoctorPlugin'
}
}
}
if (project.hasProperty('gradle.publish.key')) {
pluginBundle {
mavenCoordinates {
groupId = "org.xbib.gradle.plugin"
artifactId = "gradle-plugin-asciidoctor"
version = project.version
}
website = 'https://github.com/jprante/gradle-plugins'
vcsUrl = 'https://github.com/jprante/gradle-plugins'
plugins {
asciidoctorPlugin {
id = 'org.xbib.gradle.plugin.asciidoctor'
version = project.version
description = 'Asciidoctor plugin for building documentations'
displayName = 'Asciidoctor plugin for building documentations'
tags = ['asciidoctor']
}
}
}
}

@ -0,0 +1,32 @@
package org.xbib.gradle.plugin.asciidoctor
enum AsciidoctorBackend {
HTML('html'),
DOCBOOK('docbook'),
HTML5('html5'),
DOCBOOK45('docbook45'),
DOCBOOK5('docbook5'),
EPUB3('epub3'),
PDF('pdf'),
XHTML('xhtml'),
XHTML5('xhtml5'),
private final static Map<String, AsciidoctorBackend> ALL_BACKENDS
private final String id
static {
ALL_BACKENDS = values().collectEntries{ [it.id, it] }.asImmutable()
}
private AsciidoctorBackend(String id) {
this.id = id
}
String getId() {
id
}
static boolean isBuiltIn(String name) {
ALL_BACKENDS.containsKey(name)
}
}

@ -0,0 +1,16 @@
package org.xbib.gradle.plugin.asciidoctor
import org.gradle.api.Project
class AsciidoctorExtension {
String version = '2.5.3'
boolean addDefaultRepositories = true
final Project project
AsciidoctorExtension(Project project) {
this.project = project
}
}

@ -0,0 +1,50 @@
package org.xbib.gradle.plugin.asciidoctor
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvableDependencies
import org.gradle.api.artifacts.dsl.DependencyHandler
class AsciidoctorPlugin implements Plugin<Project> {
static final String ASCIIDOCTOR = 'asciidoctor'
static final String ASCIIDOCTORJ = 'asciidoctorj'
static final String ASCIIDOCTORJ_CORE_DEPENDENCY = 'org.asciidoctor:asciidoctorj:'
void apply(Project project) {
project.apply(plugin: 'base')
AsciidoctorExtension extension = project.extensions.create(ASCIIDOCTORJ, AsciidoctorExtension, project)
project.afterEvaluate {
if(project.extensions.asciidoctorj.addDefaultRepositories) {
project.repositories {
mavenCentral()
}
}
}
Configuration configuration = project.configurations.maybeCreate(ASCIIDOCTOR)
project.logger.info("[Asciidoctor] asciidoctorj: ${extension.version}")
configuration.incoming.beforeResolve(new Action<ResolvableDependencies>() {
@SuppressWarnings('UnusedMethodParameter')
void execute(ResolvableDependencies resolvableDependencies) {
DependencyHandler dependencyHandler = project.dependencies
def dependencies = configuration.dependencies
dependencies.add(dependencyHandler.create(ASCIIDOCTORJ_CORE_DEPENDENCY + extension.version))
}
})
project.task(ASCIIDOCTOR,
type: AsciidoctorTask,
group: 'Documentation',
description: 'Converts AsciiDoc files and copies the output files and related resources to the build directory.') {
classpath = configuration
}
}
}

@ -0,0 +1,8 @@
package org.xbib.gradle.plugin.asciidoctor
interface AsciidoctorProxy {
String convertFile(File filename, Map<String, Object> options)
void requireLibrary(String... requiredLibraries)
}

@ -0,0 +1,16 @@
package org.xbib.gradle.plugin.asciidoctor
class AsciidoctorProxyImpl implements AsciidoctorProxy {
def delegate
@Override
String convertFile(File filename, Map<String, Object> options) {
delegate.convertFile(filename, options)
}
@Override
void requireLibrary(String... requiredLibraries) {
delegate.requireLibrary(requiredLibraries)
}
}

@ -0,0 +1,863 @@
package org.xbib.gradle.plugin.asciidoctor
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.InvalidUserDataException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.file.CopySpec
import org.gradle.api.file.FileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.internal.file.copy.CopySpecInternal
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectories
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.SkipWhenEmpty
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.util.PatternSet
import org.gradle.internal.FileUtils
import org.gradle.util.CollectionUtils
import org.xbib.gradle.plugin.asciidoctor.groovydsl.AsciidoctorExtensions
import java.nio.file.Path
@SuppressWarnings(['MethodCount', 'Instanceof'])
class AsciidoctorTask extends DefaultTask {
public static final String ASCIIDOCTOR_FACTORY_CLASSNAME = 'org.asciidoctor.Asciidoctor$Factory'
private static final boolean IS_WINDOWS = System.getProperty('os.name').contains('Windows')
private static final String PATH_SEPARATOR = System.getProperty('path.separator')
private static final String DOUBLE_BACKLASH = '\\\\'
private static final String BACKLASH = '\\'
private static final String SAFE_MODE_CLASSNAME = 'org.asciidoctor.SafeMode'
private static final String DEFAULT_BACKEND = AsciidoctorBackend.HTML5.id
private boolean baseDirSetToNull
private Object outDir
private Object srcDir
private final List<Object> gemPaths = []
private Set<String> backends
private Set<String> requires
private Map opts = [:]
private Map attrs = [:]
private PatternSet sourceDocumentPattern
private CopySpec resourceCopy
private static ClassLoader cl
/**
* If set to true each backend will be output to a separate subfolder below {@code outputDir}
*/
@Input
boolean separateOutputDirs = true
@Optional
@InputDirectory
File baseDir
/**
* Logs documents as they are converted
*/
@Input
boolean logDocuments = false
/**
* Old way to set only one source document
*/
@Optional
@InputFile
File sourceDocumentName
/**
* Old way to define the backend to use
*/
@Optional
@Input
String backend
@Internal
AsciidoctorProxy asciidoctor
/**
* Stores the extensions defined in the configuration phase
* to register them in the execution phase.
*/
@Internal
List<Object> asciidoctorExtensions = []
@Internal
ResourceCopyProxy resourceCopyProxy
@Internal
Configuration classpath
AsciidoctorTask() {
srcDir = project.file('src/docs/asciidoc')
}
/**
* Returns all of the Asciidoctor options
*/
@Optional
@Input
Map getOptions() { this.opts }
/** Apply a new set of Asciidoctor options, clearing any options previously set.
*
* For backwards compatibility it is still possible to replace attributes via this call. However the
* use of {@link #setAttributes(java.util.Map)} and {@link #attributes(java.util.Map)} are the now
* correct way of working with attributes
*
* @param m Map with new options
*/
@SuppressWarnings('DuplicateStringLiteral')
void setOptions(Map m) {
if (!m) return // null check
if (m.containsKey('attributes')) {
logger.warn 'Attributes found in options. Existing attributes will be replaced due to assignment. ' +
'Please use \'attributes\' method instead as current behaviour will be removed in future'
attrs = coerceLegacyAttributeFormats(m.attributes)
m.remove('attributes')
}
this.opts = m
}
/** Appends a new set of Asciidoctor options.
*
* For backwards compatibility it is still possible to append attributes via this call. However the
* use of {@link #setAttributes(java.util.Map)} and {@link #attributes(java.util.Map)} are the now
* correct way of working with attributes
*
* @param m Map with new options
*/
@SuppressWarnings('DuplicateStringLiteral')
void options(Map m) {
if (!m) return // null check
if (m.containsKey('attributes')) {
logger.warn 'Attributes found in options. These will be added to existing attributes. ' +
'Please use \'attributes\' method instead as current behaviour will be removed in future'
attributes coerceLegacyAttributeFormats(m.attributes)
m.remove('attributes')
}
this.opts += m
}
/**
* Returns the current set of Asciidoctor attributes
*/
@Optional
@Input
Map getAttributes() { this.attrs }
/**
* Applies a new set of Asciidoctor attributes, clearing any previously set
*
* @param m New map of attributes
*/
void setAttributes(Map m) {
if (m) {
this.attrs = m
} else {
this.attrs.clear()
}
}
/**
* Appends a set of Asciidoctor attributes.
*
* @param o a Map, List or a literal (String) definition
*/
void attributes(Object... o) {
if (!o) {
this.attrs.clear()
return
}
for (input in o) {
this.attrs += coerceLegacyAttributeFormats(input)
}
}
/**
* Returns the set of Ruby modules to be included.
*/
@Optional
@Input
Set<String> getRequires() { this.requires }
/**
* Applies a new set of Ruby modules to be included, clearing any previous set.
*
* @param b One or more ruby modules to be included
*/
void setRequires(Object... b) {
this.requires?.clear()
requires(b)
}
/**
* Appends new set of Ruby modules to be included.
*
* @param b One or more ruby modules to be included
*/
@SuppressWarnings('ConfusingMethodName')
void requires(Object... b) {
if (this.requires == null) {
this.requires = []
}
if (!b) {
return
}
this.requires.addAll(CollectionUtils.stringize(b as List))
}
/**
* Returns the current set of Asciidoctor backends that will be used for document generation
*/
@Optional
@Input
Set<String> getBackends() { this.backends }
void setBackend(String b) {
if (!b) {
return
}
deprecated 'setBackend', 'backends', 'Using `backend` and `backends` together will result in `backend` being ignored.'
backend = b
}
/**
* Applies a new set of Asciidoctor backends that will be used for document generation clearing any
* previous backends
*
* @param b List of backends. Each item must be convertible to string.
*/
void setBackends(Object... b) {
this.backends?.clear()
backends(b)
}
/**
* Appends additional Asciidoctor backends that will be used for document generation.
*
* @param b List of backends. Each item must be convertible to string.
*/
@SuppressWarnings('ConfusingMethodName')
void backends(Object... b) {
if (this.backends == null) {
this.backends = []
}
if (!b) {
return
}
this.backends.addAll(CollectionUtils.stringize(b as List))
}
/** Defines extensions. The given parameters should
* either contain Asciidoctor Groovy DSL closures or files
* with content conforming to the Asciidoctor Groovy DSL.
*/
void extensions(Object... exts) {
if (!exts) return // null check
asciidoctorExtensions.addAll(exts as List)
}
/** Sets a new gemPath to be used
*
* @param f A path object can be be converted with {@code project.file}.
*/
@SuppressWarnings('ConfusingMethodName')
void gemPath(Object... f) {
if (!f) {
return
}
this.gemPaths.addAll(f as List)
}
/**
* Sets a new list of GEM paths to be used.
*
* @param f A {@code File} object pointing to list of installed GEMs
*/
void setGemPath(Object... f) {
this.gemPaths.clear()
if (!f) {
return
}
this.gemPaths.addAll(f as List)
}
/**
* Assigns a single string to a GEM path, scanning it for concatenated GEM Paths, separated by the platform
* separator. This utility is only for backwards compatibility
*
* @param s
*/
void setGemPath(Object path) {
this.gemPaths.clear()
if (path instanceof CharSequence) {
setGemPath(path.tokenize(PATH_SEPARATOR))
} else if (path instanceof List) {
this.gemPaths.addAll(path)
} else if (path instanceof File || path instanceof Path) {
this.gemPaths.add(path)
} else {
throw new IllegalArgumentException("unknown path class: " + path.getClass().getName())
}
}
/**
* Returns the list of paths to be used for {@code GEM_HOME}
*/
@Optional
@InputFiles
FileCollection getGemPath() {
project.files(this.gemPaths)
}
/**
* Returns the list of paths to be used for GEM installations in a format that is suitable for assignment to {@code GEM_HOME}
*
* Calling this will cause gemPath to be resolved immediately.
*/
String asGemPath() {
gemPath.files*.toString().join(PATH_SEPARATOR)
}
/**
* Sets the new Asciidoctor parent source directory.
*
* @param f An object convertible via {@code project.file}
*/
void sourceDir(Object f) {
this.srcDir = f
}
/**
* Sets the new Asciidoctor parent source directory.
*
* @param f A {@code File} object pointing to the parent source directory
*/
void setSourceDir(File f) {
this.srcDir = f
}
/**
* Returns the parent directory for Asciidoctor source. Default is {@code src/asciidoc}.
*/
@Optional
@InputDirectory
File getSourceDir() {
project.file(srcDir)
}
/**
* Sets the new Asciidoctor parent output directory.
*
* @param f An object convertible via {@code project.file}
*/
void outputDir(Object f) {
this.outDir = f
}
/**
* Sets the new Asciidoctor parent output directory.
*
* @param f A {@code File} object pointing to the parent output directory
*/
void setOutputDir(File f) {
this.outDir = f
}
/**
* Returns the current toplevel output directory
*/
@OutputDirectory
File getOutputDir() {
if (this.outDir == null) {
this.outDir = new File(project.buildDir, 'asciidoc')
}
project.file(this.outDir)
}
/**
* Returns the collection of source documents
*
* If sourceDocumentNames was not set or is empty, it will return all asciidoc files
* in {@code sourceDir}. Otherwise only the files provided earlier to sourceDocumentNames
* are returned if they are found below {@code sourceDir}
*/
@OutputDirectories
FileCollection getSourceDocumentNames() {
deprecated 'getSourceDocumentNames', 'getSourceFileTree'
sourceFileTree
}
/**
* Sets a single file to the main source file
*
* @param f A file that is relative to {@code sourceDir}
*/
void setSourceDocumentName(File f) {
deprecated 'setSourceDocumentName',
'setIncludes', 'File will be converted to a pattern.'
sources {
setIncludes([AsciidoctorUtils.getRelativePath(f.absoluteFile, sourceDir.absoluteFile)])
}
}
/**
* Replaces the current set of source documents with a new set
*
* @parm src List of source documents, which must be convertible using {@code project.files}
*/
@SuppressWarnings('DuplicateStringLiteral')
void setSourceDocumentNames(Object... src) {
deprecated 'setSourceDocumentNames',
'setIncludes',
'Files are converted to patterns. Some might not convert correctly. ' +
'FileCollections will not convert'
File base = sourceDir.absoluteFile
def patterns = CollectionUtils.stringize(src as List).collect { String it ->
def tmpFile = new File(it)
String relPath
if (tmpFile.isAbsolute()) {
relPath = AsciidoctorUtils.getRelativePath(tmpFile.absoluteFile, base)
} else {
relPath = it
}
logger.debug "setSourceDocumentNames - Found ${it}, converted to ${relPath}"
relPath
}
sources {
setIncludes(patterns)
}
}
void setBaseDir(File baseDir) {
this.baseDir = baseDir
baseDirSetToNull = baseDir == null
}
/**
* Returns a list of all output directories.
*/
@OutputDirectories
Set<File> getOutputDirectories() {
if (separateOutputDirs) {
backends.collect { new File(outputDir, it) } as Set
} else {
[outputDir] as Set
}
}
/**
* Returns a FileTree containing all of the source documents
*
* @return If {@code sources} was never called then all asciidoc source files below {@code sourceDir} will
* be included
*/
@InputFiles
@SkipWhenEmpty
FileTree getSourceFileTree() {
project.fileTree(sourceDir).
matching(this.sourceDocumentPattern ?: defaultSourceDocumentPattern)
}
/**
* Add patterns for source files or source files via a closure
*
* @param cfg PatternSet configuration closure
*/
void sources(Closure cfg) {
if (sourceDocumentPattern == null) {
sourceDocumentPattern = new PatternSet()
}
def configuration = cfg.clone()
configuration.delegate = sourceDocumentPattern
configuration()
}
/**
* Add to the CopySpec for extra files. The destination of these files will always have a parent directory
* of {@code outputDir} or {@code outputDir + backend}
*
* @param cfg CopySpec configuration closure
*/
void resources(Closure cfg) {
if (this.resourceCopy == null) {
this.resourceCopy = project.copySpec(cfg)
} else {
def configuration = cfg.clone()
configuration.delegate = this.resourceCopy
configuration()
}
}
/**
* The default PatternSet that will be used if {@code sources} was never called
*
* By default all *.adoc,*.ad,*.asc,*.asciidoc is included. Files beginning with underscore are excluded
*
*/
@Internal
PatternSet getDefaultSourceDocumentPattern() {
PatternSet ps = new PatternSet()
ps.include '**/*.adoc'
ps.include '**/*.ad'
ps.include '**/*.asc'
ps.include '**/*.asciidoc'
ps.exclude '**/_*'
}
/**
* The default CopySpec that will be used if {@code resources} was never called
*
* By default anything below {@code $sourceDir/images} will be included.
*
* @return A {@code CopySpec}, never null
*/
@Internal
CopySpec getDefaultResourceCopySpec() {
project.copySpec {
from(sourceDir) {
include 'images/**'
}
}
}
/**
* Gets the CopySpec for additional resources
* If {@code resources} was never called, it will return a default CopySpec otherwise it will return the
* one built up via successive calls to {@code resources}
*
* @return A {@code CopySpec}, never null
*/
@Internal
CopySpec getResourceCopySpec() {
this.resourceCopy ?: defaultResourceCopySpec
}
/**
* Gets the additional resources as a FileCollection.
* If {@code resources} was never called, it will return the file collections as per default CopySpec otherwise it
* will return the collections as built up via successive calls to {@code resources}
*
* @return A {@code FileCollection}, never null
*/
@InputFiles
@SkipWhenEmpty
@Optional
FileCollection getResourceFileCollection() {
(resourceCopySpec as CopySpecInternal).buildRootResolver().allSource
}
@TaskAction
void processAsciidocSources() {
if (sourceFileTree.files.size() == 0) {
logger.lifecycle 'Asciidoc source file tree is empty. Nothing will be processed.'
return
}
if (classpath == null) {
classpath = project.configurations.getByName(AsciidoctorPlugin.ASCIIDOCTOR)
}
setupClassLoader()
if (!asciidoctorExtensions?.empty) {
Class asciidoctorExtensionsDslRegistry = loadClass(AsciidoctorExtensions.class.getName())
asciidoctorExtensions.each { asciidoctorExtensionsDslRegistry.extensions(it) }
}
if (!asciidoctor) {
instantiateAsciidoctor()
}
if (resourceCopyProxy == null) {
resourceCopyProxy = new ResourceCopyProxyImpl(project)
}
if (requires) {
for (require in requires) {
asciidoctor.requireLibrary(require)
}
}
for (activeBackend in activeBackends()) {
if (!AsciidoctorBackend.isBuiltIn(activeBackend)) {
logger.lifecycle("Passing through unknown backend: $activeBackend")
}
processDocumentsAndResources(activeBackend)
}
}
@groovy.transform.PackageScope
File outputDirFor(final File source, final String basePath, final File outputDir, final String backend) {
String filePath = source.directory ? source.absolutePath : source.parentFile.absolutePath
String relativeFilePath = normalizePath(filePath) - normalizePath(basePath)
File baseOutputDir = outputBackendDir(outputDir, backend)
File destinationParentDir = new File(baseOutputDir, relativeFilePath)
if (!destinationParentDir.exists()) {
destinationParentDir.mkdirs()
}
destinationParentDir
}
private File outputBackendDir(final File outputDir, final String backend) {
separateOutputDirs ? new File(outputDir, FileUtils.toSafeFileName(backend)) : outputDir
}
private static String normalizePath(String path) {
if (IS_WINDOWS) {
path = path.replace(DOUBLE_BACKLASH, BACKLASH)
path = path.replace(BACKLASH, DOUBLE_BACKLASH)
}
path
}
@SuppressWarnings('CatchException')
private void instantiateAsciidoctor() {
if (gemPaths.size()) {
asciidoctor = new AsciidoctorProxyImpl(delegate: loadClass(ASCIIDOCTOR_FACTORY_CLASSNAME).create(asGemPath()))
} else {
try {
asciidoctor = new AsciidoctorProxyImpl(delegate: loadClass(ASCIIDOCTOR_FACTORY_CLASSNAME).create(null as String))
} catch (Exception e) {
asciidoctor = new AsciidoctorProxyImpl(delegate: loadClass(ASCIIDOCTOR_FACTORY_CLASSNAME).create())
}
}
}
private Set<String> activeBackends() {
if (this.backends) {
return this.backends
} else if (backend) {
return [backend]
}
[DEFAULT_BACKEND]
}
@SuppressWarnings('CatchException')
@SuppressWarnings('DuplicateStringLiteral')
private void processDocumentsAndResources(final String backend) {
try {
sourceFileTree.files.each { File file ->
if (file.name.startsWith('_')) {
throw new InvalidUserDataException('Source documents may not start with an underscore')
}
File destinationParentDir = owner.outputDirFor(file, sourceDir.absolutePath, outputDir, backend)
processSingleFile(backend, destinationParentDir, file)
}
resourceCopyProxy.copy(outputBackendDir(outputDir, backend), resourceCopySpec)
} catch (Exception e) {
throw new GradleException('Error running Asciidoctor', e)
}
}
protected void processSingleFile(String backend, File destinationParentDir, File file) {
if (logDocuments) {
logger.lifecycle("Converting $file")
}
asciidoctor.convertFile(file, mergedOptions(file,
[
project : project,
options : options,
attributes: attrs,
baseDir : !baseDir && !baseDirSetToNull ? file.parentFile : baseDir,
projectDir: project.projectDir,
rootDir : project.rootDir,
outputDir : destinationParentDir,
backend : backend]))
}
@SuppressWarnings('AbcMetric')
private static Map<String, Object> mergedOptions(File file, Map params) {
Map<String, Object> mergedOptions = [:]
mergedOptions.putAll(params.options)
mergedOptions.backend = params.backend
mergedOptions.in_place = false
mergedOptions.safe = resolveSafeModeLevel(mergedOptions.safe, 0i)
mergedOptions.to_dir = params.outputDir
if (params.baseDir) {
mergedOptions.base_dir = params.baseDir
}
if (mergedOptions.to_file) {
File toFile = new File(mergedOptions.to_file)
mergedOptions.to_file = new File(mergedOptions.remove('to_dir'), toFile.name)
}
Map attributes = [:]
processMapAttributes(attributes, params.attributes)
// Note: Directories passed as relative to work around issue #83
// Asciidoctor cannot handle absolute paths in Windows properly
attributes.projectdir = AsciidoctorUtils.getRelativePath(params.projectDir, file.parentFile)
attributes.rootdir = AsciidoctorUtils.getRelativePath(params.rootDir, file.parentFile)
// resolve these properties here as we want to catch both Map and String definitions parsed above
attributes.'project-name' = attributes.'project-name' ?: params.project.name
attributes.'project-group' = attributes.'project-group' ?: (params.project.group ?: '')
attributes.'project-version' = attributes.'project-version' ?: (params.project.version ?: '')
mergedOptions.attributes = attributes
// Issue #14 force GString -> String as jruby will fail
// to find an exact match when invoking Asciidoctor
for (entry in mergedOptions) {
if (entry.value instanceof CharSequence) {
mergedOptions[entry.key] = entry.value.toString()
} else if (entry.value instanceof List) {
mergedOptions[entry.key] = stringifyList(entry.value)
} else if (entry.value instanceof Map) {
mergedOptions[entry.key] = stringifyMap(entry.value)
} else if (entry.value instanceof File) {
mergedOptions[entry.key] = entry.value.absolutePath
}
}
mergedOptions
}
private static List stringifyList(List input) {
input.collect { element ->
if (element instanceof CharSequence) {
element.toString()
} else if (element instanceof List) {
stringifyList(element)
} else if (element instanceof Map) {
stringifyMap(element)
} else if(element instanceof File) {
element.absolutePath
} else {
element
}
}
}
private static Map stringifyMap(Map input) {
Map output = [:]
input.each { key, value ->
if (value instanceof CharSequence) {
output[key] = value.toString()
} else if (value instanceof List) {
output[key] = stringifyList(value)
} else if (value instanceof Map) {
output[key] = stringifyMap(value)
} else if(value instanceof File) {
output[key] = value.absolutePath
} else {
output[key] = value
}
}
output
}
protected static void processMapAttributes(Map attributes, Map rawAttributes) {
// copy all attributes in order to prevent changes down
// the Asciidoctor chain that could cause serialization
// problems with Gradle -> all inputs/outputs get serialized
// for caching purposes; Ruby objects are non-serializable
// Issue #14 force GString -> String as jruby will fail
// to find an exact match when invoking Asciidoctor
for (entry in rawAttributes) {
if (entry.value == null || entry.value instanceof Boolean) {
attributes[entry.key] = entry.value
} else {
attributes[entry.key] = entry.value.toString()
}
}
}
protected static void processCollectionAttributes(Map attributes, rawAttributes) {
for (attr in rawAttributes) {
if (attr instanceof CharSequence) {
def (k, v) = attr.toString().split('=', 2) as List
attributes.put(k, v != null ? v : '')
} else {
// QUESTION should we just coerce it to a String?
throw new InvalidUserDataException("Unsupported type for attribute ${attr}: ${attr.getClass()}")
}
}
}
@SuppressWarnings('DuplicateStringLiteral')
@SuppressWarnings('DuplicateNumberLiteral')
private static Map coerceLegacyAttributeFormats(Object attributes) {
Map transformedMap = [:]
switch (attributes) {
case Map:
transformedMap = attributes
break
case CharSequence:
attributes.replaceAll('([^\\\\]) ', '$1\0').replaceAll('\\\\ ', ' ').split('\0').collect {
def split = it.split('=')
if (split.size() < 2) {
throw new InvalidUserDataException("Unsupported format for attributes: ${attributes}")
}
transformedMap[split[0]] = split.drop(1).join('=')
}
break
case Collection:
processCollectionAttributes(transformedMap, attributes)
break
default:
if (attributes.class.isArray()) {
processCollectionAttributes(transformedMap, attributes)
} else {
throw new InvalidUserDataException("Unsupported type for attributes: ${attributes.class}")
}
}
transformedMap
}
private static int resolveSafeModeLevel(Object safe, int defaultLevel) {
if (safe == null) {
defaultLevel
} else if (safe.class.name == SAFE_MODE_CLASSNAME) {
safe.level
} else if (safe instanceof CharSequence) {
try {
Enum.valueOf(loadClass(SAFE_MODE_CLASSNAME), safe.toString().toUpperCase()).level
} catch (IllegalArgumentException e) {
defaultLevel
}
} else {
safe.intValue()
}
}
private static Class loadClass(String className) {
cl.loadClass(className)
}
@SuppressWarnings('AssignmentToStaticFieldFromInstanceMethod')
private void setupClassLoader() {
if (classpath?.files) {
def urls = classpath.files.collect { it.toURI().toURL() }
cl = new URLClassLoader(urls as URL[], Thread.currentThread().contextClassLoader)
Thread.currentThread().contextClassLoader = cl
} else {
cl = Thread.currentThread().contextClassLoader
}
}
private void deprecated(final String method, final String alternative, final String msg = '') {
logger.lifecycle "Asciidoctor: ${method} is deprecated and will be removed in a future version. " +
"Use ${alternative} instead. ${msg}"
}
}

@ -0,0 +1,33 @@
package org.xbib.gradle.plugin.asciidoctor
import java.util.regex.Pattern
class AsciidoctorUtils {
static String getRelativePath(File target, File base) throws IOException {
String[] baseComponents = base.canonicalPath.split(Pattern.quote(File.separator))
String[] targetComponents = target.canonicalPath.split(Pattern.quote(File.separator))
int index = 0
for (; index < targetComponents.length && index < baseComponents.length; ++index) {
if (!targetComponents[index].equals(baseComponents[index])) {
break
}
}
StringBuilder result = new StringBuilder()
if (index != baseComponents.length) {
for (int i = index; i < baseComponents.length; ++i) {
if (i != index) {
result.append(File.separator)
}
result.append('..')
}
}
for (int i = index; i < targetComponents.length; ++i) {
if (i != index) {
result.append(File.separator)
}
result.append(targetComponents[i])
}
result.toString()
}
}

@ -0,0 +1,10 @@
package org.xbib.gradle.plugin.asciidoctor
import org.gradle.api.file.CopySpec
import org.gradle.api.tasks.WorkResult
interface ResourceCopyProxy {
WorkResult copy(File outputDir, CopySpec spec)
}

@ -0,0 +1,20 @@
package org.xbib.gradle.plugin.asciidoctor
import org.gradle.api.Project
import org.gradle.api.file.CopySpec
import org.gradle.api.tasks.WorkResult
class ResourceCopyProxyImpl implements ResourceCopyProxy {
Project project
ResourceCopyProxyImpl(Project p) { project = p }
@Override
WorkResult copy(File outputDir, CopySpec spec) {
project.copy {
into outputDir
with spec
}
}
}

@ -0,0 +1,19 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl
class AsciidoctorExtensionException extends Exception {
AsciidoctorExtensionException() {
}
AsciidoctorExtensionException(String message) {
super(message)
}
AsciidoctorExtensionException(String message, Throwable cause) {
super(message, cause)
}
AsciidoctorExtensionException(Throwable cause) {
super(cause)
}
}

@ -0,0 +1,87 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl
import org.asciidoctor.Asciidoctor
import org.asciidoctor.extension.BlockMacroProcessor
import org.asciidoctor.extension.BlockProcessor
import org.asciidoctor.extension.DocinfoProcessor
import org.asciidoctor.extension.IncludeProcessor
import org.asciidoctor.extension.InlineMacroProcessor
import org.asciidoctor.extension.Postprocessor
import org.asciidoctor.extension.Preprocessor
import org.asciidoctor.extension.Treeprocessor
import org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions.DelegatingBlockMacroProcessor
import org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions.DelegatingBlockProcessor
import org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions.DelegatingDocinfoProcessor
import org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions.DelegatingIncludeProcessor
import org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions.DelegatingPostprocessor
import org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions.DelegatingPreprocessor
import org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions.DelegatingInlineMacroProcessor
import org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions.DelegatingTreeprocessor
class AsciidoctorExtensionHandler {
static final String OPTION_NAME = 'name'
static final String OPTION_FILTER = 'filter'
static final String OPTION_CONTEXTS = 'contexts'
private final Asciidoctor asciidoctor
AsciidoctorExtensionHandler(Asciidoctor asciidoctor) {
this.asciidoctor = asciidoctor
}
void block(String blockName, @DelegatesTo(BlockProcessor) Closure cl) {
block([(OPTION_NAME): blockName], cl)
}
void block(Map options=[:], @DelegatesTo(BlockProcessor) Closure cl) {
if (!options.containsKey(OPTION_NAME)) {
throw new IllegalArgumentException('Block must define a name!')
}
if (!options.containsKey(OPTION_CONTEXTS)) {
//TODO: What are sensible defaults?
options[OPTION_CONTEXTS] = [':open', ':paragraph']
}
asciidoctor.javaExtensionRegistry().block(new DelegatingBlockProcessor(options, cl))
}
void block_macro(Map options, @DelegatesTo(BlockMacroProcessor) Closure cl) {
asciidoctor.javaExtensionRegistry().blockMacro(new DelegatingBlockMacroProcessor(options[OPTION_NAME] as String, options, cl))
}
void block_macro(String name, @DelegatesTo(BlockMacroProcessor) Closure cl) {
block_macro([(OPTION_NAME): name], cl)
}
void postprocessor(Map options=[:], @DelegatesTo(Postprocessor) Closure cl) {
asciidoctor.javaExtensionRegistry().postprocessor(new DelegatingPostprocessor(options, cl))
}
void preprocessor(Map options=[:], @DelegatesTo(Preprocessor) Closure cl) {
asciidoctor.javaExtensionRegistry().preprocessor(new DelegatingPreprocessor(options, cl))
}
void include_processor(Map options=[:], @DelegatesTo(IncludeProcessor) Closure cl) {
Closure filter = options[OPTION_FILTER] as Closure
Map optionsWithoutFilter = options - options.subMap([OPTION_FILTER])
asciidoctor.javaExtensionRegistry().includeProcessor(new DelegatingIncludeProcessor(optionsWithoutFilter, filter, cl))
}
void inline_macro(Map options, @DelegatesTo(InlineMacroProcessor) Closure cl) {
asciidoctor.javaExtensionRegistry().inlineMacro(new DelegatingInlineMacroProcessor(options[OPTION_NAME] as String, options, cl))
}
void inline_macro(String macroName, @DelegatesTo(InlineMacroProcessor) Closure cl) {
inline_macro([(OPTION_NAME): macroName], cl)
}
void treeprocessor(Map options=[:], @DelegatesTo(Treeprocessor) Closure cl) {
asciidoctor.javaExtensionRegistry().treeprocessor(new DelegatingTreeprocessor(options, cl))
}
void docinfo_processor(Map options=[:], @DelegatesTo(DocinfoProcessor) Closure cl) {
asciidoctor.javaExtensionRegistry().docinfoProcessor(new DelegatingDocinfoProcessor(options, cl))
}
}

@ -0,0 +1,178 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl
import groovy.transform.CompileStatic
import org.asciidoctor.Asciidoctor
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer
import java.nio.file.Files
import java.nio.file.Path
/**
* An instance of this class holds all extension closure and scripts.
* It evaluates the blocks and scripts and forwards the extracted extensions
* to the GroovyExtensionRegistry which is service implementation
* of org.asciidoctor.extension.spi.wExtensionRegistry
*/
@CompileStatic
class AsciidoctorExtensions {
private final List<Object> registeredExtensions = []
private static final AsciidoctorExtensions INSTANCE = new AsciidoctorExtensions()
/** Add an extension from a closure
*
* @param cl Closure containing an extension
*/
void addExtension(@DelegatesTo(AsciidoctorExtensionHandler) Closure cl) {
registeredExtensions.add(cl)
}
/** Add an extension via a string.
*
* @param groovyScript String containing extension.
*/
void addExtension(final String groovyScript) {
registeredExtensions.add(groovyScript)
}
/** Add an extension via a file.
*
* @param groovyScript File containing extension
*/
void addExtension(final File groovyScript) {
registeredExtensions.add(groovyScript)
}
/** Add an extension via a path instance
*
* @param groovyScript Path pointing to an extension
*/
void addExtension(final Path groovyScript) {
registeredExtensions.add(groovyScript)
}
/** Remove all extensions.
*
*/
void clearExtensions() {
registeredExtensions.clear()
}
/** Register all extension with an instance of Asciidoctor.
*
* @param asciidoctor Asciidoctor instance awaiting extensions.
* @throw AsciidoctorExtensionException
*/
@SuppressWarnings('UnnecessarySetter')
void registerExtensionsWith(Asciidoctor asciidoctor) {
AsciidoctorExtensionHandler extensionHandler = new AsciidoctorExtensionHandler(asciidoctor)
for (def it : registeredExtensions) {
switch (it) {
case Closure:
try {
((Closure) it).delegate = extensionHandler
((Closure) it).call()
} catch (e) {
throw new AsciidoctorExtensionException("Error registering extension from class in ${it.class.name}", e)
}
break
case String:
GroovyShell shell = makeGroovyShell()
DelegatingScript script = (DelegatingScript) shell.parse((String) it)
script.setDelegate(extensionHandler)
try {
script.run()
} catch (e) {
registeredExtensions.clear()
throw new AsciidoctorExtensionException('Error registering extension from string', e)
}
break
case File:
File file = (File) it
GroovyShell shell = makeGroovyShell()
file.withReader { reader ->
DelegatingScript script = (DelegatingScript) shell.parse(reader, file.name)
script.setDelegate(extensionHandler)
try {
script.run()
} catch (e) {
throw new AsciidoctorExtensionException("Error registering extension from file ${file.name}", e)
}
}
break
case Path:
Path path = (Path) it
GroovyShell shell = makeGroovyShell()
Files.newBufferedReader(path).withReader { reader ->
DelegatingScript script = (DelegatingScript) shell.parse(reader, path.toString())
script.setDelegate(extensionHandler)
try {
script.run()
} catch (e) {
throw new AsciidoctorExtensionException("Error registering extension from file ${path}", e)
}
}
break
}
}
}
/** Adds an extension to the AsciidoctorExtension singleton instance.
*
* @param cl Closure containing an instance
*/
static void extensions(@DelegatesTo(AsciidoctorExtensionHandler) Closure cl) {
INSTANCE.addExtension(cl)
}
/** Adds an extension to the AsciidoctorExtension singleton instance.
*
* @param s String containing an instance
*/
static void extensions(String s) {
INSTANCE.addExtension(s)
}
/** Adds an extension to the AsciidoctorExtension singleton instance.
*
* @param f File containing an instance
*/
static void extensions(File f) {
INSTANCE.addExtension(f)
}
/** Attempt to register all exteniosn with ASciidoctor instance.
*
* This method has the side-effect of removing all extensions as well.
*
* @param asciidoctor
*/
static void registerTo(Asciidoctor asciidoctor) {
try {
INSTANCE.registerExtensionsWith(asciidoctor)
} finally {
INSTANCE.clearExtensions()
}
}
private static GroovyShell makeGroovyShell() {
def config = new CompilerConfiguration()
config.scriptBaseClass = DelegatingScript.name
ImportCustomizer importCustomizer = new ImportCustomizer()
importCustomizer.addStarImports(
'org.asciidoctor',
'org.asciidoctor.ast',
'org.asciidoctor.extension'
)
config.addCompilationCustomizers(
importCustomizer
)
new GroovyShell(new Binding(), config)
}
}

@ -0,0 +1,17 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl
import org.asciidoctor.Asciidoctor
import org.asciidoctor.jruby.extension.spi.ExtensionRegistry
/**
* The service implementation for org.asciidoctor.extension.spi.ExtensionRegistry.
* It simply delegates the register() call to {@link AsciidoctorExtensions}
* that owns all configured extensions and registers it on the Asciidoctor instance.
*/
class GroovyExtensionRegistry implements ExtensionRegistry {
@Override
void register(Asciidoctor asciidoctor) {
AsciidoctorExtensions.registerTo(asciidoctor)
}
}

@ -0,0 +1,20 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions
import org.asciidoctor.ast.StructuralNode
import org.asciidoctor.extension.BlockMacroProcessor
class DelegatingBlockMacroProcessor extends BlockMacroProcessor {
private final Closure cl
DelegatingBlockMacroProcessor(String name, Map options, @DelegatesTo(BlockMacroProcessor) Closure cl) {
super(name, options)
this.cl = cl
cl.delegate = this
}
@Override
Object process(StructuralNode parent, String target, Map<String, Object> attributes) {
cl.call(parent, target, attributes)
}
}

@ -0,0 +1,22 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions
import org.asciidoctor.ast.StructuralNode
import org.asciidoctor.extension.BlockProcessor
import org.asciidoctor.extension.Reader
import org.xbib.gradle.plugin.asciidoctor.groovydsl.AsciidoctorExtensionHandler
class DelegatingBlockProcessor extends BlockProcessor {
private final Closure cl
DelegatingBlockProcessor(Map<String, Object> attributes, @DelegatesTo(BlockProcessor) Closure cl) {
super(attributes[AsciidoctorExtensionHandler.OPTION_NAME] as String, attributes)
this.cl = cl
cl.delegate = this
}
@Override
Object process(StructuralNode parent, Reader reader, Map<String, Object> attributes) {
cl.call(parent, reader, attributes)
}
}

@ -0,0 +1,20 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions
import org.asciidoctor.ast.Document
import org.asciidoctor.extension.DocinfoProcessor
class DelegatingDocinfoProcessor extends DocinfoProcessor {
private final Closure cl
DelegatingDocinfoProcessor(Map options, @DelegatesTo(DocinfoProcessor) Closure cl) {
super(options)
this.cl = cl
cl.delegate = this
}
@Override
String process(Document document) {
cl.call(document)
}
}

@ -0,0 +1,31 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions
import org.asciidoctor.ast.Document
import org.asciidoctor.extension.IncludeProcessor
import org.asciidoctor.extension.PreprocessorReader
class DelegatingIncludeProcessor extends IncludeProcessor {
private final Closure filter
private final Closure cl
DelegatingIncludeProcessor(Map options, Closure filter, @DelegatesTo(IncludeProcessor) Closure cl) {
super(options)
this.filter = filter
this.cl = cl
filter.delegate = this
cl.delegate = this
}
@Override
boolean handles(String target) {
filter.call(target)
}
@Override
void process(Document document, PreprocessorReader reader, String target, Map<String, Object> attributes) {
cl.call(document, reader, target, attributes)
}
}

@ -0,0 +1,20 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions
import org.asciidoctor.ast.ContentNode
import org.asciidoctor.extension.InlineMacroProcessor
class DelegatingInlineMacroProcessor extends InlineMacroProcessor {
private final Closure cl
DelegatingInlineMacroProcessor(String name, Map options, @DelegatesTo(InlineMacroProcessor) Closure cl) {
super(name, options)
this.cl = cl
cl.delegate = this
}
@Override
Object process(ContentNode parent, String target, Map<String, Object> attributes) {
cl.call(parent, target, attributes)
}
}

@ -0,0 +1,20 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions
import org.asciidoctor.ast.Document
import org.asciidoctor.extension.Postprocessor
class DelegatingPostprocessor extends Postprocessor {
private final Closure cl
DelegatingPostprocessor(Map options, @DelegatesTo(Postprocessor) Closure cl) {
super(options)
this.cl = cl
cl.delegate = this
}
@Override
String process(Document document, String output) {
cl.call(document, output)
}
}

@ -0,0 +1,21 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions
import org.asciidoctor.ast.Document
import org.asciidoctor.extension.Preprocessor
import org.asciidoctor.extension.PreprocessorReader
class DelegatingPreprocessor extends Preprocessor {
private final Closure cl
DelegatingPreprocessor(Map options, @DelegatesTo(Preprocessor) Closure cl) {
super(options)
this.cl = cl
cl.delegate = this
}
@Override
void process(Document document, PreprocessorReader reader) {
cl.call(document, reader)
}
}

@ -0,0 +1,28 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl.extensions
import org.asciidoctor.ast.Document
import org.asciidoctor.extension.Treeprocessor
class DelegatingTreeprocessor extends Treeprocessor {
private final Closure cl
DelegatingTreeprocessor(Map options, @DelegatesTo(Treeprocessor) Closure cl) {
super(options)
this.cl = cl
cl.delegate = this
}
@Override
Document process(Document document) {
def ret = cl.call(document)
if (!(ret in Document)) {
// Assume that the closure does something as last
// statement that was not intended to be the return value
// -> Return null
null
} else {
ret
}
}
}

@ -0,0 +1,64 @@
package org.xbib.gradle.plugin.asciidoctor
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.DependencyResolutionListener
import org.gradle.api.artifacts.ResolvableDependencies
import org.gradle.testfixtures.ProjectBuilder
import spock.lang.Ignore
import spock.lang.Specification
class AsciidoctorPluginSpec extends Specification {
private static final String ASCIIDOCTOR = 'asciidoctor'
Project project
def setup() {
project = ProjectBuilder.builder().build()
}
@SuppressWarnings('MethodName')
def "Applies plugin and checks default setup"() {
expect:
project.tasks.findByName(ASCIIDOCTOR) == null
when:
project.apply plugin: AsciidoctorPlugin
then:
Task asciidoctorTask = project.tasks.findByName(ASCIIDOCTOR)
asciidoctorTask != null
asciidoctorTask.group == 'Documentation'
asciidoctorTask.sourceDir == project.file('src/docs/asciidoc')
asciidoctorTask.outputDir == new File(project.buildDir, 'asciidoc')
project.tasks.findByName('clean') != null
}
@Ignore("Method 'getDependencyResolutionBroadcast' is unknown")
def "testPluginWithAlternativeAsciidoctorVersion"() {
expect:
project.tasks.findByName(ASCIIDOCTOR) == null
when:
project.apply plugin: AsciidoctorPlugin
def expectedVersion = 'my.expected.version-SNAPSHOT'
project.asciidoctorj.version = expectedVersion
def expectedDslVersion = 'dsl.' + expectedVersion
project.asciidoctorj.groovyDslVersion = expectedDslVersion
def config = project.project.configurations.getByName('asciidoctor')
def dependencies = config.dependencies
assert dependencies.isEmpty();
// mock-trigger beforeResolve() to avoid 'real' resolution of dependencies
DependencyResolutionListener broadcast = config.getDependencyResolutionBroadcast()
ResolvableDependencies incoming = config.getIncoming()
broadcast.beforeResolve(incoming)
def dependencyHandler = project.getDependencies();
then:
assert dependencies.contains(dependencyHandler.create(AsciidoctorPlugin.ASCIIDOCTORJ_CORE_DEPENDENCY + expectedVersion))
}
}

@ -0,0 +1,190 @@
package org.xbib.gradle.plugin.asciidoctor
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.testfixtures.ProjectBuilder
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import spock.lang.Specification
class AsciidoctorTaskInlineExtensionsSpec extends Specification {
private static final String ASCIIDOCTOR = 'asciidoctor'
private static final String ASCIIDOC_RESOURCES_DIR = 'build/resources/test/src/asciidocextensions'
private static final String ASCIIDOC_BUILD_DIR = 'build/asciidocextensions'
private static final String ASCIIDOC_MACRO_EXTENSION_SCRIPT = 'blockMacro.groovy'
private static final String ASCIIDOC_INLINE_EXTENSIONS_FILE = 'inlineextensions.asciidoc'
private static final String ASCIIDOC_INLINE_EXTENSIONS_RESULT_FILE = 'inlineextensions.html'
Project project
File testRootDir
File srcDir
File outDir
def setup() {
project = ProjectBuilder.builder().withName('test').build()
project.configurations.create(ASCIIDOCTOR)
testRootDir = new File('.')
srcDir = new File(testRootDir, ASCIIDOC_RESOURCES_DIR).absoluteFile
outDir = new File(project.projectDir, ASCIIDOC_BUILD_DIR)
}
def "Should apply inline BlockProcessor"() {
given:
Task task = project.tasks.create(name: ASCIIDOCTOR, type: AsciidoctorTask) {
sourceDir = srcDir
sourceDocumentNames = [ASCIIDOC_INLINE_EXTENSIONS_FILE]
outputDir = outDir
extensions {
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, [:])
}
block("small") {
parent, reader, attributes ->
def lowerLines = reader.readLines()
.collect {it.toLowerCase()}
.inject("") {a, b -> a + '\n' + b}
createBlock(parent, "paragraph", [lowerLines], attributes, [:])
}
}
}
File resultFile = new File(outDir, 'html5' + File.separator + ASCIIDOC_INLINE_EXTENSIONS_RESULT_FILE)
when:
task.processAsciidocSources()
then:
resultFile.exists()
resultFile.getText().contains("WRITE THIS IN UPPERCASE")
resultFile.getText().contains("and write this in lowercase")
}
def "Should apply BlockProcessor from file"() {
given:
print project.files('src/test/resources/src/asciidocextensions/blockMacro.groovy').each {println ">>> $it"}
Task task = project.tasks.create(name: ASCIIDOCTOR, type: AsciidoctorTask) {
sourceDir = srcDir
sourceDocumentNames = [ASCIIDOC_INLINE_EXTENSIONS_FILE]
outputDir = outDir
extensions new File(sourceDir, ASCIIDOC_MACRO_EXTENSION_SCRIPT)
}
File resultFile = new File(outDir, 'html5' + File.separator + ASCIIDOC_INLINE_EXTENSIONS_RESULT_FILE)
when:
task.processAsciidocSources()
then:
resultFile.exists()
resultFile.getText().contains("WRITE THIS IN UPPERCASE")
resultFile.getText().contains("and write this in lowercase")
}
def "Should apply inline Postprocessor"() {
given:
String copyright = "Copyright Acme, Inc." + System.currentTimeMillis()
Task task = project.tasks.create(name: ASCIIDOCTOR, type: AsciidoctorTask) {
sourceDir = srcDir
sourceDocumentNames = [ASCIIDOC_INLINE_EXTENSIONS_FILE]
outputDir = outDir
extensions {
postprocessor {
document, String output ->
if(document.basebackend("html")) {
Document doc = Jsoup.parse(output, "UTF-8")
Element contentElement = doc.getElementById("footer-text")
contentElement.append(copyright)
doc.html()
} else {
throw new IllegalArgumentException("Expected html!")
}
}
}
}
File resultFile = new File(outDir, 'html5' + File.separator + ASCIIDOC_INLINE_EXTENSIONS_RESULT_FILE)
when:
task.processAsciidocSources()
then:
resultFile.exists()
resultFile.getText().contains(copyright)
resultFile.getText().contains("Inline Extension Test document")
}
def "Should fail if inline Postprocessor fails"() {
given:
Task task = project.tasks.create(name: ASCIIDOCTOR, type: AsciidoctorTask) {
sourceDir = srcDir
sourceDocumentNames = [ASCIIDOC_INLINE_EXTENSIONS_FILE]
outputDir = outDir
extensions {
postprocessor {
document, output ->
if (output.contains("blacklisted")) {
throw new IllegalArgumentException("Document contains a blacklisted word")
}
}
}
}
when:
task.processAsciidocSources()
then:
thrown(GradleException)
}
def "Should apply inline Preprocessor"() {
given:
Task task = project.tasks.create(name: ASCIIDOCTOR, type: AsciidoctorTask) {
sourceDir = srcDir
sourceDocumentNames = [ASCIIDOC_INLINE_EXTENSIONS_FILE]
outputDir = outDir
extensions {
preprocessor {
document, reader ->
reader.advance()
reader
}
}
}
File resultFile = new File(outDir, 'html5' + File.separator + ASCIIDOC_INLINE_EXTENSIONS_RESULT_FILE)
when:
task.processAsciidocSources()
then:
resultFile.exists()
!resultFile.getText().contains("Inline Extension Test document")
}
def "Should apply inline Includeprocessor"() {
given:
String content = "The content of the URL " + System.currentTimeMillis()
Task task = project.tasks.create(name: ASCIIDOCTOR, type: AsciidoctorTask) {
sourceDir = srcDir
sourceDocumentNames = [ASCIIDOC_INLINE_EXTENSIONS_FILE]
outputDir = outDir
extensions {
include_processor (filter: {it.startsWith('http')}) {
document, reader, target, attributes ->
reader.push_include(content, target, target, 1, attributes);
}
}
}
File resultFile = new File(outDir, 'html5' + File.separator + ASCIIDOC_INLINE_EXTENSIONS_RESULT_FILE)
when:
task.processAsciidocSources()
then:
resultFile.exists()
resultFile.getText().contains(content)
}
}

@ -0,0 +1,27 @@
package org.xbib.gradle.plugin.asciidoctor
import spock.lang.Specification
class AsciidoctorUtilsSpec extends Specification {
static s = File.separator
def 'finds relative paths'(String target, String base, String relative) {
expect:
AsciidoctorUtils.getRelativePath(new File(target), new File(base)) == relative
where:
target | base | relative
'src/test/groovy' | 'src' | "test${s}groovy"
'src/test/groovy/' | 'src/' | "test${s}groovy"
'src/test/groovy' | 'src/' | "test${s}groovy"
'src/test/groovy/' | 'src' | "test${s}groovy"
'src' | 'src/test/groovy' | "..${s}.."
'src/' | 'src/test/groovy/' | "..${s}.."
'src' | 'src/test/groovy/' | "..${s}.."
'src/' | 'src/test/groovy' | "..${s}.."
'src/test' | 'src/test' | ''
'src/test/' | 'src/test/' | ''
'src/test' | 'src/test/' | ''
'src/test/' | 'src/test' | ''
}
}

@ -0,0 +1,288 @@
package org.xbib.gradle.plugin.asciidoctor.groovydsl
import org.asciidoctor.Asciidoctor
import org.asciidoctor.SafeMode
import spock.lang.Specification
import static org.asciidoctor.OptionsBuilder.options
/**
* Asciidoctor task inline extensions specification
*
*/
class AsciidoctorGroovyDSLSpec extends Specification {
private static final String TEST_DOC_BLOCK = '''Ignore this.
[BIG]
But this should be uppercase
[small]
And THIS should be lowercase\n\
capitalize::tHIS_sets_the_fIRST_letter_in_cApItAlS_and_REMOVES_underscores[]
.Gemfile
[source,ruby]
----
include::https://raw.github.com/asciidoctor/asciidoctor/master/Gemfile[]
----
.My Gist
gist::123456[]
See man:gittutorial[7] to get started.
blacklisted is a blacklisted word.
'''
File testRootDir
def setup() {
testRootDir = new File('.')
}
def 'Should clear registry on exception when registering from a Closure'() {
given:
AsciidoctorExtensions.extensions {
throw new Exception('This error is on purpose')
}
when:
Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
def e = thrown(ServiceConfigurationError)
}
def 'Should clear registry on exception when registering from a String'() {
given:
AsciidoctorExtensions.extensions 'throw new Exception(\'This error is on purpose\')'
when:
Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
def e = thrown(ServiceConfigurationError)
}
def 'Should clear registry on exception when registering from a File'() {
given:
AsciidoctorExtensions.extensions(new File('src/test/resources/error.groovy'))
when:
Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
def e = thrown(ServiceConfigurationError)
}
def 'Should apply BlockProcessor from Script as String'() {
given:
AsciidoctorExtensions.extensions '''
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, [:])
}
block('small') {
parent, reader, attributes ->
def lowerLines = reader.readLines()
.collect {it.toLowerCase()}
.inject('') {a, b -> a + '\\n' + b}
createBlock(parent, 'paragraph', [lowerLines], attributes, [:])
}
'''
when:
String rendered = Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
rendered.contains('BUT THIS SHOULD BE UPPERCASE')
rendered.contains('and this should be lowercase')
rendered.contains('Ignore this.')
}
def 'Should apply BlockProcessor from Closure'() {
given:
AsciidoctorExtensions.extensions {
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, [:])
}
block('small') {
parent, reader, attributes ->
def lowerLines = reader.readLines()
.collect { it.toLowerCase() }
.inject('') { a, b -> a + '\n' + b }
createBlock(parent, 'paragraph', [lowerLines], attributes, [:])
}
}
when:
String rendered = Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
rendered.contains('BUT THIS SHOULD BE UPPERCASE')
rendered.contains('and this should be lowercase')
rendered.contains('Ignore this.')
}
def 'Should apply BlockProcessor from Extension file'() {
given:
AsciidoctorExtensions.extensions(new File('src/test/resources/testblockextensions.groovy'))
when:
String rendered = Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
rendered.contains('BUT THIS SHOULD BE UPPERCASE')
rendered.contains('and this should be lowercase')
rendered.contains('Ignore this.')
}
def 'Should apply Postprocessor from Extension file'() {
given:
AsciidoctorExtensions.extensions(new File('src/test/resources/testpostprocessorextension.groovy'))
when:
String rendered = Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
rendered.contains('Copyright Acme, Inc.')
}
def 'Should apply Preprocessor from Extension file'() {
given:
AsciidoctorExtensions.extensions(new File('src/test/resources/testpreprocessorextension.groovy'))
when:
String rendered = Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
!rendered.contains('Ignore this.')
}
def 'Should apply Includeprocessor from Extension file'() {
given:
AsciidoctorExtensions.extensions(new File('src/test/resources/testincludeprocessorextension.groovy'))
when:
String rendered = Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
rendered.contains('The content of the URL')
}
def 'Should apply BlockMacroProcessor from Closure'() {
given:
AsciidoctorExtensions.extensions {
block_macro ("gist") { parent, target, attributes ->
String content = """<div class="content">
<script src="https://gist.github.com/${target}.js"></script>
</div>"""
createBlock(parent, "pass", [content], attributes);
}
}
when:
String rendered = Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
rendered.contains("https://gist.github.com/123456.js")
}
def 'Should apply BlockMacroProcessor from Extension file'() {
given:
AsciidoctorExtensions.extensions(new File('src/test/resources/testblockmacroextension.groovy'))
when:
String rendered = Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
rendered.contains("https://gist.github.com/123456.js")
}
def 'Should apply InlineMacroProcessor from Closure'() {
given:
AsciidoctorExtensions.extensions {
inline_macro('man') {
parent, target, attributes ->
def options = ["type": ":link", "target": target + ".html"]
createPhraseNode(parent, "anchor", target, attributes, options).convert()
}
}
when:
String rendered = Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
rendered.contains('<a href="gittutorial.html">gittutorial</a>')
}
def 'Should apply InlineMacroProcessor from Extension file'() {
given:
AsciidoctorExtensions.extensions(new File('src/test/resources/testinlinemacroprocessorextension.groovy'))
when:
String rendered = Asciidoctor.Factory.create().convert(TEST_DOC_BLOCK, [:])
then:
rendered.contains('<a href="gittutorial.html">gittutorial</a>')
}
def 'Should apply Treeprocessor from Extension file'() {
given:
AsciidoctorExtensions.extensions(new File('src/test/resources/testtreeprocessorextension.groovy'))
when:
String rendered = Asciidoctor.Factory.create().convert('''
$ echo "Hello, World!"
$ gem install asciidoctor
''', [:])
then:
rendered.contains('<span class="command">gem install asciidoctor</span>')
}
def 'Should apply DocinfoProcessor from Closure'() {
given:
String metatag = '<meta name="hello" content="world">'
AsciidoctorExtensions.extensions {
docinfo_processor {
document -> metatag
}
}
when:
String rendered = Asciidoctor.Factory.create().convert(
'''
= Hello
World''',
options().headerFooter(true).safe(SafeMode.SERVER).toFile(false).get());
then:
// (?ms) Multiline regexp with dotall (= '.' matches newline as well)
rendered ==~ /(?ms).*<head>.*<meta name="hello" content="world">.*<\/head>.*/
}
}

@ -0,0 +1,10 @@
throw new Exception('This error is on purpose')
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, [:])
}

@ -0,0 +1,17 @@
<!--
Copyright 2013-2014 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.
-->

@ -0,0 +1,17 @@
<!--
Copyright 2013-2014 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.
-->

@ -0,0 +1,4 @@
=====================
Not a real image file
=====================

@ -0,0 +1,17 @@
<!--
Copyright 2013-2014 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.
-->

@ -0,0 +1,17 @@
<!--
Copyright 2013-2014 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.
-->

@ -0,0 +1,26 @@
Document Title
==============
Doc Writer <thedoc@asciidoctor.org>
:idprefix: id_
Preamble paragraph.
NOTE: This is test, only a test.
== Section A
*Section A* paragraph.
=== Section A Subsection
*Section A* 'subsection' paragraph.
== Section B
*Section B* paragraph.
.Section B list
* Item 1
* Item 2
* Item 3

@ -0,0 +1,26 @@
Document Title
==============
Doc Writer <thedoc@asciidoctor.org>
:idprefix: id_
Preamble paragraph.
NOTE: This is test, only a test.
== Section A
*Section A* paragraph.
=== Section A Subsection
*Section A* 'subsection' paragraph.
== Section B
*Section B* paragraph.
.Section B list
* Item 1
* Item 2
* Item 3

@ -0,0 +1,16 @@
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, [:])
}
block("small") {
parent, reader, attributes ->
def lowerLines = reader.readLines()
.collect {it.toLowerCase()}
.inject("") {a, b -> a + '\n' + b}
createBlock(parent, "paragraph", [lowerLines], attributes, [:])
}

@ -0,0 +1,23 @@
= Inline Extension Test document
[BIG]
Write this in uppercase
[small]
AND WRITE THIS IN LOWERCASE
.Gemfile
[source,ruby]
----
include::https://raw.github.com/asciidoctor/asciidoctor/master/Gemfile[]
----
.My Gist
gist::123456[]
See man:gittutorial[7] to get started.
blacklisted is a blacklisted word.

@ -0,0 +1,16 @@
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, [:])
}
block("small") {
parent, reader, attributes ->
def lowerLines = reader.readLines()
.collect {it.toLowerCase()}
.inject("") {a, b -> a + '\\n' + b}
createBlock(parent, "paragraph", [lowerLines], attributes, [:])
}

@ -0,0 +1,7 @@
block_macro (name: "gist") { parent, target, attributes ->
String content = """<div class="content">
<script src="https://gist.github.com/${target}.js"></script>
</div>"""
createBlock(parent, "pass", [content], attributes)
}

@ -0,0 +1,6 @@
String content = "The content of the URL"
include_processor (filter: {it.startsWith("http")}) {
document, reader, target, attributes ->
reader.push_include(content, target, target, 1, attributes);
}

@ -0,0 +1,4 @@
inline_macro (name: "man") { parent, target, attributes ->
options=["type": ":link", "target": target + ".html"]
createPhraseNode(parent, "anchor", target, attributes, options).render()
}

@ -0,0 +1,16 @@
import org.jsoup.*
String copyright = "Copyright Acme, Inc."
postprocessor {
document, output ->
if(document.basebackend("html")) {
org.jsoup.nodes.Document doc = Jsoup.parse(output, "UTF-8")
def contentElement = doc.getElementsByTag("body")
contentElement.append(copyright)
doc.html()
} else {
throw new IllegalArgumentException("Expected html!")
}
}

@ -0,0 +1,5 @@
preprocessor {
document, reader ->
reader.advance()
reader
}

@ -0,0 +1,15 @@
treeprocessor { document ->
def blocks = document.blocks()
(0..<blocks.size()).each {
def block = blocks[it]
def lines = block.lines()
if (lines.size() > 0 && lines[0].startsWith('$')) {
Map attributes = block.attributes
attributes['role'] = 'terminal';
def resultLines = lines.collect {
it.startsWith('$') ? "<span class=\"command\">${it.substring(2)}</span>".toString() : it
}
blocks[it] = createBlock(document, 'listing', resultLines, attributes, [:])
}
}
}

@ -1 +1 @@
version = 2.3.0
version = 2.4.0

@ -10,7 +10,7 @@ apply plugin: 'com.gradle.plugin-publish'
dependencies {
api gradleApi()
api "org.xbib:groovy-git:${project.property('groovy-git.version')}"
api "org.xbib.groovy:groovy-git:${project.property('groovy-git.version')}"
testImplementation gradleTestKit()
testImplementation "org.spockframework:spock-core:${project.property('spock.version')}"
testImplementation "junit:junit:${project.property('junit4.version')}"

@ -1 +1 @@
version = 2.1.0
version = 2.2.0

@ -1,13 +1,13 @@
plugins {
id 'java-gradle-plugin'
id 'groovy'
id 'com.gradle.plugin-publish' version '0.18.0'
}
apply plugin: 'groovy'
apply plugin: 'java-gradle-plugin'
apply plugin: 'com.gradle.plugin-publish'
apply from: rootProject.file('gradle/compile/groovy.gradle')
dependencies {
api gradleApi()
api "org.xbib:rpm-core:${project.property('rpm.version')}"

@ -1 +1 @@
version = 2.2.0
version = 2.3.0

@ -3,8 +3,13 @@ name = 'gradle-plugins'
version = 0.0.1
org.gradle.warning.mode = ALL
gradle.wrapper.version = 7.3
gradle.wrapper.version = 7.3.2
groovy.version = 3.0.9
groovy-git.version = 2.1.0
rpm.version = 2.1.0
spock.version = 2.0-groovy-3.0
junit.version = 5.8.2
junit4.version = 4.13.2
asciidoctorj.version = 2.5.2
jruby.version = 9.3.2.0
jsoup.version = 1.11.2

@ -21,7 +21,13 @@ tasks.withType(GroovyCompile) {
}
}
task groovydocJar(type: Jar, dependsOn: 'groovydoc') {
task groovydocJar(type: Jar) {
dependsOn groovydoc
from groovydoc.destinationDir
archiveClassifier.set('javadoc')
archiveClassifier.set('groovydoc')
}
tasks.assemble.dependsOn(tasks.groovydocJar)
artifacts {
archives groovydocJar
}

@ -21,13 +21,16 @@ jar {
}
}
task sourcesJar(type: Jar, dependsOn: classes) {
task sourcesJar(type: Jar) {
dependsOn classes
classifier 'sources'
from sourceSets.main.allSource
}
task javadocJar(type: Jar, dependsOn: javadoc) {
task javadocJar(type: Jar) {
dependsOn javadoc
classifier 'javadoc'
from javadoc.destinationDir
}
artifacts {

Binary file not shown.

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

257
gradlew vendored

@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,67 +17,101 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@ -106,80 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
i=`expr $i + 1`
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

@ -1,3 +1,4 @@
include 'gradle-plugin-git'
include 'gradle-plugin-rpm'
include 'gradle-plugin-docker'
include 'gradle-plugin-asciidoctor'

Loading…
Cancel
Save