commit
aefd0bc661
@ -4,6 +4,7 @@ import com.jessebrault.ssg.buildscript.GroovyBuildScriptRunner
|
||||
import com.jessebrault.ssg.part.GspPartRenderer
|
||||
import com.jessebrault.ssg.part.PartFilePartsProvider
|
||||
import com.jessebrault.ssg.part.PartType
|
||||
import com.jessebrault.ssg.provider.WithWatchableDir
|
||||
import com.jessebrault.ssg.specialpage.GspSpecialPageRenderer
|
||||
import com.jessebrault.ssg.specialpage.SpecialPageFileSpecialPagesProvider
|
||||
import com.jessebrault.ssg.specialpage.SpecialPageType
|
||||
@ -14,12 +15,19 @@ import com.jessebrault.ssg.text.MarkdownFrontMatterGetter
|
||||
import com.jessebrault.ssg.text.MarkdownTextRenderer
|
||||
import com.jessebrault.ssg.text.TextFileTextsProvider
|
||||
import com.jessebrault.ssg.text.TextType
|
||||
import groovy.io.FileType
|
||||
import org.apache.logging.log4j.Level
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.apache.logging.log4j.Logger
|
||||
import org.apache.logging.log4j.core.LoggerContext
|
||||
import picocli.CommandLine
|
||||
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardWatchEventKinds
|
||||
import java.nio.file.WatchEvent
|
||||
import java.nio.file.WatchKey
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
@CommandLine.Command(
|
||||
@ -32,6 +40,10 @@ class StaticSiteGeneratorCli implements Callable<Integer> {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(StaticSiteGeneratorCli)
|
||||
|
||||
static void main(String[] args) {
|
||||
System.exit(new CommandLine(StaticSiteGeneratorCli).execute(args))
|
||||
}
|
||||
|
||||
static class LogLevel {
|
||||
|
||||
@CommandLine.Option(names = ['--info'], description = 'Log at INFO level.')
|
||||
@ -45,13 +57,12 @@ class StaticSiteGeneratorCli implements Callable<Integer> {
|
||||
|
||||
}
|
||||
|
||||
static void main(String[] args) {
|
||||
System.exit(new CommandLine(StaticSiteGeneratorCli).execute(args))
|
||||
}
|
||||
|
||||
@CommandLine.ArgGroup(exclusive = true, heading = 'Log Level')
|
||||
LogLevel logLevel
|
||||
|
||||
@CommandLine.Option(names = ['-w', '--watch'], description = 'Run in watch mode.')
|
||||
boolean watch
|
||||
|
||||
@Override
|
||||
Integer call() {
|
||||
logger.traceEntry()
|
||||
@ -110,6 +121,17 @@ class StaticSiteGeneratorCli implements Callable<Integer> {
|
||||
// Get ssg object
|
||||
def ssg = new SimpleStaticSiteGenerator()
|
||||
|
||||
if (this.watch) {
|
||||
generate(builds, ssg)
|
||||
watch(builds, ssg)
|
||||
} else {
|
||||
generate(builds, ssg)
|
||||
}
|
||||
}
|
||||
|
||||
private static Integer generate(Collection<Build> builds, StaticSiteGenerator ssg) {
|
||||
logger.traceEntry('builds: {}, ssg: {}', builds, ssg)
|
||||
|
||||
def hadDiagnostics = false
|
||||
// Do each build
|
||||
builds.each {
|
||||
@ -131,4 +153,94 @@ class StaticSiteGeneratorCli implements Callable<Integer> {
|
||||
logger.traceExit(hadDiagnostics ? 1 : 0)
|
||||
}
|
||||
|
||||
private static Integer watch(Collection<Build> builds, StaticSiteGenerator ssg) {
|
||||
logger.traceEntry('builds: {}, ssg: {}', builds, ssg)
|
||||
|
||||
// Setup watchService and watchKeys
|
||||
def watchService = FileSystems.getDefault().newWatchService()
|
||||
Map<WatchKey, Path> watchKeys = [:]
|
||||
|
||||
// Our Closure to register a directory path
|
||||
def registerPath = { Path path ->
|
||||
if (!Files.isDirectory(path)) {
|
||||
throw new IllegalArgumentException('path must be a directory, given: ' + path)
|
||||
}
|
||||
logger.debug('registering dir with path: {}', path)
|
||||
def watchKey = path.register(
|
||||
watchService,
|
||||
StandardWatchEventKinds.ENTRY_CREATE,
|
||||
StandardWatchEventKinds.ENTRY_DELETE,
|
||||
StandardWatchEventKinds.ENTRY_MODIFY
|
||||
)
|
||||
watchKeys[watchKey] = path
|
||||
logger.debug('watchKeys: {}', watchKeys)
|
||||
}
|
||||
|
||||
// Get all base watchableDirs
|
||||
Collection<WithWatchableDir> watchableProviders = []
|
||||
builds.each {
|
||||
it.config.textProviders.each {
|
||||
if (it instanceof WithWatchableDir) {
|
||||
watchableProviders << it
|
||||
}
|
||||
}
|
||||
it.config.templatesProviders.each {
|
||||
if (it instanceof WithWatchableDir) {
|
||||
watchableProviders << it
|
||||
}
|
||||
}
|
||||
it.config.partsProviders.each {
|
||||
if (it instanceof WithWatchableDir) {
|
||||
watchableProviders << it
|
||||
}
|
||||
}
|
||||
it.config.specialPagesProviders.each {
|
||||
if (it instanceof WithWatchableDir) {
|
||||
watchableProviders << it
|
||||
}
|
||||
}
|
||||
}
|
||||
// register them and their child directories using the Closure above
|
||||
watchableProviders.each {
|
||||
def baseDirFile = it.watchableDir
|
||||
registerPath(baseDirFile.toPath())
|
||||
baseDirFile.eachFile(FileType.DIRECTORIES) {
|
||||
registerPath(it.toPath())
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection GroovyInfiniteLoopStatement
|
||||
while (true) {
|
||||
def watchKey = watchService.take()
|
||||
def path = watchKeys[watchKey]
|
||||
if (path == null) {
|
||||
logger.warn('unexpected watchKey: {}', watchKey)
|
||||
} else {
|
||||
watchKey.pollEvents().each {
|
||||
assert it instanceof WatchEvent<Path>
|
||||
def childName = it.context()
|
||||
def childPath = path.resolve(childName)
|
||||
if (it.kind() == StandardWatchEventKinds.ENTRY_CREATE && Files.isDirectory(childPath)) {
|
||||
registerPath(childPath)
|
||||
} else if (Files.isRegularFile(childPath)) {
|
||||
logger.debug('detected {} for regularFile with path {}', it.kind(), childPath)
|
||||
def t = new Thread({
|
||||
generate(builds, ssg)
|
||||
})
|
||||
t.setName('workerThread')
|
||||
t.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
def valid = watchKey.reset()
|
||||
if (!valid) {
|
||||
def removedPath = watchKeys.remove(watchKey)
|
||||
logger.debug('removed path: {}', removedPath)
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection GroovyUnreachableStatement
|
||||
logger.traceExit(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,10 +26,10 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
|
||||
def config = build.config
|
||||
|
||||
// Get all texts, templates, parts, and specialPages
|
||||
def texts = config.textProviders.collectMany { it.getTextFiles() }
|
||||
def templates = config.templatesProviders.collectMany { it.getTemplates() }
|
||||
def parts = config.partsProviders.collectMany { it.getParts() }
|
||||
def specialPages = config.specialPagesProviders.collectMany { it.getSpecialPages() }
|
||||
def texts = config.textProviders.collectMany { it.provide() }
|
||||
def templates = config.templatesProviders.collectMany { it.provide() }
|
||||
def parts = config.partsProviders.collectMany { it.provide() }
|
||||
def specialPages = config.specialPagesProviders.collectMany { it.provide() }
|
||||
|
||||
logger.debug('\n\ttexts: {}\n\ttemplates: {}\n\tparts: {}\n\tspecialPages: {}', texts, templates, parts, specialPages)
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg
|
||||
|
||||
trait WatchableProvider {
|
||||
File watchableDir
|
||||
}
|
@ -1,23 +1,28 @@
|
||||
package com.jessebrault.ssg.part
|
||||
|
||||
import com.jessebrault.ssg.provider.WithWatchableDir
|
||||
import com.jessebrault.ssg.util.FileNameHandler
|
||||
import groovy.io.FileType
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import groovy.transform.TupleConstructor
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@TupleConstructor(includeFields = true, defaults = false)
|
||||
@NullCheck
|
||||
@EqualsAndHashCode(includeFields = true)
|
||||
class PartFilePartsProvider implements PartsProvider {
|
||||
class PartFilePartsProvider implements PartsProvider, WithWatchableDir {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PartFilePartsProvider)
|
||||
|
||||
private final Collection<PartType> partTypes
|
||||
private final File partsDir
|
||||
|
||||
PartFilePartsProvider(Collection<PartType> partTypes, File partsDir) {
|
||||
this.partTypes = partTypes
|
||||
this.partsDir = partsDir
|
||||
this.watchableDir = this.partsDir
|
||||
}
|
||||
|
||||
private PartType getPartType(File file) {
|
||||
partTypes.find {
|
||||
it.ids.contains(new FileNameHandler(file).getExtension())
|
||||
@ -25,7 +30,7 @@ class PartFilePartsProvider implements PartsProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Part> getParts() {
|
||||
Collection<Part> provide() {
|
||||
if (!partsDir.isDirectory()) {
|
||||
throw new IllegalArgumentException('partsDir must be a directory')
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.jessebrault.ssg.part
|
||||
|
||||
interface PartsProvider {
|
||||
Collection<Part> getParts()
|
||||
import com.jessebrault.ssg.provider.Provider
|
||||
|
||||
interface PartsProvider extends Provider<Collection<Part>> {
|
||||
Collection<PartType> getPartTypes()
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.provider
|
||||
|
||||
interface Provider<T> {
|
||||
T provide()
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.provider
|
||||
|
||||
trait WithWatchableDir {
|
||||
File watchableDir
|
||||
}
|
@ -1,24 +1,29 @@
|
||||
package com.jessebrault.ssg.specialpage
|
||||
|
||||
import com.jessebrault.ssg.provider.WithWatchableDir
|
||||
import com.jessebrault.ssg.util.FileNameHandler
|
||||
import com.jessebrault.ssg.util.RelativePathHandler
|
||||
import groovy.io.FileType
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import groovy.transform.TupleConstructor
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@TupleConstructor(includeFields = true, defaults = false)
|
||||
@NullCheck
|
||||
@EqualsAndHashCode(includeFields = true)
|
||||
class SpecialPageFileSpecialPagesProvider implements SpecialPagesProvider {
|
||||
class SpecialPageFileSpecialPagesProvider implements SpecialPagesProvider, WithWatchableDir {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(SpecialPageFileSpecialPagesProvider)
|
||||
|
||||
private final Collection<SpecialPageType> specialPageTypes
|
||||
private final File specialPagesDir
|
||||
|
||||
SpecialPageFileSpecialPagesProvider(Collection<SpecialPageType> specialPageTypes, File specialPagesDir) {
|
||||
this.specialPageTypes = specialPageTypes
|
||||
this.specialPagesDir = specialPagesDir
|
||||
this.watchableDir = this.specialPagesDir
|
||||
}
|
||||
|
||||
private SpecialPageType getSpecialPageType(File file) {
|
||||
this.specialPageTypes.find {
|
||||
it.ids.contains(new FileNameHandler(file).getExtension())
|
||||
@ -26,7 +31,7 @@ class SpecialPageFileSpecialPagesProvider implements SpecialPagesProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<SpecialPage> getSpecialPages() {
|
||||
Collection<SpecialPage> provide() {
|
||||
if (!this.specialPagesDir.isDirectory()) {
|
||||
throw new IllegalArgumentException('specialPagesDir must be a directory')
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.jessebrault.ssg.specialpage
|
||||
|
||||
interface SpecialPagesProvider {
|
||||
Collection<SpecialPage> getSpecialPages()
|
||||
import com.jessebrault.ssg.provider.Provider
|
||||
|
||||
interface SpecialPagesProvider extends Provider<Collection<SpecialPage>> {
|
||||
Collection<SpecialPageType> getSpecialPageTypes()
|
||||
}
|
@ -1,23 +1,28 @@
|
||||
package com.jessebrault.ssg.template
|
||||
|
||||
import com.jessebrault.ssg.provider.WithWatchableDir
|
||||
import com.jessebrault.ssg.util.FileNameHandler
|
||||
import groovy.io.FileType
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import groovy.transform.TupleConstructor
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@TupleConstructor(includeFields = true, defaults = false)
|
||||
@NullCheck
|
||||
@EqualsAndHashCode(includeFields = true)
|
||||
class TemplateFileTemplatesProvider implements TemplatesProvider {
|
||||
class TemplateFileTemplatesProvider implements TemplatesProvider, WithWatchableDir {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TemplateFileTemplatesProvider)
|
||||
|
||||
private final Collection<TemplateType> templateTypes
|
||||
private final File templatesDir
|
||||
|
||||
TemplateFileTemplatesProvider(Collection<TemplateType> templateTypes, File templatesDir) {
|
||||
this.templateTypes = templateTypes
|
||||
this.templatesDir = templatesDir
|
||||
this.watchableDir = this.templatesDir
|
||||
}
|
||||
|
||||
private TemplateType getType(File file) {
|
||||
this.templateTypes.find {
|
||||
it.ids.contains(new FileNameHandler(file).getExtension())
|
||||
@ -25,7 +30,7 @@ class TemplateFileTemplatesProvider implements TemplatesProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Template> getTemplates() {
|
||||
Collection<Template> provide() {
|
||||
if (!this.templatesDir.isDirectory()) {
|
||||
throw new IllegalArgumentException('templatesDir must be a directory')
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.jessebrault.ssg.template
|
||||
|
||||
interface TemplatesProvider {
|
||||
Collection<Template> getTemplates()
|
||||
import com.jessebrault.ssg.provider.Provider
|
||||
|
||||
interface TemplatesProvider extends Provider<Collection<Template>> {
|
||||
Collection<TemplateType> getTemplateTypes()
|
||||
}
|
||||
|
@ -1,24 +1,32 @@
|
||||
package com.jessebrault.ssg.text
|
||||
|
||||
import com.jessebrault.ssg.provider.WithWatchableDir
|
||||
import com.jessebrault.ssg.util.FileNameHandler
|
||||
import com.jessebrault.ssg.util.RelativePathHandler
|
||||
import groovy.io.FileType
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import groovy.transform.TupleConstructor
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@TupleConstructor(includeFields = true, defaults = false)
|
||||
@NullCheck
|
||||
@EqualsAndHashCode(includeFields = true)
|
||||
class TextFileTextsProvider implements TextsProvider {
|
||||
class TextFileTextsProvider implements TextsProvider, WithWatchableDir {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TextFileTextsProvider)
|
||||
|
||||
private final Collection<TextType> textTypes
|
||||
private final File textsDir
|
||||
|
||||
TextFileTextsProvider(Collection<TextType> textTypes, File textsDir) {
|
||||
this.textTypes = textTypes
|
||||
this.textsDir = textsDir
|
||||
if (!this.textsDir.isDirectory()) {
|
||||
throw new IllegalArgumentException('textsDir must be a directory, given: ' + this.textsDir)
|
||||
}
|
||||
this.watchableDir = this.textsDir
|
||||
}
|
||||
|
||||
private TextType getTextType(File file) {
|
||||
this.textTypes.find {
|
||||
it.ids.contains(new FileNameHandler(file).getExtension())
|
||||
@ -26,11 +34,7 @@ class TextFileTextsProvider implements TextsProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
Collection<Text> getTextFiles() {
|
||||
if (!this.textsDir.isDirectory()) {
|
||||
throw new IllegalArgumentException('textsDir must be a directory')
|
||||
}
|
||||
|
||||
Collection<Text> provide() {
|
||||
def textFiles = []
|
||||
this.textsDir.eachFileRecurse(FileType.FILES) {
|
||||
def type = this.getTextType(it)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.jessebrault.ssg.text
|
||||
|
||||
interface TextsProvider {
|
||||
Collection<Text> getTextFiles()
|
||||
import com.jessebrault.ssg.provider.Provider
|
||||
|
||||
interface TextsProvider extends Provider<Collection<Text>> {
|
||||
Collection<TextType> getTextTypes()
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class PartFilePartsProviderTests {
|
||||
write('Hello <%= name %>!')
|
||||
}
|
||||
|
||||
def r = this.partsProvider.getParts()
|
||||
def r = this.partsProvider.provide()
|
||||
assertEquals(1, r.size())
|
||||
def p0 = r[0]
|
||||
assertEquals('testPart.gsp', p0.path)
|
||||
@ -40,7 +40,7 @@ class PartFilePartsProviderTests {
|
||||
}
|
||||
}
|
||||
|
||||
def r = this.partsProvider.getParts()
|
||||
def r = this.partsProvider.provide()
|
||||
assertEquals(1, r.size())
|
||||
def p0 = r[0]
|
||||
assertEquals('nested/testPart.gsp', p0.path)
|
||||
@ -54,7 +54,7 @@ class PartFilePartsProviderTests {
|
||||
write 'Ignored!'
|
||||
}
|
||||
|
||||
def r = this.partsProvider.getParts()
|
||||
def r = this.partsProvider.provide()
|
||||
assertEquals(0, r.size())
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ class SpecialPageFileSpecialPagesProviderTests {
|
||||
new FileTreeBuilder(this.specialPagesDir)
|
||||
.file('test.gsp', '<%= "Hello, World!" %>')
|
||||
|
||||
def r = this.specialPagesProvider.getSpecialPages()
|
||||
def r = this.specialPagesProvider.provide()
|
||||
assertEquals(1, r.size())
|
||||
def f0 = r[0]
|
||||
assertEquals('test', f0.path)
|
||||
@ -37,7 +37,7 @@ class SpecialPageFileSpecialPagesProviderTests {
|
||||
file('nested.gsp', '<%= "Hello, World!" %>')
|
||||
}
|
||||
|
||||
def r = this.specialPagesProvider.getSpecialPages()
|
||||
def r = this.specialPagesProvider.provide()
|
||||
assertEquals(1, r.size())
|
||||
def f0 = r[0]
|
||||
assertEquals('nested/nested', f0.path)
|
||||
@ -49,7 +49,7 @@ class SpecialPageFileSpecialPagesProviderTests {
|
||||
void ignoresUnsupportedFile() {
|
||||
new FileTreeBuilder(this.specialPagesDir).file('.ignored', 'Ignored!')
|
||||
|
||||
def r = this.specialPagesProvider.getSpecialPages()
|
||||
def r = this.specialPagesProvider.provide()
|
||||
assertEquals(0, r.size())
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ class PageTemplatesProviderTests {
|
||||
void findsTemplate() {
|
||||
new File(this.templatesDir, 'test.gsp').write('<% out << text %>')
|
||||
|
||||
def r = this.templatesProvider.getTemplates()
|
||||
def r = this.templatesProvider.provide()
|
||||
assertEquals(1, r.size())
|
||||
def t0 = r[0]
|
||||
assertEquals('test.gsp', t0.path)
|
||||
@ -38,7 +38,7 @@ class PageTemplatesProviderTests {
|
||||
}
|
||||
}
|
||||
|
||||
def r = this.templatesProvider.getTemplates()
|
||||
def r = this.templatesProvider.provide()
|
||||
assertEquals(1, r.size())
|
||||
def t0 = r[0]
|
||||
assertEquals('nested/nested.gsp', t0.path)
|
||||
@ -52,7 +52,7 @@ class PageTemplatesProviderTests {
|
||||
write('Ignored!')
|
||||
}
|
||||
|
||||
def r = this.templatesProvider.getTemplates()
|
||||
def r = this.templatesProvider.provide()
|
||||
assertEquals(0, r.size())
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ class TextFileTextsProviderTests {
|
||||
void findsFile() {
|
||||
new FileTreeBuilder(this.textsDir).file('test.md', '**Hello, World!**')
|
||||
|
||||
def r = this.textsProvider.getTextFiles()
|
||||
def r = this.textsProvider.provide()
|
||||
assertEquals(1, r.size())
|
||||
def f0 = r[0]
|
||||
assertEquals('test', f0.path)
|
||||
@ -36,7 +36,7 @@ class TextFileTextsProviderTests {
|
||||
file('nested.md', '**Hello!**')
|
||||
}
|
||||
|
||||
def r = this.textsProvider.getTextFiles()
|
||||
def r = this.textsProvider.provide()
|
||||
assertEquals(1, r.size())
|
||||
def f0 = r[0]
|
||||
assertEquals('nested/nested', f0.path)
|
||||
@ -48,7 +48,7 @@ class TextFileTextsProviderTests {
|
||||
void ignoresUnsupportedFile() {
|
||||
new FileTreeBuilder(this.textsDir).file('.ignored', 'Ignored!')
|
||||
|
||||
def r = this.textsProvider.getTextFiles()
|
||||
def r = this.textsProvider.provide()
|
||||
assertEquals(0, r.size())
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user