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