blob: 77c9c483cfeca04f2de7aef50c16e4a1601f853b [file] [log] [blame]
package generator
import groovy.text.markup.MarkupTemplateEngine
import groovy.text.markup.TemplateConfiguration
import groovy.transform.CompileStatic
import model.Changelog
import model.Page
import model.Section
import model.SectionItem
import model.SiteMap
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.WatchEvent
import static generator.DocumentationHTMLCleaner.cleanupPage
import static generator.DocumentationHTMLCleaner.parsePage
import static java.nio.file.StandardWatchEventKinds.*
@CompileStatic
class SiteGenerator {
private final static Closure SEMANTIC_SORT = { String v1, String v2 ->
List<String> items1 = decomposeVersion(v1)
List<String> items2 = decomposeVersion(v2)
for (int i=0; i<Math.max(items1.size(),items2.size());i++) {
if (i>=items2.size()) {
return 1
}
if (i>=items1.size()) {
return -1
}
def p1 = items1[i]
def p2 = items2[i]
if (p1.isNumber()) {
if (p2.isNumber()) {
def pi1 = p1.toInteger()
def pi2 = p2.toInteger()
if (pi1 < pi2) {
return 1
} else if (pi1 > pi2) {
return -1
}
} else {
return -1
}
} else if (p2.isNumber()) {
return 1
} else {
return p2 <=> p1
}
}
0
}
File sourcesDir
File outputDir
private MarkupTemplateEngine tplEngine
private SiteMap siteMap
void setup() {
println "Generating website using Groovy ${GroovySystem.version}"
def tplConf = new TemplateConfiguration()
tplConf.autoIndent = true
tplConf.autoNewLine = true
tplConf.baseTemplateClass = PageTemplate
def classLoader = new URLClassLoader([sourcesDir.toURI().toURL()] as URL[], this.class.classLoader)
tplEngine = new MarkupTemplateEngine(classLoader, tplConf, new MarkupTemplateEngine.CachingTemplateResolver())
siteMap = SiteMap.from(new File(sourcesDir, "sitemap.groovy"))
}
void render(String page, String target = null, Map model = [:], String baseDir=null) {
model.menu = siteMap.menu
model.currentPage = target
target = target ?: page
File root
if (baseDir) {
root = new File(outputDir, baseDir)
model[PageTemplate.BASEDIR] = baseDir
root.mkdirs()
} else {
root = outputDir
}
new File(root,"${target}.html").write(tplEngine.createTemplateByPath("pages/${page}.groovy").make(model).toString(), 'utf-8')
}
void generateSite() {
long sd = System.currentTimeMillis()
setup()
def cacheDir = new File(new File('build'), 'cache')
cacheDir.mkdirs()
println "Cache directory: $cacheDir"
def changelogs = ChangelogParser.fetchReleaseNotes(cacheDir);
renderDocumentation()
renderPages(changelogs)
renderChangelogs(changelogs)
renderReleaseNotes()
renderWiki()
long dur = System.currentTimeMillis() - sd
println "Generated site into $outputDir in ${dur}ms"
}
private List<Section> renderDocumentation() {
siteMap.documentationSections.each { Section section ->
section.items.each { SectionItem item ->
if (item.generate) {
println "Generating documentation page [$item.name]"
render 'docpage', item.targetFilename, [
category: 'Learn',
title : item.name,
page : parsePage("${DocUtils.DOCS_BASEURL}/html/documentation/${item.sourceFilename}.html")]
}
}
}
}
private List<Page> renderPages(List<Changelog> changelogs) {
siteMap.pages.each { Page page ->
println "Rendering individual page [$page.source]"
if ('changelogs' == page.source) {
page.model.versions = changelogs.groovyVersion.sort(SEMANTIC_SORT)
}
render page.source, page.target, page.model
}
}
private List<Changelog> renderChangelogs(List<Changelog> changelogs) {
changelogs.each {
println "Rendering changelog for Groovy $it.groovyVersion"
render 'changelog', "changelog-$it.groovyVersion", [groovyVersion: it.groovyVersion, issues: it.issues], 'changelogs'
}
}
private void renderReleaseNotes() {
def releaseNotesVersions = new TreeSet<String>(new Comparator<String>() {
@Override
int compare(final String v1, final String v2) {
v2.toDouble() <=> v1.toDouble()
}
})
new File(sourcesDir, 'releasenotes').eachFile { File file ->
def name = file.name.substring(0, file.name.lastIndexOf('.adoc'))
def version = name - 'groovy-'
releaseNotesVersions << version
println "Rendering release notes for Groovy $version"
render 'release-notes', name, [notes: file.getText('utf-8'), groovyVersion: version], 'releasenotes'
}
render 'releases', 'releases', [versions: releaseNotesVersions]
}
private void renderWiki() {
def asciidoctor = AsciidoctorFactory.instance
println "Rendering wiki"
def wikiDir = new File(sourcesDir, "wiki")
wikiDir.eachFileRecurse { f->
if (f.name.endsWith('.adoc')) {
def header = asciidoctor.readDocumentHeader(f)
def bn = f.name.substring(0,f.name.lastIndexOf('.adoc'))
println "Rendering $header.documentTitle.main by ${header.author?.fullName}"
def relativePath = []
def p = f.parentFile
while (p!=wikiDir) {
relativePath << p.name
p = p.parentFile
}
String baseDir = relativePath?"wiki${File.separator}${relativePath.join(File.separator)}":'wiki'
render 'wiki', bn, [notes:f.getText('utf-8'), header: header], baseDir
println baseDir
}
}
}
static void main(String... args) {
def sourcesDir = args[0] as File
def outputDir = args[1] as File
def generator = new SiteGenerator(sourcesDir: sourcesDir, outputDir: outputDir)
boolean watchMode = args.length > 2 ? Boolean.valueOf(args[2]) : false
generator.generateSite()
if (watchMode) {
println "Started watch mode"
def watcher = FileSystems.default.newWatchService()
sourcesDir.toPath().register(watcher,
ENTRY_CREATE,
ENTRY_DELETE,
ENTRY_MODIFY)
sourcesDir.eachDirRecurse { File f ->
f.toPath().register(watcher,
ENTRY_CREATE,
ENTRY_DELETE,
ENTRY_MODIFY)
}
def existingDirectories = ['pages', 'layouts', 'includes', 'html', 'assets', 'css', 'fonts', 'img', 'js', 'vendor']
while (true) {
def key = watcher.take()
def pollEvents = (List<WatchEvent<Path>>) key.pollEvents()
def changed = pollEvents.collect { "${it.context()}".toString() }.join(', ')
// only generate when the event refers to the actual file modified / created / added
// as otherwise the watcher service generates two events:
// 1) one for directory containing the modified file, and
// 2) one for the actual file being modified
// this checks avoid getting two events for one change
if (existingDirectories.every { !changed.contains(it) }) {
try {
println "Regenerating site due to changes in: ${changed}"
// todo: selective regeneration
generator.generateSite()
} finally {
key.reset()
}
}
}
}
}
static List<String> decomposeVersion(String version) {
String qualifier = ''
if (version.indexOf('-')>0) {
qualifier = version.substring(version.indexOf('-'))
version = version - qualifier
}
List<String> parts = version.split(/\./).toList()
if (qualifier) {
parts << qualifier
}
parts
}
}