From fecde624c13318bb8cdc0f4e0522b82b18447a10 Mon Sep 17 00:00:00 2001 From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com> Date: Tue, 10 Jan 2023 12:09:25 -0600 Subject: [PATCH] Watching child dirs. --- .../ssg/StaticSiteGeneratorCli.groovy | 80 ++++++++++++------- .../ssg/SimpleStaticSiteGenerator.groovy | 2 +- .../jessebrault/ssg/provider/Provider.groovy | 5 ++ .../ssg/provider/WithWatchableDir.groovy | 5 ++ .../ssg/text/TextFileTextsProvider.groovy | 13 ++- .../jessebrault/ssg/text/TextsProvider.groovy | 5 +- .../text/TextFileTextsProviderTests.groovy | 6 +- 7 files changed, 72 insertions(+), 44 deletions(-) create mode 100644 lib/src/main/groovy/com/jessebrault/ssg/provider/Provider.groovy create mode 100644 lib/src/main/groovy/com/jessebrault/ssg/provider/WithWatchableDir.groovy diff --git a/cli/src/main/groovy/com/jessebrault/ssg/StaticSiteGeneratorCli.groovy b/cli/src/main/groovy/com/jessebrault/ssg/StaticSiteGeneratorCli.groovy index f0c6a7d..c745ee1 100644 --- a/cli/src/main/groovy/com/jessebrault/ssg/StaticSiteGeneratorCli.groovy +++ b/cli/src/main/groovy/com/jessebrault/ssg/StaticSiteGeneratorCli.groovy @@ -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,6 +15,7 @@ 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 @@ -21,7 +23,11 @@ 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( @@ -149,50 +155,62 @@ class StaticSiteGeneratorCli implements Callable { private static Integer watch(Collection builds, StaticSiteGenerator ssg) { logger.traceEntry('builds: {}, ssg: {}', builds, ssg) - Collection watchableProviders = [] - - builds.each { - it.config.textProviders.each { - if (it instanceof WatchableProvider) { - watchableProviders << it - } - } - it.config.templatesProviders.each { - if (it instanceof WatchableProvider) { - watchableProviders << it - } - } - it.config.partsProviders.each { - if (it instanceof WatchableProvider) { - watchableProviders << it - } - } - it.config.specialPagesProviders.each { - if (it instanceof WatchableProvider) { - watchableProviders << it - } - } - } - + // Setup watchService and watchKeys def watchService = FileSystems.getDefault().newWatchService() + Map watchKeys = [:] - watchableProviders.each { - it.watchableDir.toPath().register( + // Our Closure to register a path + def registerPath = { Path path -> + def watchKey = path.register( watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY ) + watchKeys[watchKey] = path + } + + // Get all base watchableDirs + Collection watchableProviders = [] + builds.each { + it.config.textProviders.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() - logger.debug('watchKey: {}', watchKey) - watchKey.pollEvents().each { - logger.debug('watchEvent: {}', it) + def path = watchKeys[watchKey] + if (path == null) { + logger.warn('unexpected watchKey: {}', watchKey) + } else { + watchKey.pollEvents().each { + assert it instanceof WatchEvent + def childName = it.context() + def childPath = path.resolve(childName) + logger.debug('childName: {}, childPath: {}', childName, childPath) + if (it.kind() == StandardWatchEventKinds.ENTRY_CREATE && Files.isDirectory(childPath)) { + logger.debug('registering dir with path: {}', childPath) + registerPath(childPath) + } + } + } + def valid = watchKey.reset() + if (!valid) { + def removedPath = watchKeys.remove(watchKey) + logger.debug('removed path: {}', removedPath) } - watchKey.reset() } //noinspection GroovyUnreachableStatement diff --git a/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy b/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy index 76e97a3..19ce317 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy @@ -26,7 +26,7 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator { def config = build.config // Get all texts, templates, parts, and specialPages - def texts = config.textProviders.collectMany { it.getTextFiles() } + def texts = config.textProviders.collectMany { it.provide() } def templates = config.templatesProviders.collectMany { it.getTemplates() } def parts = config.partsProviders.collectMany { it.getParts() } def specialPages = config.specialPagesProviders.collectMany { it.getSpecialPages() } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/provider/Provider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/provider/Provider.groovy new file mode 100644 index 0000000..d006bb6 --- /dev/null +++ b/lib/src/main/groovy/com/jessebrault/ssg/provider/Provider.groovy @@ -0,0 +1,5 @@ +package com.jessebrault.ssg.provider + +interface Provider { + T provide() +} \ No newline at end of file diff --git a/lib/src/main/groovy/com/jessebrault/ssg/provider/WithWatchableDir.groovy b/lib/src/main/groovy/com/jessebrault/ssg/provider/WithWatchableDir.groovy new file mode 100644 index 0000000..cc15bb7 --- /dev/null +++ b/lib/src/main/groovy/com/jessebrault/ssg/provider/WithWatchableDir.groovy @@ -0,0 +1,5 @@ +package com.jessebrault.ssg.provider + +trait WithWatchableDir { + File watchableDir +} \ No newline at end of file diff --git a/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy index 18a4f24..8f55bbd 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy @@ -1,8 +1,8 @@ package com.jessebrault.ssg.text +import com.jessebrault.ssg.provider.WithWatchableDir import com.jessebrault.ssg.util.FileNameHandler import com.jessebrault.ssg.util.RelativePathHandler -import com.jessebrault.ssg.WatchableProvider import groovy.io.FileType import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck @@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory @NullCheck @EqualsAndHashCode(includeFields = true) -class TextFileTextsProvider implements TextsProvider, WatchableProvider { +class TextFileTextsProvider implements TextsProvider, WithWatchableDir { private static final Logger logger = LoggerFactory.getLogger(TextFileTextsProvider) @@ -21,6 +21,9 @@ class TextFileTextsProvider implements TextsProvider, WatchableProvider { TextFileTextsProvider(Collection 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 } @@ -31,11 +34,7 @@ class TextFileTextsProvider implements TextsProvider, WatchableProvider { } @Override - Collection getTextFiles() { - if (!this.textsDir.isDirectory()) { - throw new IllegalArgumentException('textsDir must be a directory') - } - + Collection provide() { def textFiles = [] this.textsDir.eachFileRecurse(FileType.FILES) { def type = this.getTextType(it) diff --git a/lib/src/main/groovy/com/jessebrault/ssg/text/TextsProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/text/TextsProvider.groovy index 12d653c..7e31617 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/text/TextsProvider.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/text/TextsProvider.groovy @@ -1,6 +1,7 @@ package com.jessebrault.ssg.text -interface TextsProvider { - Collection getTextFiles() +import com.jessebrault.ssg.provider.Provider + +interface TextsProvider extends Provider> { Collection getTextTypes() } diff --git a/lib/src/test/groovy/com/jessebrault/ssg/text/TextFileTextsProviderTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/text/TextFileTextsProviderTests.groovy index 491c75f..6092a25 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/text/TextFileTextsProviderTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/text/TextFileTextsProviderTests.groovy @@ -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()) }