Compare commits

...

16 Commits

@ -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.

@ -1,6 +1,8 @@
group = 'org.xbib.gradle.plugin'
wrapper {
gradleVersion = "${project.property('gradle.wrapper.version')}"
gradleVersion = libs.versions.gradle.get()
distributionType = Wrapper.DistributionType.ALL
}

@ -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,10 @@
This work is based on an old (before June 2018) 1.5.6 version of
https://github.com/asciidoctor/asciidoctor-gradle-plugin
You can find remnants in the official code under org.asciidoctor.gradle.compat, when it was created with
https://github.com/ysb33r/asciidoctor-gradle-plugin/commit/1ad29b2eba915f7bce3cb004e3e9582578c8ac72
License: Apache 2.0

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

@ -0,0 +1,2 @@
name = gradle-plugin-asciidoctor
version = 3.0.0

@ -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.2'
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,65 +1,33 @@
plugins {
id 'java-gradle-plugin'
id 'groovy'
id 'com.gradle.plugin-publish' version '0.17.0'
alias(libs.plugins.publish)
}
ext {
user = 'jprante'
name = 'gradle-plugin-docker'
description = 'Docker support in a Gradle plugin'
inceptionYear = '2016'
url = 'https://github.com/' + user + '/' + name
scmUrl = 'https://github.com/' + user + '/' + name
scmConnection = 'scm:git:git://github.com/' + user + '/' + name + '.git'
scmDeveloperConnection = 'scm:git:ssh://git@github.com:' + user + '/' + name + '.git'
issueManagementSystem = 'Github'
issueManagementUrl = ext.scmUrl + '/issues'
licenseName = 'The Apache License, Version 2.0'
licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
apply plugin: 'java-gradle-plugin'
apply plugin: 'com.gradle.plugin-publish'
apply from: rootProject.file('gradle/compile/groovy.gradle')
dependencies {
api gradleApi()
implementation "org.codehaus.groovy:groovy-all:${project.property('groovy.version')}"
testImplementation gradleTestKit()
}
compileGroovy {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
compileTestGroovy {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
validatePlugins {
failOnWarning = false
}
gradlePlugin {
plugins {
dockerPlugin {
id = 'org.xbib.gradle.plugin.docker'
implementationClass = 'org.xbib.gradle.plugin.docker.DockerPlugin'
}
}
}
if (project.hasProperty('gradle.publish.key')) {
pluginBundle {
website = 'https://github.com/jprante/gradle-plugin-docker'
vcsUrl = 'https://github.com/jprante/gradle-plugin-docker'
plugins {
dockerPlugin {
id = 'org.xbib.gradle.plugin.docker'
version = project.version
description = rootProject.ext.description
displayName = 'Gradle Docker Plugin'
tags = ['gradle', 'docker']
gradlePlugin {
plugins {
dockerPlugin {
id = 'org.xbib.gradle.plugin.docker'
implementationClass = 'org.xbib.gradle.plugin.docker.DockerPlugin'
version = project.version
description = 'Docker plugin for build and push by Dockerfile'
displayName = 'Docker Plugin for build and push by Dockerfile'
}
}
}
}
/*pluginBundle {
website = 'https://github.com/jprante/gradle-plugins'
vcsUrl = 'https://github.com/jprante/gradle-plugins'
tags = ['docker']
}*/
}

@ -1 +1,2 @@
version = 2.2.0
name = gradle-plugin-docker
version = 3.0.0

@ -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.

@ -1,56 +1,36 @@
plugins {
id 'java-gradle-plugin'
id 'groovy'
id 'com.gradle.plugin-publish' version '0.17.0'
alias(libs.plugins.publish)
}
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:groovy-git:${project.property('groovy-git.version')}"
api libs.groovy.git
testImplementation gradleTestKit()
testImplementation "org.spockframework:spock-core:${project.property('spock.version')}"
testImplementation "junit:junit:${project.property('junit4.version')}"
}
compileGroovy {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
compileTestGroovy {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
validatePlugins {
failOnWarning = false
}
gradlePlugin {
plugins {
gitPlugin {
id = 'org.xbib.gradle.plugin.git'
implementationClass = 'org.xbib.gradle.plugin.git.GitPlugin'
}
}
testImplementation libs.spock.core
testImplementation libs.junit4
}
if (project.hasProperty('gradle.publish.key')) {
pluginBundle {
website = 'https://github.com/jprante/groovy-git'
vcsUrl = 'https://github.com/jprante/groovy-git.git'
gradlePlugin {
plugins {
gitPlugin {
id = 'org.xbib.gradle.plugin.git'
implementationClass = 'org.xbib.gradle.plugin.git.GitPlugin'
version = project.version
description = rootProject.ext.description
displayName = rootProject.ext.description
tags = ['gradle', 'plugin', 'git']
description = 'Git client plugin based on JGit'
displayName = 'Git client plugin based on JGit'
}
}
}
/*pluginBundle {
website = 'https://github.com/jprante/gradle-plugins'
vcsUrl = 'https://github.com/jprante/gradle-plugins'
tags = ['git']
}*/
}

@ -1 +1,2 @@
version = 2.0.1
name = gradle-plugin-docker
version = 3.0.0

@ -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,52 @@
# gradle-plugin-jacc
A Gradle plugin for [Jacc](http://web.cecs.pdx.edu/~mpj/jacc/)
## Usage
plugins {
id 'org.xbib.gradle.plugin.jacc'
}
apply plugin: 'org.xbib.gradle.plugin.jacc'
Gradle will look for your jacc files in the source sets you specified.
By default, it looks with the pattern `**/*.jacc` under `src/main/jacc`
and `src/test/jacc`.
You can set up the source sets like this:
sourceSets {
main {
jacc {
srcDir "src/main/jacc"
}
java {
srcDir "build/my-generated-sources/jacc"
}
}
}
The lastJava `srcDir` definition will be used as the base for the Jacc target path.
If not given, the Jacc target path for generated Java source follows the pattern:
`${project.buildDir}/generated/sources/jacc`
The Jacc target path will be added automaticlly to the java compile task source directory
of the source set.
# License
Copyright (C) 2015-2020 Jörg Prante
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,35 @@
plugins {
id 'java-gradle-plugin'
alias(libs.plugins.publish)
}
apply plugin: 'java-gradle-plugin'
apply plugin: 'com.gradle.plugin-publish'
apply from: rootProject.file('gradle/compile/groovy.gradle')
apply from: rootProject.file('gradle/test/junit5.gradle')
dependencies {
api gradleApi()
implementation libs.jacc
testImplementation gradleTestKit()
}
if (project.hasProperty('gradle.publish.key')) {
gradlePlugin {
plugins {
jaccPlugin {
id = 'org.xbib.gradle.plugin.jacc'
implementationClass = 'org.xbib.gradle.plugin.jacc.JaccPlugin'
version = project.version
description = 'Gradle Jacc plugin'
displayName = 'Gradle Jacc plugin'
}
}
}
/*pluginBundle {
website = scmUrl
vcsUrl = scmUrl
tags = ['jacc']
}*/
}

@ -0,0 +1,2 @@
name = gradle-plugin-jacc
version = 3.0.0

@ -0,0 +1,80 @@
package org.xbib.gradle.plugin.jacc
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskProvider
class JaccPlugin implements Plugin<Project> {
private static final Logger logger = Logging.getLogger(JaccPlugin)
@Override
void apply(Project project) {
project.with {
apply plugin: 'java-library'
addSourceSetExtensions(project)
}
project.afterEvaluate {
addTasks(project)
}
}
private static void addSourceSetExtensions(Project project) {
project.sourceSets.all { SourceSet sourceSet ->
createSourceSetExtension(project, sourceSet)
createConfiguration(project, sourceSet)
}
}
private static void createSourceSetExtension(Project project, SourceSet sourceSet) {
String name = sourceSet.name
SourceDirectorySet sourceDirectorySet = project.objects.sourceDirectorySet(name, "${name} Jacc source")
sourceSet.extensions.add('jacc', sourceDirectorySet)
sourceDirectorySet.srcDir("src/${name}/jacc")
sourceDirectorySet.include("**/*.jacc")
}
private static void createConfiguration(Project project, SourceSet sourceSet) {
String configName = sourceSet.name + 'Jacc'
if (project.configurations.findByName(configName) == null) {
logger.info "create configuration ${configName}"
project.configurations.create(configName) {
visible = false
transitive = true
extendsFrom = []
}
}
}
private static void addTasks(Project project) {
project.sourceSets.all { SourceSet sourceSet ->
addTaskForSourceSet(project, sourceSet)
}
}
private static void addTaskForSourceSet(Project project, SourceSet sourceSet) {
String taskName = sourceSet.getTaskName('generate', 'jacc')
SourceDirectorySet sourceDirectorySet = sourceSet.extensions.getByName('jacc') as SourceDirectorySet
File targetFile = sourceSet.java && sourceSet.java.srcDirs ? sourceSet.java.srcDirs.last() :
project.file("${project.buildDir}/generated/sources/${sourceSet.name}")
if (sourceDirectorySet.asList()) {
TaskProvider<JaccTask> taskProvider = project.tasks.register(taskName, JaccTask) {
group = 'jacc'
description = 'Generates code from Jacc files in ' + sourceSet.name
source = sourceDirectorySet.asList()
target = targetFile
}
logger.info "created ${taskName} for sources ${sourceDirectorySet.asList()} and target ${targetFile}"
project.tasks.findByName(sourceSet.compileJavaTaskName).dependsOn taskProvider
if (sourceSet.java && sourceSet.java.srcDirs) {
sourceSet.java.srcDirs += targetFile
}
}
}
}

@ -0,0 +1,33 @@
package org.xbib.gradle.plugin.jacc
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
import org.xbib.jacc.Jacc
class JaccTask extends DefaultTask {
@InputFiles
Iterable<File> source
@OutputDirectory
File target
@TaskAction
void generateAndTransformJacc() throws Exception {
source.each { file ->
String pkg = getPackageName(file)
File fullTarget = new File(target, pkg.replace('.','/'))
project.mkdir(fullTarget)
Jacc.main([file.absolutePath, '-d', fullTarget] as String[])
}
}
static String getPackageName(File file) {
String string = file.readLines().find { line ->
line.startsWith('package')
}
return string == null ? '' : string.substring(8, string.length() - 1)
}
}

@ -0,0 +1,65 @@
package org.xbib.gradle.plugin.jacc
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import static org.junit.jupiter.api.Assertions.*
class JaccPluginTest {
private File projectDir
private File settingsFile
private File buildFile
@BeforeEach
void setup(@TempDir File testProjectDir) throws IOException {
this.projectDir = testProjectDir
this.settingsFile = new File(testProjectDir, "settings.gradle")
this.buildFile = new File(testProjectDir, "build.gradle")
}
@Test
void testJacc() {
String settingsFileContent = '''
rootProject.name = 'jacc-test'
'''
settingsFile.write(settingsFileContent)
String buildFileContent = '''
plugins {
id 'org.xbib.gradle.plugin.jacc'
}
sourceSets {
test {
jacc {
srcDir "${System.getProperty('user.dir')}/src/test/jacc"
}
java {
srcDir "${System.getProperty('user.dir')}/build/my-generated-sources/jacc"
}
}
}
'''
buildFile.write(buildFileContent)
BuildResult result = GradleRunner.create()
.withProjectDir(projectDir)
.withArguments(":build", "--info")
.withPluginClasspath()
.forwardOutput()
.build()
assertEquals(TaskOutcome.SUCCESS, result.task(":build").getOutcome())
File file = new File("${System.getProperty('user.dir')}/build/my-generated-sources/jacc")
if (file.exists()) {
List<File> list = Arrays.asList(file.listFiles())
assertEquals(2, list.size())
}
}
}

@ -0,0 +1,104 @@
// To compile and run this program using jacc and Sun's JDK:
//
// jacc simpleCalc.jacc
// javac Calc.java CalcTokens.java
// java Calc
// ... enter arithmetic expressions ... hit EOF to terminate
//
%class Calc
%interface CalcTokens
%semantic int : yylval
%get token
%next yylex()
%token '+' '-' '*' '/' '(' ')' ';' INTEGER
%left '+' '-'
%left '*' '/'
%%
prog : prog ';' expr { System.out.println($3); }
| expr { System.out.println($1); }
;
expr : expr '+' expr { $$ = $1 + $3; }
| expr '-' expr { $$ = $1 - $3; }
| expr '*' expr { $$ = $1 * $3; }
| expr '/' expr { $$ = $1 / $3; }
| '(' expr ')' { $$ = $2; }
| INTEGER { $$ = $1; }
;
%%
private void yyerror(String msg) {
System.out.println("ERROR: " + msg);
System.exit(1);
}
private int c;
/** Read a single input character from standard input.
*/
private void nextChar() {
if (c>=0) {
try {
c = System.in.read();
} catch (Exception e) {
c = (-1);
}
}
}
int token;
int yylval;
/** Read the next token and return the
* corresponding integer code.
*/
int yylex() {
for (;;) {
// Skip whitespace
while (c==' ' || c=='\n' || c=='\t' || c=='\r') {
nextChar();
}
if (c<0) {
return (token=ENDINPUT);
}
switch (c) {
case '+' : nextChar();
return token='+';
case '-' : nextChar();
return token='-';
case '*' : nextChar();
return token='*';
case '/' : nextChar();
return token='/';
case '(' : nextChar();
return token='(';
case ')' : nextChar();
return token=')';
case ';' : nextChar();
return token=';';
default : if (Character.isDigit((char)c)) {
int n = 0;
do {
n = 10*n + (c - '0');
nextChar();
} while (Character.isDigit((char)c));
yylval = n;
return token=INTEGER;
} else {
yyerror("Illegal character "+c);
nextChar();
}
}
}
}
public static void main(String[] args) {
Calc calc = new Calc();
calc.nextChar(); // prime the character input stream
calc.yylex(); // prime the token input stream
calc.parse(); // parse the input
}

@ -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,79 @@
# gradle-plugin-jflex
A Gradle plugin for [JFlex](http://jflex.de)
## Usage
plugins {
id "org.xbib.gradle.plugin.jflex" version "1.4.0"
}
Gradle will look for your JFlex files in the source sets you specified.
By default, it looks with the pattern `**/*.jflex` under `src/main/jflex`
and `src/test/jflex`.
You can set up the source sets like this:
sourceSets {
main {
jflex {
srcDir "src/main/jflex"
}
java {
srcDir "$buildDir/my-generated-sources/jflex"
}
}
}
The lastJava `srcDir` definition will be used as the base for the JFLex target path.
If not given, the JFlex target path for generated Java source follows the pattern:
`${project.buildDir}/generated/sources/jflex`
The JFlex target path will be added automatically to the java compile task source directory
of the source set.
## Parameter support
The following parameters can be set in a global `jflex` extension
in the gradle script. See also https://jflex.de/manual.html
| Name | Description |
| ------- | ---------- |
| encoding | the file encoding |
| rootDirectory | the root directory used by JFlex (modification discouraged since the directories are derived from gradle source set)
| skel | uses external skeleton <file> in UTF-8 encoding. This is mainly for JFlex maintenance and special low level customisations. Use only when you know what you are doing! JFlex comes with a skeleton file in the src directory that reflects exactly the internal, pre-compiled skeleton and can be used with the skel option. |
| verbose | display generation progress messages (disabled by default) |
| jlex | tries even harder to comply to JLex interpretation of specs |
| no_minimize | skip the DFA minimisation step during scanner generation |
| no_backup | don't write backup files if this is true |
| unused_warning | warn about unused macros (by default false) |
| progress | progress dots will be printed (by default false) |
| dot | If true, jflex will write graphviz .dot files for generated automata (by default, false) |
| time | If true, jflex will print time statistics about the generation process (by default false) |
| dump | If true, you will be flooded with information, e.g. dfa tables (by default, false) |
| legacy_dot | dot (.) meta character matches [^\n] instead of [^\n\r\u000B\u000C\u0085\u2028\u2029] |
| statistics | print output statistics (by default, false) |
## Credits
gradle-plugin-jflex is a plugin based on
[gradle-jflex-plugin](https://github.com/thomaslee/gradle-jflex-plugin)
which was written by [Tom Lee](http://tomlee.co).
# License
Copyright (C) 2015-2020 Jörg Prante
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,35 @@
plugins {
id 'java-gradle-plugin'
alias(libs.plugins.publish)
}
apply plugin: 'java-gradle-plugin'
apply plugin: 'com.gradle.plugin-publish'
apply from: rootProject.file('gradle/compile/groovy.gradle')
apply from: rootProject.file('gradle/test/junit5.gradle')
dependencies {
api gradleApi()
implementation libs.jflex
testImplementation gradleTestKit()
}
if (project.hasProperty('gradle.publish.key')) {
gradlePlugin {
plugins {
jflexPlugin {
id = 'org.xbib.gradle.plugin.jflex'
implementationClass = 'org.xbib.gradle.plugin.jflex.JFlexPlugin'
version = project.version
description = 'Gradle JFlex plugin'
displayName = 'Gradle JFlex plugin'
}
}
}
/*pluginBundle {
website = scmUrl
vcsUrl = scmUrl
tags = ['jflex']
}*/
}

@ -0,0 +1,2 @@
name = gradle-plugin-jflex
version = 3.0.0

@ -0,0 +1,67 @@
package org.xbib.gradle.plugin.jflex
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.Optional
class JFlexExtension {
@Input
@Optional
String encoding
@Input
@Optional
File rootDirectory
@Input
@Optional
File skel
@Input
@Optional
Boolean verbose = false
@Input
@Optional
Boolean jlex = false
@Input
@Optional
Boolean no_minimize = false
@Input
@Optional
Boolean no_backup = false
@Input
@Optional
Boolean unused_warning = false
@Input
@Optional
Boolean progress = false
@Input
@Optional
Boolean time = false
@Input
@Optional
Boolean dot = false
@Input
@Optional
Boolean dump = false
@Input
@Optional
Boolean legacy_dot = false
@Input
@Optional
Boolean statistics = false
@Input
@Optional
Boolean writeIntoJavaSrc = false
}

@ -0,0 +1,90 @@
package org.xbib.gradle.plugin.jflex
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.tasks.SourceSet
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
class JFlexPlugin implements Plugin<Project> {
private static final Logger logger = Logging.getLogger(JFlexPlugin)
@Override
void apply(Project project) {
logger.info "JFlex plugin says hello"
project.with {
apply plugin: 'java-library'
createJflexExtension(project)
addSourceSetExtensions(project)
}
project.afterEvaluate {
addJFlexTasks(project)
}
}
private static void addSourceSetExtensions(Project project) {
project.sourceSets.all { SourceSet sourceSet ->
createSourceSetExtension(project, sourceSet)
createConfiguration(project, sourceSet)
}
}
private static void createSourceSetExtension(Project project, SourceSet sourceSet) {
String name = sourceSet.name
SourceDirectorySet sourceDirectorySet = project.objects.sourceDirectorySet(name, "${name} JFlex source")
sourceSet.extensions.add('jflex', sourceDirectorySet)
sourceDirectorySet.srcDir("src/${name}/jflex")
sourceDirectorySet.include("**/*.jflex")
}
private static void createConfiguration(Project project, SourceSet sourceSet) {
String configName = sourceSet.name + capitalize('jflex' as CharSequence)
if (project.configurations.findByName(configName) == null) {
logger.info "create configuration ${configName}"
project.configurations.create(configName) {
visible = false
transitive = true
extendsFrom = []
}
}
}
private static void createJflexExtension(Project project) {
project.extensions.create ('jflex', JFlexExtension)
}
private static void addJFlexTasks(Project project) {
project.sourceSets.all { SourceSet sourceSet ->
addJFlexTaskForSourceSet(project, sourceSet)
}
}
private static void addJFlexTaskForSourceSet(Project project, SourceSet sourceSet) {
String taskName = sourceSet.getTaskName('generate', 'jflex')
SourceDirectorySet sourceDirectorySet = sourceSet.extensions.getByName('jflex') as SourceDirectorySet
File targetFile = project.file("${project.buildDir}/generated/sources/${sourceSet.name}")
if (sourceDirectorySet.asList()) {
TaskProvider<JFlexTask> taskProvider = project.tasks.register(taskName, JFlexTask) {
group = 'jflex'
description = 'Generates code from JFlex files in ' + sourceSet.name
source = sourceDirectorySet.asList()
target = targetFile
theSourceSet = sourceSet
}
logger.info "created ${taskName} for sources ${sourceDirectorySet.asList()} and target ${targetFile}"
project.tasks.named(sourceSet.compileJavaTaskName).configure({
dependsOn taskProvider
})
if (sourceSet.java && sourceSet.java.srcDirs) {
sourceSet.java.srcDirs += targetFile
}
}
}
private static String capitalize(CharSequence charSequence) {
return charSequence.length() == 0 ? "" : "" + Character.toUpperCase(charSequence.charAt(0)) + charSequence.subSequence(1, charSequence.length())
}
}

@ -0,0 +1,90 @@
package org.xbib.gradle.plugin.jflex
import jflex.exceptions.GeneratorException
import jflex.generator.LexGenerator
import jflex.logging.Out
import jflex.option.Options
import jflex.skeleton.Skeleton
import org.gradle.api.DefaultTask
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.StopActionException
import org.gradle.api.tasks.TaskAction
import java.nio.charset.Charset
@CacheableTask
class JFlexTask extends DefaultTask {
private final Logger logger = Logging.getLogger(JFlexTask)
@InputFiles
@PathSensitive(PathSensitivity.RELATIVE)
Iterable<File> source
@OutputDirectory
File target
@Internal
SourceSet theSourceSet
@TaskAction
void generateAndTransformJflex() throws Exception {
JFlexExtension ext = project.extensions.findByType(JFlexExtension)
Options.setRootDirectory(ext.rootDirectory ? ext.rootDirectory : new File(""))
Skeleton.readDefault()
if (ext.skel) {
Skeleton.readSkelFile(ext.skel)
}
Options.encoding = ext.encoding ? Charset.forName(ext.encoding) : Charset.defaultCharset()
Options.verbose = ext.verbose
Options.progress = ext.progress
Options.jlex = ext.jlex
Options.no_minimize = ext.no_minimize
Options.no_backup = ext.no_backup
Options.time = ext.time
Options.dot = ext.dot
Options.dump = ext.dump
Options.legacy_dot = ext.legacy_dot
// hack for writing directly into java source. Not recommended.
if (ext.writeIntoJavaSrc) {
if (theSourceSet.java && theSourceSet.java.srcDirs) {
logger.info "java sources: ${theSourceSet.java.srcDirs}"
target = theSourceSet.java.srcDirs.first()
logger.info "switching to first java source directory ${target}"
} else {
logger.warn "writing into java source not possible, is empty"
}
}
source.each { file ->
String pkg = getPackageName(file)
File fullTarget = new File(target, pkg.replace('.','/'))
project.mkdir(fullTarget)
Options.directory = fullTarget
logger.info "jflex task: source=${file} pkg=${pkg} dir=${target}"
try {
new LexGenerator(file).generate()
} catch (GeneratorException e) {
Logging.getLogger(JFlexTask).error("JFlex error: ${e.message}", e)
throw new StopActionException('an error occurred during JFlex code generation')
}
}
if (ext.statistics) {
Out.statistics()
}
}
static String getPackageName(File file) {
String string = file.readLines().find { line ->
line.startsWith('package')
}
return string == null ? '' : string.substring(8, string.length() - 1)
}
}

@ -0,0 +1,194 @@
package org.xbib.gradle.plugin.test
import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.nio.file.Files
import static org.junit.jupiter.api.Assertions.*
class JFlexPluginTest {
private File projectDir
private File settingsFile
private File buildFile
@BeforeEach
void setup(@TempDir File testProjectDir) throws IOException {
this.projectDir = testProjectDir
this.settingsFile = new File(testProjectDir, "settings.gradle")
this.buildFile = new File(testProjectDir, "build.gradle")
}
@Test
void testJFlex() {
String settingsFileContent = '''
rootProject.name = 'jflex-test'
'''
settingsFile.write(settingsFileContent)
String buildFileContent = '''
plugins {
id 'org.xbib.gradle.plugin.jflex'
}
sourceSets {
test {
jflex {
// point to our test directory where the jflex file lives
srcDir "${System.getProperty('user.dir')}/src/test/jflex"
}
}
}
jflex {
verbose = true
dump = false
progress = false
}
'''
buildFile.write(buildFileContent)
BuildResult result = GradleRunner.create()
.withProjectDir(projectDir)
.withArguments(":build", "--info")
.withPluginClasspath()
.forwardOutput()
.build()
assertEquals(TaskOutcome.SUCCESS, result.task(":build").getOutcome())
// default output dir
File target = new File(projectDir, "build/generated/sources/test")
boolean found = false
if (target.exists()) {
// check for generated output
assertEquals(1, target.listFiles().length)
target.eachFileRecurse {
if (it.isFile()) {
println "found: ${it}"
found = true
}
}
} else {
fail("directory not found: ${target}")
}
if (!found) {
fail("jflex output not found")
}
}
@Test
void testJFlexWriteIntoJavaSrc() {
Files.createDirectories(projectDir.toPath().resolve('src/main/java'))
Files.createDirectories(projectDir.toPath().resolve('src/test/java'))
String settingsFileContent = '''
rootProject.name = 'jflex-test'
'''
settingsFile.write(settingsFileContent)
String buildFileContent = '''
plugins {
id 'org.xbib.gradle.plugin.jflex'
}
sourceSets {
main {
java {
srcDir "src/main/java"
}
jflex {
// point to our test directory where the jflex file lives
srcDir "${System.getProperty('user.dir')}/src/test/jflex"
}
}
test {
java {
srcDir "src/test/java"
}
}
}
jflex {
verbose = true
dump = false
progress = false
// enable legacy behavior of writing directly into Java source directory. Not recommended.
writeIntoJavaSrc = true
}
'''
buildFile.write(buildFileContent)
BuildResult result = GradleRunner.create()
.withProjectDir(projectDir)
.withArguments(":build", "--info")
.withPluginClasspath()
.forwardOutput()
.build()
assertEquals(TaskOutcome.SUCCESS, result.task(":build").getOutcome())
// search the Java source directory
File target = new File(projectDir, "src/main/java")
boolean found = false
if (target.exists()) {
// check for generated file
assertEquals(1, target.listFiles().length)
target.eachFileRecurse {
if (it.isFile()) {
println "found: ${it}"
found = true
}
}
} else {
fail("directory not found: ${target}")
}
if (!found) {
fail("jflex output not found")
}
}
@Test
void testTaskIsNotStarted() {
String buildFileContent = '''
plugins {
id 'org.xbib.gradle.plugin.jflex'
}
sourceSets {
test {
jflex {
srcDir "${System.getProperty('user.dir')}/src/test/jflex"
}
java {
srcDir "${System.getProperty('user.dir')}/build/my-generated-sources/jflex"
}
}
}
jflex {
verbose = false
dump = false
progress = false
}
def configuredTasks = []
tasks.configureEach {
configuredTasks << it
}
gradle.buildFinished {
def configuredTaskPaths = configuredTasks*.path
assert configuredTaskPaths == [':help',':clean']
}
'''
buildFile.write(buildFileContent)
GradleRunner.create()
.withProjectDir(projectDir)
.withArguments(":help")
.withPluginClasspath()
.forwardOutput()
.build()
}
}

@ -0,0 +1,43 @@
package org.xbib.gradle.plugin.test;
import java.io.IOException;
%%
%class Test
%int
%unicode
%line
%column
%{
int token;
double yylval;
int nextToken() {
try {
return token = yylex();
} catch (IOException e) {
return token = -1;
}
}
int getToken() {
return token;
}
double getSemantic() {
return yylval;
}
%}
ws = [ \t\f]
digit = [0-9]
number = {digit}+(\.{digit}+)?(E[+\-]?{digit}+)?
%%
\r|\n|\r\n { return 0; }
{ws}+ { }
{number} { yylval = Double.parseDouble(yytext()); return 1; }
[+\-*/()=] { return (int)(yytext().charAt(0)); }
"*+" { return 2; }
. { throw new Error(yytext()); }

@ -0,0 +1,8 @@
Very loosely based on a 2018 version of nebula.rpm
https://github.com/nebula-plugins/gradle-ospackage-plugin
but completely revised, using the xbib rpm library, not redline rpm.
Lincense: Apache 2.0

@ -1,55 +1,44 @@
plugins {
id 'java-gradle-plugin'
id 'groovy'
id 'com.gradle.plugin-publish' version '0.17.0'
alias(libs.plugins.publish)
}
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')}"
api libs.rpm
testImplementation gradleTestKit()
}
compileGroovy {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
compileTestGroovy {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
validatePlugins {
// disable warning as failures because gradle plugin does not recognize @Delegate tag
failOnWarning = false
}
gradlePlugin {
plugins {
rpmPlugin {
id = 'org.xbib.gradle.plugin.rpm'
implementationClass = 'org.xbib.gradle.plugin.RpmPlugin'
publishing {
repositories {
maven {
name = 'localRepository'
url = 'build/local-repository'
}
}
}
if (project.hasProperty('gradle.publish.key')) {
pluginBundle {
website = 'https://github.com/jprante/rpm'
vcsUrl = 'https://github.com/jprante/rpm'
gradlePlugin {
plugins {
rpmPlugin {
id = 'org.xbib.gradle.plugin.rpm'
group = project.group
version = project.version
description = rootProject.ext.description
displayName = rootProject.ext.description
tags = ['gradle', 'plugin', 'rpm']
description = 'Java implementation for RPM packaging'
displayName = 'Java implementation for RPM packaging'
implementationClass = 'org.xbib.gradle.plugin.RpmPlugin'
}
}
}
/*pluginBundle {
website = 'https://github.com/jprante/gradle-plugins'
vcsUrl = 'https://github.com/jprante/gradle-plugins'
tags = [ 'rpm' ]
}*/
}

@ -1 +1,2 @@
name = gradle-plugin-rpm
version = 3.0.0

@ -1,19 +1,13 @@
package org.xbib.gradle.plugin
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.BasePlugin
import org.gradle.util.GradleVersion
class RpmPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
String version = '6.4'
if (GradleVersion.current() < GradleVersion.version(version)) {
throw new GradleException("need Gradle ${version} or higher")
}
project.plugins.apply(BasePlugin)
project.ext.Rpm = Rpm.class
}

@ -0,0 +1,40 @@
plugins {
id 'java-gradle-plugin'
alias(libs.plugins.publish)
}
apply plugin: 'java-gradle-plugin'
apply plugin: 'com.gradle.plugin-publish'
apply from: rootProject.file('gradle/compile/groovy.gradle')
apply from: rootProject.file('gradle/test/junit5.gradle')
dependencies {
api gradleApi()
implementation libs.asm
implementation libs.asm.commons
implementation libs.asm.util
testImplementation gradleTestKit()
testImplementation libs.spock.core
testImplementation libs.spock.junit4
}
if (project.hasProperty('gradle.publish.key')) {
gradlePlugin {
plugins {
shadowPlugin {
id = 'org.xbib.gradle.plugin.shadow'
implementationClass = 'org.xbib.gradle.plugin.shadow.ShadowPlugin'
group = project.group
version = project.version
description = 'Shadow plugin for Gradle'
displayName = 'Shadow plugin for Gradle'
}
}
}
/*pluginBundle {
website = scmUrl
vcsUrl = scmUrl
tags = [ 'shadow' ]
}*/
}

@ -0,0 +1,2 @@
name = gradle-plugin-shadow
version = 3.0.0

@ -0,0 +1,28 @@
:tests: ../../test/groovy/org/xbib/gradle/plugin/shadow
== Introduction
Shadow is a Gradle plugin for combining dependency classes and resources with a project's into a single
output Jar.
The combined Jar is often referred to a __fat-jar__ or __uber-jar__.
Shadow utilizes `JarInputStream` and `JarOutputStream` to efficiently process dependent libraries
into the output jar without incurring the I/O overhead of expanding the jars to disk.
=== Benefits of Shadow
Shadowing a project output has a major use case:
. Bundling and relocating common dependencies in libraries to avoid classpath conflicts
==== Bundling
Dependency bundling and relocation is the main use case for __library__ authors.
The goal of a bundled library is to create a pre-packaged dependency for other libraries or applications to utilize.
Often in these scenarios, a library may contain a dependency that a downstream library or application also uses.
In __some__ cases, different versions of this common dependency can cause an issue in either the upstream library or
the downstream application.
These issues often manifest themselves as binary incompatibilities in either the library or application code.
By utilizing Shadow's ability to __relocate__ the package names for dependencies, a library author can ensure that the
library's dependencies will not conflict with the same dependency being declared by the downstream application.
include::01-getting-started.adoc[]

@ -0,0 +1,96 @@
=== Getting Started
[source,groovy,subs="+attributes"]
----
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.xbib.gradle.plugin:gradle-plugin-shadow:{project-version}'
}
}
apply plugin: 'java'
apply plugin: 'org.xbib.gradle.plugin.shadow'
----
Alternatively, the Gradle Plugin syntax can be used:
[source,groovy,subs="+attributes"]
----
plugins {
id 'java'
id 'org.xbib.gradle.plugin.shadow' version '{project-version}'
}
----
Shadow is a reactive plugin.
This means that applying Shadow by itself will perform no configuration on your project.
Instead, Shadow __reacts__ to the application of other plugins to decorate the project.
This means, that for most users, the `java` or `groovy` plugins must be __explicitly__ applied
to have the desired effect.
=== Default Java/Groovy Tasks
In the presence of the `java` or `groovy` plugins, Shadow will automatically configure the
following behavior:
* Adds a `shadowJar` task to the project.
* Adds a `shadow` configuration to the project.
* Configures the `shadowJar` task to include all sources from the project's `main` sourceSet.
* Configures the `shadowJar` task to bundle all dependencies from the `runtime` configuration.
* Configures the __classifier__ attribute of the `shadowJar` task to be `'all'` .
* Configures the `shadowJar` task to generate a `Manifest` with:
** Inheriting all configuration from the standard `jar` task.
** Adds a `Class-Path` attribute to the `Manifest` that appends all dependencies from the `shadow` configuration
* Configures the `shadowJar` task to __exclude__ any JAR index or cryptographic signature files matching the following patterns:
** `META-INF/INDEX.LIST`
** `META-INF/*.SF`
** `META-INF/*.DSA`
** `META-INF/*.RSA`
* Creates and registers the `shadow` component in the project (used for integrating with `maven-publish`).
* Configures the `uploadShadow` task (as part of the `maven` plugin) with the following behavior:
** Removes the `compile` and `runtime` configurations from the `pom.xml` file mapping.
** Adds the `shadow` configuration to the `pom.xml` file as `RUNTIME` scope.
=== Shadowing Gradle Plugins
Shadow is capable of automatically configuring package relocation for your dependencies.
This is useful especially when building Gradle plugins where you want your dependencies to not conflict with versions
provided by the Gradle runtime.
[source,groovy,subs="+attributes"]
----
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.xbib.gradle.plugin:shadow:{project-version}'
}
}
apply plugin: 'org.xbib.gradle.plugin.shadow'
apply plugin: 'java'
----
Alternatively, the Gradle Plugin syntax can be used:
[source,groovy,subs="+attributes"]
----
plugins {
id 'java'
id 'org.xbib.gradle.plugin.plugin-shadow' version '{project-version}'
}
----
Applying the `plugin-shadow` plugin is the same as applying the standard `shadow` plugin with the additional creation
of the `configureRelocationShadowJar` task.
This task runs before the `shadowJar` task and scans the packages present in the dependencies that will be merged into
the final jar and automatically configures relocation for them.
By default the tasks relocates all packages to the `shadow.` prefix.
For example `org.jdom2.JDOMException` becomes `shadow.org.jdom2.JDOMException`
For more details see the sectinon <<Using Shadow to Package Gradle Plugins>>

@ -0,0 +1,106 @@
== Configuring Shadow
The link:{api}/tasks/ShadowJar.html[`ShadowJar`] task type extends from Gradle's
https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html[`Jar`] type.
This means that all attributes and methods available on `Jar` are also available on
link:{api}/tasks/ShadowJar.html[`ShadowJar`].
Refer the __Gradle User Guide__ for https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html[Jar] for
details.
=== Configuring Output Name
Shadow configures the default `shadowJar` task to set the output JAR's `destinationDir`, `baseName`, `appendix`,
`version`, and `extension` to the same default values as Gradle does for all `Jar` tasks.
Additionally, it configures the `classifier` to be `all`.
If working with a Gradle project with the name `myApp` and version `1.0`, the default `shadowJar` task will output a
file at: `build/libs/myApp-1.0-all.jar`
As with all `Jar` tasks in Gradle, these values can be overridden:
.Output to `build/libs/shadow.jar`
[source,groovy,indent=0]
----
include::{tests}/ShadowPluginSpec.groovy[tags=rename]
----
=== Configuring the Runtime Classpath
Each Java JAR file contains a manifest file that provides meta data about the contents of the JAR file itself.
When using a shadowed JAR file as an executable JAR, it is assumed that all necessary runtime classes are contained
within the JAR itself.
There may be situations where the desire is to **not** bundle select dependencies into the shadowed JAR file but
they are still required for runtime execution.
In these scenarios, Shadow creates a `shadow` configuration to declare these dependencies.
Dependencies added to the `shadow` configuration are *not* bundled into the output JAR.
Think of `configurations.shadow` as unmerged, runtime dependencies.
The integration with the `maven` and `maven-publish` plugins will automatically configure dependencies added
to `configurations.shadow` as `RUNTIME` scope dependencies in the resulting POM file.
Additionally, Shadow automatically configures the manifest of the `shadowJar` task to contain a `Class-Path` entry
in the JAR manifest.
The value of the `Class-Path` entry is the name of all dependencies resolved in the `shadow` configuration
for the project.
[source,groovy,indent=0]
----
include::{tests}/ShadowPluginSpec.groovy[tags=shadowConfig]
----
Inspecting the `META-INF/MANIFEST.MF` entry in the JAR file will reveal the following attribute:
[source,property,indent=0]
----
Class-Path: junit-3.8.2.jar
----
When deploying a shadowed JAR as an execution JAR, it is important to note that any non-bundled runtime dependencies
**must** be deployed in the location specified in the `Class-Path` entry in the manifest.
=== Configuring the JAR Manifest
Beyond the automatic configuration of the `Class-Path` entry, the `shadowJar` manifest is configured in a number of ways.
First, the manifest for the `shadowJar` task is configured to __inherit__ from the manifest of the standard `jar` task.
This means that any configuration performed on the `jar` task will propagate to the `shadowJar` tasks.
[source,groovy,indent=0]
----
include::{tests}/ShadowPluginSpec.groovy[tags=jarManifest]
----
Inspecting the `META-INF/MANIFEST.MF` entry in the JAR file will revel the following attribute:
[source,property,indent=0]
----
Class-Path: /libs/a.jar
----
If it is desired to inherit a manifest from a JAR task other than the standard `jar` task, the `inheritFrom` methods
on the `shadowJar.manifest` object can be used to configure the upstream.
[source,groovy,indent=0]
----
task testJar(type: Jar) {
manifest {
attributes 'Description': 'This is an application JAR'
}
}
shadowJar {
manifest {
inheritFrom project.tasks.testJar.manifest
}
}
----
include::11-filtering-contents.adoc[]
include::12-controlling-dependencies.adoc[]
include::13-controlling-merging.adoc[]
include::14-package-relocation.adoc[]
include::15-minimizing.adoc[]
include::16-reproducible-builds.adoc[]

@ -0,0 +1,27 @@
=== Filtering Shadow Jar Contents
The final contents of a shadow JAR can be filtered using the `exclude` and `include` methods inherited from Gradle's
`Jar` task type.
Refer to the https://docs.gradle.org/current/dsl/org.gradle.api.tasks.bundling.Jar.html[Jar] documentation for details
on the various versions of the methods and their behavior.
When using `exclude`/`include` with a `ShadowJar` task, the resulting copy specs are applied to the __final__ JAR
contents.
This means that, the configuration is applied to the individual files from both the project source set or __any__
of the dependencies to be merged.
.Exclude a file from Shadow Jar
[source,groovy,indent=0]
----
include::{tests}/FilteringSpec.groovy[tags=excludeFile]
----
Excludes and includes can be combined just like a normal `Jar` task, with `excludes` taking precendence over `includes`.
Additionally, ANT style patterns can be used to match multiple files.
.Configuring output using ANT patterns
[source,groovy,indent=0]
----
include::{tests}/FilteringSpec.groovy[tags=excludeOverInclude]
----

@ -0,0 +1,118 @@
=== Configuring Shadowed Dependencies
Shadow configures the default `shadowJar` task to merge all dependencies from the project's `runtime` configuration
into the final JAR.
The configurations to from which to source dependencies for the merging can be configured using the `configurations` property
of the link:{api}/tasks/ShadowJar.html[`ShadowJar`] task type.
[source,groovy,indent=0]
----
shadowJar {
configurations = [project.configurations.compile]
}
----
The above code sample would configure the `shadowJar` task to merge depdencies from only the `compile` configuration.
This means any dependency declared in the `runtime` configuration would be **not** be included in the final JAR.
[NOTE]
====
Note the literal use of `project.configurations` when setting the `configurations` attribute of a
link:{api}/tasks/ShadowJar.html[`ShadowJar`] task.
This is **required**. It maybe be tempting to specify `configurations = [configurations.compile]` but this will not
have the intended effect, as `configurations.compile` will try to delegate to the `configurations` property of the
the link:{api}/tasks/ShadowJar.html[`ShadowJar`] task instead of the `project`
====
=== Embedding Jar Files Inside Your Shadow Jar
Because of the way that Gradle handles dependency configuration, from a plugin perspective, shadow is unable to distinguish between a jar file configured as a dependency and a jar file included in the resource folder. This means that any jar found in a resource directory will be merged into the shadow jar the same as any other dependency. If your intention is to embed the jar inside, you must rename the jar as to not end with `.jar` before the shadow task begins.
=== Filtering Dependencies
Individual dependencies can be filtered from the final JAR by using the `dependencies` block of a
link:{api}/tasks/ShadowJar.html[`ShadowJar`] task.
Dependency filtering does **not** apply to transitive dependencies.
That is, excluding a dependency does not exclude any of its dependencies from the final JAR.
The `dependency` blocks provides a number of methods for resolving dependencies using the notations familiar from
Gradle's `configurations` block.
.Exclude an Module Dependency
[source,groovy,indent=0]
----
include::{tests}/FilteringSpec.groovy[tags=excludeDep]
----
.Exclude a Project Dependency
[source,groovy,indent=0]
----
include::{tests}/FilteringSpec.groovy[tags=excludeProject]
----
[NOTE]
====
While not being able to filter entire transitive dependency graphs might seem like an oversight, it is necessary
because it would not be possible to intelligently determine the build author's intended results when there is a
common dependency between two 1st level dependencies when one is excluded and the other is not.
====
==== Using Regex Patterns to Filter Dependencies
Dependencies can be filtered using regex patterns.
Coupled with the `<group>:<artifact>:<version>` notation for dependencies, this allows for excluding/including
using any of these individual fields.
.Exclude Any Version of a Dependency
[source,groovy,indent=0]
----
include::{tests}/FilteringSpec.groovy[tags=excludeDepWildcard]
----
Any of the individual fields can be safely absent and will function as though a wildcard was specified.
.Ignore Dependency Version
[source,groovy,indent=0]
----
shadowJar {
dependencies {
exclude(dependency('shadow:d'))
}
}
----
The above code snippet is functionally equivalent to the previous example.
This same patten can be used for any of the dependency notation fields.
.Ignoring An Artifact Regardless of Group
[source,groovy,indent=0]
----
shadowJar {
dependencies {
exclude(dependency(':d:1.0'))
}
}
----
.Excluding All Artifacts From Group
[source,groovy,indent=0]
----
shadowJar {
dependencies {
exclude(dependency('shadow::1.0'))
}
}
----
==== Programmatically Selecting Dependencies to Filter
If more complex decisions are needed to select the dependencies to be included, the
link:{api}/tasks/ShadowJar.html#dependencies(Action<DependencyFilter>)[`dependencies`] block provides a
method that accepts a `Closure` for selecting dependencies.
.Selecting Dependencies to Filter With a Spec
[source,groovy,indent=0]
----
include::{tests}/FilteringSpec.groovy[tags=excludeSpec]
----

@ -0,0 +1,151 @@
=== Controlling JAR Content Merging
Shadow allows for customizing the process by which the output JAR is generated through the
link:{api}/transformers/Transformer.html[`Transformer`] interface.
This is a concept that has been carried over from the original Maven Shade implementation.
A link:{api}/transformers/Transformer.html[`Transformer`] is invoked for each entry in the JAR before being written to
the final output JAR.
This allows a link:{api}/transformers/Transformer.html[`Transformer`] to determine if it should process a particular
entry and apply any modifications beforewriting the stream to the output.
.Adding a Transformer
[source,groovy,indent=0]
----
shadowJar {
transform(MyTransformer.class)
}
----
Additionally, a `Transformer` can accept a `Closure` to configure the provided `Transformer`.
.Configuring a Transformer
[source,groovy,indent=0]
----
shadowJar {
transform(MyTransformer.class) {
enable = true
}
}
----
An instantiated instance of a `Transformer` can also be provided.
.Adding a Transformer Instance
[source,groovy,indent=0]
----
shadowJar {
transform(new MyTransformer(enabled: true))
}
----
==== Merging Service Descriptor Files
Java libraries often contain service descriptors files in the `META-INF/services` directory of the JAR.
A service descriptor typically contains a line delimited list of classes that are supported for a particular __service__.
At runtime, this file is read and used to configure library or application behavior.
Multiple dependencies may use the same service descriptor file name.
In this case, it is generally desired to merge the content of each instance of the file into a single output file.
The link:{api}/transformers/ServiceFileTransformer.html[`ServiceFileTransformer`] class is used to perform this merging.
By default, it will merge each copy of a file under `META-INF/services` into a single file in the output JAR.
.Merging Service Files
[source,groovy,indent=0]
----
shadowJar {
mergeServiceFiles()
}
----
The above code snippet is a convenience syntax for calling
link:{api}/tasks/ShadowJar.html#transform(Class++<? extends Transformer>++)[`transform(ServiceFileTransformer.class)`].
[NOTE]
====
Groovy Extension Module descriptor files (located at `META-INF/services/org.codehaus.groovy.runtime.ExtensionModule`)
are ignored by the link:{api}/transformers/ServiceFileTransformer.html[`ServiceFileTransformer`].
This is due to these files having a different syntax than standard service descriptor files.
Use the link:{api}/tasks/ShadowJar.html#mergeGroovyExtensionModules()[`mergeGroovyExtensionModules()`] method to merge
these files if your dependencies contain them.
====
===== Configuring the Location of Service Descriptor Files
By default the link:{api}/transformers/ServiceFileTransformer.html[`ServiceFileTransformer`] is configured to merge
files in `META-INF/services`.
This directory can be overridden to merge descriptor files in a different location.
.Merging Service Files in a Specific Directory
[source,groovy,indent=0]
----
shadowJar {
mergeServiceFiles {
path = 'META-INF/custom'
}
}
----
===== Excluding/Including Specific Service Descriptor Files From Merging
The link:{api}/transformers/ServiceFileTransformer.html[`ServiceFileTransformer`] class supports specifying specific
files to include or exclude from merging.
.Excluding a Service Descriptor From Merging
[source,groovy,indent=0]
----
shadowJar {
mergeServiceFiles {
exclude 'META-INF/services/com.acme.*'
}
}
----
==== Merging Groovy Extension Modules
Shadow provides a specific transformer for dealing with Groovy extension module files.
This is due to their special syntax and how they need to be merged together.
The link:{api}/transformers/GroovyExtensionModuleTransformer.html[`GroovyExtensionModuleTransformer`] will handle these
files.
The link:{api}/tasks/ShadowJar.html[`ShadowJar`] task also provides a short syntax method to add this transformer.
.Merging Groovy Extension Modules
[source,groovy,indent=0]
----
shadowJar {
mergeGroovyExtensionModules()
}
----
==== Appending Text Files
Generic text files can be appended together using the
link:{api}/transformers/AppendingTransformer.html[`AppendingTransformer`].
Each file is appended using new lines to separate content.
The link:{api}/tasks/ShadowJar.html[`ShadowJar`] task provides a short syntax method of
link:{api}/tasks/ShadowJar.html#append(java.lang.String)[`append(String)`] to configure this transformer.
.Appending a Property File
[source,groovy,indent=0]
----
shadowJar {
append 'test.properties'
}
----
==== Appending XML Files
XML files require a special transformer for merging.
The link:{api}/transformers/XmlAppendingTransformer.html[`XmlAppendingTransformer`] reads each XML document and merges
each root element into a single document.
There is no short syntax method for the link:{api}/transformers/XmlAppendingTransformer.html[`XmlAppendingTransformer`].
It must be added using the link:{api}/tasks/ShadowJar.html#transform(++Class<? extends Transformer>++)[`transform`] methods.
.Appending a XML File
[source,groovy,indent=0]
----
shadowJar {
tranform(XmlAppendingTransformer.class) {
resource = 'properties.xml'
}
}
----

@ -0,0 +1,72 @@
=== Relocating Packages
Shade is capable of scanning a project's classes and relocating specific dependencies to a new location.
This is often required when one of the dependencies is susceptible to breaking changes in versions or
to classpath pollution in a downstream project.
[NOTE]
====
Google's Guava and the ASM library are typical cases where package relocation can come in handy.
====
Shadow uses the ASM library to modify class byte code to replace the package name and any import
statements for a class.
Any non-class files that are stored within a package structure are also relocated to the new location.
.Relocating a Package
[source,groovy,indent=0]
----
include::{tests}/RelocationSpec.groovy[tags=relocate]
----
The code snippet will rewrite the location for any class in the `junit.framework` to be `shadow.junit`.
For example, the class `junit.textui.TestRunner` becomes `shadow.junit.TestRunner`.
In the resulting JAR, the class file is relocated from `junit/textui/TestRunner.class` to
`shadow/junit/TestRunner.class`.
[NOTE]
====
Relocation operates at a package level.
It is not necessary to specify any patterns for matching, it will operate simply on the prefix
provided.
====
[NOTE]
====
Relocation will be applied globally to all instance of the matched prefix.
That is, it does **not** scope to __only__ the dependencies being shadowed.
Be specific as possible when configuring relocation as to avoid unintended relocations.
====
==== Filtering Relocation
Specific classes or files can be `included`/`excluded` from the relocation operation if necessary.
.Configuring Filtering for Relocation
[source,groovy,indent=0]
----
include::{tests}/RelocationSpec.groovy[tags=relocateFilter]
----
==== Automatically Relocating Dependencies
Shadow ships with a task that can be used to automatically configure all packages from all dependencies to be relocated.
To configure automatic dependency relocation, declare a task of type `ConfigureShadowRelocation` and configure the
`target` parameter to be the `ShadowJar` task you wish to auto configure. You will also need to declared a task
dependency so the tasks execute in the correct order.
.Configure Auto Relocation
[source,groovy]
----
task relocateShadowJar(type: ConfigureShadowRelocation) {
target = tasks.shadowJar
prefix = "myapp" // Default value is "shadow"
}
tasks.shadowJar.dependsOn tasks.relocateShadowJar
----
[NOTE]
====
Configuring package auto relocation can add significant time to the shadow process as it will process all dependencies
in the configurations declared to be shadowed. By default, this is the `runtime` or `runtimeClasspath` configurations.
Be mindful that some Gradle plugins (such as `java-gradle-plugin` will automatically add dependencies to your class path
(e.g. `java-gradle-plugin` automatically adds the full Gradle API to your `compile` configuratinon. You may need to
remove these dependencies if you do not intend to shadow them into your library.
====

@ -0,0 +1,25 @@
=== Minimizing
Shadow can automatically remove all classes of dependencies that are not used by the project, thereby minimizing the resulting shadowed JAR.
.Minimizing an shadow JAR
[source,groovy,indent=0]
----
shadowJar {
minimize()
}
----
A dependency can be excluded from the minimization process thereby forcing it's inclusion the shadow JAR.
This is useful when the dependency analyzer cannot find the usage of a class programmatically, for example if the class
is loaded dynamically via `Class.forName(String)`.
.Force a class to be retained during minimization
[source,groovy,indent=0]
----
shadowJar {
minimize {
exclude(dependency('org.scala-lang:.*:.*'))
}
}
----

@ -0,0 +1,16 @@
=== Reproducible Builds
Because JAR files contain the timestamp of the included files, it is often difficult to create reproducible builds
from a source commit that results in a hash identical file.
Gradle supports reproducible JAR creation by setting the timestamps of included files to a consistent value.
Shadow includes support for overriding file timestamps. By default, Shadow will preserve
the file timestamps when creating the Shadow JAR. To set timestamps to a consistent value (1980/1/1 00:00:00),
set the `preserveFileTimestamps` property to `false` on the `ShadowJar` task.
.Reset file timestamps
[source,groovy,indent=0]
----
shadowJar {
preserveFileTimestamps = false
}
----

@ -0,0 +1,20 @@
== Creating a Custom ShadowJar Task
The built in `shadowJar` task only provides an output for the `main` source set of the project.
It is possible to add arbitrary link:{api}/tasks/ShadowJar.html[`ShadowJar`] tasks to a project.
When doing so, ensure that the `configurations` property is specified to inform Shadow which dependencies to merge
into the output.
.Shadowing Test Sources and Dependencies
[source,groovy,indent=0]
----
task testJar(type: ShadowJar) {
classifier = 'tests'
from sourceSets.test.output
configurations = [project.configurations.testRuntime]
}
----
The code snippet above will geneated a shadowed JAR contain both the `main` and `test` sources as well as all `runtime`
and `testRuntime` dependencies.
The file is output to `build/libs/<project>-<version>-tests.jar`.

@ -0,0 +1,69 @@
== Publishing Shadow JARs
=== Publishing with Maven-Publish Plugin
The Shadow plugin will automatically configure the necessary tasks in the presence of Gradle's
`maven-publish` plugin.
The plugin provides the `component` method from the `shadow` extension to configure the
publication with the necessary artifact and dependencies in the POM file.
.Publishing a Shadow JAR with the Maven-Publish Plugin
[source,groovy,indent=0]
----
apply plugin: 'java'
apply plugin: 'maven-publish'
apply plugin: 'org.xbib.plugin.gradle.shadow'
publishing {
publications {
shadow(MavenPublication) { publication ->
project.shadow.component(publication)
}
}
repositories {
maven {
url "http://repo.myorg.com"
}
}
}
----
=== Publishing with Maven Plugin
The Shadow plugin will automatically configure the necessary tasks in the presence of Gradle's
`maven` plugin.
To publish the JAR, simply configure the publish location for the `uploadShadow` task and execute it.
.Publishing a Shadow JAR with the Maven Plugin
[source,groovy,indent=0]
----
apply plugin: 'java'
apply plugin: 'maven'
apply plugin: 'org.xbib.gradle.plugin.shadow'
uploadShadow {
repositories {
mavenDeployer {
repository(url: "http://repo.myorg.com")
}
}
}
----
=== Shadow Configuration and Publishing
The Shadow plugin provides a custom configuration (`configurations.shadow`) to specify
runtime dependencies that are *not* merged into the final JAR file.
When configuring publishing with the Shadow plugin, the dependencies in the `shadow`
configuration, are translated to become `RUNTIME` scoped dependencies of the
published artifact.
No other dependencies are automatically configured for inclusion in the POM file.
For example, excluded dependencies are *not* automatically added to the POM file or
if the configuration for merging are modified by specifying
`shadowJar.configurations = [configurations.myconfiguration]`, there is no automatic
configuration of the POM file.
This automatic configuration occurs _only_ when using the above methods for
configuring publishing. If this behavior is not desirable, then publishing *must*
be manually configured.

@ -0,0 +1,21 @@
== Using Shadow in Multi-Project Builds
When using Shadow in a multi-project build, project dependencies will be treated the same as
external dependencies.
That is a project dependency will be merged into the `shadowJar` output of the project that
is applying the Shadow plugin.
=== Depending on the Shadow Jar from Another Project
In a multi-project build there may be one project that applies Shadow and another that
requires the shadowed JAR as a dependency.
In this case, use Gradle's normal dependency declaration mechanism to depend on the `shadow`
configuration of the shadowed project.
.Depending On Shadow Output of Project
[source,groovy,indent=0]
----
dependencies {
compile project(path: 'api', configuration: 'shadow')
}
----

@ -0,0 +1,49 @@
== Using Shadow to Package Gradle Plugins
In some scenarios, writing a Gradle plugin can be problematic because your plugin may depend on a version that
conflicts with the same dependency provided by the Gradle runtime. If this is the case, then you can utilize Shadow
to relocate your dependencies to a different package name to avoid the collision.
Configuring the relocation has always been possible, but the build author is required to know all the package names
before hand. Shadow introduces a new task type `ConfigureShadowRelocation`.
Tasks of this type are configured to target an instance of a `ShadowJar` task and run immediately before it.
The `ConfigureShadowRelocation` task, scans the dependencies from the configurations specified on the associated
`ShadowJar` task and collects the package names contained within them. It then configures relocation for these
packages using the specified `prefix` on the associated `ShadowJar` task.
While this is useful for developing Gradle plugins, nothing about the `ConfigureShadowRelocation` task is tied to
Gradle projects. It can be used for standard Java or Groovy projects.
A simple Gradle plugin can use this feature by applying the `shadow` plugin and configuring the dependencies
like so:
[source,groovy,subs="+attributes"]
----
import org.xbib.gradle.plugin.shadow.tasks.ConfigureShadowRelocation
plugins {
id 'java'
id 'org.xbib.gradle.plugin.shadow' version '{project-version}'
}
dependencies {
shadow localGroovy()
shadow gradleApi()
compile 'org.ow2.asm:asm:7.0-beta'
compile 'org.ow2.asm:asm-commons:7.0-beta'
compile 'org.ow2.asm:asm-util:7.0-beta'
}
task relocateShadowJar(type: ConfigureShadowRelocation) {
target = tasks.shadowJar
}
tasks.shadowJar.dependsOn tasks.relocateShadowJar
----
Note that the `localGroovy()` and `gradleApi()` dependencies are added to the `shadow` configuration instead of the
normal `compile` configuration. These dependencies are provided by Gradle to compile your project but are ultimately
provided by the Gradle runtime when executing the plugin. Thus, it is __not__ advisable to bundle these dependencies
with your plugin.

@ -0,0 +1,23 @@
== About This Project
John Engelman about the original Shadow plugin:
____
I started this project in December of 2012. We were working on converting from a monolithic application into a the
new hot jazz of "microservices" using Dropwizard.
I had also just started learning about Gradle and I knew that the incremental build system it provided would benefit
our development team greatly.
Unfortunately, the closest thing that Gradle had to Maven's Shade plugin was its ability to create application TARs and
ZIPs.
So, Charlie Knudsen and myself set out to port the existing Shade code into a Gradle plugin.
This port is what existing up until the `0.9` milestone releases for Shadow.
It functioned, but it wasn't idiomatic Gradle by any means.
Starting with 0.9, Shadow was rewritten from the ground up as standard Gradle plugin and leveraged as much of Gradle's
classes and concepts as possible.
At the same time as the 0.9 release, Gradle was announcing the https://plugins.gradle.org[Gradle Plugin Portal] and
so Shadow was published there.
Shadow has had nearly ~900,000 downloads from Bintray and countless more from the Gradle Plugin Portal.
____

@ -0,0 +1,32 @@
:src: ../
:tests: {src}/test/groovy/org/xbib/gradle/plugin/shadow
:api: api/org/xbib/gradle/plugin/shadow
:docinfo1:
ifdef::env-github[]
:note-caption: :information-source:
endif::[]
= Gradle Shadow Plugin User Guide & Examples
:revnumber: {project-version}
NOTE: This documentation was taken from the original Shadow Plugin and is copyrighted by John R. Engelman.
References to older original Shadow Plugin versions have been removed for consistency.
The references to the application mode (runShadow) are no longer supported and have been removed.
To create application images, use Java Platform Module System, especially the `jlink` tool.
link:api/index.html[API Docs]
include::00-intro.adoc[]
include::10-configuring.adoc[]
include::20-custom-tasks.adoc[]
include::40-publishing.adoc[]
include::50-multi-project-builds.adoc[]
include::60-shadowing-plugins.adoc[]
include::99-about.adoc[]

@ -0,0 +1,21 @@
package org.xbib.gradle.plugin.shadow
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.util.GradleVersion
class ShadowBasePlugin implements Plugin<Project> {
static final String EXTENSION_NAME = 'shadow'
static final String CONFIGURATION_NAME = 'shadow'
@Override
void apply(Project project) {
if (GradleVersion.current() < GradleVersion.version('4.10')) {
throw new IllegalArgumentException('shadow requires at least Gradle 4.10')
}
project.extensions.create(EXTENSION_NAME, ShadowExtension, project)
project.configurations.create(CONFIGURATION_NAME)
}
}

@ -0,0 +1,38 @@
package org.xbib.gradle.plugin.shadow
import org.gradle.api.Project
import org.gradle.api.artifacts.SelfResolvingDependency
import org.gradle.api.file.CopySpec
import org.gradle.api.publish.maven.MavenPom
import org.gradle.api.publish.maven.MavenPublication
class ShadowExtension {
CopySpec applicationDistribution
Project project
ShadowExtension(Project project) {
this.project = project
applicationDistribution = project.copySpec {}
}
void component(MavenPublication publication) {
publication.artifact(project.tasks.shadowJar)
publication.pom { MavenPom pom ->
pom.withXml { xml ->
def dependenciesNode = xml.asNode().appendNode('dependencies')
project.configurations.shadow.allDependencies.each {
if (! (it instanceof SelfResolvingDependency)) {
def dependencyNode = dependenciesNode.appendNode('dependency')
dependencyNode.appendNode('groupId', it.group)
dependencyNode.appendNode('artifactId', it.name)
dependencyNode.appendNode('version', it.version)
dependencyNode.appendNode('scope', 'runtime')
}
}
}
}
}
}

@ -0,0 +1,58 @@
package org.xbib.gradle.plugin.shadow
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginConvention
import org.gradle.configuration.project.ProjectConfigurationActionContainer
import org.xbib.gradle.plugin.shadow.tasks.ShadowJar
import javax.inject.Inject
class ShadowJavaPlugin implements Plugin<Project> {
static final String SHADOW_JAR_TASK_NAME = 'shadowJar'
static final String SHADOW_GROUP = 'Shadow'
private final ProjectConfigurationActionContainer configurationActionContainer;
@Inject
ShadowJavaPlugin(ProjectConfigurationActionContainer configurationActionContainer) {
this.configurationActionContainer = configurationActionContainer
}
@Override
void apply(Project project) {
configureShadowTask(project)
project.configurations.compileClasspath.extendsFrom project.configurations.shadow
}
protected void configureShadowTask(Project project) {
JavaPluginConvention convention = project.convention.getPlugin(JavaPluginConvention)
project.tasks.register(SHADOW_JAR_TASK_NAME, ShadowJar) { shadow ->
shadow.group = SHADOW_GROUP
shadow.description = 'Create a combined JAR of project and runtime dependencies'
shadow.archiveClassifier.set("all")
shadow.manifest.inheritFrom project.tasks.jar.manifest
def libsProvider = project.provider { -> [project.tasks.jar.manifest.attributes.get('Class-Path')] }
def files = project.objects.fileCollection().from { ->
project.configurations.findByName(ShadowBasePlugin.CONFIGURATION_NAME)
}
shadow.doFirst {
if (!files.empty) {
def libs = libsProvider.get()
libs.addAll files.collect { "${it.name}" }
manifest.attributes 'Class-Path': libs.findAll { it }.join(' ')
}
}
shadow.from(convention.sourceSets.main.output)
shadow.configurations = [project.configurations.findByName('runtimeClasspath') ?
project.configurations.runtimeClasspath : project.configurations.runtime]
shadow.exclude('META-INF/INDEX.LIST', 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'module-info.class')
shadow.dependencies {
exclude(dependency(project.dependencies.gradleApi()))
}
project.artifacts.add(ShadowBasePlugin.CONFIGURATION_NAME, shadow)
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save