GuidePlugin.groovy
001 /*
002  * SPDX-License-Identifier: Apache-2.0
003  *
004  * Copyright 2018-2019 Andres Almiray.
005  *
006  * Licensed under the Apache License, Version 2.0 (the "License");
007  * you may not use this file except in compliance with the License.
008  * You may obtain a copy of the License at
009  *
010  *     https://www.apache.org/licenses/LICENSE-2.0
011  *
012  * Unless required by applicable law or agreed to in writing, software
013  * distributed under the License is distributed on an "AS IS" BASIS,
014  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015  * See the License for the specific language governing permissions and
016  * limitations under the License.
017  */
018 package org.kordamp.gradle.plugin.guide
019 
020 import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin
021 import org.asciidoctor.gradle.jvm.AsciidoctorTask
022 import org.gradle.BuildAdapter
023 import org.gradle.api.Action
024 import org.gradle.api.DefaultTask
025 import org.gradle.api.Project
026 import org.gradle.api.Task
027 import org.gradle.api.invocation.Gradle
028 import org.gradle.api.tasks.Copy
029 import org.gradle.api.tasks.bundling.Zip
030 import org.kordamp.gradle.plugin.AbstractKordampPlugin
031 import org.kordamp.gradle.plugin.apidoc.ApidocPlugin
032 import org.kordamp.gradle.plugin.base.BasePlugin
033 import org.kordamp.gradle.plugin.base.ProjectConfigurationExtension
034 import org.kordamp.gradle.plugin.sourcehtml.SourceHtmlPlugin
035 
036 import static org.kordamp.gradle.PluginUtils.resolveEffectiveConfig
037 
038 /**
039  @author Andres Almiray
040  @since 0.3.0
041  */
042 class GuidePlugin extends AbstractKordampPlugin {
043     static final String CREATE_GUIDE_TASK_NAME = 'createGuide'
044     static final String INIT_GUIDE_TASK_NAME = 'initGuide'
045     static final String ZIP_GUIDE_TASK_NAME = 'zipGuide'
046     static final String ASCIIDOCTOR = 'asciidoctor'
047     static final String ASCIIDOCTOR_SRC_DIR = 'src/docs/asciidoc'
048     static final String ASCIIDOCTOR_RESOURCE_DIR = 'src/docs/resources'
049 
050     Project project
051 
052     void apply(Project project) {
053         this.project = project
054 
055         if (hasBeenVisited(project)) {
056             return
057         }
058         setVisited(project, true)
059 
060         BasePlugin.applyIfMissing(project)
061         ApidocPlugin.applyIfMissing(project.rootProject)
062         SourceHtmlPlugin.applyIfMissing(project.rootProject)
063         project.pluginManager.apply(AsciidoctorJPlugin)
064 
065         project.extensions.create('guide', GuideExtension, project)
066 
067         project.afterEvaluate {
068             configureAsciidoctorTask(project)
069             createGuideTaskIfNeeded(project)
070             createInitGuideTask(project)
071         }
072     }
073 
074     static void applyIfMissing(Project project) {
075         if (!project.plugins.findPlugin(GuidePlugin)) {
076             project.pluginManager.apply(GuidePlugin)
077         }
078     }
079 
080     private void configureAsciidoctorTask(Project project) {
081         project.tasks.named(ASCIIDOCTOR).configure(new Action<AsciidoctorTask>() {
082             @Override
083             void execute(AsciidoctorTask t) {
084                 ProjectConfigurationExtension effectiveConfig = resolveEffectiveConfig(project.rootProject?: resolveEffectiveConfig(project)
085 
086                 Map attrs = [:]
087                 attrs.putAll(t.attributes)
088                 checkAttribute(attrs, t.attributes, 'toc', 'left')
089                 checkAttribute(attrs, t.attributes, 'doctype', 'book')
090                 checkAttribute(attrs, t.attributes, 'icons', 'font')
091                 checkAttribute(attrs, t.attributes, 'encoding', 'utf-8')
092                 checkAttribute(attrs, t.attributes, 'sectlink', true)
093                 checkAttribute(attrs, t.attributes, 'sectanchors', true)
094                 checkAttribute(attrs, t.attributes, 'numbered', true)
095                 checkAttribute(attrs, t.attributes, 'linkattrs', true)
096                 checkAttribute(attrs, t.attributes, 'imagesdir', 'images')
097                 checkAttribute(attrs, t.attributes, 'linkcss', true)
098                 checkAttribute(attrs, t.attributes, 'source-highlighter', 'coderay')
099                 checkAttribute(attrs, t.attributes, 'coderay-linenums-mode', 'table')
100                 checkAttribute(attrs, t.attributes, 'project-title', effectiveConfig.info.description)
101                 checkAttribute(attrs, t.attributes, 'project-inception-year', effectiveConfig.info.inceptionYear)
102                 checkAttribute(attrs, t.attributes, 'project-copyright-year', effectiveConfig.info.copyrightYear)
103                 checkAttribute(attrs, t.attributes, 'project-author', effectiveConfig.info.getAuthors().join(', '))
104                 checkAttribute(attrs, t.attributes, 'project-url', effectiveConfig.info.url)
105                 checkAttribute(attrs, t.attributes, 'project-scm', effectiveConfig.info.links.scm)
106                 checkAttribute(attrs, t.attributes, 'project-issue-tracker', effectiveConfig.info.links.issueTracker)
107                 checkAttribute(attrs, t.attributes, 'project-group', project.group)
108                 checkAttribute(attrs, t.attributes, 'project-version', project.version)
109                 checkAttribute(attrs, t.attributes, 'project-name', project.rootProject.name)
110 
111                 checkAttribute(attrs, t.attributes, 'build-by', effectiveConfig.buildInfo.buildBy)
112                 checkAttribute(attrs, t.attributes, 'build-date', effectiveConfig.buildInfo.buildDate)
113                 checkAttribute(attrs, t.attributes, 'build-time', effectiveConfig.buildInfo.buildTime)
114                 checkAttribute(attrs, t.attributes, 'build-revision', effectiveConfig.buildInfo.buildRevision)
115                 checkAttribute(attrs, t.attributes, 'build-jdk', effectiveConfig.buildInfo.buildJdk)
116                 checkAttribute(attrs, t.attributes, 'build-created-by', effectiveConfig.buildInfo.buildCreatedBy)
117 
118                 t.attributes(attrs)
119 
120                 t.sourceDir = project.file(ASCIIDOCTOR_SRC_DIR)
121 
122                 t.sources {
123                     include 'index.adoc'
124                 }
125 
126                 t.resources {
127                     from project.file(ASCIIDOCTOR_RESOURCE_DIR)
128                 }
129             }
130         })
131     }
132 
133     private static void checkAttribute(Map dest, Map src, String key, value) {
134         if (!src.containsKey(key)) dest[key + '@'= value
135     }
136 
137     private void createGuideTaskIfNeeded(Project project) {
138         Task guideTask = project.tasks.findByName(CREATE_GUIDE_TASK_NAME)
139 
140         if (!guideTask) {
141             guideTask = project.tasks.create(CREATE_GUIDE_TASK_NAME, Copy) {
142                 group 'Documentation'
143                 description 'Creates an Asciidoctor based guide.'
144             }
145         }
146 
147         guideTask.configure {
148             dependsOn project.tasks.named(ASCIIDOCTOR)
149             destinationDir project.file("${project.buildDir}/guide")
150             from(project.tasks.asciidoctor.outputDir)
151         }
152 
153         Task zipGuideTask = project.tasks.findByName(ZIP_GUIDE_TASK_NAME)
154         if (!zipGuideTask) {
155             zipGuideTask = project.tasks.create(ZIP_GUIDE_TASK_NAME, Zip) {
156                 dependsOn guideTask
157                 group 'Documentation'
158                 description 'An archive of the generated guide.'
159                 baseName = project.rootProject.name + '-guide'
160                 from guideTask.destinationDir
161             }
162         }
163 
164         project.rootProject.gradle.addBuildListener(new BuildAdapter() {
165             @Override
166             void projectsEvaluated(Gradle gradle) {
167                 GuideExtension extension = project.extensions.findByType(GuideExtension)
168 
169                 Task task = project.rootProject.tasks.findByName(ApidocPlugin.AGGREGATE_JAVADOCS_TASK_NAME)
170                 if (task?.enabled) {
171                     guideTask.configure {
172                         dependsOn task
173                         from(task.destinationDir) { into extension.javadocApiDir }
174                     }
175                 }
176 
177                 task = project.rootProject.tasks.findByName(ApidocPlugin.AGGREGATE_GROOVYDOCS_TASK_NAME)
178                 if (task?.enabled) {
179                     guideTask.configure {
180                         dependsOn task
181                         from(task.destinationDir) { into extension.groovydocApiDir }
182                     }
183                 }
184 
185                 task = project.rootProject.tasks.findByName(SourceHtmlPlugin.AGGREGATE_SOURCE_HTML_TASK_NAME)
186                 if (task?.enabled) {
187                     guideTask.configure {
188                         dependsOn project.rootProject.tasks.findByName(SourceHtmlPlugin.AGGREGATE_SOURCE_HTML_TASK_NAME)
189                         from(task.destinationDir) { into extension.sourceHtmlDir }
190                     }
191                 }
192             }
193         })
194     }
195 
196     private void createInitGuideTask(Project project) {
197         project.tasks.create(INIT_GUIDE_TASK_NAME, DefaultTask) {
198             group 'Project Setup'
199             description 'Initializes directories and files required by the guide.'
200             outputs.dir(ASCIIDOCTOR_SRC_DIR)
201             outputs.dir(ASCIIDOCTOR_RESOURCE_DIR)
202             doFirst {
203                 GuidePlugin.initGuide(project)
204             }
205         }
206     }
207 
208     static void initGuide(Project project) {
209         GuideExtension extension = project.extensions.findByType(GuideExtension)
210 
211         project.file(ASCIIDOCTOR_RESOURCE_DIR).mkdirs()
212         File asciidocDir = project.file(ASCIIDOCTOR_SRC_DIR)
213         asciidocDir.mkdirs()
214 
215         touchFile(project.file("${asciidocDir}/_links.adoc"))
216 
217         File index = touchFile(project.file("${asciidocDir}/index.adoc"))
218         if (!index.text) {
219             index.text = """|= {project-title}
220                             |:author: {project-author}
221                             |:revnumber: {project-version}
222                             |:toclevels: 4
223                             |:docinfo1:
224                             |
225                             |include::{includedir}/_links.adoc[]
226                             |
227                             |:leveloffset: 1
228                             |include::{includedir}/introduction.adoc[]
229                             |include::{includedir}/usage.adoc[]
230                             |
231                             |= Links
232                             |
233                             |link:${extension.javadocApiDir}/index.html[Javadoc, window="_blank"]
234                             |
235                             |link:${extension.sourceHtmlDir}/index.html[Source, window="_blank"]""".stripMargin('|')
236         }
237 
238         File introduction = touchFile(project.file("${asciidocDir}/introduction.adoc"))
239         if (!introduction.text) {
240             introduction.text = """|
241                                    |[[_introduction]]
242                                    |= Introduction
243                                    |
244                                    |Lorem ipsum dolor sit amet
245                                    |""".stripMargin('|')
246         }
247 
248         File usage = touchFile(project.file("${asciidocDir}/usage.adoc"))
249         if (!usage.text) {
250             usage.text = """|
251                                    |[[_usage]]
252                                    |= Usage
253                                    |
254                                    |Lorem ipsum dolor sit amet
255                                    |""".stripMargin('|')
256         }
257     }
258 
259     static File touchFile(File file) {
260         file.parentFile.mkdirs()
261         if (!file.exists()) {
262             file.text = ''
263         }
264         file
265     }
266 }